"""구조물도 상단 그림 — **골막이 · 떼흙막이 · 바닥막이** (정본 개소당·㎡당 구조물, PLAN 12장). ⚠ 치수는 **표가 쓴 그 한 벌**에서 읽음 — 골막이 `erosion_check_section` · 떼흙막이 `SOIL_GUARD_SOD.dims` · 바닥막이 `BED_SILL_FORMS` + 산출 조건 기초잡석 두께. ⚠ 사유 글자도 표 사유와 같은 상수(`BED_SILL_TRENCH_NOTE` 등). ⚠ 좌표는 m · 위가 +y · 모양 목록 규약은 `B08_Quantity_Engine_StructureFigure` 와 같음. """ from __future__ import annotations import math from typing import Any from B08_Quantity.B08_Quantity_Engine_StructureFigure import ( _LABEL_SIZE, SCALE_MM_PER_M, _path, _text, ) def _title(sheet: dict[str, Any], y: float) -> dict[str, Any]: return _text( f"{sheet.get('title') or ''} (축척 1/{int(1000 / SCALE_MM_PER_M)})", (0.0, y), "left", _LABEL_SIZE, ) def _front_with_trench( top: float, bottom: float, height: float, depth: float, x0: float = 0.0 ) -> list[dict[str, Any]]: """정면 사다리꼴(윗변 `top` · 밑변 `bottom` 가운데 정렬) + 사면 둘·밑변을 따라 판 띠(점선).""" inset = (top - bottom) / 2 corners = [(x0, height), (x0 + inset, 0.0), (x0 + inset + bottom, 0.0), (x0 + top, height)] shapes = [_path(corners + [corners[0]], "wall")] if depth > 0: # 사면 둘·밑변을 바깥으로 `depth` 만큼 민 선 — 이웃 변의 교점으로 잇음. normals = [] for (ax, ay), (bx, by) in zip(corners[:3], corners[1:4]): length = math.hypot(bx - ax, by - ay) normals.append(((by - ay) / length, -(bx - ax) / length)) offset = [] for index, (px, py) in enumerate(corners): used = [normals[i] for i in (index - 1, index) if 0 <= i < 3] nx = sum(n[0] for n in used) ny = sum(n[1] for n in used) dot = used[0][0] * nx + used[0][1] * ny offset.append((px + nx * depth / dot, py + ny * depth / dot)) shapes.append(_path(offset, "guide", dash=True)) return shapes def _circle(cx: float, cy: float, r: float) -> list[tuple[float, float]]: return [ (cx + r * math.cos(math.tau * i / 24), cy + r * math.sin(math.tau * i / 24)) for i in range(25) ] def erosion_check_figure(sheet: dict[str, Any]) -> list[dict[str, Any]]: """골막이 — 정면(사다리꼴 · 방수로 관 · 바닥파기 띠) + 옆 단면(상부·하부 두께).""" from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import erosion_check_section geo = erosion_check_section(float(sheet.get("height_m") or 0.0), sheet.get("options") or {}) top, bottom, height = geo["top_m"], geo["bottom_m"], geo["height_m"] shapes = _front_with_trench(top, bottom, height, geo["trench_depth"]) if geo["spillway"]: shapes.append(_path(_circle(top / 2, geo["pipe_r"], geo["pipe_r"]), "guide")) # 옆 단면 — 뒷면 수직 · 앞면이 하부 두께에서 상부 두께로 좁아짐(표의 두께식 그대로). x0 = top + 1.2 shapes.append( _path( [ (x0, 0.0), (x0, height), (x0 + geo["top_t"], height), (x0 + geo["bottom_t"], 0.0), (x0, 0.0), ], "wall", ) ) right = x0 + geo["bottom_t"] + 0.3 shapes += [ _title(sheet, height + 0.55), _text(f"상장 ⓐ {top:g} m", (top / 2, height + 0.14), "center"), _text(f"하장 ⓑ {bottom:g} m", (top / 2, -geo["trench_depth"] - 0.18), "center"), _text(f"H = {height:g} m", (-0.3 - geo["trench_depth"], height / 2), "right"), _text( f"상부 {geo['top_t']:.2f} · 하부 {geo['bottom_t']:.2f} · 평균 {geo['thickness']:.2f} m" f" (뒷길이 {geo['back_cm']}㎝ + 0.1H / 0.4H)", (right, height * 0.85), "left", ), _text(f"반수면 1:{geo['slope']:g}", (right, height * 0.62), "left"), _text( f"바닥파기 폭 {geo['trench_width']:.2f} × 깊이 {geo['trench_depth']:g} m" f" (사면장 {geo['slant']:g} × 2 + 하장)", (right, height * 0.39), "left", ), ] if geo["spillway"]: shapes.append( _text( f"방수로 파형강관 Ø{geo['pipe_r'] * 2:g} — 정면적에서 뺌", (right, height * 0.16), "left", ) ) return shapes def soil_guard_figure(sheet: dict[str, Any]) -> list[dict[str, Any]]: """떼흙막이 — 정본 평균 붙박이 치수의 정면(떼 두께 · 바닥파기 띠).""" from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import SOIL_GUARD_SOD d = SOIL_GUARD_SOD["dims"] top, bottom, height = d["top_m"], d["bottom_m"], d["height_m"] shapes = _front_with_trench(top, bottom, height, d["trench_depth_m"]) # 머리떼 — 윗변 위 떼 한 켜(두께 = 떼 폭 0.2 로 보임). shapes.append( _path( [(0.0, height), (0.0, height + d["sod_m"]), (top, height + d["sod_m"]), (top, height)], "guide", ) ) right = top + 0.4 shapes += [ _title(sheet, height + d["sod_m"] + 0.45), _text(f"상단 {top:g} m", (top / 2, height + d["sod_m"] + 0.12), "center"), _text(f"하단 {bottom:g} m", (top / 2, -d["trench_depth_m"] - 0.15), "center"), _text(f"H = {height:g} m", (-0.3, height / 2), "right"), _text( f"떼 20×20㎝ · 폭 {d['sod_m']:g} · 기슭(사면장) {d['slant_m']:g} m", (right, height * 0.9), "left", ), _text( f"바닥파기 폭 {d['trench_width_m']:g} × 깊이 {d['trench_depth_m']:g} m" " (기슭 × 2 + 하단)", (right, height * 0.45), "left", ), _text("치수 — 정본 「떼흙막이」 평균 붙박이(개소 제원을 안 봄)", (right, 0.0), "left"), ] return shapes def bed_sill_figure( sheet: dict[str, Any], rubble_thickness_m: float | None ) -> list[dict[str, Any]]: """바닥막이(돌붙임) — ㎡당 한 칸의 단면: 돌 켜 · (찰) 버림 · 기초잡석 · 터파기(돌 두께만).""" from B08_Quantity.B08_Quantity_Engine_UnitQuantity import RUBBLE_BASE_THICKNESS_M from B08_Quantity.B08_Quantity_Engine_UnitQuantity_Revetment import ( BED_SILL_FORMS, BED_SILL_TRENCH_NOTE, ) form = str((sheet.get("options") or {}).get("form") or "").strip() table = BED_SILL_FORMS[form] back = table["back_len_m"] blinding = table["blinding_m3_per_m2"] or 0.0 # ㎥/㎡ = 두께(m) rubble = RUBBLE_BASE_THICKNESS_M if rubble_thickness_m is None else float(rubble_thickness_m) rubble = rubble if blinding > 0 else 0.0 # 표도 버림이 선 곳에만 기초잡석을 세움 width = 1.0 shapes = [_path([(0.0, 0.0), (0.0, back), (width, back), (width, 0.0), (0.0, 0.0)], "wall")] if blinding > 0: shapes.append( _path([(0.0, 0.0), (0.0, -blinding), (width, -blinding), (width, 0.0)], "guide") ) if rubble > 0: shapes.append( _path( [ (0.0, -blinding), (0.0, -blinding - rubble), (width, -blinding - rubble), (width, -blinding), ], "guide", dash=True, ) ) # 터파기 — 표 식 그대로 면적 × 돌 두께(윗면에서 돌 켜 밑까지). shapes.append( _path( [(-0.15, back), (-0.15, 0.0), (width + 0.15, 0.0), (width + 0.15, back)], "guide", dash=True, ) ) right = width + 0.4 shapes += [ _title(sheet, back + 0.4), _text( f"{form} — 두께(뒷길이) {back:g} m · {table['stone_spec']}", (right, back * 0.75), "left", ), _text("폭 1 m 당(표는 ㎡당)", (width / 2, back + 0.12), "center"), _text(f"터파기 깊이 {back:g} m (면적 × 두께)", (right, back * 0.3), "left"), ] if blinding > 0: shapes.append(_text(f"버림 T={blinding:g} m", (right, -blinding / 2), "left")) if rubble > 0: shapes.append( _text(f"기초잡석 T={rubble:g} m — 산출 조건", (right, -blinding - rubble / 2), "left") ) shapes.append(_text(BED_SILL_TRENCH_NOTE, (0.0, -blinding - rubble - 0.25), "left")) return shapes