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:
1 parent
c9dae4ff9b
commit
9e1e25f8fe
5 files changed
+56
-23
No files matched your search
+17
-9
@@ -77,6 +77,7 @@ export interface ColumnModel {
|
||||
|
||||
export interface FootingModel {
|
||||
s: number;
|
||||
k: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
length: number;
|
||||
@@ -85,6 +86,7 @@ export interface FootingModel {
|
||||
|
||||
export interface BlindingModel {
|
||||
s: number;
|
||||
k: number;
|
||||
top: number;
|
||||
length: number;
|
||||
width: number;
|
||||
@@ -178,7 +180,7 @@ export function solveIp4(ip1: Vec2, ip2: Vec2, ip3: Vec2, angleDeg: number): Vec
|
||||
* Builds the first-stage model of a T-type pier from one input record. Pure function.
|
||||
* Coordinates: g = arc length from the coping start (BP1), so g = 0 at BP1 and g = L at BP2.
|
||||
* Each object carries its stretch k and a local s measured from that stretch's base (k = 0: BP1, k = 1: IP4).
|
||||
* A bent coping (IP4 angle ≠ 0) is supported for 현장타설말뚝 only; footings and piles on a bent axis are stage 3.
|
||||
* A bent coping (IP4 angle ≠ 0) puts each footing in its stretch's frame; footings that would cross the kink are not combined.
|
||||
*/
|
||||
export function buildPier(input: PierInput): PierModel {
|
||||
const ip = (name: string) => {
|
||||
@@ -191,9 +193,6 @@ export function buildPier(input: PierInput): PierModel {
|
||||
const ip3 = ip('IP3');
|
||||
const ip4 = ip('IP4');
|
||||
const bent = ip4.angleDeg !== 0;
|
||||
if (bent && input.foundationType !== '현장타설말뚝') {
|
||||
throw new Error('미실시: 꺾인 코핑의 확대기초·말뚝은 3단계에서 구현합니다. 현재는 현장타설말뚝만 지원합니다.');
|
||||
}
|
||||
|
||||
const L = input.copingLengthMm / 1000;
|
||||
const B = input.copingWidthMm / 1000;
|
||||
@@ -325,6 +324,14 @@ export function buildPier(input: PierInput): PierModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Footings are grouped per stretch so that a 일체기초 never crosses the kink; each group is aligned with its stretch.
|
||||
const footingSets: { s: number; k: number; length: number }[] = [];
|
||||
for (let k = 0; k < stretchCount; k++) {
|
||||
for (const g of footingGroups(colG.filter((_, i) => colK[i] === k))) {
|
||||
footingSets.push({ s: g.s - base[k], k, length: g.length });
|
||||
}
|
||||
}
|
||||
|
||||
const footingTop = input.foundationType === '현장타설말뚝' ? input.footingBottomEl : input.footingBottomEl + DIM.footingH;
|
||||
const columns: ColumnModel[] = colG.map((g, i) => ({
|
||||
s: g - base[colK[i]],
|
||||
@@ -345,17 +352,18 @@ export function buildPier(input: PierInput): PierModel {
|
||||
piles.push({ s: c.s, k: c.k, n: 0, top: input.footingBottomEl, bottom: pileTip, diameter: DIM.columnDiameter });
|
||||
});
|
||||
} else {
|
||||
for (const g of footingGroups(colG)) {
|
||||
footings.push({ s: g.s, top: footingTop, bottom: input.footingBottomEl, length: g.length, width: DIM.footingW });
|
||||
for (const f of footingSets) {
|
||||
footings.push({ s: f.s, k: f.k, top: footingTop, bottom: input.footingBottomEl, length: f.length, width: DIM.footingW });
|
||||
blindings.push({
|
||||
s: g.s,
|
||||
s: f.s,
|
||||
k: f.k,
|
||||
top: input.footingBottomEl,
|
||||
length: g.length + DIM.blindingMargin,
|
||||
length: f.length + DIM.blindingMargin,
|
||||
width: DIM.footingW + DIM.blindingMargin,
|
||||
thickness: DIM.blindingT,
|
||||
});
|
||||
if (input.foundationType === '말뚝기초') {
|
||||
piles.push(...pilesFor(g.s, input.footingBottomEl, pileLength, g.length));
|
||||
piles.push(...pilesFor(f.s, input.footingBottomEl, pileLength, f.length).map((p) => ({ ...p, k: f.k })));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ const EXPECTED_COLUMNS: Record<PierType, number> = { 'T형': 1, '2주형': 2, '3
|
||||
/** Rules from spec §10 that are not implemented in this stage. Shown to the user, never silently skipped. */
|
||||
export const NOT_IMPLEMENTED_RULES = [
|
||||
'E04 fck 미입력 (배근 단계, 3단계)',
|
||||
'꺾인 코핑의 확대기초·말뚝 (3단계; 현장타설말뚝만 지원)',
|
||||
'W06 속성 복사 건너뜀 (2단계)',
|
||||
'W08 솔플레이트 T1−T2 검사 (2단계)',
|
||||
] as const;
|
||||
|
||||
+9
-3
@@ -44,6 +44,12 @@ export function stretchMatrix(m: PierModel, k: number): THREE.Matrix4 {
|
||||
* so each stretch's full-length slab is trimmed to its own half and the two meet at the mitred joint.
|
||||
* Three.js keeps fragments with a non-negative distance from the plane, hence the sign flip for stretch 0.
|
||||
*/
|
||||
/** Mitre plane for one stretch as a list, or undefined on a straight axis. */
|
||||
export function planesOf(m: PierModel, k: number): THREE.Plane[] | undefined {
|
||||
const plane = mitrePlane(m, k);
|
||||
return plane ? [plane] : undefined;
|
||||
}
|
||||
|
||||
/** Straight-frame matrix (stretch 0). */
|
||||
export function frameMatrix(m: PierModel): THREE.Matrix4 {
|
||||
return stretchMatrix(m, 0);
|
||||
@@ -102,19 +108,19 @@ export function buildPierScene(m: PierModel): PierScene {
|
||||
for (const f of m.footings) {
|
||||
const g = new THREE.BoxGeometry(f.length, f.top - f.bottom, f.width);
|
||||
g.translate(f.s, (f.top + f.bottom) / 2, 0);
|
||||
add('footings', g);
|
||||
add('footings', g, 1, f.k, planesOf(m, f.k));
|
||||
}
|
||||
|
||||
for (const b of m.blindings) {
|
||||
const g = new THREE.BoxGeometry(b.length, b.thickness, b.width);
|
||||
g.translate(b.s, b.top - b.thickness / 2, 0);
|
||||
add('blinding', g);
|
||||
add('blinding', g, 1, b.k, planesOf(m, b.k));
|
||||
}
|
||||
|
||||
for (const p of m.piles) {
|
||||
const g = new THREE.CylinderGeometry(p.diameter / 2, p.diameter / 2, p.top - p.bottom, 20);
|
||||
g.translate(p.s, (p.top + p.bottom) / 2, p.n);
|
||||
add('piles', g, 1, p.k);
|
||||
add('piles', g, 1, p.k, planesOf(m, p.k));
|
||||
}
|
||||
|
||||
for (const b of m.blocks) {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 배치');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user