Files
Aislo/resources/tester/helper_234_1_structure_station.cjs
T
eomsangdonandClaude Opus 5.5 93e86f87c6 fix(b05,b06): 측점 한 벌 — 정수 m · 같은 m 측점 하나 · 목록 전체 다시 맞춤 (272)
- 규칙: 구조물 · 관 기준 측점 = 정수 m 사사오입(일반 측점 1 m 붙기 뺌) · 측점 심는 자리 TS 짝(plantedChainages ↔ resolve_extra_stations) · 같은 m 면 구조물 측점 하나(격자 자리 grid) · 떠나면 일반 측점(oneStationList ↔ _merge_irregular_into_longitudinal)
- 서버: B05 [저장] · B06 저장 · [측점 만들기]가 목록 전체 다시 맞춤(빈 목록도 · 옛 글 · 횡단 파일 글) · 계획선 측점 줄 다시 셈 · 정본에 없는 횡단 행은 0.5 m 안 새 측점으로 옮기고 나머지 지움 · 옛 소수 측점 꼭짓점 · 편집은 정수 자리로 · 맞춤 실패는 저장을 안 막음
- 화면: 종단 그래프 · 3D · 표 · ▲▼ 한 목록(초안 반영) · C군 구간형 · 독립 기슭막이 시작 · 끝 측점 · B06 카드는 초안에 없는 옛 구조물 측점 숨김 · 고름은 같은 m 구조물 측점 먼저 · 폼 거리 칸 정수 표기 · C군 계산 구간 · 포장 나눔 경계 정수
- 옛 자료: [정수 맞춤] 단추 없앰 · 종단 보기 열 때 한 번 자동 정수 · 알림 한 줄 · [저장]하면 남음
- 시험: test_272_1 · test_272_2 새로 · 234_1 · 238_1 · b05_extra_stations_supplier · b06_patch_station · b05_station_roundtrip · b05_b06_save_roundtrip 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLCk2y5xE8YGVgjbTntWsY
2026-10-11 19:53:24 +09:00

194 lines
7.3 KiB
JavaScript

/* 234-1 · 272 구조물 기준 측점 = 정수 m · 옛 소수 자동 맞춤(단추 없음 · 167).
* 진짜 `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;
Promise.resolve(
snap.snapOldStations({
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: snap.snapOldStations(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);
});