Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1

This commit is contained in:
2026-10-12 21:58:59 +09:00
28 changed files with 331 additions and 63 deletions
@@ -24,7 +24,10 @@ const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const station = loadTs(path.join(ROOT, "common_util", "common_util_structure_station.ts"));
const ford = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts"), {
"./B06_Section_UI_Cross_Wall_Hatch": { appendChuteBand() {} },
"./B06_Section_UI_Cross_Wall_Hatch": loadTs(
path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Wall_Hatch.ts"),
{ "./B06_Section_UI_Cross_Culvert_Const": {} },
),
"./B06_Section_UI_Cross_Design_Surface": { appendPavementOverlay() {} },
});
// 286-3 도수로 · 돌붙임 길이 — 유출(좌 · 우가 높음) 성토사면 1:1.5(5 m 내려감 · 사면 길이 √(7.5²+5²) ≈ 9.01)
@@ -55,8 +55,40 @@ pave.appendFordPavementOverlay(
);
const paveLayer = svg.children.find((k) => (k.attrs.class ?? "").includes("ford-pavement"));
// 294-4 도수로 · 돌붙임 끝단 · 길이
const hatch = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Wall_Hatch.ts"));
const P = (offset, elevation) => ({ offset, elevation });
const slope = [{ points: [P(0, 0), P(3, -4)], kind: "fill" }]; // 사면 5 m
const flat = [P(3, -4), P(5, -4), P(8, -4)]; // 사면 끝 바깥 원지반
const pts = (lines) =>
lines.map((l) => l.points.map((p) => [+p.offset.toFixed(4), +p.elevation.toFixed(4)]));
const dipGround = hatch.groundLine(input.dipSamples);
const fillGround = hatch.groundLine(input.fillSamples);
const calc = load(path.join(ROOT, "A00_Common", "design", "design_structure_ford_calc.ts"));
const formOf = (state) => ({ state: () => state, setValue: (c, i, v) => (state[c][0][i] = v) });
const fordState = { ford: [{ width: "8" }], chute_add: [{ length_m: "2.5", area: "30" }] };
calc.syncChuteArea(formOf(fordState), { id: "chute_add" }, { id: "length_m" }, "chute_add", "ford");
const guard = load(path.join(ROOT, "A00_Common", "design", "design_structure_guard.ts"));
const fordPave = load(path.join(ROOT, "A00_Common", "design", "design_structure_ford_pavement.ts"));
const out = {
edges: pave.fordEdges(design),
pavePoints: paveLayer?.attrs.points ?? null,
runNone: pts(hatch.chuteRun(slope)),
runShort: pts(hatch.chuteRun(slope, 2)),
runLong: pts(hatch.chuteRun(slope, 7, flat)),
runPointOnly: pts(hatch.chuteRun([{ points: [P(3, -4)] }])),
runPointLength: pts(hatch.chuteRun([{ points: [P(3, -4)] }], 2, flat)),
dipLine: pave.outletFillLine(design, dipGround),
dipChute: pave.fordChuteLine(design, {}, dipGround),
dipChuteLength: pave.fordChuteLine(design, { length_m: 1.5 }, dipGround),
fillLine: pave.outletFillLine(input.fillDesign, fillGround),
fillLineOld: pave.outletFillLine(input.fillDesign),
fillChuteLength: pave.fordChuteLine(input.fillDesign, { length_m: 1 }, fillGround),
fordArea: fordState.chute_add[0].area,
chuteItems: guard.CHUTE_ITEMS.map((i) => i.id),
fordPaveChuteSame:
fordPave.FORD_PAVEMENT_SPEC.containers.find((c) => c.id === "chute").items ===
guard.CHUTE_ITEMS,
};
process.stdout.write(JSON.stringify(out));
+2 -3
View File
@@ -55,9 +55,8 @@ def test_거름_한_벌_배선() -> None:
culvert = _src("B06_Section/B06_Section_UI_Cross_Culvert.ts")
assert "layout.outletFill.segments.filter(isChuteSegment)," in culvert
corridor = _src("B05_Profile/B05_Profile_UI_Corridor_Structures.ts")
assert (
"chuteBands(layout.outletFill.segments, section.culvert?.chute, isChuteSegment)" in corridor
)
flat = " ".join(corridor.split())
assert "chuteBands( layout.outletFill.segments, section.culvert?.chute, isChuteSegment," in flat
hatch = _src("B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts")
assert '"3도 절토선"' in hatch
# 박스 · 세월교 본체 끝은 성토만(3도 규칙 안 넓힘)
@@ -174,8 +174,7 @@ def test_도수로_길이_없음_원지반까지_있음_그_길이(ts):
def test_컨테이너_같은_칸_맨_아래_길이():
spec = _read("A00_Common", "design", "design_structure_ford_pavement.ts")
assert (
'items: [...CHUTE_ITEMS, { id: "length_m", label: "길이 (m)", kind: "number", step: 0.1 }],'
in spec
"items: CHUTE_ITEMS," in spec # 공용 칸 맨 아래 길이(294-4 · CHUTE_ITEMS 로 옮김)
)
@@ -5,6 +5,10 @@
처음 만나는 자리까지(그 쪽 절토 사면 없음 · `tilt_reach`) · 성토 쪽 그대로 · 반폭 안에서 못 만나면 옛 길 ·
서버 정본 ↔ TS 짝 같은 값 = 횡단 설계 거울(`test_b06_cross_design_mirror` 「물넘이포장 낮은 쪽 노면
연장」) · 그림 = 콘크리트(포장층)가 늘인 끝까지.
- 294-4 도수로/돌붙임 띠 = 낮은 쪽 끝단(늘인 끝 · 아니면 유출 쪽 노견 끝)에서 성토 사면 따라 · 길이 비움 =
원지반 닿으면 끝 · 양측 절토 물넘이 = 띠 없음 · 길이 = 그 길이(사면보다 길면 원지반 따라) · 다른 구조물
(배관 · 박스 · 세월교) 같은 규칙(`chuteRun` · 원지반 `layout.ground`) + 같은 「길이 (m)」 칸 · 면적 = 세월교만
월류 폭 × 길이(배관 · 박스 = 폭 정의 없어 자동 안 함) · 길이는 집계 · B08 수량에 안 실림.
진짜 TS 는 `helper_294_3_ford_extend_chute.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
"""
@@ -60,7 +64,15 @@ def dip() -> dict:
@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")
fill_samples = _line(lambda o: 100.0 + 0.35 * o)
fill = compute_cross_design(fill_samples, 100.4, **KW)
payload = {
"design": dip,
"dipSamples": _dip_ground(),
"fillSamples": fill_samples,
"fillDesign": fill,
}
source.write_text(json.dumps(payload), encoding="utf-8")
proc = subprocess.run(
["node", str(HERE / "helper_294_3_ford_extend_chute.cjs"), str(source)],
capture_output=True,
@@ -110,3 +122,80 @@ def test_콘크리트는_늘인_끝까지(dip, ts):
}
first = ts["pavePoints"].split()[1] # 포장층 윗면 우 끝
assert float(first.split(",")[0]) == pytest.approx(-4.3333, abs=1e-4)
def test_띠_길이_맞춤_공용(ts):
assert ts["runNone"] == [[[0, 0], [3, -4]]] # 길이 없음 = 원지반 닿으면 끝(그대로)
assert ts["runShort"] == [[[0, 0], [1.2, -1.6]]] # 길이 2 = 사면 따라 2 m
assert ts["runLong"] == [[[0, 0], [3, -4]], [[3, -4], [5, -4]]] # 사면 5 + 원지반 2
assert ts["runPointOnly"] == [] # 끝단이 이미 원지반 · 길이 없음 = 안 그림
assert ts["runPointLength"] == [[[3, -4], [5, -4]]]
def test_양측_절토_물넘이_띠_없음(ts):
assert len(ts["dipLine"]) == 1 # 늘인 끝이 원지반
assert ts["dipLine"][0]["offset"] == pytest.approx(-4.3333, abs=1e-4)
assert ts["dipChute"] == []
tail = ts["dipChuteLength"] # 길이를 넣으면 늘인 끝에서 원지반 따라 그 길이
assert tail[0]["offset"] == pytest.approx(-4.3333, abs=1e-4) and len(tail) >= 2
def test_성토_쪽_띠는_원지반_닿으면_끝(ts):
line = ts["fillLine"]
end = line[-1]
assert end["elevation"] == pytest.approx(100.0 + 0.35 * end["offset"], abs=1e-3) # 원지반 위
assert len(ts["fillLineOld"]) > len(line) # 원지반을 모르면 내려가는 원지반까지 따라갔음
chute = ts["fillChuteLength"]
start, stop = chute[0], chute[-1]
assert (stop["offset"] - start["offset"]) ** 2 + (
stop["elevation"] - start["elevation"]
) ** 2 == pytest.approx(1.0, abs=1e-6)
def test_다른_구조물_같은_규칙과_길이_칸(ts):
assert ts["chuteItems"][-1] == "length_m"
assert ts["fordPaveChuteSame"] is True
assert ts["fordArea"] == "20" # 세월교 월류 폭 8 × 길이 2.5
hatch = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Wall_Hatch.ts").read_text(
encoding="utf-8"
)
assert "const lines = chuteRun(allLines, chute.length_m, ground);" in hatch
for name in ("Culvert", "Box", "Ford"):
overlay = (ROOT / "B06_Section" / f"B06_Section_UI_Cross_{name}.ts").read_text(
encoding="utf-8"
)
assert "layout.ground," in overlay
form = (ROOT / "B05_Profile" / "B05_Profile_UI_Drainage_Facility.ts").read_text(
encoding="utf-8"
)
assert form.count("syncChuteArea(") == 2 # 물넘이 · 세월교만(배관 · 박스 = 폭 정의 없음)
def test_길이는_집계_수량에_안_실림():
from B06_Section.B06_Section_Engine_Culvert import _chute
from B08_Quantity.B08_Quantity_Router_Summary import _chutes
from M02_MasterTemplete.M02_Table_Fill_Spec import container_rows
options = {
"chute_add_1_structure": "돌붙임",
"chute_add_1_bond": "찰붙임",
"chute_add_1_area": 12,
}
longer = {**options, "chute_add_1_length_m": 7}
assert _chute(longer)["length_m"] == 7 and "length_m" not in _chute(options)
class Sheet:
def __init__(self) -> None:
self.rows: list[tuple] = []
def add(self, at, key, sort, group, label, value, combos) -> None:
self.rows.append((key, label, value))
sheet = Sheet()
_chutes(sheet, 50.0, longer, {"gchuteadd1": "SS1"}, {})
assert sheet.rows == [("chute|찰붙임", "㎡", 12.0)]
item = {"type_id": "pipe", "options": options, "복사본": "pipe@50.0", "흙막이단": 0}
plain = container_rows(item, {"gchuteadd1": "SS1"})
long = container_rows({**item, "options": longer}, {"gchuteadd1": "SS1"})
for key in ("length_m", "inlet_revet_length_m", "outlet_revet_length_m", "area_m"):
assert plain[0]["options"].get(key) == long[0]["options"].get(key)
@@ -113,4 +113,7 @@ def test_정의_파일에_세월교_이름_표가_올라_있음():
os.path.join(ROOT, "resources", "master_template", "구조물정의.json"), encoding="utf-8"
) as f:
doc = json.load(f)
assert doc["종류"]["ford_bridge"]["이름"] == names
# 그림 전용 길이 칸(294-4)은 마스터 정의 이름 표에 안 올림(마스터 손대지 않음) — 대조 밖
assert doc["종류"]["ford_bridge"]["이름"] == {
k: v for k, v in names.items() if k != "chute_add.length_m"
}
@@ -102,7 +102,8 @@ def test_정의_파일에_이름_표_그룹_선택지가_올라_있음():
os.path.join(ROOT, "resources", "master_template", "구조물정의.json"), encoding="utf-8"
) as f:
doc = json.load(f)
assert set(names) == set(doc["종류"]["box_culvert"]["이름"])
# 그림 전용 길이 칸(294-4)은 마스터 정의 이름 표에 안 올림(마스터 손대지 않음) — 대조 밖
assert set(names) - {"chute_add.length_m"} == set(doc["종류"]["box_culvert"]["이름"])
assert [g["id"] for g in doc["그룹"]["box_culvert"]] == ["gboxbody", "gboxin", "gboxout"]
box = next(t for t in doc["types"] if t["type_id"] == "box_culvert")
wing = {o["key"]: o for o in box["options"]}
@@ -107,5 +107,6 @@ def test_정의_파일에_이름_표가_올라_있음():
) as f:
doc = json.load(f)
on_file = doc["종류"]["pipe"]["이름"]
assert set(names) == set(on_file)
# 그림 전용 길이 칸(294-4)은 마스터 정의 이름 표에 안 올림(마스터 손대지 않음) — 대조 밖
assert set(names) - {"chute_add.length_m"} == set(on_file)
assert on_file["inlet.structure=기슭막이"] == "흙막이"
@@ -73,6 +73,7 @@ def test_유입_유출은_구조와_흙막이_칸만(out):
["bond", None, "메붙임"],
["thickness", None, "45"],
["area", None, "30"],
["length_m", None, None], # 그림 전용 길이(294-4)
]
keys = out[f"{spec}Keys"]
assert keys["prefixes"] == ["soil_add_", "chute_add_"]