Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
110 lines
4.5 KiB
JavaScript
110 lines
4.5 KiB
JavaScript
/* 139-2 연동 풀림 굳힘 헬퍼 — 화면 TS(`_Page_Link_Session.freezeLinkedWalls` · `_Cross_Culvert_Wire`)를
|
|
* 그 자리에서 트랜스파일해 Node 로 돌림. 입력 = 73-4 픽스처(박스 352.14 흙막이 · 옆 측점 340 · 360).
|
|
* 짝 = `test_139_2_link_freeze.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 noop = new Proxy({}, { get: () => () => "" });
|
|
// 조정창 모듈이 불러올 때 문서 이벤트를 검 — 기하엔 안 쓰임
|
|
globalThis.document = { addEventListener: () => undefined };
|
|
globalThis.window = globalThis;
|
|
|
|
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 },
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
cache.set(full, box.exports);
|
|
const localRequire = (request) => {
|
|
// 화면 전용(토스트 · 글 · 설정) — 기하엔 안 쓰임
|
|
if (request.startsWith("@ui/") || request.startsWith("@config/")) return noop;
|
|
if (request.startsWith("@util/"))
|
|
return loadTs(resolve(path.join(ROOT, "common_util", request.slice(6))));
|
|
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`].find((p) => fs.existsSync(p) && fs.statSync(p).isFile()) ?? `${base}.ts`;
|
|
|
|
const B06 = (f) => path.join(ROOT, "B06_Section", f);
|
|
const { freezeLinkedWalls } = loadTs(B06("B06_Section_UI_Page_Link_Session.ts"));
|
|
const { computeCardCulvert, culvertLinkFor } = loadTs(B06("B06_Section_UI_Cross_Culvert_Wire.ts"));
|
|
|
|
const sections = JSON.parse(
|
|
fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"),
|
|
).cross_sections;
|
|
const at = (c) => sections.find((s) => Math.abs(s.chainage_m - c) < 0.6);
|
|
|
|
// 제어기 흉내 — 측점:역할 → 4축(세션 맵과 같은 꼴)
|
|
const stored = new Map();
|
|
const keyOf = (c, role) => `${c.toFixed(2)}:${role}`;
|
|
const ZERO = { x: 0, d: null, h: null, m: null };
|
|
const revetOffset = {
|
|
adjustFor: (s, role) => ({ ...ZERO, ...(stored.get(keyOf(s.chainage_m, role)) ?? {}) }),
|
|
storedAdjustFor: (s, role) => stored.get(keyOf(s.chainage_m, role)) ?? null,
|
|
update: (c, role, patch) =>
|
|
stored.set(keyOf(c, role), { ...ZERO, ...(stored.get(keyOf(c, role)) ?? {}), ...patch }),
|
|
reset: (c, role) => stored.delete(keyOf(c, role)),
|
|
syncApplied: () => undefined,
|
|
selectedFor: () => null,
|
|
highlightFor: () => null,
|
|
select: () => undefined,
|
|
};
|
|
const extraWalls = { countFor: () => 0, consumeEqualize: () => false };
|
|
let linked = true;
|
|
const revetLink = { linkedFor: () => linked, followGradeFor: () => true };
|
|
const controls = { revetOffset, extraWalls, revetLink };
|
|
|
|
const round = (v) => Math.round(v * 1000) / 1000;
|
|
const shape = (s) => {
|
|
const link = culvertLinkFor(s, sections, () => null);
|
|
const layout = computeCardCulvert(
|
|
s,
|
|
s.samples,
|
|
null,
|
|
revetOffset,
|
|
undefined,
|
|
extraWalls,
|
|
link,
|
|
revetLink,
|
|
);
|
|
return (layout?.walls ?? []).map((w) => ({
|
|
role: w.role,
|
|
points: w.points.map((p) => [round(p.offset), round(p.elevation)]),
|
|
}));
|
|
};
|
|
|
|
const owner = at(352.14);
|
|
const card = at(360);
|
|
// 소유 측점 벽을 조금 옮겨 둠 — 옆 카드가 물려받는 자리가 자동 자리가 아니게
|
|
revetOffset.update(owner.chainage_m, "outlet", { x: 0.7, h: 2.3 });
|
|
const linkedShape = shape(card);
|
|
freezeLinkedWalls(card, sections, controls);
|
|
const frozen = Object.fromEntries(
|
|
[...stored].filter(([k]) => k.startsWith("360.00:")).map(([k, v]) => [k.slice(7), v]),
|
|
);
|
|
linked = false;
|
|
const detachedShape = shape(card);
|
|
// 푼 뒤 소유 측점을 또 옮김 — 푼 카드는 안 따라감
|
|
revetOffset.update(owner.chainage_m, "outlet", { x: 2.0 });
|
|
const afterOwnerMove = shape(card);
|
|
linked = true; // 대조 — 연동이면 소유 측점 이동을 따라감(시험이 실제로 재는지)
|
|
const linkedAfterMove = shape(card);
|
|
process.stdout.write(
|
|
JSON.stringify({ linkedShape, frozen, detachedShape, afterOwnerMove, linkedAfterMove }),
|
|
);
|