Files
Aislo/B06_Section/B06_Section_UI_Cross_Ford.ts
eomsangdonandClaude Opus 5 98380056cc fix(B06): 세월교 관 위 성토 채움 삭제
채움 색이 --color-chart-0(파랑 rgb 91,141,239)이라 관 선 색(rgb 79,142,247)과
겹쳐 관 일부처럼 읽혔다. 사용자 확정에 따라 면 자체를 지운다 — 배수관 세트와
같이 관 위는 비워 둔다.

- FordLayout.fill·FordSideLayout.roadFace와 전용 보조 함수 제거
- .b06-chart__ford-fill 스타일 삭제

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:12:55 +09:00

172 lines
6.6 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Cross_Ford.ts
* 세월교 측점 횡단 카드의 구체(측벽·바닥판·관·성토 채움) **오버레이 그리기**.
*
* 기하 계산은 `B06_Section_UI_Cross_Ford_Geom.ts`가 맡는다(700줄 제한 분리).
* 여기서는 계산 결과(`FordLayout`)를 SVG 도형으로 옮기기만 한다 — 치수 결정 금지.
*
* 그리는 순서 = 시공 순서: ① 바닥판 → ② 측벽(ㄴ형 집수정 재사용) → ③ 관 →
* ④ 계류측 성토부선·절토선. 관 위 성토 채움은 그리지 않는다(2026-08-25 사용자).
* ========================================================================== */
import type { FordLayout, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom";
import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types";
export {
computeFordLayout,
DEFAULT_FORD_ADJUST,
DEFAULT_FORD_WALL_ADJUST,
} from "./B06_Section_UI_Cross_Ford_Geom";
export type {
FordAdjust,
FordLayout,
FordSideLayout,
FordWallAdjust,
FordWallRole,
} from "./B06_Section_UI_Cross_Ford_Geom";
const SVG_NS = "http://www.w3.org/2000/svg";
/** 벽 강조를 카드 재생성 없이 갈아 끼우는 setter. */
export type FordHighlightSetter = (key: FordWallRole | null) => void;
/**
* 세월교 구체 오버레이 — computeFordLayout 결과를 그린다.
*
* `onSelectWall`이 오면 측벽이 **선택 대상**이 된다(집수정과 같은 규칙).
* 클릭은 여기서 멈춘다 — 밑에 깔린 면적 밴드로 번지면 강조가 딴 것으로 바뀐다.
*/
export function appendFordOverlay(
layer: SVGElement,
layout: FordLayout,
x: (offset: number) => number,
toDisplayY: (elevation: number) => number,
onSelectWall?: (key: FordWallRole) => void,
): FordHighlightSetter {
const { ford } = layout;
const toXY = (point: OffsetPoint): [number, number] => [
x(point.offset),
toDisplayY(point.elevation),
];
const polygon = (
points: OffsetPoint[],
className: string,
tooltip: string,
): SVGPolygonElement => {
const shape = document.createElementNS(SVG_NS, "polygon");
shape.setAttribute("points", points.map((point) => toXY(point).join(",")).join(" "));
shape.setAttribute("class", className);
const title = document.createElementNS(SVG_NS, "title");
title.textContent = tooltip;
shape.append(title);
return shape;
};
const planLine = (points: OffsetPoint[], tooltip: string): void => {
if (points.length < 2) return;
const line = document.createElementNS(SVG_NS, "polyline");
line.setAttribute("points", points.map((point) => toXY(point).join(",")).join(" "));
// 성토·절토 접속선은 공사 계획선 — 설계선과 같은 보라 실선.
line.setAttribute("class", "b06-chart__design-cross");
const title = document.createElementNS(SVG_NS, "title");
title.textContent = tooltip;
line.append(title);
layer.append(line);
};
const wingText = (wing: {
installed: boolean;
length_m: number | null;
angle_deg: number | null;
}): string => (wing.installed ? `${wing.length_m ?? 0}${wing.angle_deg ?? 0}°` : "없음");
// ① 바닥판 — 두 측벽 사이를 잇는 판. 각 측 바깥 연장은 측벽 부재로 함께 그린다.
layer.append(
polygon(
layout.slabBridge,
"b06-chart__ford-slab",
`세월교 바닥판 — 길이 ${layout.slabLengthM.toFixed(2)}m · 두께 ${ford.slab_thickness_m.toFixed(2)}m` +
` (날개벽 유입 ${wingText(ford.wing_in)} / 유출 ${wingText(ford.wing_out)} 투영 연장 반영)`,
),
);
// ② 측벽 — ㄴ형 집수정 부재(기운 벽 + 바닥판)를 좌우에 하나씩.
const walls = new Map<FordWallRole, SVGPolygonElement[]>();
for (const side of layout.sides) {
const role = side.role === "inlet" ? "유입" : "유출";
const shapes: SVGPolygonElement[] = [];
for (const part of side.parts) {
const shape = polygon(
part.points,
`b06-chart__culvert-basin${onSelectWall ? " is-selectable" : ""}`,
`${role} 측벽(ㄴ형) ${part.kind === "wall" ? "벽" : "바닥판"} — 높이 ${side.heightM.toFixed(2)}m` +
` · 두께 ${ford.wall_thickness_m.toFixed(2)}m` +
(onSelectWall ? " (클릭 후 조정창에서 높이·좌우·상하 조정)" : ""),
);
if (onSelectWall) {
shape.addEventListener("click", (event) => {
event.stopPropagation();
onSelectWall(side.role);
});
}
shapes.push(shape);
layer.append(shape);
}
walls.set(side.role, shapes);
}
// ③ 관 — 바닥판 상면에 안힌다. 단면엔 1개만, 련수는 라벨로 알린다.
layer.append(
polygon(
layout.pipe.outer,
"b06-chart__culvert-pipe b06-chart__culvert-pipe--outer",
`${layout.label.text} — 관 밑이 바닥판 상면`,
),
polygon(layout.pipe.inner, "b06-chart__culvert-pipe", layout.label.text),
);
// ④ 계류측 마감선 — 구조물이 원지반 위면 성토부선, 안이면 절토선(집수정과 같은 체계).
for (const side of layout.sides) {
const role = side.role === "inlet" ? "유입" : "유출";
for (const segment of side.fillSegments) {
if (segment.kind === "cut") continue;
planLine(
segment.points,
`${role} 측벽 계류측 성토부선 — 사면길이 ${segment.lengthM.toFixed(2)}m`,
);
}
if (side.cutLine) {
planLine([side.cutLine.from, side.cutLine.to], `${role} 측벽 절토선(계획선)`);
}
}
// 관·채움이 벽 위에 겹쳐 클릭을 가로챈다 — 투명 겹면을 맨 위에 얹어 벽을 고른다
// (배수관 기슭막이 `culvert-revet-hit`와 같은 수법).
if (onSelectWall) {
for (const side of layout.sides) {
const wall = side.parts.find((part) => part.kind === "wall");
if (!wall) continue;
const hit = polygon(wall.points, "b06-chart__culvert-revet-hit", "");
hit.addEventListener("click", (event) => {
event.stopPropagation();
onSelectWall(side.role);
});
layer.append(hit);
}
}
const label = document.createElementNS(SVG_NS, "text");
const [labelX, labelY] = toXY(layout.label.at);
label.setAttribute("x", String(labelX));
label.setAttribute("y", String(labelY));
label.setAttribute("text-anchor", "middle");
label.setAttribute("class", "b06-chart__culvert-label");
label.textContent = layout.label.text;
layer.append(label);
return (key: FordWallRole | null): void => {
for (const [role, shapes] of walls) {
for (const shape of shapes) shape.classList.toggle("is-active", role === key);
}
};
}