"""구조물도 상단 그림 — **골막이 · 떼흙막이 · 바닥막이** (정본 개소당·㎡당 구조물, 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 _rect(x0: float, y0: float, x1: float, y1: float) -> list[tuple[float, float]]: return [(x0, y0), (x0, y1), (x1, y1), (x1, y0), (x0, y0)] def inlet_basin_figure(sheet: dict[str, Any], entry: dict[str, Any]) -> list[dict[str, Any]]: """집수정(□형) — 긴 벽 정면(바닥기초 · 관 · 방수로 · 터파기) + 평면(바깥·안 벽). 치수는 관측 줄 `section`(표 수량이 나온 그 치수). 관·방수로 **자리**는 원문 입체도 모식. """ section = entry["section"] inner, wall, base = section["inner"], section["wall_m"], section["base_m"] spill, dig = section["spillway"], section["dig"] length, width = inner["length_m"] + 2 * wall, inner["width_m"] + 2 * wall height = inner["height_m"] top = base + height radius = section["pipe_d_m"] / 2 mid = length / 2 notch_half_top, notch_half_bottom = spill["top_m"] / 2, spill["bottom_m"] / 2 # 긴 벽 정면 — 윗변 가운데에 방수로 한 곳(모두 `count` 곳). outline = [ (0.0, 0.0), (0.0, top), (mid - notch_half_top, top), (mid - notch_half_bottom, top - spill["depth_m"]), (mid + notch_half_bottom, top - spill["depth_m"]), (mid + notch_half_top, top), (length, top), (length, 0.0), (0.0, 0.0), ] dig_len = length + dig["extra_length_m"] shapes = [ _path(outline, "wall"), _path([(0.0, base), (length, base)], "guide"), _path(_circle(mid, base + radius, radius), "guide"), _path( _rect(-dig["extra_length_m"] / 2, 0.0, length + dig["extra_length_m"] / 2, top), "guide", dash=True, ), ] # 평면 — 바깥 벽 · 안 벽 · 터파기 평면(점선). px = length + 1.2 shapes += [ _path(_rect(px, 0.0, px + length, width), "wall"), _path(_rect(px + wall, wall, px + length - wall, width - wall), "guide"), _path( _rect( px - dig["extra_length_m"] / 2, (width - dig["width_m"]) / 2, px + length + dig["extra_length_m"] / 2, (width + dig["width_m"]) / 2, ), "guide", dash=True, ), ] right = px + length + 0.5 shapes += [ _title(sheet, max(top, dig["width_m"]) + 0.55), _text("정면(긴 벽)", (mid, top + 0.15), "center"), _text("평면", (px + length / 2, width + 0.15 + (dig["width_m"] - width) / 2), "center"), _text( f"안 {inner['length_m']:g}×{inner['width_m']:g}×{height:g} · 벽 {wall:g} · 바닥기초" f" {length:g}×{width:g}×{base:g} m", (right, top * 0.9), "left", ), _text(f"관 Ø{section['pipe_d_m']:g}", (right, top * 0.65), "left"), _text( f"방수로 {spill['count']}곳 (상장 {spill['top_m']:g} · 하장 {spill['bottom_m']:g}" f" · 고 {spill['depth_m']:g})", (right, top * 0.4), "left", ), _text( f"터파기 {dig_len:g} × {dig['width_m']:g} × 깊이 {top:g} m", (right, top * 0.15), "left", ), _text("관·방수로 자리는 원문 입체도 모식 — 치수는 산출식", (0.0, -0.3), "left"), ] return shapes def pavement_figure(sheet: dict[str, Any], entry: dict[str, Any]) -> list[dict[str, Any]]: """물넘이포장 — ㎡당 한 칸 단면: 슬래브 두께(규격) · 와이어메쉬(2/3T) · 터파기(두께만큼).""" thickness = float(entry["spec"]["thickness_cm"]) / 100 mesh = thickness * entry["section"]["mesh_from_bottom_ratio"] width = 1.0 shapes = [ _path(_rect(0.0, 0.0, width, thickness), "wall"), _path([(0.05, mesh), (width - 0.05, mesh)], "guide", dash=True), _path(_rect(-0.12, 0.0, width + 0.12, thickness), "guide", dash=True), ] right = width + 0.4 shapes += [ _title(sheet, thickness + 0.45), _text("폭 1 m 당(표는 ㎡당)", (width / 2, thickness + 0.12), "center"), _text(f"콘크리트 T={thickness:g} m", (right, thickness * 0.8), "left"), _text(f"와이어메쉬 — 밑에서 {mesh:.3f} m (2/3T)", (right, mesh - 0.02), "left"), _text(f"터파기 깊이 {thickness:g} m (= 두께)", (right, 0.0), "left"), ] for index, note in enumerate(entry.get("notes") or []): shapes.append(_text(str(note), (0.0, -0.25 - index * 0.14), "left")) return shapes 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