Files
Aislo/resources/tester/helper_234_1_structure_station.cjs
T
eomsangdonandClaude Opus 5.5 994b91636c feat(B05·B06): 구조물 · 관 기준 측점 정수 m · 일반 측점 1 m 안 붙기 · [정수 맞춤] 단추(234-1)
- 맞춤 규칙 한 벌 — common_util_structure_station(파이썬 · TS 짝 · 거울 시험)
- 입력 — 좌측 폼 칸(1 m 단계 · 친 값만) · 끌기 · 우클릭 · 방향키(1 m 걸음) · 배수유역도 관 · B06 관 옮김 · 추가
- 구간형 시작 · 끝도 정수 m · 일반 측점 = 횡단 상세를 읽을 때 등록
- 서버 자동 배치 · 노선 재투영 자리도 정수 m(확정본은 단추로만)
- [정수 맞춤] — 구조물 목록 머리 · 확인 창 · 관은 하나씩 폼 [수정] 길 · 계획고 편집 따라감 · 결과 n건 알림
- 찾는 폭 한 값 — 손으로 적은 0.51 을 PIPE_MATCH_M 으로
- 시험 — test_234_1 새로 · 1 m 걸음 · 정수 입력에 맞춘 기대값 고침(219_1 · station_roundtrip) · 헬퍼 대역 보탬

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
2026-10-10 19:05:52 +09:00

193 lines
7.2 KiB
JavaScript

/* 234-1 구조물 기준 측점 = 정수 m · 일반 측점 붙기 · [정수 맞춤].
* 진짜 `common_util_structure_station` · `_Structures_Snap` · `_Structures_Fields` · `_Drainage_Pipes` ·
* `followedStation` 을 Node 로 돌림. 입력은 argv[2] JSON(파이썬과 같은 값) · 표준출력 JSON 한 벌.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
function loadTs(file, stubs = {}) {
const js = ts.transpileModule(fs.readFileSync(path.join(ROOT, file), "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
const localRequire = (request) => {
if (stubs[request]) return stubs[request];
throw new Error(`stub 없음: ${request} (${file})`);
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
return box.exports;
}
/* 가짜 DOM — 폼 칸(input · label · span)만. */
function fakeElement(tag) {
const listeners = {};
const classes = new Set();
return {
tagName: tag,
value: "",
className: "",
style: {},
children: [],
classList: {
toggle: (name, on) => (on ? classes.add(name) : classes.delete(name)),
add: (name) => classes.add(name),
remove: (name) => classes.delete(name),
contains: (name) => classes.has(name),
},
append(...nodes) {
this.children.push(...nodes);
},
addEventListener(type, fn) {
(listeners[type] ??= []).push(fn);
},
fire(type) {
(listeners[type] ?? []).forEach((fn) => fn({ type }));
},
focus() {},
};
}
global.document = { createElement: fakeElement };
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const station = loadTs("common_util/common_util_structure_station.ts");
const STATIONS = input.stations;
const out = {};
// ① 맞춤 · 걸음 — 파이썬과 같은 값(거울)
out.snap = input.samples.map((value) => station.snapStructureStation(value, STATIONS));
out.step = input.steps.map(([from, dir]) => station.stepStructureStation(from, dir, STATIONS));
out.snapTwice = input.samples.every(
(value) =>
station.snapStructureStation(station.snapStructureStation(value, STATIONS), STATIONS) ===
station.snapStructureStation(value, STATIONS),
);
// 일반 측점 등록 — 구조물 측점(irregular)은 뺌 · 등록값이 기본으로 쓰임
station.setGeneralStations(
STATIONS.map((value) => ({ chainage_m: value, kind: "regular" })).concat([
{ chainage_m: 80.17, kind: "irregular" },
]),
);
out.general = [...station.generalStations()];
out.defaultSnap = station.snapStructureStation(80.4);
// ② 좌측 폼 칸 — 친 값 80.17 → 80 · 안 고친 옛 값은 그대로
const fields = loadTs("B05_Profile/B05_Profile_UI_Structures_Fields.ts", {
"./B05_Profile_Util_Station": loadTs("B05_Profile/B05_Profile_Util_Station.ts"),
"../B06_Section/B06_Section_UI_Cross_Culvert_Basin": { DEFAULT_BASIN_SHAPE: {} },
"../common_util/common_util_culvert_sets": { fillPipeHeights: () => {} },
"../common_util/common_util_structure_station": station,
});
const anchor = fields.stationFields("기준 측점", () => 20);
out.remainderStep = anchor.remainder.step;
anchor.station.value = "4";
anchor.remainder.value = "0.17";
out.typed = anchor.read(20, true);
anchor.remainder.value = "0.6";
out.typedNear = anchor.read(20, true);
anchor.remainder.value = "1";
out.typedOne = anchor.read(20, true);
anchor.write(80.17, 20);
out.untouched = anchor.read(20, true);
// ③ 구조물 옮김 · [정수 맞춤] 셈 · 단추
const toasts = [];
let confirmText = null;
const snap = loadTs("B05_Profile/B05_Profile_UI_Structures_Snap.ts", {
"@ui/ui_template_elements": {
showToast: (message, kind) => toasts.push({ message, kind }),
showConfirmDialog: async (message) => ((confirmText = message), true),
},
"../common_util/common_util_structure_station": station,
"../A00_Common/design/design_selection": { PIPE_MATCH_M: 0.51 },
"./B05_Profile_Api_Structures": {
structureAnchorM: (entry) => entry.chainage_m ?? entry.start_m ?? 0,
},
});
out.movedPoint = snap.movedStructure({ placement: "point", chainage_m: 127.86 }, 127.86);
out.movedWall = snap.movedStructure(
{ placement: "interval", chainage_m: 80.17, start_m: 75.17, end_m: 95.67 },
80.17,
);
out.movedDrag = snap.movedStructure(
{ placement: "interval", chainage_m: 50, start_m: 50, end_m: 62 },
133.4,
);
const structures = input.structures.map((entry) => ({ ...entry }));
const pipes = input.pipes.map((chainage_m) => ({
chainage_m,
facility: "pipe",
options: { pipe_kind: "흄관" },
summary_item: null,
group_items: null,
}));
const plan = snap.snapPlan(structures, pipes);
out.plan = {
structures: plan.structures.map(({ index, next }) => ({ index, next })),
pipes: plan.pipes.map(({ from, to }) => [from, to]),
skipped: plan.skipped,
};
const moves = [];
let emits = 0;
let closed = 0;
snap
.runSnap({
structures,
pipes: () => pipes,
emit: () => (emits += 1),
movePipe: (from, to, attributes) => moves.push({ from, to, attributes }),
closeForm: () => (closed += 1),
})
.then(async (count) => {
out.run = { count, emits, closed, moves, structures, confirmText, toasts: [...toasts] };
// 두 번째 누름 = 옮길 것 없음(관은 위 moves 로 옮겨졌다고 보고 목록을 맞춤)
moves.forEach(({ from, to }) => {
const hit = pipes.find((pipe) => pipe.chainage_m === from);
if (hit) hit.chainage_m = to;
});
toasts.length = 0;
const again = { structures, pipes: () => pipes, emit() {}, movePipe() {}, closeForm() {} };
out.second = { count: await snap.runSnap(again), toasts: [...toasts] };
// ④ 계획고 편집 따라감 — 한꺼번에 여럿 옮김(사라진 둘 · 새 둘)
const alignment = loadTs("B05_Profile/B05_Profile_UI_Profile_Alignment.ts");
const gone = [
{ id: "pipe-80.17", chainage_m: 80.17 },
{ id: "pipe-263.31", chainage_m: 263.31 },
];
const born = [
{ id: "pipe-80.00", chainage_m: 80 },
{ id: "pipe-263.00", chainage_m: 263 },
];
out.follow = gone.map((entry) => {
const old = alignment.followedStation(entry, gone, born);
const next = old ?? snap.followSnapped(entry, born);
return { old: old?.chainage_m ?? null, next: next?.chainage_m ?? null };
});
// ⑤ 배수유역도 관 지점 — 옮김 · 우클릭 추가 = 정수 m
const pipesEditor = loadTs("B05_Profile/B05_Profile_UI_Drainage_Pipes.ts", {
"@ui/ui_template_palette": { themeColor: () => "#000" },
"../common_util/common_util_structure_station": station,
});
const editor = pipesEditor.createPipeEditor(() => {});
editor.setContext(null, [
{ x: 0, y: 0, chainage_m: 0 },
{ x: 300, y: 0, chainage_m: 300 },
]);
editor.setPipes([{ chainage_m: 80.17, reason: "stream" }]);
editor.moveTo(0, 141.37);
editor.addAtChainage(200.6);
out.editor = editor.pipes().map((pipe) => pipe.chainage_m);
process.stdout.write(JSON.stringify(out));
})
.catch((error) => {
process.stderr.write(String(error && error.stack ? error.stack : error));
process.exit(1);
});