Files
Aislo/E05_StabReport/E05_StabReport_Section.py
T

319 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""E05 보고서 s5 대표단면 · s10 부록 절편 — 단면 하나 = 한 쪽(PLAN 534-3 · 534-5).
칸 자리 = `resources/master_template/report/stab_report_slots.md` s5 · s10.
그림 = `E05_StabReport_Figure.section_svg`(값 셈 없음 · 해석 저장본 옮겨 그림).
"""
from __future__ import annotations
import copy
import math
from typing import Any
from E03_StabSection import E03_StabSection_View as view2d
from E03_StabSection.E03_StabSection_Engine_Model import final_surface
from E05_StabReport import E05_StabReport_Figure as figure
from E05_StabReport.E05_StabReport_Cells import (
A1,
CASES,
GROUND_NAMES,
METHOD_NAMES,
REASONS,
SIDE_NAMES,
_criterion,
_label_at,
_labels,
_seismic,
_set,
_shift,
_verdict,
fig_key,
)
BLOCK = 28 # 대표단면 한 쪽 = 28 행
LOWEST_N = view2d.REPRESENTATIVE_LOWEST
CIRCLED = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
CONDITION_NAMES = {"dry": "건기", "wet": "우기", "seismic": "지진"}
SLICE_COLS = "ABCDEFGHIJKLM"
SLICE_TPL = 5 # 부록 절편 덩이 꼴 = 양식 1~5 행(제목 · 단위 · 머리 · 줄 · 합계)
def _case_slip(result: dict[str, Any] | None, condition: str, side: str, method: str):
"""조건 하나 — 그 비탈 역할(깎기 · 쌓기) 중 그 방법 최소 면 · 높이."""
best = None
for case in (result or {}).get("cases") or []:
if case.get("condition") != condition:
continue
if side in ("cut", "fill") and case.get("role") not in (side, None):
continue
methods = (case.get("result") or {}).get("methods") or {}
found = (methods.get(method) or {}).get("min") or next(
(v["min"] for v in methods.values() if v.get("min")), None
)
if found and found.get("F") is not None and (best is None or found["F"] < best[0]["F"]):
best = (found, case)
return best
def _block_copy(
sheet: dict[str, Any], template: dict[str, Any], dr: int, rows: int, tag: str
) -> None:
"""양식 1~rows 행 덩이를 dr 행 아래로(칸 · 병합 · 행 높이 · 조건부서식)."""
for a1, cell in template["칸"].items():
if int(A1.match(a1).group(2)) <= rows:
sheet["칸"][_shift(a1, dr)] = copy.deepcopy(cell)
for m in template.get("병합", []):
if int(A1.match(m.split(":")[0]).group(2)) <= rows:
sheet["병합"].append(":".join(_shift(p, dr) for p in m.split(":")))
for r, info in (template.get("행") or {}).items():
if int(r) <= rows:
sheet["행"][str(int(r) + dr)] = dict(info)
for rule in template.get("조건부서식", []):
moved = copy.deepcopy(rule)
moved["id"] = f"{rule['id']}_{tag}"
moved["범위"] = [{**g, "r0": g["r0"] + dr, "r1": g["r1"] + dr} for g in rule["범위"]]
sheet["조건부서식"].append(moved)
def _clear(sheet: dict[str, Any]) -> dict[str, Any]:
"""시트를 비우고 양식 사본을 돌려줌."""
template = copy.deepcopy(sheet)
sheet["칸"] = {}
sheet["병합"], sheet["행"], sheet["조건부서식"] = [], {}, []
if "그림" in sheet:
sheet["그림"] = []
return template
def _mark(k: int) -> str:
return CIRCLED[k] if k < len(CIRCLED) else str(k + 1)
def _picks(rep: dict[str, Any], ground: dict[str, Any]) -> dict[str, Any]:
"""대표 단면 하나 — 조건별 (면, 경우) · 쓰는 조건 · 지배 조건(Fs / 기준 가장 낮음)."""
st = rep["summary"]
side, method = st.get("side"), st.get("method") or "bishop"
used = [c for c in CASES if c != "seismic" or _seismic(ground)]
picks = {c: _case_slip(rep.get("result"), c, side, method) for c in used}
fs = st.get("fs_by_condition") or {}
ratios = [
(float(fs[c]) / _criterion(ground, c, side), c)
for c in used
if fs.get(c) is not None and picks.get(c)
]
governing = min(ratios)[1] if ratios else None
return {"side": side, "method": method, "used": used, "picks": picks, "governing": governing}
def fill_sections(sheet: dict[str, Any], inputs: dict[str, Any]) -> list[tuple[str, str]]:
"""s5 대표단면 — 단면 하나 = 28 행 덩이 = 한 쪽 · 그림 일감 [(열쇠, SVG)]."""
reps = inputs["representatives"]
ground = inputs["ground"]
labels = _labels(inputs["stations"])
template = _clear(sheet)
if not reps:
_block_copy(sheet, template, 0, BLOCK, "0")
_set(sheet, "A1", "대표 단면 — 해당 없음")
sheet["인쇄"]["쪽나눔"] = []
return []
jobs: list[tuple[str, str]] = []
for k, rep in enumerate(reps):
dr = BLOCK * k
_block_copy(sheet, template, dr, BLOCK, str(k))
st = rep["summary"]
got = _picks(rep, ground)
side, method, picks = got["side"], got["method"], got["picks"]
label = _label_at(labels, float(st["chainage_m"]))
dry = picks.get("dry")
height = float(dry[1].get("height_m") or 0) if dry else 0.0
_set(
sheet,
f"A{1 + dr}",
f"대표 단면 {_mark(k)} — {label} · {SIDE_NAMES.get(side, side or '-')} · "
f"H {height:.1f} m",
)
reason = REASONS.get(rep.get("reason"), rep.get("reason") or "")
if rep.get("reason") == "lowest":
reason = f"{reason} {LOWEST_N}"
_set(sheet, f"B{2 + dr}", reason)
key = fig_key(rep["station_key"])
sheet["그림"].append(
{"열쇠": key, "닻": f"A{3 + dr}:H{22 + dr}", "이름": f"대표 단면 {label}"}
)
fs = st.get("fs_by_condition") or {}
slips, box = [], []
for r, case in zip((24, 25, 26), CASES):
row = r + dr
if case not in got["used"]:
_set(sheet, f"B{row}", "검토 안 함")
continue
value = fs.get(case)
crit = _criterion(ground, case, side)
ok = None if value is None else float(value) >= crit - 1e-9
pick = picks.get(case)
_set(sheet, f"B{row}", METHOD_NAMES.get(method, method))
_set(sheet, f"C{row}", float(value) if value is not None else None)
_set(sheet, f"D{row}", crit)
_verdict(sheet, f"E{row}", ok)
slip = pick[0] if pick else None
center = (slip or {}).get("center")
circle = bool(slip and slip.get("radius") and center)
_set(sheet, f"F{row}", float(slip["radius"]) if circle else "-" if slip else None)
_set(sheet, f"G{row}", float(center[0]) if circle else "-" if slip else None)
_set(sheet, f"H{row}", float(center[1]) if circle else "-" if slip else None)
if slip and slip.get("points"):
slips.append({"condition": case, "F": slip.get("F"), "points": slip["points"]})
box.append({"condition": case, "F": value, "required": crit, "ok": ok})
design = (rep.get("cross") or {}).get("design") or {}
kind = (dry[0].get("kind") if dry else None) or ""
layers = rep.get("layers") or {}
note = [
{"circle": "활동면 원호", "polyline": "활동면 비원호"}.get(kind, ""),
f"지반 {GROUND_NAMES.get(design.get('ground_type'), design.get('ground_type') or '-')}",
f"토층 두께 {layers['soil_thickness_m']:g} m" if layers.get("soil_thickness_m") else "",
f"부록 {_mark(k)} 절편 계산표" if got["governing"] else "",
]
_set(sheet, f"B{27 + dr}", " · ".join(n for n in note if n))
jobs.append((key, _section_figure(rep, slips, box, ground)))
sheet["인쇄"]["쪽나눔"] = [BLOCK * (k + 1) for k in range(len(reps))]
return jobs
def _strata(rep: dict[str, Any], ground: dict[str, Any], fill: bool) -> list[dict[str, str]]:
"""지층 범례 — 성토(공사 뒤 지표가 원지반 위인 곳이 있으면) · 위 지층 · 아래 지층."""
names = {m["id"]: m for m in ground.get("materials") or []}
layers = rep.get("layers") or {}
out = []
picks = [
("fill", (ground.get("label_map") or {}).get("fill") or "fill", fill),
("soil", layers.get("upper"), True),
("rock", layers.get("lower"), bool(layers.get("soil_thickness_m"))),
]
for kind, mid, show in picks:
m = names.get(mid)
if show and m:
text = f"{m['name']} γt {m['gamma_t']:g} · c {m['c']:g} · φ {m['phi']:g}"
out.append({"kind": kind, "text": text})
return out
def _section_figure(
rep: dict[str, Any], slips: list[dict[str, Any]], box: list[dict[str, Any]], soil: dict
) -> str:
cross = rep.get("cross") or {}
design = cross.get("design") or {}
ground = sorted( # 해석에 쓴 넓힌 지반선(임계면이 횡단 표본 밖까지 감) · 없으면 횡단 표본
(float(x), float(z)) for x, z in rep.get("ground") or []
) or sorted(
(float(s["offset_m"]), float(s["z"]))
for s in cross.get("samples") or []
if s.get("valid", True) and s.get("z") is not None
)
line = sorted(
(float(p["offset_m"]), float(p["elevation_m"])) for p in design.get("design_line") or []
)
surface = [tuple(p) for p in final_surface(ground, line)] if ground and line else ground
thickness = (rep.get("layers") or {}).get("soil_thickness_m")
rock = [(x, z - float(thickness)) for x, z in ground] if thickness else None
edges = design.get("road_edges") or {}
road = None
if edges.get("left") and edges.get("right"):
road = (float(edges["right"]["offset_m"]), float(edges["left"]["offset_m"]))
heights = []
if road and line:
for case in (rep.get("result") or {}).get("cases") or []:
if case.get("condition") != "dry" or not case.get("height_m"):
continue
left = case.get("side") == "left"
edge = road[1] if left else road[0]
day = line[-1][0] if left else line[0][0]
z_edge, z1 = figure._interp(surface, edge), figure._interp(surface, day)
if z_edge is not None and z1 is not None: # 높이 = 엔진 값(비탈 끝에서 길 쪽으로)
h = float(case["height_m"])
z0 = z1 + h if z_edge > z1 else z1 - h
heights.append({"x": day, "z0": z0, "z1": z1, "label": f"H {h:.2f} m"})
filled = any(
(z_g := figure._interp(ground, x)) is not None and z - z_g > 0.05 for x, z in surface
)
return figure.section_svg(
title="",
ground=ground,
surface=surface,
rock=rock,
slips=slips,
road=road,
heights=heights,
wet=any(s["condition"] == "wet" for s in slips),
strata=_strata(rep, soil, filled),
fs_box=box,
)
# ── s10 부록 절편 ────────────────────────────────────────────────────────────
def fill_slices(sheet: dict[str, Any], inputs: dict[str, Any]) -> None:
"""s10 부록 절편 — 대표 단면마다 지배 조건(Fs / 기준 가장 낮음) 임계면 절편표 한 쪽."""
ground = inputs["ground"]
labels = _labels(inputs["stations"])
template = _clear(sheet)
data_tpl = {c: dict(template["칸"].get(f"{c}4") or {}) for c in SLICE_COLS}
sum_tpl = {c: dict(template["칸"].get(f"{c}5") or {}) for c in SLICE_COLS}
data_h = (template.get("행") or {}).get("4")
sum_h = (template.get("행") or {}).get("5")
dr, breaks = 0, []
for k, rep in enumerate(inputs["representatives"]):
got = _picks(rep, ground)
cond = got["governing"]
slip = got["picks"][cond][0] if cond else None
rows = (slip or {}).get("slices") or []
if not rows:
continue
_block_copy(sheet, template, dr, 3, str(k))
label = _label_at(labels, float(rep["summary"]["chainage_m"]))
F = float(slip["F"])
_set(
sheet,
f"A{1 + dr}",
f"부록 {_mark(k)} 절편 계산표 — {label} · {SIDE_NAMES.get(got['side'], '-')} · "
f"{CONDITION_NAMES[cond]} · {METHOD_NAMES.get(got['method'], got['method'])} · "
f"Fs {F:.3f}",
)
kind = {"circle": "원호", "polyline": "비원호"}.get(slip.get("kind"), "")
_set(
sheet,
f"A{2 + dr}",
f"대표 단면 {_mark(k)} 의 {CONDITION_NAMES[cond]} 임계 활동면({kind}) · "
f"{template['칸']['A2']['값']}",
)
sums = [0.0, 0.0, 0.0, 0.0]
for i, s in enumerate(rows):
r = 4 + dr + i
for c, cell in data_tpl.items():
sheet["칸"][f"{c}{r}"] = dict(cell)
if data_h:
sheet["행"][str(r)] = dict(data_h)
drive = (s["weight"] + s["surcharge"]) * math.sin(math.radians(s["alpha_deg"]))
resist = s["shear_mobilized"] * F
values = (
i + 1, s["width"], s["height"], s["alpha_deg"], s["weight"], s["surcharge"],
s["pore_pressure"], s["base_length"], s["c"], s["phi_deg"], s["normal_eff"],
drive, resist,
) # fmt: skip
for c, v in zip(SLICE_COLS, values):
_set(sheet, f"{c}{r}", float(v) if c != "A" else v)
for j, v in enumerate((s["weight"], s["surcharge"], drive, resist)):
sums[j] += v
r = 4 + dr + len(rows)
for c, cell in sum_tpl.items():
sheet["칸"][f"{c}{r}"] = dict(cell)
if sum_h:
sheet["행"][str(r)] = dict(sum_h)
_set(sheet, f"A{r}", "합계")
for c, v in zip("EFLM", sums):
_set(sheet, f"{c}{r}", v)
dr = r
breaks.append(r)
if not breaks:
_block_copy(sheet, template, 0, 2, "0")
_set(sheet, "A1", "부록 — 절편 계산표 없음(대표 단면 없음)")
sheet["인쇄"]["쪽나눔"] = breaks