feat(view): 꺾인 코핑의 코핑 철근을 3D로 그린다

꺾인 코핑은 코핑 철근을 그리지 않았다. 다주형은 접힌 조각(pieces)을 각 구간 좌표로 그리고, T형은 같은
접기(coping-fold.ts)로 편 좌표 철근을 미터점에서 꺾어 그린다.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-10 17:12:55 +09:00
1 parent 6a15a13fc7
commit 8f808f453e
2 files changed
+59 -25

No files matched your search

+38 -25
View File
@@ -7,6 +7,7 @@ import { sideRowLevels } from '../core/coping-geom';
import { columnRebar, columnInputFor } from '../core/rebar/column';
import { castRebarLayout } from '../core/rebar/cast-layout';
import { multicolCopingBars } from '../core/rebar/multicol-coping';
import { copingFold, foldBar } from '../core/rebar/coping-fold';
import { barDepths, barsAcross } from '../core/rebar/bearing-block';
import { blockFramePoint } from '../core/geometry';
import { INTEGRATED_BLOCK } from '../core/integrated-block';
@@ -98,7 +99,6 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
const Ms = m.stretches.map((_, k) => stretchMatrix(m, k));
const L = m.copingL;
const B = m.copingB;
const straight = m.kink === null;
/**
* A bar through several points as one tube, each bend rounded to its centre-line radius (수정 2026-10-23).
@@ -163,14 +163,27 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
byName.set(name, list);
};
// Coping (straight coping only; a bent coping is not drawn in 3D yet).
if (straight && m.columns.length > 1) {
// Coping. A bent coping draws each bar's pieces in their own stretch (coping-fold.ts, 2026-11-10).
if (m.columns.length > 1) {
for (const b of multicolCopingBars(m, mCoping)) {
if (/^C[1-8](-\d+)?$/.test(b.name)) addPath(b.name, 0, b.pts, b.diameterMm, COLOR.coping);
else for (let i = 1; i < b.pts.length; i++) addBar(b.name, 0, b.pts[i - 1], b.pts[i], b.diameterMm, COLOR.shear);
for (const p of b.pieces ?? [{ k: 0 as const, pts: b.pts }]) {
if (/^C[1-8](-\d+)?$/.test(b.name)) addPath(b.name, p.k, p.pts, b.diameterMm, COLOR.coping);
else for (let i = 1; i < p.pts.length; i++) addBar(b.name, p.k, p.pts[i - 1], p.pts[i], b.diameterMm, COLOR.shear);
}
}
}
if (straight && m.columns.length <= 1) {
if (m.columns.length <= 1) {
// T형 on a bent coping: the unfolded bars turn at their mitre point (the mitre shear set and the wedge are multi-column only).
const fold = copingFold(m);
const tBar = (name: string, k: number, a: [number, number, number], b: [number, number, number], dMm: number, color: number) => {
if (!fold) return addBar(name, k, a, b, dMm, color);
for (const p of foldBar(fold, [a, b]).pieces) for (let i = 1; i < p.pts.length; i++) addBar(name, p.k, p.pts[i - 1], p.pts[i], dMm, color);
};
const tPath = (name: string, k: number, pts: [number, number, number][], dMm: number, color: number) => {
if (!fold) return addPath(name, k, pts, dMm, color);
for (const p of foldBar(fold, pts).pieces) addPath(name, p.k, p.pts, dMm, color);
};
const w = widthLayout(B * 1000, m.copingRebar);
const xs = [w.yE, ...Array.from({ length: w.n + 1 }, (_, i) => w.yE + w.g + w.spacing * i), B * 1000 - w.yE].map((v) => mm(v) - B / 2);
const cover = 0.1;
@@ -189,8 +202,8 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
xs.forEach((n, j) => {
const name = `C${k + (j % 2 === 1 ? 0 : 3)}`;
if (sp) {
addPath(name, 0, [[xk, z - hookTop, n], [xk, z, n], [splitX + half, z, n]], NOMINAL_DIAMETER.H29, COLOR.coping);
addPath(name, 0, [[splitX - half, z, n], [L - xk, z, n], [L - xk, z - hookTop, n]], NOMINAL_DIAMETER.H29, COLOR.coping);
tPath(name, 0, [[xk, z - hookTop, n], [xk, z, n], [splitX + half, z, n]], NOMINAL_DIAMETER.H29, COLOR.coping);
tPath(name, 0, [[splitX - half, z, n], [L - xk, z, n], [L - xk, z - hookTop, n]], NOMINAL_DIAMETER.H29, COLOR.coping);
return;
}
const pts: [number, number, number][] = [
@@ -199,7 +212,7 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
[L - xk, z, n],
[L - xk, z - hookTop, n],
];
addPath(name, 0, pts, NOMINAL_DIAMETER.H29, COLOR.coping);
tPath(name, 0, pts, NOMINAL_DIAMETER.H29, COLOR.coping);
});
}
// Bottom bars C7 / C8 (코핑철근.md §5.2–5.3): they follow the sloped underside, 127 above it, with a vertical leg up
@@ -218,7 +231,7 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
const right = left.map(([x, y, zz]): [number, number, number] => [L - x, y, zz]);
const name = j % 2 === 1 ? 'C7' : 'C8';
for (const pts of [left, right]) {
addPath(name, 0, pts, NOMINAL_DIAMETER.H29, COLOR.coping);
tPath(name, 0, pts, NOMINAL_DIAMETER.H29, COLOR.coping);
}
});
// Shear loops C9 / C10 / C11 and the gaps between columns: a closed U at each position.
@@ -244,12 +257,12 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
// The three 폐합 stack to the right, so no U meets another loop's U or 삿갓 (SHEAR_LOOP_STEP).
const xu = xm + mm(i * SHEAR_LOOP_STEP);
const xc = xu + mm(dShear);
addBar(name, 0, [xu, bot, a], [xu, bot, b], dShear, COLOR.shear);
addBar(name, 0, [xu, bot, a], [xu, top, a], dShear, COLOR.shear);
addBar(name, 0, [xu, bot, b], [xu, top, b], dShear, COLOR.shear);
addBar(name, 0, [xc, top, a], [xc, top, b], dShear, COLOR.shear);
addBar(name, 0, [xc, top, a], [xc, capBot, a], dShear, COLOR.shear);
addBar(name, 0, [xc, top, b], [xc, capBot, b], dShear, COLOR.shear);
tBar(name, 0, [xu, bot, a], [xu, bot, b], dShear, COLOR.shear);
tBar(name, 0, [xu, bot, a], [xu, top, a], dShear, COLOR.shear);
tBar(name, 0, [xu, bot, b], [xu, top, b], dShear, COLOR.shear);
tBar(name, 0, [xc, top, a], [xc, top, b], dShear, COLOR.shear);
tBar(name, 0, [xc, top, a], [xc, capBot, a], dShear, COLOR.shear);
tBar(name, 0, [xc, top, b], [xc, capBot, b], dShear, COLOR.shear);
}
}
}
@@ -262,14 +275,14 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
for (const [name, rows] of [['C12', side.c12], ['C13', side.c13]] as const) {
for (const r of rows) {
const xw = mm(r.xw);
addBar(name, 0, [xw, r.z, -sideFace], [L - xw, r.z, -sideFace], dSide, COLOR.shear);
addBar(name, 0, [xw, r.z, sideFace], [L - xw, r.z, sideFace], dSide, COLOR.shear);
tBar(name, 0, [xw, r.z, -sideFace], [L - xw, r.z, -sideFace], dSide, COLOR.shear);
tBar(name, 0, [xw, r.z, sideFace], [L - xw, r.z, sideFace], dSide, COLOR.shear);
const zh = r.z + mm(dSide);
for (const x0 of [xw, L - xw]) {
const leg = mm(SIDE_HOOK_LEG) * Math.sign(L / 2 - x0);
addBar(name, 0, [x0, zh, -sideFace], [x0, zh, sideFace], dSide, COLOR.shear);
addBar(name, 0, [x0, zh, -sideFace], [x0 + leg, zh, -sideFace], dSide, COLOR.shear);
addBar(name, 0, [x0, zh, sideFace], [x0 + leg, zh, sideFace], dSide, COLOR.shear);
tBar(name, 0, [x0, zh, -sideFace], [x0, zh, sideFace], dSide, COLOR.shear);
tBar(name, 0, [x0, zh, -sideFace], [x0 + leg, zh, -sideFace], dSide, COLOR.shear);
tBar(name, 0, [x0, zh, sideFace], [x0 + leg, zh, sideFace], dSide, COLOR.shear);
}
}
}
@@ -280,18 +293,18 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
const c14Nb = B / 2 - mm(C14_INSET);
for (const b of c14Bars(L * 1000, topEl, [...side.c13, ...side.c12], (x) => undersideAt(L * 1000, x, m.endBottom, m.levelBottom, m.copingGeom.levelWidth), m.copingRebar.mainLayerGap / 1000)) {
const xm = mm(b.x);
addBar('C14', 0, [xm, b.z, c14Na], [xm, b.z, c14Nb], NOMINAL_DIAMETER.H19, COLOR.coping);
tBar('C14', 0, [xm, b.z, c14Na], [xm, b.z, c14Nb], NOMINAL_DIAMETER.H19, COLOR.coping);
// 연직 발 below the coping underside is left out (코핑철근.md §8).
if (b.legs) {
addBar('C14', 0, [xm, b.z - c14Hook, c14Na], [xm, b.z, c14Na], NOMINAL_DIAMETER.H19, COLOR.coping);
addBar('C14', 0, [xm, b.z, c14Nb], [xm, b.z - c14Hook, c14Nb], NOMINAL_DIAMETER.H19, COLOR.coping);
tBar('C14', 0, [xm, b.z - c14Hook, c14Na], [xm, b.z, c14Na], NOMINAL_DIAMETER.H19, COLOR.coping);
tBar('C14', 0, [xm, b.z, c14Nb], [xm, b.z - c14Hook, c14Nb], NOMINAL_DIAMETER.H19, COLOR.coping);
}
}
// C15 (⊓) and C16 (L형) on the model's own steps (결정 2026-11-10, src/core/rebar/step-bars.ts).
for (const b of tStepBars(m, mCoping).bars) {
const n = b.n;
for (let i = 1; i < b.pts.length; i++) {
addBar(b.name, 0, [mm(b.pts[i - 1][0]), b.pts[i - 1][1], n], [mm(b.pts[i][0]), b.pts[i][1], n], NOMINAL_DIAMETER.H19, COLOR.coping);
tBar(b.name, 0, [mm(b.pts[i - 1][0]), b.pts[i - 1][1], n], [mm(b.pts[i][0]), b.pts[i][1], n], NOMINAL_DIAMETER.H19, COLOR.coping);
}
}
}