Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_7' into sub_laptop_8

This commit is contained in:
2026-10-12 21:49:19 +09:00
9 changed files with 306 additions and 1 deletions
@@ -0,0 +1,62 @@
/* 294 물넘이 낮은 쪽 연장 · 도수로 끝단 · 띠 ↔ 사면 고름 헬퍼 — 진짜 TS 를 Node 로 돌려 JSON 한 벌로. */
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const cache = new Map();
const load = (file) => {
if (cache.has(file)) return cache.get(file);
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(file, box.exports);
const near = (m) => {
if (m.startsWith("@config/")) return load(path.join(ROOT, "config", `${m.slice(8)}.ts`));
if (m.startsWith("@util/")) return load(path.join(ROOT, "common_util", `${m.slice(6)}.ts`));
if (m.startsWith(".")) return load(path.join(path.dirname(file), `${m}.ts`));
return new Proxy({}, { get: () => () => {} });
};
new Function("exports", "module", "require", js)(box.exports, box, near);
return box.exports;
};
class Node {
constructor(tag) {
this.tag = tag;
this.attrs = {};
this.children = [];
this.dataset = {};
this.style = {};
}
setAttribute(k, v) {
this.attrs[k] = String(v);
}
getAttribute(k) {
return this.attrs[k] ?? null;
}
append(...kids) {
this.children.push(...kids);
}
}
globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) };
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const pave = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts"));
const design = input.design; // 서버가 낸 양측 절토 · 낮은 쪽 연장 설계
const svg = new Node("g");
pave.appendFordPavementOverlay(
svg,
{ span_m: 8, depth_m: 0.3, slope_pct: 5 },
design,
(o) => o,
(e) => e,
);
const paveLayer = svg.children.find((k) => (k.attrs.class ?? "").includes("ford-pavement"));
const out = {
edges: pave.fordEdges(design),
pavePoints: paveLayer?.attrs.points ?? null,
};
process.stdout.write(JSON.stringify(out));
@@ -0,0 +1,112 @@
"""294 물넘이 낮은 쪽 연장 · 도수로/돌붙임 끝단 · 띠 ↔ 성토 사면 고름(요구 188-2 · 188-3 · 189 · 그림_188).
못박는 것
- 294-3 물넘이 측점 낮은 쪽 노면 끝 원지반이 노면보다 높으면(절토) 노면을 같은 기울기로 늘여 원지반과
처음 만나는 자리까지(그 쪽 절토 사면 없음 · `tilt_reach`) · 성토 쪽 그대로 · 반폭 안에서 못 만나면 옛 길 ·
서버 정본 ↔ TS 짝 같은 값 = 횡단 설계 거울(`test_b06_cross_design_mirror` 「물넘이포장 낮은 쪽 노면
연장」) · 그림 = 콘크리트(포장층)가 늘인 끝까지.
진짜 TS 는 `helper_294_3_ford_extend_chute.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
import pytest
HERE = Path(__file__).resolve().parent
ROOT = HERE.parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
if str(HERE) not in sys.path:
sys.path.insert(0, str(HERE))
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
from config.config_system import STANDARD_CROSS_SECTION # noqa: E402
from test_b06_cross_design_mirror import CASES, _dip_ground # noqa: E402
KW = {
"ground_type": "soil",
"section_mode": "left_cut",
"ditch_side": "left",
"paved": True,
"tilted": True,
"tilt_pct": 5.0,
"standard": STANDARD_CROSS_SECTION,
}
pytestmark = pytest.mark.skipif(
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
reason="프론트엔드 의존성 미설치",
)
def _line(samples_fn) -> list[dict]:
return [
{"offset_m": o / 2, "elevation_m": round(samples_fn(o / 2), 4), "valid": True}
for o in range(-24, 25)
]
@pytest.fixture(scope="module")
def dip() -> dict:
return compute_cross_design(_dip_ground(), 100.4, **KW)
@pytest.fixture(scope="module")
def ts(dip, tmp_path_factory) -> dict:
source = tmp_path_factory.mktemp("s294") / "in.json"
source.write_text(json.dumps({"design": dip}), encoding="utf-8")
proc = subprocess.run(
["node", str(HERE / "helper_294_3_ford_extend_chute.cjs"), str(source)],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_양측_절토_낮은_쪽_늘인_끝은_원지반_교점(dip):
reach = dip["tilt_reach"]
# 원지반 101.05 + 0.2·o(−4.5 까지) · 노면 100.4 + 0.05·o → −4.3333 에서 만남
assert reach["side"] == "right"
assert reach["offset_m"] == pytest.approx(-4.3333, abs=1e-4)
assert reach["elevation_m"] == pytest.approx(100.4 - 0.05 * 4.3333, abs=1e-4)
edge = dip["road_edges"]["right"]["offset_m"]
for p in dip["design_line"]: # 노견 끝 ~ 늘인 끝 = 노면 기울기 그대로(절토 사면 없음)
if reach["offset_m"] - 1e-6 <= p["offset_m"] <= edge:
assert p["elevation_m"] == pytest.approx(100.4 + 0.05 * p["offset_m"], abs=1e-4)
assert dip["ditch_enabled"] is False
def test_성토_쪽은_그대로():
got = compute_cross_design(_line(lambda o: 100.0 + 0.35 * o), 100.4, **KW)
assert "tilt_reach" not in got # 낮은 쪽(우) 노면 끝 원지반이 노면 아래 = 성토 사면
def test_반폭_안에서_못_만나면_옛_길():
got = compute_cross_design(_line(lambda o: 101.0 + 0.02 * o), 100.4, **KW)
assert "tilt_reach" not in got # 늘인 노면이 끝까지 원지반 아래 = 절토 사면 그대로
right = [
p for p in got["design_line"] if p["offset_m"] < got["road_edges"]["right"]["offset_m"]
]
assert any(p["elevation_m"] > got["road_edges"]["right"]["elevation_m"] for p in right)
def test_서버_TS_같은_값은_거울_시험():
assert any(case["name"] == "물넘이포장 낮은 쪽 노면 연장" for case in CASES)
def test_콘크리트는_늘인_끝까지(dip, ts):
assert ts["edges"]["right"] == {
"offset_m": dip["tilt_reach"]["offset_m"],
"elevation_m": dip["tilt_reach"]["elevation_m"],
}
first = ts["pavePoints"].split()[1] # 포장층 윗면 우 끝
assert float(first.split(",")[0]) == pytest.approx(-4.3333, abs=1e-4)
@@ -51,6 +51,16 @@ def _ground(slope: float, *, gap: tuple[float, float] | None = None) -> list[dic
return samples
def _dip_ground() -> list[dict]:
"""양측 절토 · 우측 노면 끝 너머(−4.5 m 밖)에서 원지반이 급히 내려가는 지반(294-3)."""
samples = []
for index in range(49):
offset = -12.0 + index * 0.5
z = 101.05 + 0.2 * offset if offset >= -4.5 else 100.15 + 1.0 * (offset + 4.5)
samples.append({"offset_m": offset, "elevation_m": round(z, 4), "valid": True})
return samples
# 갈래를 골고루 태운다 — 지반유형 셋 · 단면유형 · 측구 형식 · 포장 · 2단 경사 · 세월교
# 노면 하강 · 곡선부 확폭(바깥쪽 좌/우) · 지반선 결측 구간.
CASES = [
@@ -245,6 +255,20 @@ CASES = [
"tilt_pct": 5.2408,
},
},
{
# 294-3 — 양측 절토 · 낮은 쪽(우) 노면을 늘여 원지반이 내려와 만나는 자리까지
"name": "물넘이포장 낮은 쪽 노면 연장",
"samples": _dip_ground(),
"design_elevation_m": 100.4,
"options": {
"ground_type": "soil",
"section_mode": "left_cut",
"ditch_side": "left",
"paved": True,
"tilted": True,
"tilt_pct": 5.0,
},
},
]
_TS_RUNNER = """