Files
Pier_v1/src/view/bearing-plan-svg.ts
T
ypshimandClaude Haiku 5.5 1d31d2f3a9 feat(abut): 교대 형상·입력 패널과 교각 받침·솔플레이트 개편
- 교대 형상(src/core/abut, 3D 장면, 목록·카메라·맞춤)과 교대 입력 패널 추가
- 교각 받침 기준 카드 제거, 솔플레이트 규칙을 교각별 입력으로 변경
- 받침 ELEVATION·솔플레이트 TABLE을 모델값으로 연결
- 교대 입력값 카드: 형식 행에 시점·종점 표시, 제목을 "A1 입력값 [입력]"으로 변경
- 테스트 614건 통과, 타입 검사 통과

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-03 14:51:34 +09:00

97 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PierModel } from '../core/geometry';
import { bearingEdgeDistances, pairEccentricities } from '../core/bearing-layout';
import { BLOCK_DEPTH_MM } from '../core/bearing-table';
/** Number of pairs the 상세 보기 chips can show (①–⑩ in the mockup). */
export const PAIR_CHIP_LABELS = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩'];
/**
* 교량받침 상세 · 평면 for one pair (승인 2026-10-26): the bearings keep their actual 교각축 positions (the offset
* between the two bearings is shown as it is), and the blocks and solplates follow them. 일체 draws one 2500 × 1200 outline; 분리 draws two 1200 × 1200 outlines. Dimensions: C1 (bearing to block edge),
* C2 (between the bearings), C3 (block span). C4 and C5 are the outer margins along the 교축.
*/
/**
* A rectangle centred on (cx, cy), turned by the bearing's 방위 (승인 2026-10-26). The angle is −rowSkewDeg, the same
* turn as the 3D block (rotateY(−skew)): the block's sides follow the bearing row.
*/
function rect(cx: number, cy: number, w: number, h: number, skewDeg: number, attrs: string): string {
return `<rect x="${cx - w / 2}" y="${cy - h / 2}" width="${w}" height="${h}" transform="rotate(${-skewDeg} ${cx} ${cy})" ${attrs}></rect>`;
}
export function bearingPlanSvg(m: PierModel, pairIndex: number): string {
const pair = pairEccentricities(m.blocks)[pairIndex];
if (!pair) return '';
const f = m.blocks.find((b) => b.no === pair.frontNo && b.row === '전열')!;
const k = m.blocks.find((b) => b.no === pair.backNo && b.row === '후열')!;
const nc = (f.n + k.n) / 2;
const sc = (f.s + k.s) / 2;
// x: 교축 (n); y: 교각축 (s), the actual positions relative to the pair's mean. The offset is kept, not aligned.
const X = (n: number) => (n - nc) * 1000;
const Y = (s: number) => (s - sc) * 1000;
const eccentricity = Math.abs(f.s - k.s) * 1000;
const parts: string[] = [];
// Axes: 교축 (horizontal, to the right) and 교각축 방향 (vertical, downward).
parts.push(`<line x1="-1700" y1="0" x2="1650" y2="0" stroke="#B4531F" stroke-width="6" stroke-dasharray="120 60 20 60"></line>`);
parts.push(`<polygon points="1650,0 1560,-40 1560,40" fill="#B4531F"></polygon>`);
parts.push(`<text x="1640" y="-60" text-anchor="end" font-size="90" fill="#B4531F">교축</text>`);
parts.push(`<line x1="0" y1="-900" x2="0" y2="900" stroke="#B4531F" stroke-width="6" stroke-dasharray="120 60 20 60"></line>`);
parts.push(`<polygon points="0,-900 -40,-810 40,-810" fill="#B4531F"></polygon>`);
parts.push(`<text x="60" y="-860" font-size="90" fill="#B4531F">교각축 방향</text>`);
// Block outline(s): one row.
const ib = m.integratedBlocks.find((x) => x.frontNo === pair.frontNo);
if (ib) {
parts.push(rect(X(ib.n), Y(ib.s), 2500, 1200, ib.rowSkewDeg, `fill="none" stroke="#6E8496" stroke-width="12"`));
} else {
for (const b of [f, k]) {
parts.push(rect(X(b.n), Y(b.s), 1200, 1200, b.rowSkewDeg, `fill="none" stroke="#6E8496" stroke-width="12"`));
}
}
// Solplates: the pier's plan (L across the 교축, W along the 교각축), centred on each bearing.
for (const b of [f, k]) {
parts.push(rect(X(b.n), Y(b.s), b.plan.L, b.plan.W, b.rowSkewDeg, `fill="#EEF3F7" stroke="#2F6DB5" stroke-width="10"`));
}
// Bearing centres and numbers.
for (const b of [f, k]) {
parts.push(`<circle cx="${X(b.n)}" cy="${Y(b.s)}" r="40" fill="#18212B"></circle>`);
parts.push(`<text x="${X(b.n) + 70}" y="${Y(b.s) - 60}" font-size="90" fill="#18212B">${b.no} · 방위 ${b.rowSkewDeg.toFixed(2)}°</text>`);
}
if (eccentricity > 0.5) {
parts.push(`<text x="0" y="${-700}" text-anchor="middle" font-size="80" fill="#55606E">편심 ${eccentricity.toFixed(0)} mm (교각축 방향, 위치 그대로)</text>`);
}
// Dimensions along the bottom: C1 (bearing to block edge), C2 (between the bearings), C1, and C3 (block span).
const dist = bearingEdgeDistances(m);
const d = (no: number) => dist.find((x) => x.no === no)!;
const xLeft = ib ? X(ib.n - 1.25) : X(f.n) - 600;
const xRight = ib ? X(ib.n + 1.25) : X(k.n) + 600;
const dimLine = (x1: number, x2: number, y: number, label: string) =>
`<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="#55606E" stroke-width="6"></line><line x1="${x1}" y1="${y - 50}" x2="${x1}" y2="${y + 50}" stroke="#55606E" stroke-width="6"></line><line x1="${x2}" y1="${y - 50}" x2="${x2}" y2="${y + 50}" stroke="#55606E" stroke-width="6"></line><text x="${(x1 + x2) / 2}" y="${y + 110}" text-anchor="middle" font-size="90" fill="#18212B">${label}</text>`;
const y1 = 760;
const y2 = 1000;
// C1 and C2 are along the 교축 (horizontal), measured on the drawn positions.
parts.push(dimLine(xLeft, X(f.n), y1, `C1 ${Math.round(X(f.n) - xLeft)}`));
parts.push(dimLine(X(f.n), X(k.n), y1, `C2 ${Math.round(Math.abs(k.n - f.n) * 1000)}`));
parts.push(dimLine(X(k.n), xRight, y1, `C1 ${Math.round(xRight - X(k.n))}`));
parts.push(dimLine(xLeft, xRight, y2, `C3 ${Math.round((xRight - xLeft))}`));
// C4 and C5 as in the 시안: vertical dimensions on the left of the blocks, from the top edge to the bearing row (C4) and
// from the bearing row to the bottom edge (C5). Values: the model's C4 and C5 (outer margins).
const xv = xLeft - 300;
const halfDepth = 600;
const vdim = (y1: number, y2: number, label: string) =>
`<line x1="${xv}" y1="${y1}" x2="${xv}" y2="${y2}" stroke="#55606E" stroke-width="6"></line><line x1="${xv - 50}" y1="${y1}" x2="${xv + 50}" y2="${y1}" stroke="#55606E" stroke-width="6"></line><line x1="${xv - 50}" y1="${y2}" x2="${xv + 50}" y2="${y2}" stroke="#55606E" stroke-width="6"></line><text x="${xv - 90}" y="${(y1 + y2) / 2 + 30}" text-anchor="end" font-size="90" fill="#18212B">${label}</text>`;
// C4: from the low-side block edge to the front bearing; C5: from the back bearing to the high-side block edge.
const lowEdge = ib ? Y(ib.s) - 600 : Y(f.s) - 600;
const highEdge = ib ? Y(ib.s) + 600 : Y(k.s) + 600;
parts.push(vdim(lowEdge, Y(f.s), `C4 ${Math.round(d(f.no).leftMm)}`));
parts.push(vdim(Y(k.s), highEdge, `C5 ${Math.round(d(k.no).rightMm)}`));
// C6: block depth along the 교각축 (1200 for 분리 and 일체 alike).
parts.push(`<text x="${xRight + 60}" y="${highEdge + 110}" font-size="90" fill="#18212B">C6 ${BLOCK_DEPTH_MM} (교각축 방향 블록 폭)</text>`);
return `<svg viewBox="-1900 -1300 3800 2600" width="100%" style="max-width: 560px; height: auto" role="img" aria-label="교량받침 삽도 평면: 쌍 ${pair.frontNo}·${pair.backNo}, 교축·교각축 방향, C1–C6">${parts.join('')}</svg>`;
}