From bec7e841e0a20752fc0c6c3da74a2abfcc36b93d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 21:35:24 +0900 Subject: [PATCH] =?UTF-8?q?feat(B06):=20=ED=9A=A1=EB=8B=A8=EB=8F=84?= =?UTF-8?q?=EC=97=90=20=ED=84=B0=ED=8C=8C=EA=B8=B0=20=EC=84=A0=20=E2=80=94?= =?UTF-8?q?=20=EA=B4=80=EC=9D=80=20=EB=B2=95=EC=A0=95=20=ED=91=9C=EA=B0=92?= =?UTF-8?q?=20=ED=8F=AD,=20=EB=B2=BD=EC=9D=80=20=EC=8B=A4=EB=AC=B4=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B8=20=EC=88=98=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 확정 ④ 「터파기가 횡단도에서 표현이 안 된다 → 교본·실무문서로 구현 → 노티스 → 사용자가 선택」. - 관: 관 위에서 관 바닥까지 **표값 폭**으로 수직(KCS 44 40 10 그림 3.2-1). 표에 없는 관경이면 **안 그린다** — 폭을 지어내지 않는다. - 벽: 벽 바닥선 아래로 **수직**(정본 식에 기울기 항 없음). 폭 = 바닥폭 + 여유 0.2, 깊이 = 기초유 0.5 / 기초버림 0.1. - ⚠ `foundation` 이 비어 있으면 **안 그린다** — 고르기 전에는 근거가 없다. - 말풍선에 **근거의 급을 갈라** 적는다: 관은 「법정 표」, 벽은 「품셈에 규정이 없어 실무 관행으로 정한 값」. - 파선(`.b06-chart__excavation`)으로 구조물보다 먼저 그려 벽 밑에 깔린다. 화면 확인: 관 터파기 5개소, Φ1000 → 1.80m · Φ800 → 1.60m (표값 그대로). Co-Authored-By: Claude Opus 5 (1M context) --- B06_Section/B06_Section_Api_Types.ts | 3 + B06_Section/B06_Section_Engine_Culvert.py | 4 + B06_Section/B06_Section_UI_Cross_Culvert.ts | 31 +++++ .../B06_Section_UI_Cross_Excavation.ts | 119 ++++++++++++++++++ .../B06_Section_UI_Style_Cross_Areas.css | 9 ++ common_util/common_util_culvert_sets.ts | 2 + 6 files changed, 168 insertions(+) create mode 100644 B06_Section/B06_Section_UI_Cross_Excavation.ts diff --git a/B06_Section/B06_Section_Api_Types.ts b/B06_Section/B06_Section_Api_Types.ts index 45a7dab6..7fbca2a6 100644 --- a/B06_Section/B06_Section_Api_Types.ts +++ b/B06_Section/B06_Section_Api_Types.ts @@ -233,6 +233,9 @@ export interface CulvertSet { side?: string; /** 독립 기슭막이 다단 요청 수(1이면 단일 벽). */ tiers?: number; + /** 기초 축 — "기초유" | "기초버림"(실무 정본 탭 제목). **비어 있으면 터파기를 안 그린다** + * — 고르기 전에는 근거가 없다(2026-09-09 사용자 확정 ④). */ + foundation?: string | null; } /** 세월교 날개벽 한쪽 — 횡단면엔 안 보이고 바닥판 연장량만 넘긴다. */ diff --git a/B06_Section/B06_Section_Engine_Culvert.py b/B06_Section/B06_Section_Engine_Culvert.py index 865df085..de563489 100644 --- a/B06_Section/B06_Section_Engine_Culvert.py +++ b/B06_Section/B06_Section_Engine_Culvert.py @@ -263,6 +263,10 @@ def _revet_set(options: dict[str, Any] | None) -> dict[str, Any]: "hidden_pipe": True, "side": str(values.get("side") or "양쪽"), "tiers": int(_number(values.get("tiers"), 1.0) or 1), + # 기초 축(기초유/기초버림) — 터파기 그림·물량이 같이 쓴다. 비어 있으면 안 그린다. + "foundation": (str(values["foundation"]).strip() or None) + if values.get("foundation") is not None + else None, "pipe_kind": None, "diameter_m": 0.3, "min_cover_m": 0.0, diff --git a/B06_Section/B06_Section_UI_Cross_Culvert.ts b/B06_Section/B06_Section_UI_Cross_Culvert.ts index 94c24efe..37865f31 100644 --- a/B06_Section/B06_Section_UI_Cross_Culvert.ts +++ b/B06_Section/B06_Section_UI_Cross_Culvert.ts @@ -23,6 +23,11 @@ import type { PipeEnd, WallLayout, } from "./B06_Section_UI_Cross_Culvert_Geom"; +import { + appendPipeTrench, + appendWallTrench, + foundationChoice, +} from "./B06_Section_UI_Cross_Excavation"; import { appendWallHatch } from "./B06_Section_UI_Cross_Wall_Hatch"; // 기하 계산 진입점과 공개 상수·타입은 여기서 재수출한다 — B05(최소 토피)와 횡단 뷰가 @@ -162,6 +167,12 @@ export function appendCulvertOverlay( const revetShapes = new Map(); /** 집수정 부재 도형 — 유입 선택 강조에 쓴다(2026-08-22 사용자 ③). */ const basinShapes: SVGPolygonElement[] = []; + // 터파기는 구조물보다 **먼저** 그린다 — 파선이 벽 밑에 깔려야 도면처럼 보인다. + // 저장 제원 `foundation`(기초유/기초버림)이 비어 있으면 그리지 않는다 — 근거가 없다. + const wallFoundation = foundationChoice(culvert.foundation); + for (const { wall } of wallsInOrder) { + appendWallTrench(layer, wall, wallFoundation, x, toDisplayY); + } for (const { wall, key: wallKey } of wallsInOrder) { // 합성 단면(하부 사다리꼴 + 상부 평행사변형) — 상단 배면이 사면선 접점(사용자 ①·②). const revetShape = polygon( @@ -272,6 +283,26 @@ export function appendCulvertOverlay( const inletFinal = pushOut(pipeCorners.inlet, -1); const outletFinal = pushOut(pipeCorners.outlet, 1); + // 관 터파기 — 표값 폭으로 관 위 지반선에서 관 바닥까지 수직(KCS 44 40 10 그림 3.2-1). + if (!hidePipe) { + const invert = Math.min( + pipeCorners.inlet.bottom.elevation, + pipeCorners.outlet.bottom.elevation, + ); + const centerOffset = (pipeCorners.inlet.bottom.offset + pipeCorners.outlet.bottom.offset) / 2; + appendPipeTrench( + layer, + { + centerOffsetM: centerOffset, + invertM: invert, + topM: invert + diameter + culvert.min_cover_m, + diameterMm: Math.round(diameter * 1000), + }, + x, + toDisplayY, + ); + } + const kindLabel = culvert.pipe_kind ? `${culvert.pipe_kind} ` : ""; const wallThickness = pipeWallThicknessM(culvert.pipe_kind, diameter); const tip = diff --git a/B06_Section/B06_Section_UI_Cross_Excavation.ts b/B06_Section/B06_Section_UI_Cross_Excavation.ts new file mode 100644 index 00000000..d7d1120a --- /dev/null +++ b/B06_Section/B06_Section_UI_Cross_Excavation.ts @@ -0,0 +1,119 @@ +/* ============================================================================= + * B06_Section_UI_Cross_Excavation.ts + * 횡단도에 **터파기 선**을 얹는다 — 관·벽 두 갈래(2026-09-09 사용자 확정 ④). + * + * 왜 있나 — 「터파기가 현재 횡단도에서 표현이 안 된다」가 사용자 지적이었다. 수량을 + * 세기 전에 **도면에 그것이 보여야** 한다. + * + * ⚠ 근거의 급이 다르므로 **노티스를 갈라 적는다** — 값은 `common_util_excavation` 한 곳에서 + * 온다(파이썬 짝과 거울 시험으로 묶여 있다). 여기서는 **그리기만** 한다. + * 관 : 법정 시방서 표(KCS 44 40 10 그림 3.2-1) + * 벽 : 법정 근거 없음 — 실무 정본 식(구조도 기슭막이 xls) + * ⚠ **비탈을 주지 않는다 — 수직으로 그린다.** 정본 식에 기울기 항이 없다. + * ========================================================================== */ + +import { + BASIS_PIPE, + BASIS_WALL, + WALL_BLINDING_DEPTH_M, + WALL_BLINDING_WIDTH_M, + WALL_FOUNDATION_DEPTH_M, + WALL_FOUNDATION_WIDTH_M, + WALL_TRENCH_CLEARANCE_M, + pipeTrenchWidthM, +} from "@util/common_util_excavation"; +import type { WallLayout } from "./B06_Section_UI_Cross_Culvert_Types"; + +const SVG_NS = "http://www.w3.org/2000/svg"; + +/** 「기초유」인가 — 저장 제원 `foundation` 의 값. 비어 있으면 **그리지 않는다**(근거 없음). */ +export function foundationChoice(value: unknown): "기초유" | "기초버림" | null { + const text = typeof value === "string" ? value.trim() : ""; + if (text === "기초유") return "기초유"; + if (text === "기초버림") return "기초버림"; + return null; +} + +function trenchPath(layer: SVGElement, corners: Array<[number, number]>, tooltip: string): void { + const line = document.createElementNS(SVG_NS, "polyline"); + line.setAttribute("points", corners.map(([px, py]) => `${px},${py}`).join(" ")); + line.setAttribute("class", "b06-chart__excavation"); + const title = document.createElementNS(SVG_NS, "title"); + title.textContent = tooltip; + line.append(title); + layer.append(line); +} + +/** + * 관 터파기 — **표값 폭**으로 관 위 지반선에서 관 바닥까지 수직으로 판다. + * 표에 없는 관경이면 **아무것도 그리지 않는다**(폭을 지어내지 않는다). + */ +export function appendPipeTrench( + layer: SVGElement, + input: { + /** 관 중심 offset(m). */ + centerOffsetM: number; + /** 관 바닥(invert) 표고. */ + invertM: number; + /** 터파기 윗면 표고 — 관 위 지반(또는 노면)선. */ + topM: number; + diameterMm: number | null | undefined; + }, + x: (offset: number) => number, + toDisplayY: (elevation: number) => number, +): number | null { + const width = pipeTrenchWidthM(input.diameterMm); + if (width === null || !(input.topM > input.invertM)) return null; + const half = width / 2; + const left = input.centerOffsetM - half; + const right = input.centerOffsetM + half; + trenchPath( + layer, + [ + [x(left), toDisplayY(input.topM)], + [x(left), toDisplayY(input.invertM)], + [x(right), toDisplayY(input.invertM)], + [x(right), toDisplayY(input.topM)], + ], + `관 터파기 폭 ${width.toFixed(2)}m · 깊이 ${(input.topM - input.invertM).toFixed(2)}m\n` + + `Φ${Math.round(Number(input.diameterMm))}㎜ — ${BASIS_PIPE}`, + ); + return width; +} + +/** + * 벽 터파기 — 벽 바닥선 아래로 **수직**. 폭은 바닥 폭 + 여유 0.2, 깊이는 기초 몫. + * `foundation` 이 비어 있으면 **그리지 않는다** — 사용자가 고르기 전에는 근거가 없다. + */ +export function appendWallTrench( + layer: SVGElement, + wall: WallLayout, + foundation: "기초유" | "기초버림" | null, + x: (offset: number) => number, + toDisplayY: (elevation: number) => number, +): { widthM: number; depthM: number } | null { + if (foundation === null) return null; + const hasFoundation = foundation === "기초유"; + const depth = hasFoundation ? WALL_FOUNDATION_DEPTH_M : WALL_BLINDING_DEPTH_M; + // 폭: 기초 몫은 정본이 정한 값(0.9 / 0.7), 다만 벽 바닥이 그보다 넓으면 그 폭 + 여유. + const baseWidth = Math.abs(wall.bottomFront.offset - wall.bottomBack.offset); + const minWidth = hasFoundation ? WALL_FOUNDATION_WIDTH_M : WALL_BLINDING_WIDTH_M; + const width = Math.max(minWidth, baseWidth + (hasFoundation ? WALL_TRENCH_CLEARANCE_M : 0)); + const center = (wall.bottomFront.offset + wall.bottomBack.offset) / 2; + const top = Math.max(wall.bottomFront.elevation, wall.bottomBack.elevation); + const bottom = top - depth; + const left = center - width / 2; + const right = center + width / 2; + trenchPath( + layer, + [ + [x(left), toDisplayY(top)], + [x(left), toDisplayY(bottom)], + [x(right), toDisplayY(bottom)], + [x(right), toDisplayY(top)], + ], + `${foundation} 터파기 폭 ${width.toFixed(2)}m · 깊이 ${depth.toFixed(2)}m (수직)\n` + + `품셈에 규정이 없어 실무 관행으로 정한 값 — ${BASIS_WALL}`, + ); + return { widthM: width, depthM: depth }; +} diff --git a/B06_Section/B06_Section_UI_Style_Cross_Areas.css b/B06_Section/B06_Section_UI_Style_Cross_Areas.css index d6728a14..535a53c9 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Areas.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Areas.css @@ -379,3 +379,12 @@ stroke-dasharray: 4 3; stroke-linejoin: round; } + +/* 터파기 선(2026-09-09 사용자 확정 ④) — **파선**으로 그린다. 구조물 실체가 아니라 + 「여기를 판다」는 표시이고, 근거의 급(법정 표 / 실무 관행)은 말풍선에 적힌다. */ +.b06-chart__excavation { + fill: none; + stroke: #8a5c3c; + stroke-dasharray: 4 3; + stroke-width: 1; +} diff --git a/common_util/common_util_culvert_sets.ts b/common_util/common_util_culvert_sets.ts index 1b67957e..51e77620 100644 --- a/common_util/common_util_culvert_sets.ts +++ b/common_util/common_util_culvert_sets.ts @@ -174,6 +174,8 @@ function revetSet(values: Record): CulvertSetSpec { hidden_pipe: true, side: String(values.side ?? "양쪽"), tiers: Math.trunc(num(values.tiers, 1.0) || 1), + // 기초 축(기초유/기초버림) — 터파기 그림·물량이 같이 쓴다. 비어 있으면 안 그린다. + foundation: text(values.foundation), pipe_kind: null, diameter_m: 0.3, min_cover_m: 0.0,