feat(core,view): bent coping stage 3 — footings and piles in stretch frames

- Footings, blindings and piles on a bent axis are placed in their stretch's
  frame; 일체기초 is grouped per stretch, so a combined footing never crosses
  the kink (separate footings, each aligned with its stretch).
- Footings, blindings and piles are trimmed at the mitre plane like the coping.
- The stage-2 refusal for bent foundations is removed; the Level-half-width
  refusal for columns at the kink remains.
- Tests: T형 footing crossing the kink stays in stretch 0; 2주형 with columns
  on both sides gives one footing per stretch, with mitre clipping in the scene.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-22 09:38:01 +09:00
1 parent c9dae4ff9b
commit 9e1e25f8fe
5 files changed
+56 -23

No files matched your search

+28 -8
View File
@@ -17,10 +17,10 @@ const FRONT_N = -0.7;
const BACK_N = 0.6;
const GS = [0.6, 1.9, 3.2];
function bentInput(foundationType: PierInput['foundationType'] = '현장타설말뚝'): PierInput {
function bentInput(foundationType: PierInput['foundationType'] = '현장타설말뚝', type: PierInput['type'] = 'T형', coping = L): PierInput {
const base = loadP1();
const axis = buildAxis(IP1, IP3, solveIp4(IP1, { e: 1, n: 0 }, IP3, ANGLE));
const sStart = copingSpan(axis, L).sStart;
const sStart = copingSpan(axis, coping).sStart;
const z = base.copingTopEl + 0.15;
const bearingsAt = (row: '전열' | '후열', n: number) =>
GS.map((g, i) => {
@@ -29,10 +29,10 @@ function bentInput(foundationType: PierInput['foundationType'] = '현장타설
});
return {
...base,
type: 'T형',
columnCount: 1,
type,
columnCount: type === '2주형' ? 2 : 1,
foundationType,
copingLengthMm: L * 1000,
copingLengthMm: coping * 1000,
copingWidthMm: B * 1000,
ips: [
{ name: 'IP1', x: IP1.e, y: IP1.n, angleDeg: 0 },
@@ -123,9 +123,29 @@ describe('bent coping model (stage 2)', () => {
}
});
it('bent coping with footings or piles on the axis is refused until stage 3', () => {
expect(() => buildPier(bentInput('말뚝기초'))).toThrow('미실시');
expect(() => buildPier(bentInput('직접기초'))).toThrow('미실시');
it('footings are aligned with their stretch: a T형 footing crossing the kink stays in stretch 0 and is trimmed there', () => {
const piled = buildPier(bentInput('말뚝기초'));
expect(piled.footings).toHaveLength(1);
expect(piled.footings[0].k).toBe(0);
expect(piled.footings[0].length).toBeCloseTo(10.4, 6);
expect(piled.blindings[0].k).toBe(0);
expect(piled.piles).toHaveLength(20);
expect(piled.piles.every((p) => p.k === 0)).toBe(true);
expect(buildPier(bentInput('직접기초')).piles).toHaveLength(0);
});
it('2주형 bent with columns on both sides of the kink: separate footings, one per stretch', () => {
const m = buildPier(bentInput('말뚝기초', '2주형', 10));
expect(m.footings.map((f) => f.k)).toEqual([0, 1]);
expect(m.footings.every((f) => Math.abs(f.length - 10.4) < 1e-9)).toBe(true);
expect(m.piles).toHaveLength(40);
expect(m.piles.filter((p) => p.k === 1)).toHaveLength(20);
const scene = buildPierScene(m);
const footings = scene.parts.footings.children as THREE.Mesh[];
expect(footings).toHaveLength(2);
for (const mesh of footings) {
expect(((mesh.material as THREE.MeshStandardMaterial).clippingPlanes ?? []).length).toBe(1);
}
});
it('a kink outside the coping is refused', () => {
+2 -2
View File
@@ -181,9 +181,9 @@ describe('꺾인 코핑 IP4 (spec §4.1)', () => {
expect(solveIp4({ e: 0, n: 0 }, { e: 1, n: 0 }, { e: 10, n: 2 }, 0)).toBeNull();
});
it('bent coping is still reported as 미실시 by buildPier', () => {
it('a bend on a straight P1 sheet (IP1, IP2, IP3 collinear) is refused: IP4 would land on IP3', () => {
const p1 = loadP1();
const ips = p1.ips.map((p) => (p.name === 'IP4' ? { ...p, angleDeg: 5 } : p));
expect(() => buildPier({ ...p1, ips })).toThrow('미실시');
expect(() => buildPier({ ...p1, ips })).toThrow('꺾임각과 IP 배치');
});
});