- 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
156 lines
5.5 KiB
JavaScript
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));
|