fix(B06): 세월교 기본 바닥 원지반 안 봄 — 노면 − (관경 + 최소 토피) · 박스 원지반 사본 자리 그대로 시험(107-2)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
This commit is contained in:
@@ -30,7 +30,7 @@ import { designInterpolator, groundInterpolator } from "./B06_Section_UI_Cross_C
|
||||
|
||||
/** 측벽 한 매의 조작값 — 집수정 9키와 같은 축(높이·좌우·상하 대각). */
|
||||
export interface FordWallAdjust {
|
||||
/** 벽 높이(m, 노견 상단 ~ 바닥판 상면). null = 자동(바닥판이 계류 하상에 놓임). */
|
||||
/** 벽 높이(m, 노견 상단 ~ 바닥판 상면). null = 기본 = 관경 + 최소 토피(원지반 안 봄 · 107-2). */
|
||||
heightM: number | null;
|
||||
/** 좌우(m, + = 노견 바깥). 안쪽으로는 못 간다. */
|
||||
lateralM: number;
|
||||
@@ -103,7 +103,8 @@ export interface FordLayout {
|
||||
adjust: FordAdjust;
|
||||
}
|
||||
|
||||
/** 측벽 높이 하한 = 관경 + 최소 토피. 관이 노면 위로 솟지 않게 막는다. */
|
||||
/** 측벽 높이 하한 · 기본 = 관경 + 최소 토피 — 바닥 = 노면 − (관경 + 토피)(박스 「노면 − 복토」 꼴 ·
|
||||
* 원지반 안 봄 · 깊은 계류면 ▲▼ 로 내림 · 107-2). 관이 노면 위로 솟지 않게 막는다. */
|
||||
function minWallHeight(ford: FordSet): number {
|
||||
return ford.diameter_m + ford.min_cover_m;
|
||||
}
|
||||
@@ -149,15 +150,6 @@ export function computeFordLayout(
|
||||
const inletOnLeft = uphill === "left";
|
||||
const roadTopAt = roadTopFactory(section, edges.left, edges.right);
|
||||
|
||||
// 바닥판이 놓일 계류 하상 — 두 노견 사이 원지반 최저점.
|
||||
const bedElevation = (() => {
|
||||
let low = Math.min(groundAt(edges.left.offset_m), groundAt(edges.right.offset_m));
|
||||
for (let offset = edges.right.offset_m; offset <= edges.left.offset_m; offset += 0.25) {
|
||||
low = Math.min(low, groundAt(offset));
|
||||
}
|
||||
return low;
|
||||
})();
|
||||
|
||||
const buildSide = (role: FordWallRole): FordSideLayout | null => {
|
||||
const onLeft = role === "inlet" ? inletOnLeft : !inletOnLeft;
|
||||
const edge = onLeft ? edges.left : edges.right;
|
||||
@@ -166,8 +158,7 @@ export function computeFordLayout(
|
||||
const wing = role === "inlet" ? ford.wing_in : ford.wing_out;
|
||||
const wanted = role === "inlet" ? adjust.inlet : adjust.outlet;
|
||||
const lateralM = Math.max(wanted.lateralM, 0);
|
||||
const autoHeight = roadTopAt(edge.offset_m) - (bedElevation + ford.slab_thickness_m);
|
||||
const heightM = Math.max(wanted.heightM ?? autoHeight, minWallHeight(ford));
|
||||
const heightM = Math.max(wanted.heightM ?? minWallHeight(ford), minWallHeight(ford));
|
||||
// 벽 상단이 노견에 붙도록 바닥판 상면을 역산한다. 상하 이동은 buildBasin이 얹는다.
|
||||
const anchor: OffsetPoint = {
|
||||
offset: edge.offset_m,
|
||||
|
||||
@@ -69,4 +69,88 @@ bodies.ford.control.select(173.09, null);
|
||||
bodies.box.control.select(352.14, "left");
|
||||
out.released = released;
|
||||
|
||||
// 107-2 — 원지반을 바꾼 사본: 세월교 바닥 = 노면 − (관경 + 토피) · 박스 본체 · 끝 · 날개 · 흙막이 자리 그대로
|
||||
const { computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts.ts"));
|
||||
const fixture = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"),
|
||||
);
|
||||
const sections = fixture.cross_sections;
|
||||
const at = (c) => sections.find((x) => Math.abs(x.chainage_m - c) < 0.6);
|
||||
const clone = (v) => JSON.parse(JSON.stringify(v));
|
||||
const r3 = (v) => Math.round(v * 1000) / 1000;
|
||||
const pts = (list) => (list ?? []).map((p) => [r3(p.offset), r3(p.elevation)]);
|
||||
/** 원지반 사본 — [from, to] 안 샘플을 dz 만큼 옮김. */
|
||||
const shiftGround = (x, dz, from = -Infinity, to = Infinity) => {
|
||||
for (const p of x.samples)
|
||||
if (p.offset_m >= from && p.offset_m <= to && p.elevation_m != null) p.elevation_m += dz;
|
||||
};
|
||||
const GROUNDS = {
|
||||
base: () => undefined,
|
||||
bedDown2: (x) => shiftGround(x, -2, -2, 2),
|
||||
allUp05: (x) => shiftGround(x, 0.5),
|
||||
allDown1: (x) => shiftGround(x, -1),
|
||||
};
|
||||
const walls = (culvert) =>
|
||||
(culvert?.walls ?? []).filter((w) => w.role !== "extra").map((w) => pts(w.points));
|
||||
function fordShape(base, edit, ground) {
|
||||
const x = clone(base);
|
||||
edit(x);
|
||||
ground(x);
|
||||
const { ford, culvert } = computeStoredLayouts(
|
||||
x,
|
||||
sections.map((y) => (y === base ? x : y)),
|
||||
);
|
||||
return {
|
||||
sides: ford.sides.map((side) => ({
|
||||
role: side.role,
|
||||
parts: side.parts.map((part) => pts(part.points)),
|
||||
slabTop: r3(side.slabTopElevation),
|
||||
// 노면(벽 상단) − 바닥판 상면 = 관경 + 최소 토피
|
||||
depth: r3(side.top.elevation - side.slabTopElevation),
|
||||
})),
|
||||
pipeCover: r3(ford.ford.diameter_m + ford.ford.min_cover_m),
|
||||
slab: pts(ford.slabBridge),
|
||||
pipe: pts(ford.pipe.outer),
|
||||
walls: walls(culvert),
|
||||
};
|
||||
}
|
||||
function boxShape(base, edit, ground) {
|
||||
const x = clone(base);
|
||||
edit(x);
|
||||
ground(x);
|
||||
const { box, culvert } = computeStoredLayouts(
|
||||
x,
|
||||
sections.map((y) => (y === base ? x : y)),
|
||||
);
|
||||
return {
|
||||
top: pts(box.topSlab),
|
||||
bottom: pts(box.bottomSlab),
|
||||
barrel: pts(box.barrel),
|
||||
sides: box.sides.map((side) => ({
|
||||
role: side.role,
|
||||
offset: r3(side.offset),
|
||||
top: r3(side.topElevation),
|
||||
bottom: r3(side.bottomElevation),
|
||||
apron: pts(side.apron),
|
||||
})),
|
||||
walls: walls(culvert),
|
||||
};
|
||||
}
|
||||
const FORD = at(173.087);
|
||||
const BOX = at(352.14);
|
||||
const keep = () => undefined;
|
||||
const inletWing = (x) => {
|
||||
x.culvert.inlet = { role: "inlet", structure: "" };
|
||||
};
|
||||
// 날개벽 박스 — 흙막이 없이 양쪽 날개벽(에이프런 1.414 m)
|
||||
const noGuard = (x) => {
|
||||
delete x.culvert;
|
||||
for (const wing of [x.box.wing_in, x.box.wing_out])
|
||||
Object.assign(wing, { installed: true, slab_extend_m: 1.414 });
|
||||
};
|
||||
const run = (shape, base, edit) =>
|
||||
Object.fromEntries(Object.entries(GROUNDS).map(([k, g]) => [k, shape(base, edit, g)]));
|
||||
out.ford = { guards: run(fordShape, FORD, keep), inletWing: run(fordShape, FORD, inletWing) };
|
||||
out.box = { guards: run(boxShape, BOX, keep), bare: run(boxShape, BOX, noGuard) };
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -68,7 +68,7 @@ const layoutsOf = (s, base) =>
|
||||
sections.map((x) => (x === base ? s : x)),
|
||||
);
|
||||
|
||||
// ① 91-2 세월교 바닥 — 자동은 하상을 따라감 · 손 값(측벽 높이)은 원지반을 바꿔도 그대로 · 흙막이 기본 높이도
|
||||
// ① 91-2 · 107-2 세월교 바닥 — 기본 · 손 값(측벽 높이)은 원지반을 바꿔도 그대로 · 흙막이 기본 높이도
|
||||
const FORD = at(173.087);
|
||||
function fordFloor(edit) {
|
||||
const s = clone(FORD);
|
||||
|
||||
@@ -51,3 +51,24 @@ def test_벽을_고르면_세월교_박스_고름을_풂() -> None:
|
||||
# 카드 안 — 벽을 고르면 본체 창을 닫고 · 본체를 고르면 벽 창을 닫음
|
||||
assert "bodies.clearSelection(); // 세월교 · 박스 고름을 풂" in view
|
||||
assert "if (activeRevet !== null) toggleRevet(activeRevet);" in view
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", ["guards", "inletWing"])
|
||||
def test_세월교_원지반을_바꿔도_바닥_그대로(out: dict, variant: str) -> None:
|
||||
"""107-2 — 바닥 = 노면 − (관경 + 최소 토피) · 하상 −2 m · 전체 +0.5 / −1 m 사본도 같은 모양."""
|
||||
runs = out["ford"][variant]
|
||||
for name, shape in runs.items():
|
||||
assert shape == runs["base"], name
|
||||
base = runs["base"]
|
||||
assert all(side["depth"] == pytest.approx(base["pipeCover"]) for side in base["sides"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", ["guards", "bare"])
|
||||
def test_박스_원지반을_바꿔도_본체_끝_날개_흙막이_그대로(out: dict, variant: str) -> None:
|
||||
runs = out["box"][variant]
|
||||
for name, shape in runs.items():
|
||||
assert shape == runs["base"], name
|
||||
if variant == "bare":
|
||||
assert all(side["apron"] for side in runs["base"]["sides"])
|
||||
else:
|
||||
assert len(runs["base"]["walls"]) == 2
|
||||
|
||||
@@ -29,9 +29,9 @@ def fit() -> dict:
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
def test_세월교_바닥_자동은_하상을_따라감(fit: dict) -> None:
|
||||
for before, after in zip(fit["fordAuto"], fit["fordAutoBedDown"]):
|
||||
assert after["slabTop"] < before["slabTop"] - 1.0
|
||||
def test_세월교_바닥_기본도_원지반을_바꿔도_그대로(fit: dict) -> None:
|
||||
"""107-2 — 기본 바닥 = 노면 − (관경 + 토피) · 하상을 안 따라감(옛 91-2 「자동은 하상」 뒤집음)."""
|
||||
assert fit["fordAuto"] == fit["fordAutoBedDown"]
|
||||
|
||||
|
||||
def test_세월교_바닥_손_값은_원지반을_바꿔도_그대로(fit: dict) -> None:
|
||||
|
||||
Reference in New Issue
Block a user