Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1
This commit is contained in:
@@ -114,6 +114,7 @@
|
||||
| 6 행부터 가정 하나 × 조건 하나 = 한 줄 | A 측점 · B 비탈(좌 · 우 · 깎기 · 쌓기)(서식 7) · C 가정 글(1:n 전 → 후 · 소단 폭 / 높이 · 최소 완화)(5) · D 조건(7) · E 전 Fs · F 후 Fs · G 기준(8) · H 전 판정 · I 후 판정(7 → N.G 10 · O.K 11) · 없으면 「가정 없음」 |
|
||||
| 가정 줄 다음 한 줄 띄고 `8` · `9` 행 꼴(제목 3 · 머리 6) | 2. 벽 외적 안정 |
|
||||
| 그 아래 벽 하나 × 경우 하나 = 한 줄 | A 측점(7) · B 벽 이름 · 평상시 / 지진 시 · 토압법 · 배면 수위(5) · C 활동 · E 전도 · G 지지력 Fs(8) · D · F · H 기준(8 · KDS 11 80 05) · I 판정(7 → 10 · 11) · 없으면 「벽 검토 없음」 |
|
||||
| 네일 · 앵커 가정이 있을 때만 — 벽 줄 다음 한 줄 띄고 `8` · `9` 행 꼴(제목 · 머리 글 = 프로그램) | 3. 네일 · 앵커 · 가정 하나 × 조건 하나 × 네일 하나 = 한 줄(10 행 서식) · A 측점 · B 비탈 · 조건 · C 번호 · 종류 · D 머리 x / z · E 길이 · F 각도 · G 간격 · H 동원력 kN/m · I 지배(인장 · 인발 · 머리 쪽 빠짐 · 활동면 안 만남 · 결과 없음 「-」) · 1 절 가정 줄에도 들어감(C 가정 글 = 종류 · 개수 · 길이 · 각도 · 간격 · 최소 길이) |
|
||||
|
||||
- 6 행 · 10 행 서식을 줄마다 복사 · 저장본 = (측점 · 종류 · 비탈)마다 가장 최근 하나.
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
- 길(검증_sub2 동결 복사 · 벽 측점 + 하나): 지금 기울기 그대로 = 원본 안전율(±3 %) · 최소 완화가
|
||||
목표 안전율을 맞춤 · 원본 측점 · 요약 저장본 그대로 · 벽 측점 외적 안정 · 벽 쪽 비탈 가정 거절 ·
|
||||
E05 보강검토 시트(있을 때만)
|
||||
- 네일 · 앵커(뒷부분): 배치 → supports 꼴 · 그림 좌표(머리 · 끝 · 정착 구간 · 활동면 교점 · mode) = 해석해(TS) ·
|
||||
엔진 고리 전 = 그림만(supports_pending) · 가짜 엔진(supports 인자 · 길이 비례 Fs)으로 최소 길이 찾기 ·
|
||||
E05 네일 · 앵커 줄
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_536_3_e03_reinforce.py -q
|
||||
"""
|
||||
@@ -14,8 +17,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import math
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -33,6 +38,7 @@ from E01_StabRegister import E01_StabRegister_Source as src # noqa: E402
|
||||
from E02_StabGround import E02_StabGround_Router as e02 # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router as router # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router_WhatIf as wf # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_View as view # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Worker as worker # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_WallCheck as wc # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_WhatIf as whatif # noqa: E402
|
||||
@@ -326,3 +332,145 @@ def test_E05_보강검토_시트(project, monkeypatch):
|
||||
empty = build.form()
|
||||
build.build_program(empty, build.gather(project))
|
||||
assert "s8" not in {s["id"] for s in empty["시트"]} # 없으면 시트 뺌
|
||||
|
||||
|
||||
# ── 네일 · 앵커(뒷부분) ──────────────────────────────────────────────────────
|
||||
TYPESCRIPT = ROOT / "config" / "node_modules" / "typescript"
|
||||
SUPPORT_TS = ROOT / "E03_StabSection" / "logic" / "E03_StabSection_Support.ts"
|
||||
SUPPORT_SCRIPT = r"""
|
||||
const ts = require(process.argv[1]);
|
||||
const fs = require("fs");
|
||||
const src = fs.readFileSync(process.argv[2], "utf8");
|
||||
const opts = { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 };
|
||||
const js = ts.transpileModule(src, { compilerOptions: opts }).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", js)(box.exports, box);
|
||||
const m = box.exports;
|
||||
const c = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const nail = { ...m.SUPPORT_DEFAULTS.nail, rows: 9, length_m: 6, angle_deg: 15 };
|
||||
const cut = m.layoutSupports(c.surface, "left", "cut", 4.5, nail);
|
||||
const anchor = { ...m.SUPPORT_DEFAULTS.anchor, rows: 1 };
|
||||
const fill = m.layoutSupports(c.surface, "right", "fill", 3.0, anchor);
|
||||
process.stdout.write(JSON.stringify({
|
||||
cut, fill,
|
||||
draws: m.supportDraws(cut, -1, c.critical, [{ index: 0, mobilized_kN_per_m: 12.5, mode: "pullout" }]),
|
||||
anchor: m.supportDraws(fill, -1, null, null),
|
||||
none: m.layoutSupports([[1, 0], [2, 0]], "left", "cut", 3, nail),
|
||||
}));
|
||||
"""
|
||||
# 노면 −3 ~ 3 (EL 100) · 좌 깎기 1:1 (3, 100) → (13, 110) · 우 쌓기 1:1.5 (−3, 100) → (−7.5, 97)
|
||||
SURFACE = [[13, 110], [3, 100], [-3, 100], [-7.5, 97], [-20, 97]]
|
||||
CRITICAL = [[3, 100], [6, 101], [12, 109]] # 머리 (4, 101) 네일과 만나는 활동면
|
||||
|
||||
|
||||
def test_네일_배치_supports_꼴_그림_좌표():
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", "-e", SUPPORT_SCRIPT, str(TYPESCRIPT), str(SUPPORT_TS)],
|
||||
input=json.dumps({"surface": SURFACE, "critical": CRITICAL}),
|
||||
capture_output=True, text=True, cwd=str(ROOT),
|
||||
) # fmt: skip
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
out = json.loads(proc.stdout)
|
||||
cut = out["cut"]
|
||||
# 행 = 1 · 2.5 · 4.0 m(4.5 m 비탈 안만) · 머리 = 1:1 면 위
|
||||
assert [(p["x"], p["z"]) for p in cut] == [(4.0, 101.0), (5.5, 102.5), (7.0, 104.0)]
|
||||
keys = {"type", "side", "x", "z", "angle_deg", "length_m", "spacing_m", "tensile_kN",
|
||||
"bond_kPa", "bond_length_m", "hole_dia_mm", "prestress_kN"} # fmt: skip
|
||||
assert all(set(p) == keys and p["side"] == "left" and p["prestress_kN"] == 0 for p in cut)
|
||||
assert cut[0]["spacing_m"] == 1.5 and cut[0]["bond_length_m"] == 0
|
||||
# 쌓기 밑 = 노면 − 3 m · 첫 행 1.5 m = EL 98.5 · 1:1.5 면 x = −3 − 1.5 × 1.5
|
||||
(anchor,) = out["fill"]
|
||||
assert (anchor["x"], anchor["z"], anchor["side"]) == (-5.25, 98.5, "right")
|
||||
assert anchor["bond_length_m"] == 6 and anchor["prestress_kN"] == 200
|
||||
assert out["none"] == []
|
||||
# 그림 — 깎기 좌(활동 −x) = 비탈 속(+x)으로 · 수평 아래 15° · 네일 정착 = 전 길이
|
||||
d0 = out["draws"][0]
|
||||
a = math.radians(15)
|
||||
tail = (4 + 6 * math.cos(a), 101 - 6 * math.sin(a))
|
||||
assert d0["tail"] == pytest.approx(tail, abs=1e-3)
|
||||
assert d0["bond"][0] == [4.0, 101.0] and d0["bond"][1] == pytest.approx(tail, abs=1e-3)
|
||||
assert (d0["mode"], d0["force"]) == ("pullout", 12.5)
|
||||
# 교점 — (3,100)~(6,101) 선 z = 100 + (x−3)/3 과 네일 z = 101 − (x−4)·tan15
|
||||
t15 = math.tan(a)
|
||||
x = (101 + 4 * t15 - 99) / (1 / 3 + t15)
|
||||
assert d0["cross"] == pytest.approx([x, 100 + (x - 3) / 3], abs=1e-4) # 끝 1 mm 반올림
|
||||
assert [d["mode"] for d in out["draws"][1:]] == ["pending", "pending"]
|
||||
# 앵커 — 쌓기 우(활동 −x 바깥) = 비탈 속(+x)으로 · 정착 = 끝에서 6 m · 결과 없음
|
||||
(da,) = out["anchor"]
|
||||
b = math.radians(30)
|
||||
want = (-5.25 + 12 * math.cos(b), 98.5 - 12 * math.sin(b))
|
||||
assert da["tail"] == pytest.approx(want, abs=1e-3)
|
||||
assert da["bond"][0] == pytest.approx(
|
||||
(-5.25 + 6 * math.cos(b), 98.5 - 6 * math.sin(b)), abs=1e-3
|
||||
)
|
||||
assert da["cross"] is None and da["mode"] == "pending"
|
||||
|
||||
|
||||
def _nails(side: str, n: int = 2) -> list[wf.SupportIn]:
|
||||
return [
|
||||
wf.SupportIn(type="nail", x=5.0 * (1 if side == "left" else -1), z=1.0 + i, angle_deg=15,
|
||||
length_m=6, spacing_m=1.5, tensile_kN=121.6, bond_kPa=80, hole_dia_mm=100)
|
||||
for i in range(n)
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
def _engine_with_supports(cross, design, *, supports=None, **kw):
|
||||
"""가짜 엔진 — supports 인자 · 그 비탈 대표 방법 Fs × (1 + 0.05 L) · 동원력 = 10 L."""
|
||||
out = _fast(cross, design, **kw)
|
||||
for case in out["cases"]:
|
||||
mine = [i for i, s in enumerate(supports or []) if s["side"] == case["side"]]
|
||||
best = case["result"]["methods"].get(view.PRIMARY, {}).get("min")
|
||||
if not mine or not best:
|
||||
continue
|
||||
length = supports[mine[0]]["length_m"]
|
||||
best["F"] *= 1 + 0.05 * length
|
||||
case["result"]["support_forces"] = [
|
||||
{"index": i, "mobilized_kN_per_m": 10.0 * length, "mode": "pullout"} for i in mine
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def test_네일_엔진_전_그림만_엔진_뒤_최소_길이(project, monkeypatch):
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
|
||||
key, side, before, _ = _plain_station(project)
|
||||
stamps = _stamps(project)
|
||||
pending = wf.run_whatif(project, key, wf.WhatIfRequest(side=side, supports=_nails(side)))
|
||||
assert pending["type"] == "support" and pending["supports_pending"] is True
|
||||
assert pending["support_forces"] == {} and close(pending["after"]["dry"]["fs"], before, 1e-6)
|
||||
assert [s["side"] for s in pending["assume"]["supports"]] == [side, side]
|
||||
|
||||
monkeypatch.setattr(wf, "analyze_station", _engine_with_supports)
|
||||
assert wf.engine_takes_supports()
|
||||
target = round(before * 1.24, 3) # L ≥ 4.8 m → 0.5 m 단위 = 5.0 m
|
||||
ask = wf.WhatIfRequest(side=side, supports=_nails(side), search=True, condition="dry",
|
||||
target_fs=target) # fmt: skip
|
||||
rec = wf.run_whatif(project, key, ask)
|
||||
assert rec["supports_pending"] is False and rec["search"]["found"] == 5.0
|
||||
assert all(s["length_m"] == 5.0 for s in rec["assume"]["supports"])
|
||||
assert rec["after"]["dry"]["fs"] >= target
|
||||
assert all(not ok for n, ok in rec["search"]["tried"] if n < 5.0)
|
||||
forces = rec["support_forces"]["dry"]
|
||||
assert [(f["index"], f["mobilized_kN_per_m"], f["mode"]) for f in forces] == [
|
||||
(0, 50.0, "pullout"),
|
||||
(1, 50.0, "pullout"),
|
||||
]
|
||||
assert _stamps(project) == stamps # 원본 측점 · 요약 저장본 그대로
|
||||
|
||||
recs = wf.records(project)
|
||||
assert "support" in {r["type"] for r in recs}
|
||||
book = build.form()
|
||||
build.build_program(book, build.gather(project))
|
||||
cells = next(s for s in book["시트"] if s["id"] == "s8")["칸"]
|
||||
head = next(
|
||||
int(a1[1:]) for a1, c in cells.items() if str(c.get("값", "")).startswith("3. 네일")
|
||||
)
|
||||
assert cells[f"I{head + 1}"]["값"] == "지배"
|
||||
rows = [
|
||||
r
|
||||
for r in range(head + 2, head + 40)
|
||||
if f"C{r}" in cells and "네일" in str(cells[f"C{r}"].get("값"))
|
||||
]
|
||||
assert len(rows) == 2 * len(rec["after"])
|
||||
assert cells[f"E{rows[0]}"]["값"] == 5.0 and cells[f"I{rows[0]}"]["값"] == "인발"
|
||||
assert any("네일 2개 · L 5 m" in str(c.get("값")) for a1, c in cells.items() if a1[0] == "C")
|
||||
|
||||
Reference in New Issue
Block a user