From bec7e841e0a20752fc0c6c3da74a2abfcc36b93d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 21:35:24 +0900 Subject: [PATCH 1/2] =?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, From ae52ba76992ba73e0c7a1957dd781c08e71198c4 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 21:48:54 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(B05=C2=B7B06):=20=ED=84=B0=ED=8C=8C?= =?UTF-8?q?=EA=B8=B0=20=EC=B6=95=EC=9D=84=20=EA=B4=80=EC=97=90=EB=8F=84=20?= =?UTF-8?q?=C2=B7=20=EB=8F=85=EB=A6=BD=20=EB=B2=BD=EA=B9=8C=EC=A7=80=20?= =?UTF-8?q?=EC=9E=87=EA=B3=A0,=20=EA=B3=84=EC=88=98=C2=B7=EA=B0=95?= =?UTF-8?q?=EB=8F=84=20=EC=B9=B8=20=EC=8B=A0=EC=84=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `pipe.revet_foundation`(기슭막이 기초) 신설 — **유입·유출 한 칸**. 한 공사에서 한쪽만 기초유로 하는 일이 드물어 폼을 둘로 안 늘렸다. 실무가 갈라 쓰면 그때 나눌 것. - 독립 기슭막이·돌쌓기 갈래도 이음: 구조물 정본 `foundation` → `RevetmentSpec` → `RevetmentLayout.foundation` → 터파기 그림. ⚠ 이름을 저장 칸과 **같은 글자**로 뒀다. - ⑨ `stone_coeff_basis`(품셈/실무 관행) · ⑩ `fill_concrete_mpa`(180/210) 칸 신설. ⚠ **기본값 없음** — 「비어 있음 = 아직 안 정함」이고 기본값(⑨ 품셈 · ⑩ 210)은 계산 쪽이 갖는다. - 저장 왕복 확인: PUT 200 → 되읽기에서 "실무 관행"·"180"·"기초유" 그대로. Co-Authored-By: Claude Opus 5 (1M context) --- B05_Profile/B05_Profile_Structure_Types.json | 126 ++++++++++++++++++ B06_Section/B06_Section_Engine_Culvert.py | 10 +- .../B06_Section_Engine_Structures_Wall.py | 3 + B06_Section/B06_Section_UI_Cross_Revetment.ts | 11 ++ common_util/common_util_culvert_sets.ts | 4 + 5 files changed, 151 insertions(+), 3 deletions(-) diff --git a/B05_Profile/B05_Profile_Structure_Types.json b/B05_Profile/B05_Profile_Structure_Types.json index c3fbe39f..4f4e5f58 100644 --- a/B05_Profile/B05_Profile_Structure_Types.json +++ b/B05_Profile/B05_Profile_Structure_Types.json @@ -179,6 +179,15 @@ "default": 2.5, "required": false, "phase": "detail" + }, + { + "key": "revet_foundation", + "label": "기슭막이 기초", + "input": "select", + "choices": ["기초유", "기초버림"], + "default": null, + "required": false, + "phase": "detail" } ] }, @@ -202,6 +211,15 @@ "default": 2.0, "required": false }, + { + "key": "revet_foundation", + "label": "기슭막이 기초", + "input": "select", + "choices": ["기초유", "기초버림"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "body_height_m", "label": "본체 높이", @@ -779,6 +797,24 @@ "required": false, "phase": "detail" }, + { + "key": "stone_coeff_basis", + "label": "야면석 계수", + "input": "select", + "choices": ["품셈", "실무 관행"], + "default": null, + "required": false, + "phase": "detail" + }, + { + "key": "fill_concrete_mpa", + "label": "채움 강도", + "input": "select", + "choices": ["180", "210"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "face_slope_ratio", "label": "전면 기울기 (1:n 의 n)", @@ -879,6 +915,24 @@ "required": false, "phase": "detail" }, + { + "key": "stone_coeff_basis", + "label": "야면석 계수", + "input": "select", + "choices": ["품셈", "실무 관행"], + "default": null, + "required": false, + "phase": "detail" + }, + { + "key": "fill_concrete_mpa", + "label": "채움 강도", + "input": "select", + "choices": ["180", "210"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "face_slope_ratio", "label": "전면 기울기 (1:n 의 n)", @@ -1044,6 +1098,24 @@ "required": false, "phase": "detail" }, + { + "key": "stone_coeff_basis", + "label": "야면석 계수", + "input": "select", + "choices": ["품셈", "실무 관행"], + "default": null, + "required": false, + "phase": "detail" + }, + { + "key": "fill_concrete_mpa", + "label": "채움 강도", + "input": "select", + "choices": ["180", "210"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "face_slope_ratio", "label": "전면 기울기 (1:n 의 n)", @@ -1166,6 +1238,24 @@ "required": false, "phase": "detail" }, + { + "key": "stone_coeff_basis", + "label": "야면석 계수", + "input": "select", + "choices": ["품셈", "실무 관행"], + "default": null, + "required": false, + "phase": "detail" + }, + { + "key": "fill_concrete_mpa", + "label": "채움 강도", + "input": "select", + "choices": ["180", "210"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "face_slope_ratio", "label": "전면 기울기 (1:n 의 n)", @@ -1228,6 +1318,24 @@ "required": false, "phase": "detail" }, + { + "key": "stone_coeff_basis", + "label": "야면석 계수", + "input": "select", + "choices": ["품셈", "실무 관행"], + "default": null, + "required": false, + "phase": "detail" + }, + { + "key": "fill_concrete_mpa", + "label": "채움 강도", + "input": "select", + "choices": ["180", "210"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "face_slope_ratio", "label": "전면 기울기 (1:n 의 n)", @@ -1353,6 +1461,24 @@ "required": false, "phase": "detail" }, + { + "key": "stone_coeff_basis", + "label": "야면석 계수", + "input": "select", + "choices": ["품셈", "실무 관행"], + "default": null, + "required": false, + "phase": "detail" + }, + { + "key": "fill_concrete_mpa", + "label": "채움 강도", + "input": "select", + "choices": ["180", "210"], + "default": null, + "required": false, + "phase": "detail" + }, { "key": "face_slope_ratio", "label": "전면 기울기 (1:n 의 n)", diff --git a/B06_Section/B06_Section_Engine_Culvert.py b/B06_Section/B06_Section_Engine_Culvert.py index de563489..5f18bbd2 100644 --- a/B06_Section/B06_Section_Engine_Culvert.py +++ b/B06_Section/B06_Section_Engine_Culvert.py @@ -213,6 +213,12 @@ def _culvert_set(options: dict[str, Any] | None) -> dict[str, Any]: "pipe_kind": str(kind) if kind else None, "diameter_m": round((diameter_mm or 1000.0) / 1000.0, 3), "min_cover_m": MIN_PIPE_COVER_M, + # 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다(2026-09-09). 한쪽만 기초유로 + # 하는 일이 드물어 폼을 둘로 늘리지 않았다. 실무가 갈라 쓰면 그때 나눌 것. + "foundation": ( + str(values.get("revet_foundation") or defaults.get("revet_foundation") or "").strip() + or None + ), "inlet": _side_spec(values, defaults, "inlet"), "outlet": _side_spec(values, defaults, "outlet"), } @@ -264,9 +270,7 @@ def _revet_set(options: dict[str, Any] | None) -> dict[str, Any]: "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, + "foundation": (str(values.get("foundation") or "").strip() or None), "pipe_kind": None, "diameter_m": 0.3, "min_cover_m": 0.0, diff --git a/B06_Section/B06_Section_Engine_Structures_Wall.py b/B06_Section/B06_Section_Engine_Structures_Wall.py index d4b3a70d..637ebcec 100644 --- a/B06_Section/B06_Section_Engine_Structures_Wall.py +++ b/B06_Section/B06_Section_Engine_Structures_Wall.py @@ -73,6 +73,9 @@ def load_wall_structures(project_root: Path) -> list[dict[str, Any]]: # C군 폼에는 설치 측 칸이 없다 — 비워 두면 화면이 **성토가 나는 쪽**으로 # 세운다(`computeRevetmentLayout`). 사용자가 정하고 싶어지면 그때 칸을 낸다. "side": options.get("side"), + # 기초 축(기초유/기초버림) — 터파기 그림·물량이 같이 쓴다. + # 비어 있으면 화면이 터파기를 안 그린다(근거 없음). + "foundation": options.get("foundation"), "tiers": options.get("tiers"), "lift_m": options.get("lift_m"), "shift_m": options.get("shift_m"), diff --git a/B06_Section/B06_Section_UI_Cross_Revetment.ts b/B06_Section/B06_Section_UI_Cross_Revetment.ts index 5c45ff3d..d6f4578c 100644 --- a/B06_Section/B06_Section_UI_Cross_Revetment.ts +++ b/B06_Section/B06_Section_UI_Cross_Revetment.ts @@ -37,6 +37,7 @@ import { type WallAdjust, type WallLayout, } from "./B06_Section_UI_Cross_Culvert_Types"; +import { appendWallTrench, foundationChoice } from "./B06_Section_UI_Cross_Excavation"; import { appendPlanLine, buildRevetWallGeometry, drawRevetWall } from "./B06_Section_UI_Cross_Wall"; const SVG_NS = "http://www.w3.org/2000/svg"; @@ -53,6 +54,8 @@ export interface RevetmentSpec { height_m?: number | null; /** 사용자가 고른 설치 측 — "좌" | "우". */ side?: string | null; + /** 기초 축 — "기초유" | "기초버림". **비어 있으면 터파기를 안 그린다**(근거 없음). */ + foundation?: string | null; /** 단 수(다단). 1이면 단일 벽. */ tiers?: number | null; /** (구 모델) 기준 올림·좌우 이동 — 배관식 전환 뒤 자리는 조정창 4축(x·d)이 정한다. */ @@ -89,6 +92,8 @@ export interface RevetmentLayout { top: RevetPoint; /** 벽 목록(1단 + 다단) — 배관 벽과 같은 `WallLayout`, 그리기가 그대로 쓴다. */ walls: WallLayout[]; + /** 기초 축 — 저장 칸과 **같은 글자**(`foundation`). 터파기 그림이 이 값으로 갈린다. */ + foundation: string | null; /** 벽 사이·벽 아래 성토부선(배관 다단과 같은 체계). */ fillSegments: OutletFillSegment[]; /** 성토 설계선을 벽 상단에서 끊고 노견→벽 성토선을 대신 그리는 트림. */ @@ -304,6 +309,7 @@ export function computeRevetmentLayout( requestedTiers, top: jt, walls, + foundation: typeof spec.foundation === "string" ? spec.foundation.trim() || null : null, fillSegments: extras.segments, designTrim, appliedAdjust: { x: appliedX, d: appliedDrop, h: adjust?.h ?? null, m: null }, @@ -359,6 +365,11 @@ export function appendRevetmentOverlay( ` · 성토 물매 1:${(segment.ratio ?? 1.2).toFixed(2)}`, ); } + // 터파기는 벽보다 **먼저** — 파선이 벽 밑에 깔린다. 기초 축이 비어 있으면 안 그린다. + const trenchFoundation = foundationChoice(layout.foundation); + for (const wall of layout.walls) { + appendWallTrench(svg, wall, trenchFoundation, x, y); + } const drawn: SVGPolygonElement[] = []; layout.walls.forEach((wall, index) => { const keyId = index === 0 ? "own" : `own-extra${index - 1}`; diff --git a/common_util/common_util_culvert_sets.ts b/common_util/common_util_culvert_sets.ts index 51e77620..165ed43c 100644 --- a/common_util/common_util_culvert_sets.ts +++ b/common_util/common_util_culvert_sets.ts @@ -138,6 +138,10 @@ function culvertSet( pipe_kind: text(options.pipe_kind) ?? text(defaults.pipe_kind), diameter_m: pipeDiameterM(diameterMm), min_cover_m: MIN_PIPE_COVER_M, + // 관 기슭막이 기초 축 — **유입·유출 한 칸으로** 받는다. 한 공사에서 한쪽만 기초유로 + // 하는 일은 드물어 폼을 둘로 늘리지 않았다(2026-09-09). 실무가 갈라 쓰는 것이 + // 확인되면 그때 나눌 것. + foundation: text(options.revet_foundation) ?? text(defaults.revet_foundation), inlet: sideSpec(options, defaults, "inlet"), outlet: sideSpec(options, defaults, "outlet"), };