feat(view): 꺾인 코핑의 코핑 철근을 3D로 그린다
꺾인 코핑은 코핑 철근을 그리지 않았다. 다주형은 접힌 조각(pieces)을 각 구간 좌표로 그리고, T형은 같은 접기(coping-fold.ts)로 편 좌표 철근을 미터점에서 꺾어 그린다. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a15a13fc7
commit
8f808f453e
2 files changed
+59
-25
No files matched your search
+38
-25
@@ -7,6 +7,7 @@ import { sideRowLevels } from '../core/coping-geom';
|
|||||||
import { columnRebar, columnInputFor } from '../core/rebar/column';
|
import { columnRebar, columnInputFor } from '../core/rebar/column';
|
||||||
import { castRebarLayout } from '../core/rebar/cast-layout';
|
import { castRebarLayout } from '../core/rebar/cast-layout';
|
||||||
import { multicolCopingBars } from '../core/rebar/multicol-coping';
|
import { multicolCopingBars } from '../core/rebar/multicol-coping';
|
||||||
|
import { copingFold, foldBar } from '../core/rebar/coping-fold';
|
||||||
import { barDepths, barsAcross } from '../core/rebar/bearing-block';
|
import { barDepths, barsAcross } from '../core/rebar/bearing-block';
|
||||||
import { blockFramePoint } from '../core/geometry';
|
import { blockFramePoint } from '../core/geometry';
|
||||||
import { INTEGRATED_BLOCK } from '../core/integrated-block';
|
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 Ms = m.stretches.map((_, k) => stretchMatrix(m, k));
|
||||||
const L = m.copingL;
|
const L = m.copingL;
|
||||||
const B = m.copingB;
|
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).
|
* 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);
|
byName.set(name, list);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Coping (straight coping only; a bent coping is not drawn in 3D yet).
|
// Coping. A bent coping draws each bar's pieces in their own stretch (coping-fold.ts, 2026-11-10).
|
||||||
if (straight && m.columns.length > 1) {
|
if (m.columns.length > 1) {
|
||||||
for (const b of multicolCopingBars(m, mCoping)) {
|
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);
|
for (const p of b.pieces ?? [{ k: 0 as const, pts: b.pts }]) {
|
||||||
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);
|
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 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 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;
|
const cover = 0.1;
|
||||||
@@ -189,8 +202,8 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
|
|||||||
xs.forEach((n, j) => {
|
xs.forEach((n, j) => {
|
||||||
const name = `C${k + (j % 2 === 1 ? 0 : 3)}`;
|
const name = `C${k + (j % 2 === 1 ? 0 : 3)}`;
|
||||||
if (sp) {
|
if (sp) {
|
||||||
addPath(name, 0, [[xk, z - hookTop, n], [xk, z, n], [splitX + half, z, 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);
|
||||||
addPath(name, 0, [[splitX - half, z, n], [L - xk, z, n], [L - xk, z - hookTop, 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;
|
return;
|
||||||
}
|
}
|
||||||
const pts: [number, number, number][] = [
|
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, n],
|
||||||
[L - xk, z - hookTop, 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
|
// 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 right = left.map(([x, y, zz]): [number, number, number] => [L - x, y, zz]);
|
||||||
const name = j % 2 === 1 ? 'C7' : 'C8';
|
const name = j % 2 === 1 ? 'C7' : 'C8';
|
||||||
for (const pts of [left, right]) {
|
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.
|
// 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).
|
// 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 xu = xm + mm(i * SHEAR_LOOP_STEP);
|
||||||
const xc = xu + mm(dShear);
|
const xc = xu + mm(dShear);
|
||||||
addBar(name, 0, [xu, bot, a], [xu, bot, b], dShear, COLOR.shear);
|
tBar(name, 0, [xu, bot, a], [xu, bot, b], dShear, COLOR.shear);
|
||||||
addBar(name, 0, [xu, bot, a], [xu, top, a], dShear, COLOR.shear);
|
tBar(name, 0, [xu, bot, a], [xu, top, a], dShear, COLOR.shear);
|
||||||
addBar(name, 0, [xu, bot, b], [xu, top, b], dShear, COLOR.shear);
|
tBar(name, 0, [xu, bot, b], [xu, top, b], dShear, COLOR.shear);
|
||||||
addBar(name, 0, [xc, top, a], [xc, top, b], dShear, COLOR.shear);
|
tBar(name, 0, [xc, top, a], [xc, top, b], dShear, COLOR.shear);
|
||||||
addBar(name, 0, [xc, top, a], [xc, capBot, a], dShear, COLOR.shear);
|
tBar(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, [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 [name, rows] of [['C12', side.c12], ['C13', side.c13]] as const) {
|
||||||
for (const r of rows) {
|
for (const r of rows) {
|
||||||
const xw = mm(r.xw);
|
const xw = mm(r.xw);
|
||||||
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);
|
||||||
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);
|
||||||
const zh = r.z + mm(dSide);
|
const zh = r.z + mm(dSide);
|
||||||
for (const x0 of [xw, L - xw]) {
|
for (const x0 of [xw, L - xw]) {
|
||||||
const leg = mm(SIDE_HOOK_LEG) * Math.sign(L / 2 - x0);
|
const leg = mm(SIDE_HOOK_LEG) * Math.sign(L / 2 - x0);
|
||||||
addBar(name, 0, [x0, zh, -sideFace], [x0, zh, sideFace], dSide, COLOR.shear);
|
tBar(name, 0, [x0, zh, -sideFace], [x0, 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 + 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 + 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);
|
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)) {
|
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);
|
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).
|
// 연직 발 below the coping underside is left out (코핑철근.md §8).
|
||||||
if (b.legs) {
|
if (b.legs) {
|
||||||
addBar('C14', 0, [xm, b.z - c14Hook, c14Na], [xm, b.z, c14Na], NOMINAL_DIAMETER.H19, COLOR.coping);
|
tBar('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, 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).
|
// 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) {
|
for (const b of tStepBars(m, mCoping).bars) {
|
||||||
const n = b.n;
|
const n = b.n;
|
||||||
for (let i = 1; i < b.pts.length; i++) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { PierInput } from '../src/core/types';
|
|||||||
import { rebarSummary } from '../src/core/rebar/summary';
|
import { rebarSummary } from '../src/core/rebar/summary';
|
||||||
import { multicolCopingInfo } from '../src/core/rebar/multicol-coping';
|
import { multicolCopingInfo } from '../src/core/rebar/multicol-coping';
|
||||||
import { copingFold, inWedge, isTransverse } from '../src/core/rebar/coping-fold';
|
import { copingFold, inWedge, isTransverse } from '../src/core/rebar/coping-fold';
|
||||||
|
import { buildRebar3D } from '../src/view/rebar3d';
|
||||||
|
|
||||||
// 산외교 P11 (샘플01) with the coping bent 8.767° at IP4 (2026-11-10 검토): back ⑥ g 11.64, ⑦ g 18.49, kink g 15.14.
|
// 산외교 P11 (샘플01) with the coping bent 8.767° at IP4 (2026-11-10 검토): back ⑥ g 11.64, ⑦ g 18.49, kink g 15.14.
|
||||||
const SAMPLE = fileURLToPath(new URL('../project_data/01_엑셀/샘플01_산외교_교각입력데이터.xlsx', import.meta.url));
|
const SAMPLE = fileURLToPath(new URL('../project_data/01_엑셀/샘플01_산외교_교각입력데이터.xlsx', import.meta.url));
|
||||||
@@ -186,3 +187,23 @@ describe('꺾인 코핑 철근 접기 (B·C, 2026-11-10)', () => {
|
|||||||
expect(multicolCopingInfo(straight).bars.every((b) => b.pieces === undefined)).toBe(true);
|
expect(multicolCopingInfo(straight).bars.every((b) => b.pieces === undefined)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('꺾인 코핑 3D 철근 (E, 2026-11-10)', () => {
|
||||||
|
const straightOf = (input: PierInput) => ({ ...input, ips: input.ips.map((q) => (q.name === 'IP4' ? { ...q, angleDeg: 0 } : q)) });
|
||||||
|
const count = (rb: ReturnType<typeof buildRebar3D>, name: string) => rb.byName.get(name)?.length ?? 0;
|
||||||
|
|
||||||
|
it('a bent multi-column coping now draws its coping bars, runs across the kink in two pieces', () => {
|
||||||
|
const bent = buildRebar3D(buildPier(p11()));
|
||||||
|
for (const name of ['C1', 'C7', 'C9', 'C10', 'C12', 'C13', 'C15', 'C16']) expect(count(bent, name), name).toBeGreaterThan(0);
|
||||||
|
const straight = buildRebar3D(buildPier(straightOf(p11())));
|
||||||
|
expect(count(bent, 'C7')).toBeGreaterThan(count(straight, 'C7'));
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
it('a bent T형 coping draws its coping bars through the same fold', () => {
|
||||||
|
const t = { ...p11(), type: 'T형' as const, columnCount: 1 };
|
||||||
|
const bent = buildRebar3D(buildPier(t));
|
||||||
|
const straight = buildRebar3D(buildPier(straightOf(t)));
|
||||||
|
expect(count(bent, 'C1')).toBeGreaterThan(0);
|
||||||
|
expect(count(bent, 'C1')).toBeGreaterThan(count(straight, 'C1'));
|
||||||
|
}, 60000);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user