BOX암거를 세월교와 같은 계류 방향 절단면으로 그리고, 3D는 속이 빈 서피스 셸로 만든다. 관통 컷·중공 튜브는 배수관 세트와 세월교에도 함께 적용한다. B06 횡단도 - `_box_set()` — 내공 폭·높이(B05 옵션), 부재 두께 세월교 승계(측벽 0.2·상하판 0.3), 복토 0.5m(별표2 교차 참조), 날개벽 투영 저판 연장 - 상판 윗면 = 노면 − 복토, 구체는 수평 설치(교본 3장). 계류 하상과의 고저차는 툴팁 표기 - 구체가 노면 아래 묻히므로 계획선은 자르지 않는다(세월교와 다른 점) 3D - 로프트가 **링별 폴리곤**을 받는다 — 구조물을 위·아래 조각으로 갈라 링마다 틈을 벌려 빈 공간을 만든다. 틈 0이면 속 찬 단면 그대로라 토폴로지가 유지된다 - BOX = 내공 구간만 틈을 벌려 측벽이 저절로 생기는 셸 + 날개벽 4매(날개 축 프레임) - 관통 보어 = 링 y에서 틈 높이 2·√(r²−(y−y₀)²) → 배관 외측 기준 원형 구멍. 기슭막이·집수정·세월교 측벽에 적용 - 배관은 중공 튜브(바깥·안쪽 원통 + 양 끝 고리)로 내부 유로를 보인다 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
3.6 KiB
TypeScript
94 lines
3.6 KiB
TypeScript
/* =============================================================================
|
||
* B06_Section_UI_Cross_Box.ts
|
||
* BOX암거 측점 횡단 카드의 구체(상판·저판·내공) **오버레이 그리기**.
|
||
*
|
||
* 기하 계산은 `B06_Section_UI_Cross_Box_Geom.ts`가 맡는다(700줄 제한 분리).
|
||
* 여기서는 계산 결과(`BoxLayout`)를 SVG 도형으로 옮기기만 한다 — 치수 결정 금지.
|
||
*
|
||
* 그리는 순서 = 시공 순서: ① 저판 → ② 상판 → ③ 내공 윤곽·라벨.
|
||
* 콘크리트 색은 세월교 바닥판 클래스를 그대로 쓴다.
|
||
* ========================================================================== */
|
||
|
||
import type { BoxLayout } from "./B06_Section_UI_Cross_Box_Geom";
|
||
import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types";
|
||
|
||
export { computeBoxLayout } from "./B06_Section_UI_Cross_Box_Geom";
|
||
export type { BoxLayout } from "./B06_Section_UI_Cross_Box_Geom";
|
||
|
||
const SVG_NS = "http://www.w3.org/2000/svg";
|
||
|
||
/** BOX암거 구체 오버레이 — computeBoxLayout 결과를 그린다. */
|
||
export function appendBoxOverlay(
|
||
layer: SVGElement,
|
||
layout: BoxLayout,
|
||
x: (offset: number) => number,
|
||
toDisplayY: (elevation: number) => number,
|
||
): void {
|
||
const { box } = 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 wingText = (wing: {
|
||
installed: boolean;
|
||
length_m: number | null;
|
||
angle_deg: number | null;
|
||
}): string => (wing.installed ? `${wing.length_m ?? 0}m·${wing.angle_deg ?? 0}°` : "없음");
|
||
|
||
// ① 저판 — 날개벽 투영만큼 계류 상·하류로 길어진다.
|
||
layer.append(
|
||
polygon(
|
||
layout.bottomSlab,
|
||
"b06-chart__ford-slab",
|
||
`BOX암거 저판 — 길이 ${layout.slabLengthM.toFixed(2)}m · 두께 ${box.slab_thickness_m.toFixed(2)}m` +
|
||
` (날개벽 유입 ${wingText(box.wing_in)} / 유출 ${wingText(box.wing_out)} 투영 연장 반영)` +
|
||
(layout.bedGapM > 0.05
|
||
? ` · 계류 하상보다 ${layout.bedGapM.toFixed(2)}m 높음`
|
||
: layout.bedGapM < -0.05
|
||
? ` · 계류 하상보다 ${Math.abs(layout.bedGapM).toFixed(2)}m 낮음(터파기)`
|
||
: ""),
|
||
),
|
||
);
|
||
|
||
// ② 상판 — 윗면이 노면 아래 복토 두께만큼 내려온다.
|
||
layer.append(
|
||
polygon(
|
||
layout.topSlab,
|
||
"b06-chart__ford-slab",
|
||
`BOX암거 상판 — 두께 ${box.top_thickness_m.toFixed(2)}m · 윗면이 노면 −${box.cover_m.toFixed(2)}m(복토)`,
|
||
),
|
||
);
|
||
|
||
// ③ 내공(유로) — 면은 비우고 윤곽만 점선으로 두른다.
|
||
const barrel = polygon(
|
||
layout.barrel,
|
||
"b06-chart__box-barrel",
|
||
`BOX암거 내공(유로) ${box.inner_width_m.toFixed(1)}×${box.inner_height_m.toFixed(1)}m` +
|
||
` · 측벽 두께 ${box.wall_thickness_m.toFixed(2)}m`,
|
||
);
|
||
layer.append(barrel);
|
||
|
||
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);
|
||
}
|