Files
Aislo/resources/tester/helper_79_guard_shape.cjs
T

251 lines
9.5 KiB
JavaScript

/* 79 흙막이 · 세월교 · 박스 형상 헬퍼 — 화면 TS 를 그 자리에서 트랜스파일해 Node 로 돌림.
* 입력 = 73-4 픽스처(검증_sub7 상세 일부) · 결과 = 표준출력 JSON 한 벌. 짝 = `test_79_guard_shape.py`.
*/
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 STUBS = {
"@ui/ui_template_toast": { showToast: () => undefined },
};
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
if (full.endsWith(".json")) {
const data = JSON.parse(fs.readFileSync(full, "utf8"));
cache.set(full, data);
return data;
}
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
esModuleInterop: true,
},
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (STUBS[request]) return STUBS[request];
const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" };
for (const [prefix, dir] of Object.entries(alias))
if (request.startsWith(prefix))
return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length))));
return loadTs(resolve(path.join(path.dirname(full), request)));
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const resolve = (base) =>
[base, `${base}.ts`, path.join(base, "index.ts")].find(
(p) => fs.existsSync(p) && fs.statSync(p).isFile(),
) ?? `${base}.ts`;
const B06 = (f) => path.join(ROOT, "B06_Section", f);
const { computeStoredLayouts, structureAreaRows } = loadTs(B06("B06_Section_Structure_Layouts.ts"));
const { bodyGuardBase } = loadTs(B06("B06_Section_UI_Cross_Body_Guard.ts"));
const { computeBoxLayout } = loadTs(B06("B06_Section_UI_Cross_Box_Geom.ts"));
const { computeCulvertLayout } = loadTs(B06("B06_Section_UI_Cross_Culvert_Geom.ts"));
const { computeRevetmentLayout } = loadTs(B06("B06_Section_UI_Cross_Revetment.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((s) => Math.abs(s.chainage_m - c) < 0.6);
const clone = (v) => JSON.parse(JSON.stringify(v));
const r3 = (v) => Math.round(v * 1000) / 1000;
const out = {};
/** 벽 윗면 · 바닥 · 전면(관 끝 자리) ↔ 본체 끝 윗면 · 바닥 · 자리. */
function guardFit(section, list = sections) {
const layouts = computeStoredLayouts(section, list);
const base = bodyGuardBase(section, section.samples);
return (layouts.culvert?.walls ?? []).map((w) => {
const end = base.ends[w.role];
const top = Math.max(...w.points.map((p) => p.elevation));
const bottom = Math.min(...w.points.map((p) => p.elevation));
return {
role: w.role,
outward: w.outward,
topGap: r3(top - end.top),
bottomGap: r3(bottom - end.bottom),
pureHeight: r3(w.height + 0.5),
endHeight: r3(end.top - end.bottom),
autoHeight: r3(end.height),
};
});
}
// ① 높이 빈 칸 = 본체 끝 높이 · 윗면 = 본체 윗면(세월교 · 박스)
const blank = (s) => {
const c = clone(s);
for (const role of ["inlet", "outlet"]) c.culvert[role].revet_height_m = null;
return c;
};
const boxBlank = blank(at(352.14));
const fordBlank = blank(at(173.087));
out.boxAuto = guardFit(
boxBlank,
sections.map((s) => (s === at(352.14) ? boxBlank : s)),
);
out.fordAuto = guardFit(
fordBlank,
sections.map((s) => (s === at(173.087) ? fordBlank : s)),
);
// 높이를 적으면 윗면은 그대로 · 바닥이 따라 움직임
out.boxSet = guardFit(at(352.14));
// ② 세월교 흙막이 쪽 ㄴ형 측벽 · 선 걷음
const fordLayouts = computeStoredLayouts(at(173.087), sections);
out.fordParts = fordLayouts.ford.sides.map((s) => ({ role: s.role, parts: s.parts.length }));
const fordOneGuard = clone(at(173.087));
fordOneGuard.culvert.outlet = { role: "outlet", structure: "" };
const oneList = sections.map((s) => (s === at(173.087) ? fordOneGuard : s));
out.fordOneGuardParts = computeStoredLayouts(fordOneGuard, oneList).ford.sides.map((s) => ({
role: s.role,
parts: s.parts.length,
}));
// ③ 박스 본체 수평 — 좌 · 우 표고 조정이 달라도
const box = at(352.14);
const tilted = computeBoxLayout(box, box.samples, {
left: { lengthM: 0, riseM: 0.6 },
right: { lengthM: 0, riseM: 0 },
});
out.boxLevel = {
left: r3(tilted.sides[0].topElevation),
right: r3(tilted.sides[1].topElevation),
slope: tilted.slopeRatio,
};
// ④ 박스 날개벽 쪽 끝 절 · 성토선(흙막이 없는 박스)
const boxWing = clone(box);
delete boxWing.culvert;
const wingLayout = computeBoxLayout(boxWing, boxWing.samples);
out.boxWing = wingLayout.sides.map((s) => ({
role: s.role,
cut: s.cutLine ? 1 : 0,
fill: s.fillSegments.length,
}));
// ⑤ 흙막이 끝 절토 = 수평공간 뒤 절토 경사 — 수평공간 칸 값을 따름
function cutTails(section, list) {
const culvert = computeStoredLayouts(section, list).culvert;
const tagged = (segments, role) => segments.map((g) => ({ ...g, role }));
return [
...tagged(culvert.outletFill.segments, "outlet"),
...tagged(culvert.basinFill.segments, "inlet"),
]
.filter((g) => g.kind === "cut")
.map((g) => ({
role: g.role,
points: g.points.length,
berm: r3(Math.abs(g.points[1].offset - g.points[0].offset)),
flat: r3(g.points[1].elevation - g.points[0].elevation),
}));
}
out.cutDefault = [cutTails(at(352.14), sections), cutTails(at(173.087), sections)];
const berm2 = clone(at(352.14));
berm2.culvert.inlet.revet_berm_m = 2;
berm2.culvert.outlet.revet_berm_m = 2; // 유출은 칸이 없어 무시(89-4)
out.cutBerm2 = cutTails(
berm2,
sections.map((s) => (s === at(352.14) ? berm2 : s)),
);
// ⑤b 유입 수평공간 시작 높이 = 본체 바닥(세월교 바닥판 상면 · 박스 저판 상면 · 89-3)
out.inletFloor = [352.14, 173.087].map((chainage) => {
const section = at(chainage);
const layouts = computeStoredLayouts(section, sections);
const start = layouts.culvert.basinFill.segments[0]?.points[0];
const inletLeft = (section.uphill_side ?? "left") === "left";
const floor = layouts.ford
? layouts.ford.sides.find((s) => s.role === "inlet").slabTopElevation
: layouts.box.sides.find((s) => (s.role === "left") === inletLeft).bottomElevation +
layouts.box.box.slab_thickness_m;
return { start: start ? r3(start.elevation) : null, floor: r3(floor) };
});
// ⑥ 관 보호공 기본 자리 = 제일 위(성토선 0 지점 · d 0) · 관 길이 m 단위
const pipe = clone(at(180));
pipe.culvert = {
type: "pipe",
chainage_m: 180,
pipe_kind: "흄관",
diameter_m: 1.0,
min_cover_m: 0.5,
inlet: { role: "inlet", structure: "기슭막이", revet_form: "돌쌓기(찰)" },
outlet: { role: "outlet", structure: "기슭막이", revet_form: "돌쌓기(찰)" },
};
pipe.design.inlet_structure = "revet";
const pipeLayout = computeCulvertLayout(pipe, pipe.samples, undefined, "revet");
out.pipe = pipeLayout
? {
d: { inlet: pipeLayout.revetShift.inlet.d, outlet: pipeLayout.revetShift.outlet.d },
walls: pipeLayout.walls.map((w) => ({
role: w.role,
topBelowEdge: r3(
(w.outward > 0 ? pipe.design.road_edges.left : pipe.design.road_edges.right).elevation_m -
w.topJoint.elevation,
),
})),
lengthM: pipeLayout.pipe.lengthM,
}
: null;
// ⑥-2 손으로 x 를 넣은 관 보호공 = 종전 자리(d 기본 = 경사길이 4m 지점 · x 원점 그대로 · 79b)
const pipeX = computeCulvertLayout(
pipe,
pipe.samples,
{ outlet: { x: 1, d: null, h: null, m: null } },
"revet",
);
out.pipeStoredX = pipeX ? { x: pipeX.revetShift.outlet.x, d: r3(pipeX.revetShift.outlet.d) } : null;
// ⑦ 독립 기슭막이 · 옹벽 기본 자리 = 제일 위(d 0)
const revet = clone(at(180));
revet.revetment = {
structure_id: "t",
type_id: "masonry_wet",
name: "t",
start_m: 170,
end_m: 190,
anchor_m: 180,
form: "돌쌓기(찰)",
height_m: 2,
side: "우",
};
const own = computeRevetmentLayout(revet, null);
out.revet = own
? {
d: own.appliedAdjust.d,
topBelowEdge: r3(revet.design.road_edges.right.elevation_m - own.top.elevation),
linked: own.linked,
}
: null;
// 저장 x 가 있는 벽 = 종전 자리 그대로(x 원점 = 경사길이 4m 지점 · d 빈 칸 = 종전 기본 · 79b)
const autoX = computeRevetmentLayout(revet, null).walls[0].bottomBack.offset;
const storedX = computeRevetmentLayout(revet, { x: 1, d: null });
const storedXD = computeRevetmentLayout(revet, { x: 1, d: 1 });
out.revetStoredX = {
x: storedX.appliedAdjust.x,
d: storedX.appliedAdjust.d,
// 노견 → 4m 지점(3.07) + x 1 = 제일 위 자리에서 바깥 4.07 m
shift: r3(Math.abs(storedX.walls[0].bottomBack.offset - autoX)),
shiftXD: r3(Math.abs(storedXD.walls[0].bottomBack.offset - autoX)),
};
const neighbour = clone(revet);
neighbour.chainage_m = 170;
out.revetLinked = computeRevetmentLayout(neighbour, null)?.linked ?? null;
out.areas = structureAreaRows(sections).filter((r) =>
[173.087, 352.14].some((c) => Math.abs(r.chainage_m - c) < 0.6),
);
process.stdout.write(JSON.stringify(out, null, 1));