Files
Pier_v1/src/view/bearing-plan-svg.ts
T
ypshimandClaude Haiku 5.5 c4d4957dd2 feat(bearing): show each bearing's 방위 in the plan and turn the overlays with it
- Blocks, solplates, and the dashed drawing outlines are turned by the bearing's
  방위 (rowSkewDeg), the same turn as the 3D block; each bearing is labelled
  with its 방위 in degrees.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-26 13:24:20 +09:00

110 lines
7.0 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';
/** Drawing values (A, B of the solplate, mm) of the front and back bearings, to overlay on the model (승인 2026-10-26). */
export interface BearingPlanOverlay {
front: { A: number; B: number };
back: { A: number; B: number };
}
/** 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. The model's solplates are solid, the drawing values
* dashed. 일체 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, overlay?: BearingPlanOverlay): 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 model's plan (A across the 교축, B along the 교각축), centred on each bearing.
for (const b of [f, k]) {
if (b.plan) {
const { A, B } = b.plan;
parts.push(rect(X(b.n), Y(b.s), A, B, b.rowSkewDeg, `fill="#EEF3F7" stroke="#2F6DB5" stroke-width="10"`));
}
}
// Drawing values overlaid (dashed) on the same bearings (#1 겹쳐 보이게).
if (overlay) {
for (const [b, v] of [[f, overlay.front], [k, overlay.back]] as const) {
parts.push(rect(X(b.n), Y(b.s), v.A, v.B, b.rowSkewDeg, `fill="none" stroke="#B4531F" stroke-width="10" stroke-dasharray="40 25"`));
}
}
// 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)}`));
return `<svg viewBox="-1900 -1300 3800 2600" width="100%" style="max-width: 560px; height: auto" role="img" aria-label="교량받침 상세 평면: 쌍 ${pair.frontNo}·${pair.backNo}, 블록 한 줄, 교축·교각축 방향, C1·C2·C3, C4·C5, 도면값 겹쳐 보기">${parts.join('')}</svg>`;
}