diff --git a/src/main.ts b/src/main.ts index 46b1f6d..5c2cc4f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -702,7 +702,7 @@ function setupScene(): void { state.scene = scene; setView(state.view); } - // ③ 교대 (2026-11-04): with the piers hidden, the selected abutment's 저판 · 벽체 · 흉벽 · 날개벽 · 접속슬래브 rebar is the ③ rebar (표 · 3D 선택 공통). + // ③ 교대 (2026-11-04): with the piers hidden, the selected abutment's 저판 · 벽체 · 흉벽 · 날개벽 · 접속슬래브 · 받침블럭 rebar is the ③ rebar (표 · 3D 선택 공통). const abutRec = piersShown(state.stage, state.abutActive) ? undefined : state.abutments.find((a) => a.name === state.abutActive); const abutGroup = abutRec ? abutGroupByName.get(abutRec.name) : undefined; const footing = abutRec ? abutFootingBuilt(abutRec) : null; @@ -710,7 +710,7 @@ function setupScene(): void { const wall = abutWallBuilt(abutRec, footing.result); const para = wall ? abutParapetBuilt(abutRec, wall) : null; const wings = abutWingsBuilt(abutRec, footing.result).map((w) => w.result); - rebar3d = buildAbutRebar3D(footing.geom, footing.result, footing.skewK, footing.backM, footing.bottomEl, wall?.result, para?.result, wings, abutSlabBuilt(abutRec)?.result); + rebar3d = buildAbutRebar3D(footing.geom, footing.result, footing.skewK, footing.backM, footing.bottomEl, wall?.result, para?.result, wings, abutSlabBuilt(abutRec)?.result, abutBlockBuilt(abutRec)?.result); rebar3d.group.applyMatrix4(abutGroup.matrix); rebar3d.group.visible = state.stage === 'rebar'; rebar3d.setColorMode(state.rebarColor); diff --git a/src/view/abut-rebar3d.ts b/src/view/abut-rebar3d.ts index 90f7f1b..cdf5a1f 100644 --- a/src/view/abut-rebar3d.ts +++ b/src/view/abut-rebar3d.ts @@ -15,6 +15,8 @@ import { ABUT_PARAPET_COLORS } from './abut-parapet-rebar-svg'; import { ABUT_WING_SIZES, type AbutWingRebar } from '../core/abut/rebar/wing'; import { ABUT_WING_COLORS } from './abut-wing-rebar-svg'; import { ABUT_SLAB_SIZES, type AbutSlabRebar } from '../core/abut/rebar/slab'; +import { ABUT_BLOCK_SIZES, type AbutBlockRebar } from '../core/abut/rebar/block'; +import { ABUT_BLOCK_COLORS } from './abut-block-rebar-svg'; import { ABUT_SLAB_COLORS } from './abut-slab-rebar-svg'; import { ABUT_FOOTING_COLORS } from './abut-footing-rebar-svg'; import { ABUT_WALL_COLORS } from './abut-wall-rebar-svg'; @@ -36,12 +38,13 @@ export function buildAbutRebar3D( parapet?: AbutParapetRebar, wings: AbutWingRebar[] = [], slab?: AbutSlabRebar, + blocks?: AbutBlockRebar, ): RebarScene { const group = new THREE.Group(); group.name = 'abut-rebar'; const byName = new Map(); const C = Object.fromEntries(Object.entries(ABUT_FOOTING_COLORS).map(([k, v]) => [k, parseInt(v.slice(1), 16)])) as Record; - const sizeOf = new Map([...r.bars.map((b) => [b.mark, b.size] as const), ...Object.entries(ABUT_WALL_SIZES), ...Object.entries(ABUT_PARAPET_SIZES), ...Object.entries(ABUT_WING_SIZES), ...Object.entries(ABUT_SLAB_SIZES)]); + const sizeOf = new Map([...r.bars.map((b) => [b.mark, b.size] as const), ...Object.entries(ABUT_WALL_SIZES), ...Object.entries(ABUT_PARAPET_SIZES), ...Object.entries(ABUT_WING_SIZES), ...Object.entries(ABUT_SLAB_SIZES), ...Object.entries(ABUT_BLOCK_SIZES)]); // (s, x, z) in mm: s along the skew grid (끝면과 나란히), x from the 배면, z from the 저판 하면. const P = (s: number, x: number, z: number): P3 => [s / 1000 + skewK * (backM + x / 1000), bottomEl + z / 1000, backM + x / 1000]; // Option ②: square to the axis, s in the 삽도's frame (abutFootingRebarPlanSvg SQ), the same for every x. @@ -139,5 +142,9 @@ export function buildAbutRebar3D( const slabColor = (mark: string) => (mark === 'R1' || mark === 'R3' ? S.main : mark === 'R7' ? S.corner : mark === 'R8' ? S.shear : S.dist); for (const p of slab.paths) add(p.mark, p.pts.map(([s, t, z]): P3 => [s / 1000, bottomEl + z / 1000, t / 1000]), slabColor(p.mark)); } + if (blocks) { + const B = Object.fromEntries(Object.entries(ABUT_BLOCK_COLORS).map(([k, v]) => [k, parseInt(v.slice(1), 16)])) as Record<'s1' | 's2', number>; + for (const p of blocks.paths) add(p.mark, p.pts.map(([s, t, z]): P3 => [s / 1000, bottomEl + z / 1000, t / 1000]), p.mark === 'S1' ? B.s1 : B.s2); + } return { group, byName, records: [], ...rebarControls(byName) }; } diff --git a/tests/abut-rebar-inside.test.ts b/tests/abut-rebar-inside.test.ts index 9132089..920aa4c 100644 --- a/tests/abut-rebar-inside.test.ts +++ b/tests/abut-rebar-inside.test.ts @@ -9,6 +9,7 @@ import { buildMembers } from '../src/core/abut/members'; import { buildWings } from '../src/core/abut/wings'; import { resolveSlab, slabGradePct, slabSection } from '../src/core/abut/slab'; import { abutSlabGeom, abutSlabRebar } from '../src/core/abut/rebar/slab'; +import { abutBlockRebar } from '../src/core/abut/rebar/block'; import { abutFootingGeom, abutFootingRebar } from '../src/core/abut/rebar/footing'; import { skewAngle } from '../src/core/abut/rebar/skew-layout'; import { abutWallGeom, abutWallRebar } from '../src/core/abut/rebar/wall'; @@ -42,7 +43,9 @@ const res: Record = {}; const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68, seats, k))); const grade = slabGradePct(slab, model.wing?.slopePct ?? 0); const slabG = abutSlabGeom(slab.form, abutPlanOutline(model, members, wings, slab).slab, slabSection(slab, grade, members.shape), { startEl: members.parapetTopStartEl, endEl: members.parapetTopEndEl, L: model.length, bottomEl: members.footingBottomEl! }, [300, 300], slab.lengthMm, grade); - const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR, abutSlabRebar(slabG)); + const blockIn = model.bearings.map((b) => ({ no: b.no, s: b.s, t: model.frontSide * b.lateral, rotDeg: b.rotDeg, seatEl: b.seatEl, blockHeightMm: b.blockHeightMm })); + const blockR = abutBlockRebar(blockIn, members.footingBottomEl!); + const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR, abutSlabRebar(slabG), blockR); const group = buildAbutGroup(model, members, wings); group.updateMatrixWorld(true); const solids: THREE.Mesh[] = []; diff --git a/tests/abut-rebar3d.test.ts b/tests/abut-rebar3d.test.ts index 6782df3..6340a8b 100644 --- a/tests/abut-rebar3d.test.ts +++ b/tests/abut-rebar3d.test.ts @@ -158,3 +158,18 @@ describe('교대 날개벽 3D 철근 (③, 2026-11-04)', () => { } }); }); + +import { abutBlockRebar } from '../src/core/abut/rebar/block'; + +describe('교대 받침블럭 3D 철근 (③, 2026-11-05)', () => { + it('S1 · S2: 블럭 5개 × 8본 = 40본씩, 본마다 다리 · 직선 · 다리 3조각 (120 조각)', () => { + const blockR = abutBlockRebar( + model.bearings.map((b) => ({ no: b.no, s: b.s, t: model.frontSide * b.lateral, rotDeg: b.rotDeg, seatEl: b.seatEl, blockHeightMm: b.blockHeightMm })), + bottom, + ); + const footing = abutFootingRebar(geom); + const scene = buildAbutRebar3D(geom, footing, k, back, bottom, undefined, undefined, [], undefined, blockR); + expect(scene.byName.get('S1')?.length).toBe(120); + expect(scene.byName.get('S2')?.length).toBe(120); + }); +}); diff --git a/verify/dump/abut-rebar-outside.dump.ts b/verify/dump/abut-rebar-outside.dump.ts index 7f93651..b629c59 100644 --- a/verify/dump/abut-rebar-outside.dump.ts +++ b/verify/dump/abut-rebar-outside.dump.ts @@ -11,6 +11,7 @@ import { buildMembers } from '../../src/core/abut/members'; import { buildWings } from '../../src/core/abut/wings'; import { resolveSlab, slabGradePct, slabSection } from '../../src/core/abut/slab'; import { abutSlabGeom, abutSlabRebar } from '../../src/core/abut/rebar/slab'; +import { abutBlockRebar } from '../../src/core/abut/rebar/block'; import { abutFootingGeom, abutFootingRebar } from '../../src/core/abut/rebar/footing'; import { skewAngle } from '../../src/core/abut/rebar/skew-layout'; import { abutWallGeom, abutWallRebar } from '../../src/core/abut/rebar/wall'; @@ -42,7 +43,9 @@ it('dump 콘크리트 밖 철근', () => { const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68, seats, k))); const grade = slabGradePct(slab, model.wing?.slopePct ?? 0); const slabG = abutSlabGeom(slab.form, abutPlanOutline(model, members, wings, slab).slab, slabSection(slab, grade, members.shape), { startEl: members.parapetTopStartEl, endEl: members.parapetTopEndEl, L: model.length, bottomEl: members.footingBottomEl! }, [300, 300], slab.lengthMm, grade); - const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR, abutSlabRebar(slabG)); + const blockIn = model.bearings.map((b) => ({ no: b.no, s: b.s, t: model.frontSide * b.lateral, rotDeg: b.rotDeg, seatEl: b.seatEl, blockHeightMm: b.blockHeightMm })); + const blockR = abutBlockRebar(blockIn, members.footingBottomEl!); + const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR, abutSlabRebar(slabG), blockR); const group = buildAbutGroup(model, members, wings); group.updateMatrixWorld(true); const solids: THREE.Mesh[] = [];