Files
Aislo/resources/tester/helper_266_card_toggle.mjs
T
eomsangdonandClaude Opus 5.5 97b5a78695 fix(441 B05 스냅 끌기): 맞춤 교점 이동 한도 = 간격 · 미리보기 측점 밖 늘면 그 걸음 되돌림 · 초안 없는 측점 단계 = 저장 스냅
- StationFit reach = intervalM(반 간격이면 MC 18+9 ~ 19+1 못 맞춤)
- 끌기 · 방향키 — 미리보기 측점 밖 곡선 수가 누를 때보다 늘면 그 걸음 되돌림 · dragMoved 는 막힘 검사 뒤(433-14)
- 계산 줄 stations 단계 · follow 새 측점 — 초안 없으면 저장 노선 station_snap · station_snap_step(snapOptions)
- 시험 — 390_5 끌기 훑기(놓은 뒤 측점 밖 0 · 자동 · 고정 R) · 344_3 저장 스냅 · 266 사면 이동 한 걸음 · 다시 그림 한 번

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-18 18:42:06 +09:00

156 lines
5.5 KiB
JavaScript

/* PLAN 439 헬퍼 — 저장 노선 cross 차례(`refreshSavedCross`)가 카드 단추(복합경사 · 측구 · 지반)를 늘 다시 셈:
* 묶음 측점 = 상세 측점(돌려줌) · 다름 1 mm 넘게(null = 묶음 엔진 · 카드 자료는 고침) · 서버 폴백에 카드 선택 실음.
* node helper_266_card_toggle.mjs <묶음.mjs> <job.json>
* 짝 = `test_266_card_toggle_mismatch.py` · 표준출력 JSON 한 벌.
*/
import { readFileSync } from "node:fs";
import { pathToFileURL } from "node:url";
const store = () => {
const m = new Map();
return {
getItem: (k) => (m.has(k) ? m.get(k) : null),
setItem: (k, v) => m.set(k, String(v)),
removeItem: (k) => m.delete(k),
key: (i) => [...m.keys()][i] ?? null,
get length() {
return m.size;
},
clear: () => m.clear(),
};
};
globalThis.window = globalThis;
globalThis.sessionStorage = store();
globalThis.localStorage = store();
const any = new Proxy(function () {}, {
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any),
apply: () => any,
construct: () => any,
});
globalThis.document = any;
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
globalThis.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} });
globalThis.addEventListener = () => undefined;
globalThis.removeEventListener = () => undefined;
globalThis.screen = { width: 1920, height: 1080 };
globalThis.location = {
href: "http://localhost/",
pathname: "/",
search: "",
hash: "",
origin: "http://localhost",
};
const job = JSON.parse(readFileSync(process.argv[3], "utf8"));
// 서버 폴백 창구만 받아 적음 — 그 밖 요청은 404(격자 없음 = 넓힘 안 함)
const previews = [];
globalThis.fetch = async (url, init) => {
if (String(url).includes("/cross-design/preview")) {
previews.push(JSON.parse(init.body));
return Response.json({ designs: [] });
}
return new Response("{}", { status: 404 });
};
const m = await import(pathToFileURL(process.argv[2]).href);
const R = 7;
const EDITS = { station_offsets: {}, curve_radii: {} };
const alignment = {
stations: job.sections.map((s) => ({ chainage_m: s.chainage_m, plan_elevation_m: s.plan_z })),
samples: job.sections.map((s) => ({ chainage_m: s.chainage_m, elevation_m: s.plan_z })),
balance: { cut_area_m2: 0, fill_area_m2: 0, net_area_m2: 0 },
};
const detailOf = (withLine) => ({
longitudinal: withLine
? { length_m: 1000, stations: [], profile_alignment: { policy: {} } }
: { length_m: 1000, stations: [] },
cross_sections: job.sections.map(({ plan_z, ...section }) => ({
...structuredClone(section),
design: {
ground_type: "ripping_rock",
section_mode: job.mode,
design_elevation_m: plan_z,
},
})),
});
const pick = (design) => ({
ground_type: design.ground_type,
two_stage_slope: design.two_stage_slope ?? null,
ditch_enabled: design.ditch_enabled ?? null,
cut: design.cut_area_m2 ?? null,
fill: design.fill_area_m2 ?? null,
line: JSON.stringify(design.design_line ?? null),
});
/** 카드 단추 줄 차례 — 처음 셈 → 복합경사 켬 → 측구 끔 → 지반 흙. `shift` = 묶음 측점 어긋남(m). */
async function toggles(projectId, shift) {
m.writeState("std-cross", job.standard, projectId);
m.rememberRockBoundaryDefault(projectId, job.rockDefault);
const detail = detailOf(true);
const target = detail.cross_sections[job.target];
const stations = detail.cross_sections.map((s, i) => ({
key: `s${i}`,
chainageM: s.chainage_m + shift,
}));
const steps = [];
const run = async (tag) => {
const updated = [];
let calls = 0; // 받는 쪽 = 카드 다시 그림 + 종단 · 유토 다시 그림(design_shared) — 한 걸음에 한 번
const out = await m.refreshSavedCross(
{ projectId, routeId: R, detail, edits: EDITS, alignment },
stations,
(rows) => (calls++, updated.push(...rows)),
);
steps.push({
tag,
out: out ? Object.keys(out.byKey).length : null,
updated,
calls,
...pick(target.design),
});
};
await run("open");
const choice = {
ground_type: "ripping_rock",
section_mode: job.mode,
ditch_side: null,
ditch_type: "standard",
paved: false,
two_stage_slope: false,
ditch_choice: null,
};
for (const [tag, over] of [
["two_stage", { two_stage_slope: true }],
["ditch_off", { ditch_choice: false }],
["soil", { ground_type: "soil" }],
]) {
Object.assign(choice, over);
m.writeCrossDesignChoice(projectId, R, target.chainage_m, { ...choice });
await run(tag);
}
// 사면 이동 한 걸음(B06 ◀▶ 0.1 m · 깎기 쪽 · 441 · 요구 268-4)
m.writeShoulderExtend(projectId, R, target.chainage_m, job.mode.split("_")[0], 0.1);
await run("slope");
return { target: target.chainage_m, steps };
}
const out = { same: await toggles("p266a", 0), mismatch: await toggles("p266b", 0.002) };
/* 서버 폴백(선형 저장분 없음) — 카드 선택이 요청에 실림 · 열쇠 = 측점 자리 */
{
const projectId = "p266c";
m.writeState("std-cross", job.standard, projectId);
const detail = detailOf(false);
const at = detail.cross_sections[job.target].chainage_m;
m.writeCrossDesignChoice(projectId, R, at, {
ground_type: "soil",
section_mode: job.mode,
two_stage_slope: true,
ditch_choice: false,
});
await m.refreshSavedCross({ projectId, routeId: R, detail, edits: EDITS }, [], () => undefined);
out.server = { at, choices: previews.at(-1)?.design_choices ?? null };
}
process.stdout.write(JSON.stringify(out));