From a39a1ba8d231fb1f2b3f8fedd1237181db7bcd44 Mon Sep 17 00:00:00 2001 From: ypshim Date: Tue, 10 Nov 2026 15:51:10 +0900 Subject: [PATCH] =?UTF-8?q?fix(shape):=20=EC=A0=84=C2=B7=ED=9B=84=EC=97=B4?= =?UTF-8?q?=20=EB=82=98=EB=88=94=EC=84=A0=EC=9D=84=20=EA=B5=90=EA=B0=81=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=EC=97=90=EC=84=9C=20=ED=95=98=EB=82=98?= =?UTF-8?q?=EB=A1=9C=20=EB=91=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 횡방향 단차 코핑의 전·후열 나눔선을 구간마다 계산해, 꺾인 교각에서 두 구간의 판이 꺾임점에서 옆으로 어긋났다 (산외교 P11: −0.05 / +0.11). 두 열 전체 받침의 평균 n 하나를 쓰고, 어느 받침이든 그 선에서 블럭 반폭(0.6 m)보다 가까우면 지금처럼 구간별 선을 쓴다. Co-Authored-By: Claude Haiku 5.5 --- src/core/geometry.ts | 10 +++++++--- tests/kink-steps.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/core/geometry.ts b/src/core/geometry.ts index 1f52265..110edf0 100644 --- a/src/core/geometry.ts +++ b/src/core/geometry.ts @@ -409,12 +409,16 @@ export function buildPier(input: PierInput, ctx: PierContext = {}): PierModel { const meanN = (rows: { n: number }[]) => rows.reduce((sum, r) => sum + r.n, 0) / rows.length; const copingParts: CopingPart[] = []; const stretchCount = kinkG === null ? 1 : 2; + // One front/back line for the whole pier (2026-11-10), so the parts of the two stretches meet at the kink without a + // sideways jog; per stretch only when some bearing would be closer than half a block to that line. + const nAll = (meanN(frontPlaced) + meanN(backPlaced)) / 2; + const oneLine = [...frontPlaced, ...backPlaced].every((b) => Math.abs(b.n - nAll) >= halfBlock); for (let k = 0; k < stretchCount; k++) { - const fRows = frontPlaced.filter((b) => b.k === k); - const bRows = backPlaced.filter((b) => b.k === k); + const fRows = oneLine ? frontPlaced : frontPlaced.filter((b) => b.k === k); + const bRows = oneLine ? backPlaced : backPlaced.filter((b) => b.k === k); const nStep = fRows.length && bRows.length ? (meanN(fRows) + meanN(bRows)) / 2 - : (meanN(frontPlaced) + meanN(backPlaced)) / 2; + : nAll; if (lateralStep) { // The front row may lie on either side of the axis (it depends on the IP1 → IP3 direction), so each part takes the // side of its own row; the order stays [front, back]. diff --git a/tests/kink-steps.test.ts b/tests/kink-steps.test.ts index 0e890c6..0f7da7d 100644 --- a/tests/kink-steps.test.ts +++ b/tests/kink-steps.test.ts @@ -53,3 +53,39 @@ describe('꺾임점 근처 계단 경계 (A, 2026-11-10)', () => { expect(m.backSegments![2].s0).toBeCloseTo(own, 9); }); }); + +describe('전·후열 나눔선 하나 (B, 2026-11-10)', () => { + const split = (m: ReturnType, k: number) => { + const [front] = m.copingParts.filter((p) => p.k === k); + return Math.abs(front.nFrom) < Math.abs(front.nTo) ? front.nFrom : front.nTo; + }; + const meanN = (m: ReturnType, row: string, k?: number) => { + const rows = m.bearings.filter((b) => b.row === row && (k === undefined || b.k === k)); + return rows.reduce((s, b) => s + b.n, 0) / rows.length; + }; + + it('both stretches use the same line, the mean of the two rows over the whole pier (no jog at the kink)', () => { + const m = buildPier(p11()); + expect(m.copingParts).toHaveLength(4); + const nAll = (meanN(m, '전열') + meanN(m, '후열')) / 2; + expect(split(m, 0)).toBeCloseTo(nAll, 9); + expect(split(m, 1)).toBeCloseTo(nAll, 9); + // Per stretch the lines would differ by about 0.16 m. + const per = (k: number) => (meanN(m, '전열', k) + meanN(m, '후열', k)) / 2; + expect(Math.abs(per(0) - per(1))).toBeGreaterThan(0.1); + }); + + it('falls back to a line per stretch when a bearing would be closer than half a block to the common line', () => { + const input = p11(); + const base = buildPier(input); + const nrm = base.stretches[1].nrm; + // Move back ⑦ towards the axis (+n side here is the front), to n ≈ +0.1. + const b7 = base.bearings.find((b) => b.no === 7)!; + const d = 0.1 - b7.n; + const bearings = input.bearings.map((b) => (b.no === 7 ? { ...b, x: b.x + d * nrm.e, y: b.y + d * nrm.n } : b)); + const m = buildPier({ ...input, bearings }); + const nAll = (meanN(m, '전열') + meanN(m, '후열')) / 2; + expect(m.bearings.some((b) => Math.abs(b.n - nAll) < DIM.blockSize / 2)).toBe(true); + for (const k of [0, 1]) expect(split(m, k)).toBeCloseTo((meanN(m, '전열', k) + meanN(m, '후열', k)) / 2, 9); + }); +});