fix(abut): 뒷굽 헌치를 삽도대로 날개벽과 확대기초 사이에 둠

- 벽체 배면 쪽 아래 헌치를 삭제 (삽도에 근거 없음)
- 뒷굽 헌치 추가: 뒷굽 끝 H = (저판 뒷굽, 저판 상면)에서 날개벽 하면을 따라 0.3 m, 뒷굽 면을 따라 아래로 0.3 m인 (t, EL) 직각삼각형을 날개벽 두께 0.45 m만큼 s 방향으로 밈. 단축형 날개벽에는 없음
- 위 헌치(흉벽 배면)는 그대로
- 테스트 갱신. 전체 638건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-03 16:51:25 +09:00
1 parent 3aa7cc06b6
commit ea36df58cc
3 files changed
+38 -8

No files matched your search

+24 -2
View File
@@ -38,8 +38,13 @@ export interface WingSolid {
topLine: { dM: number; el: number }[];
/** 하단선: 저판 상면 → 뒷굽 끝 → 단부 하단 (일반형), 또는 저판 상면 평탄 (단축형). */
bottomLine: { dM: number; el: number }[];
/** 헌치 300 × 300 (위·아래), 평면 삼각형과 높이 범위. */
/** 헌치 300 × 300 (위 헌치, 흉벽 배면 모서리). 평면 삼각형과 높이 범위. */
haunches: Haunch[];
/**
* 뒷굽 헌치 (삽도, 날개벽과 확대기초): (t, EL) 직각삼각형. 꼭짓점은 뒷굽 끝 H, 날개벽 하면을 따라 0.3 m, 뒷굽 면을 따라 아래로 0.3 m.
* 날개벽 두께(0.45 m)만큼 s 방향으로 민다. 단축형 날개벽에는 없다.
*/
heelHaunch?: [number, number][];
/** 수평 단 (QA-12, spec_abut §4.7): 등단면 / 변화단면1 / 변화단면2. 경계는 단부 높이 1.5 m와 교좌면 EL. */
bands: WingBand[];
}
@@ -125,7 +130,23 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef
zTopEl: kind === 'upper' ? topAt(d) : bracketTop,
};
};
const haunches = [haunchAt('upper', members.shape.parapetBackM), haunchAt('lower', members.shape.wallBackM)];
const haunches = [haunchAt('upper', members.shape.parapetBackM)];
// 뒷굽 헌치 (삽도): at the heel H = (footing back, footing top). The 300 legs run along the wing underside and down the 뒷굽 face.
const tOf = (dM: number) => (members.shape.footingBackM * dM) / heelDistanceM;
const underside = bottomLine.find((p) => p.dM > heelDistanceM + 1e-9);
const heelHaunch: [number, number][] | undefined =
underside === undefined
? undefined
: (() => {
const H: [number, number] = [members.shape.footingBackM, footingTop];
const ux = tOf(underside.dM) - H[0];
const uy = underside.el - H[1];
const len = Math.hypot(ux, uy);
const A: [number, number] = [H[0] + (0.3 * ux) / len, H[1] + (0.3 * uy) / len];
const B: [number, number] = [H[0], H[1] - 0.3];
return [H, A, B];
})();
// 수평 단 (QA-12): 등단면 = 끝 상단 − 1.5 m 위, 변화단면1 = 그 아래 ~ 교좌면 EL, 변화단면2 = 교좌면 EL ~ 저판 상면.
// 교좌면 EL은 날개벽 끝에 가장 가까운 받침의 교좌면이다.
@@ -151,6 +172,7 @@ export function buildWings(model: AbutModel, members: AbutMembers, forms?: { lef
topLine,
bottomLine,
haunches,
heelHaunch,
bands,
};
};
+5
View File
@@ -284,6 +284,11 @@ export function buildAbutGroup(
const lower = prism(clipBelowX(below, members.shape.wallBackM / dir.wt), WING_THICKNESS_M, new THREE.Vector3(sEnd, 0, 0), ax, ey, new THREE.Vector3(a, 0, 0));
add('wings', upper, 0xc9a77c);
add('wings', lower, 0xc9a77c);
// 뒷굽 헌치 (삽도): (t, EL) triangle, extruded across the wing thickness along s.
if (wing.heelHaunch) {
const profile = wing.heelHaunch;
add('haunches', prism(profile, WING_THICKNESS_M, new THREE.Vector3(sEnd, 0, 0), ez, ey, new THREE.Vector3(a, 0, 0)), 0xc9a77c);
}
for (const h of wing.haunches) {
const shape: [number, number][] = h.corners.map((c) => {
const p = toAxis(model, c);
+9 -6
View File
@@ -117,20 +117,23 @@ describe('교대 날개벽 하단 선 · 헌치 (AA3c, spec_abut §4.7)', () =>
expect(line[2].el).toBeCloseTo(45.214, 2);
});
it('two 300 × 300 haunches: upper at the 흉벽 배면, lower at the 벽체 배면, legs 0.3 m', () => {
expect(a2Left.haunches.map((h) => h.kind)).toEqual(['upper', 'lower']);
it('the upper 300 × 300 haunch sits at the 흉벽 배면 (legs 0.3 m); the 뒷굽 haunch is a (t, EL) triangle with 0.3 m legs at the heel', () => {
expect(a2Left.haunches.map((h) => h.kind)).toEqual(['upper']);
const tri = a2Left.heelHaunch!;
expect(tri).toHaveLength(3);
expect(tri[0][0]).toBeCloseTo(-1.8, 6); // 뒷굽 끝 (t)
expect(Math.hypot(tri[1][0] - tri[0][0], tri[1][1] - tri[0][1])).toBeCloseTo(0.3, 6);
expect(tri[2][1]).toBeCloseTo(tri[0][1] - 0.3, 6); // leg down the 뒷굽 face
const [c0, c1, c2] = a2Left.haunches[0].corners;
expect(Math.hypot(c1.e - c0.e, c1.n - c0.n)).toBeCloseTo(0.3, 6);
expect(Math.hypot(c2.e - c0.e, c2.n - c0.n)).toBeCloseTo(0.3, 6);
});
it('upper haunch runs from the bracket top (흉벽 상단 − 0.85) to the wing top; lower from the footing top to the bracket top', () => {
const [up, low] = a2Left.haunches;
it('upper haunch runs from the bracket top (흉벽 상단 − 0.85) to the wing top', () => {
const [up] = a2Left.haunches;
// The upper haunch rests on the highest point of the 브라켓 top over its footprint (the top falls along s), never below it.
expect(up.zBottomEl).toBeGreaterThanOrEqual(46.838 - 0.85 - 1e-6);
expect(up.zBottomEl).toBeLessThan(46.838 - 0.85 + 0.05);
expect(low.zBottomEl).toBeCloseTo(41.995, 3);
expect(low.zTopEl).toBeCloseTo(46.838 - 0.85, 2);
expect(up.zTopEl).toBeGreaterThan(up.zBottomEl);
});
});