fix(shape): 꺾임점 근처 계단 경계를 꺾임점에 맞춘다
계단 경계가 꺾임점에서 블럭 반폭(0.6 m) 이내이면 경계를 꺾임점으로 옮겨, 계단면이 미터면과 같아져 두 구간이 쐐기 틈 없이 만난다 (산외교 P11 후열 ⑥|⑦: 15.07 → 15.14). 양쪽 받침이 꺾임점에서 블럭 반폭 이상 떨어질 때만 옮긴다. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bd84ac6176
commit
9777938aa8
2 files changed
+62
-2
No files matched your search
@@ -334,9 +334,14 @@ export function buildPier(input: PierInput, ctx: PierContext = {}): PierModel {
|
|||||||
if (unequalRows) { frontPlaced.sort((a,b)=>a.g-b.g); backPlaced.sort((a,b)=>a.g-b.g); }
|
if (unequalRows) { frontPlaced.sort((a,b)=>a.g-b.g); backPlaced.sort((a,b)=>a.g-b.g); }
|
||||||
|
|
||||||
// Step boundaries sit midway between consecutive front bearings in coping coordinates (spec §4.3).
|
// Step boundaries sit midway between consecutive front bearings in coping coordinates (spec §4.3).
|
||||||
|
// A step boundary less than half a block from the kink moves onto it (2026-11-10), so the step face is the mitre
|
||||||
|
// plane and the two stretches meet without a wedge; only when both bearings stay half a block away from the kink.
|
||||||
|
const halfBlock = DIM.blockSize / 2;
|
||||||
|
const toKink = (bound: number, gLeft: number, gRight: number) =>
|
||||||
|
kinkG !== null && Math.abs(bound - kinkG) < halfBlock && kinkG - gLeft >= halfBlock && gRight - kinkG >= halfBlock ? kinkG : bound;
|
||||||
const bounds = [0];
|
const bounds = [0];
|
||||||
for (let k = 0; k < frontPlaced.length - 1; k++) {
|
for (let k = 0; k < frontPlaced.length - 1; k++) {
|
||||||
bounds.push((frontPlaced[k].g + frontPlaced[k + 1].g) / 2);
|
bounds.push(toKink((frontPlaced[k].g + frontPlaced[k + 1].g) / 2, frontPlaced[k].g, frontPlaced[k + 1].g));
|
||||||
}
|
}
|
||||||
bounds.push(L);
|
bounds.push(L);
|
||||||
// 교좌면 EL per bearing (2026-11-10): when given, it is the coping top of that bearing's step; otherwise z − h1 (z − h2 behind).
|
// 교좌면 EL per bearing (2026-11-10): when given, it is the coping top of that bearing's step; otherwise z − h1 (z − h2 behind).
|
||||||
@@ -349,7 +354,7 @@ export function buildPier(input: PierInput, ctx: PierContext = {}): PierModel {
|
|||||||
// front bound snaps to it, so slightly staggered rows (P1, ≈ 0.09 m) keep one boundary and no sliver step.
|
// front bound snaps to it, so slightly staggered rows (P1, ≈ 0.09 m) keep one boundary and no sliver step.
|
||||||
const backBound = (k: number) => {
|
const backBound = (k: number) => {
|
||||||
const own = (backPlaced[k-1].g + backPlaced[k].g) / 2;
|
const own = (backPlaced[k-1].g + backPlaced[k].g) / 2;
|
||||||
return !unequalRows && Math.abs(own - bounds[k]) < DIM.blockSize / 2 ? bounds[k] : own;
|
return !unequalRows && Math.abs(own - bounds[k]) < halfBlock ? bounds[k] : toKink(own, backPlaced[k-1].g, backPlaced[k].g);
|
||||||
};
|
};
|
||||||
const backSegments: Segment[] = backPlaced.map((b,k) => ({
|
const backSegments: Segment[] = backPlaced.map((b,k) => ({
|
||||||
s0: k === 0 ? 0 : backBound(k),
|
s0: k === 0 ? 0 : backBound(k),
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import { parseWorkbook } from '../src/core/parseWorkbook';
|
||||||
|
import { buildPier, DIM, defaultLevelWidthMm } from '../src/core/geometry';
|
||||||
|
import type { PierInput } from '../src/core/types';
|
||||||
|
|
||||||
|
// 산외교 P11 (샘플01) with the coping bent 8.767° at IP4 (2026-11-10 검토): back ⑥ g 11.64, ⑦ g 18.49, kink g 15.14.
|
||||||
|
const SAMPLE = fileURLToPath(new URL('../project_data/01_엑셀/샘플01_산외교_교각입력데이터.xlsx', import.meta.url));
|
||||||
|
|
||||||
|
function p11(): PierInput {
|
||||||
|
const wb = XLSX.read(readFileSync(SAMPLE), { type: 'buffer' });
|
||||||
|
wb.Sheets['IP']['F46'] = { t: 'n', v: 8.767 };
|
||||||
|
const input = parseWorkbook(wb).piers.find((p) => p.name === 'P11')!;
|
||||||
|
return { ...input, levelWidthMm: input.levelWidthMm ?? defaultLevelWidthMm(input.type, 2000) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const kinkOf = (m: ReturnType<typeof buildPier>) => m.stretches[1].base;
|
||||||
|
const gOf = (m: ReturnType<typeof buildPier>, no: number) => m.bearings.find((b) => b.no === no)!.g;
|
||||||
|
|
||||||
|
describe('꺾임점 근처 계단 경계 (A, 2026-11-10)', () => {
|
||||||
|
it('a back boundary 0.07 m from the kink moves onto the kink (⑥|⑦)', () => {
|
||||||
|
const m = buildPier(p11());
|
||||||
|
const kink = kinkOf(m);
|
||||||
|
const own = (gOf(m, 6) + gOf(m, 7)) / 2;
|
||||||
|
expect(Math.abs(own - kink)).toBeLessThan(0.1);
|
||||||
|
expect(m.backSegments![2].s0).toBeCloseTo(kink, 9);
|
||||||
|
expect(m.backSegments![1].s1).toBeCloseTo(kink, 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('boundaries far from the kink stay midway between their bearings', () => {
|
||||||
|
const m = buildPier(p11());
|
||||||
|
expect(m.backSegments![1].s0).toBeCloseTo((gOf(m, 5) + gOf(m, 6)) / 2, 9);
|
||||||
|
expect(m.backSegments![3].s0).toBeCloseTo((gOf(m, 7) + gOf(m, 8)) / 2, 9);
|
||||||
|
m.segments.slice(1).forEach((seg) => expect(Math.abs(seg.s0 - kinkOf(m))).toBeGreaterThan(DIM.blockSize / 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('no snap when a bearing would end up closer than half a block to the kink', () => {
|
||||||
|
const input = p11();
|
||||||
|
const base = buildPier(input);
|
||||||
|
// ⑦ 0.4 m past the kink, ⑥ 0.6 m before it: midpoint 0.1 m before the kink, but ⑦ is within half a block.
|
||||||
|
const u1 = base.stretches[1].u;
|
||||||
|
const u0 = base.stretches[0].u;
|
||||||
|
const kink = kinkOf(base);
|
||||||
|
const d7 = kink + 0.4 - gOf(base, 7);
|
||||||
|
const d6 = kink - 0.6 - gOf(base, 6);
|
||||||
|
const bearings = input.bearings.map((b) =>
|
||||||
|
b.no === 7 ? { ...b, x: b.x + d7 * u1.e, y: b.y + d7 * u1.n } : b.no === 6 ? { ...b, x: b.x + d6 * u0.e, y: b.y + d6 * u0.n } : b);
|
||||||
|
const m = buildPier({ ...input, bearings });
|
||||||
|
const own = (gOf(m, 6) + gOf(m, 7)) / 2;
|
||||||
|
expect(kinkOf(m) - own).toBeCloseTo(0.1, 3);
|
||||||
|
expect(m.backSegments![2].s0).toBeCloseTo(own, 9);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user