From 686d87bad9ae8ca8ab7791ad633557db7921829c Mon Sep 17 00:00:00 2001 From: ypshim Date: Tue, 3 Nov 2026 17:35:06 +0900 Subject: [PATCH] =?UTF-8?q?fix(abut):=20=EC=97=B0=EC=A7=81=20=ED=97=8C?= =?UTF-8?q?=EC=B9=98=EB=A5=BC=20=EB=82=A0=EA=B0=9C=EB=B2=BD=20=EB=82=B4?= =?UTF-8?q?=EC=B8=A1=EB=A9=B4(=EB=91=90=20=EB=82=A0=EA=B0=9C=EB=B2=BD=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=20=EB=A9=B4)=20=EC=9C=84=EC=97=90=20?= =?UTF-8?q?=EB=86=93=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 헌치 모서리를 축 끝에서 날개벽 두께(0.45 m)만큼 안쪽, 즉 내측면으로 옮김. 이전에는 날개벽 위치와 겹쳤음 - 헌치는 내측면에서 중앙 방향으로만 0.3 m 뻗음 - 테스트: 헌치 정점이 날개벽 내측면의 중앙 쪽에 있음 (이전 배치에서는 실패) - 전체 테스트 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시 Co-Authored-By: Claude Haiku 5.5 --- src/core/abut/wings.ts | 3 ++- tests/abut-blocks-slab.test.ts | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/core/abut/wings.ts b/src/core/abut/wings.ts index fd1da01..1c72592 100644 --- a/src/core/abut/wings.ts +++ b/src/core/abut/wings.ts @@ -120,8 +120,9 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef const backTopAt = (s: number) => members.parapetTopStartEl + ((members.parapetTopEndEl - members.parapetTopStartEl) * s) / model.length - members.shape.bracketHeightM; const haunchAt = (kind: 'upper' | 'lower', t: number): Haunch => { const d = Math.abs(t) / k; - const c = pointAt(d); + // The corner lies on the 날개벽 내측면 (the face between the two wings), WING_THICKNESS_M in from the axis end. const inwardUnit = { e: model.u.e * a, n: model.u.n * a }; + const c = shift(pointAt(d), inwardUnit, WING_THICKNESS_M); const corners: [AbutPoint, AbutPoint, AbutPoint] = [c, shift(c, w, HAUNCH_LEG_M), shift(c, inwardUnit, HAUNCH_LEG_M)]; return { kind, diff --git a/tests/abut-blocks-slab.test.ts b/tests/abut-blocks-slab.test.ts index d6a337c..73f8de7 100644 --- a/tests/abut-blocks-slab.test.ts +++ b/tests/abut-blocks-slab.test.ts @@ -101,4 +101,29 @@ describe('교대 3D: 받침블럭과 접속슬래브 (spec §4.2, §4.8)', () => if (pos.getZ(i) >= members.shape.wallBackM) expect(pos.getY(i)).toBeGreaterThanOrEqual(backTop(pos.getX(i)) - 1e-6); } }); + + it('the 헌치 sits on the 날개벽 내측면 (the face between the two wings), on the centre side, and does not enter the wing', () => { + const axis = (p: { e: number; n: number }) => { + const d = { e: p.e - model.ip1.e, n: p.n - model.ip1.n }; + const f = { e: model.nLeft.e * model.frontSide, n: model.nLeft.n * model.frontSide }; + return { s: d.e * model.u.e + d.n * model.u.n, t: d.e * f.e + d.n * f.n }; + }; + // Inner face of each wing: s = sEnd + a·0.45 + (ws/wt)·t (a = +1 at IP1, −1 at IP3). Haunch vertices must lie on the centre side of it. + const inner = wings.map((w) => { + const p0 = axis(w.corners[0]); + const p1 = axis(w.corners[1]); + const a = w.sEnd === 0 ? 1 : -1; + return { sEnd: w.sEnd, a, k: (p1.s - p0.s) / (p1.t - p0.t), tRoot: p0.t }; + }); + for (const h of (named('haunches') as THREE.Group).children) { + const pos = (h as THREE.Mesh).geometry.getAttribute('position'); + for (let i = 0; i < pos.count; i++) { + const x = pos.getX(i); + const z = pos.getZ(i); + const w = x < model.length / 2 ? inner[0] : inner[1]; + const sIn = w.sEnd + w.a * 0.45 + w.k * (z - w.tRoot); + expect(w.a * (x - sIn)).toBeGreaterThanOrEqual(-1e-6); + } + } + }); });