diff --git a/src/core/rebar/summary.ts b/src/core/rebar/summary.ts index e1dbaa2..14450b8 100644 --- a/src/core/rebar/summary.ts +++ b/src/core/rebar/summary.ts @@ -57,6 +57,8 @@ const META: Record = { S1: '받침블럭', S2: '받침블럭', C1: '주철근', C2: '주철근', C3: '주철근', C4: '주철근', C5: '주철근', C6: '주철근', C7: '주철근', C8: '주철근', C9: '전단·띠', C10: '전단·띠', C11: '전단·띠', C12: '측면 수평', C13: '측면 수평', C14: '직각 수평', C15: '계단부', C16: '계단부', - P1: '주철근', P2: '주철근', 커플러: '조립', P3: '띠철근', P5: '띠철근', P4: '띠철근', P6: '크로스타이', + P1: '주철근', P2: '주철근', '주철근 커플러': '조립', '띠철근 커플러': '조립', 커플러: '조립', P3: '띠철근', P5: '띠철근', P4: '띠철근', P6: '크로스타이', B1: '주철근', B2: '주철근', B3: '주철근', B4: '주철근', B5: '주철근', B6: '주철근', B7: '주철근', B8: '주철근', 'B10-1': '측면 수평', 'B10-2': '측면 수평', B11: '조립 수평', B12: '조립 수평', B9: '전단·띠', }; diff --git a/src/view/rebar3d.ts b/src/view/rebar3d.ts index 31e3b24..4205e7a 100644 --- a/src/view/rebar3d.ts +++ b/src/view/rebar3d.ts @@ -25,6 +25,8 @@ const COLOR = { shear: 0x6b3fa0, column: 0x0f5c66, tie: 0x6b3fa0, + couplerMain: 0x0b3f46, + couplerTie: 0x553088, footing: 0x0f5c66, footingBottom: 0x2e7d32, block: 0xa07a10, @@ -213,6 +215,52 @@ export function buildRebar3D(m: PierModel): RebarScene { for (const z of col.p3) ring('P3', c.k, sc, base + mm(z), rt, NOMINAL_DIAMETER.H19); for (const z of col.p5) ring('P5', c.k, sc, base + mm(z), rt, NOMINAL_DIAMETER.H19); for (const z of col.p4) ring('P4', c.k, sc, base + mm(z), rt, NOMINAL_DIAMETER.H19); + + // Main bar couplers (D32, L150 Ø60, 4단 × 46줄 × 2열 = 368개): P1 at col.couplerP1, P2 at col.couplerP2 (+150 mm) + const r1 = mm(col.r1); + const r2 = mm(col.r2); + for (let i = 0; i < COLUMN_P1.nDiv; i++) { + const th = (2 * Math.PI * i) / COLUMN_P1.nDiv; + const cosTh = Math.cos(th); + const sinTh = Math.sin(th); + // P1 couplers (184) + for (const z of col.couplerP1) { + const cx = sc + r1 * cosTh; + const cz = r1 * sinTh; + const cy = base + mm(z); + addBar('주철근 커플러', c.k, [cx, cy - 0.075, cz], [cx, cy + 0.075, cz], 60, COLOR.couplerMain); + } + // P2 couplers (184, shifted +150 mm) + for (const z of col.couplerP2) { + const cx = sc + r2 * cosTh; + const cz = r2 * sinTh; + const cy = base + mm(z); + addBar('주철근 커플러', c.k, [cx, cy - 0.075, cz], [cx, cy + 0.075, cz], 60, COLOR.couplerMain); + } + } + + // Tie couplers (D19, L100 Ø40, 총 35개): P5 1개 (19개) · P4 2개 (16개) + col.p5.forEach((z, j) => { + const th = (2 * Math.PI * (j * 7 + 0.5)) / COLUMN_P1.nDiv; + const cx = sc + rt * Math.cos(th); + const cz = rt * Math.sin(th); + const cy = base + mm(z); + const dx = 0.05 * -Math.sin(th); + const dz = 0.05 * Math.cos(th); + addBar('띠철근 커플러', c.k, [cx - dx, cy, cz - dz], [cx + dx, cy, cz + dz], 40, COLOR.couplerTie); + }); + col.p4.forEach((z) => { + const angles = [(2 * Math.PI * 0.5) / COLUMN_P1.nDiv, (2 * Math.PI * 0.5) / COLUMN_P1.nDiv + Math.PI]; + angles.forEach((th) => { + const cx = sc + rt * Math.cos(th); + const cz = rt * Math.sin(th); + const cy = base + mm(z); + const dx = 0.05 * -Math.sin(th); + const dz = 0.05 * Math.cos(th); + addBar('띠철근 커플러', c.k, [cx - dx, cy, cz - dz], [cx + dx, cy, cz + dz], 40, COLOR.couplerTie); + }); + }); + const hookP6 = mm(Math.round(hookLength('tie90', NOMINAL_DIAMETER.H16))); // 135 mm const dP6 = NOMINAL_DIAMETER.H16; // 15.9 mm const p6Angles = [0, 46.96, 90, 133.04]; @@ -373,10 +421,16 @@ export function buildRebar3D(m: PierModel): RebarScene { for (const [key, meshes] of byName) { for (const mesh of meshes) { const mat = mesh.material as THREE.MeshStandardMaterial; - const on = name === null || key === name; + const on = + name === null || + key === name || + (name === '커플러' && key.includes('커플러')) || + (name === '주철근 커플러' && key === '주철근 커플러') || + (name === '띠철근 커플러' && key === '띠철근 커플러'); mat.transparent = !on; mat.opacity = on ? 1 : 0.18; - mat.emissive.setHex(name !== null && key === name ? 0x6b5a00 : 0x000000); + const match = name !== null && (key === name || (name === '커플러' && key.includes('커플러'))); + mat.emissive.setHex(match ? 0x6b5a00 : 0x000000); } } }; diff --git a/tests/rebar-summary.test.ts b/tests/rebar-summary.test.ts index d636cdc..7fa1d74 100644 --- a/tests/rebar-summary.test.ts +++ b/tests/rebar-summary.test.ts @@ -27,7 +27,8 @@ describe('③ 철근표 요약 (P1)', () => { it('column: P1 230, P2 230, couplers 368, P3 38, P5 19, P4 8, P6 32', () => { expect(count('P1')).toBe(230); expect(count('P2')).toBe(230); - expect(count('커플러')).toBe(368); + expect(count('주철근 커플러')).toBe(368); + expect(count('띠철근 커플러')).toBe(35); expect(count('P3')).toBe(38); expect(count('P5')).toBe(19); expect(count('P4')).toBe(8); diff --git a/tests/rebar3d.test.ts b/tests/rebar3d.test.ts index a0ca489..9cc810b 100644 --- a/tests/rebar3d.test.ts +++ b/tests/rebar3d.test.ts @@ -67,6 +67,11 @@ describe('3D 철근 모델 (③)', () => { expect(rb.byName.get('P6')!.length).toBe(8 * 4 * 3); }); + it('couplers in 3D: main bar couplers (368) and tie couplers (35: P5 19 + P4 16)', () => { + expect(rb.byName.get('주철근 커플러')!.length).toBe(368); + expect(rb.byName.get('띠철근 커플러')!.length).toBe(35); + }); + it('highlight dims the other bars and keeps the chosen one', () => { rb.highlight('P3'); const p3 = (rb.byName.get('P3')![0].material as THREE.MeshStandardMaterial).opacity;