/* 472-1 헬퍼 — 브라우저 길 기준값(진짜 단계 함수 · vite ssr 묶음 · helper_472_1_browser_entry.ts). * node helper_472_1_browser.mjs <묶음.mjs> <고정 자료 폴더> <격자 .bin> [옮김 .json] * 옮김(475-1) = { node, dx, dy, structures?, plus? } — 열기 뒤 `pipe.update("route", 초안)`(노드 하나 옮김 · 페이지 * `flattenServerPlan` 초안 꼴) · 구조물 · 추가 측점 바꿔 넣기. * 페이지 묶음(`design_pipeline_page`)과 같은 단계 붙임 — 저장 노선 · 초안 없음 · 세션 = 메모리 가짜 · fetch 없음 · * 격자 = 서버 `dtm_grid_bytes` 가 고정 npz 로 지은 이진. 묶음 출력 → 새 노선 상세(`draftSectionDetail`) → 구조물 * 면적 얹기 → 누가토량. 표준출력 JSON 한 벌(값 꼴 = test_472_1_golden.py `values_of`). */ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; const storage = () => { 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 = storage(); globalThis.localStorage = storage(); 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.fetch = () => Promise.reject(new Error("서버 없음")); const [bundle, dir, gridPath, movePath] = process.argv.slice(2); const move = movePath ? JSON.parse(readFileSync(movePath, "utf8")) : {}; const m = await import(pathToFileURL(bundle).href); const read = (name) => JSON.parse(readFileSync(join(dir, name), "utf8")); const detail = read("detail.json"); const plan = read("plan.json"); const context = read("context.json"); const pipes = read("pipes.json").pipe_points.map(({ lonlat: _l, ...pipe }) => pipe); const structures = move.structures ?? read("structures.json").structures; const types = read("structure_types.json").types; const plus = move.plus ?? read("plus_stations.json").chainages; const P = context.project_id; const R = context.route_id; const bin = readFileSync(gridPath); const grid = m.parseDtmGrid(bin.buffer.slice(bin.byteOffset, bin.byteOffset + bin.byteLength)); // 페이지가 열 때 세션에 두는 것 — 표준 · 암 경계 기본 · 저장 상세(design_view_profile · 상세 가게) localStorage.setItem("frd_current_project_id", P); m.writeState("std-cross-default", context.standard_cross_section, P); m.writeStateRaw("rock-boundary-default", String(context.rock_boundary_default_offset_m), P); m.writeState("section-detail", detail, P, R); const interval = m.SECTION_STATION_INTERVAL_M; const enabled = types.filter((type) => type.enabled !== false); const savedLine = () => plan.planned.map(([x, y]) => [x, y]); const savedCurves = () => (plan.curves ?? []).map(({ start, apex, end, radius_m }) => ({ start, apex, end, radius_m })); const savedStations = m.buildStations(savedLine(), savedCurves(), pipes, structures, { intervalM: interval, types: enabled, stationSnap: plan.station_snap ?? false, stationSnapStep: plan.station_snap_step, }); const crossSource = m.pipelineCrossSource(P, detail, savedStations, R); const edits = m.readAlignment(detail.longitudinal).edits; const bermAt = (list) => { const spans = m.bermSpansFromStructures(Array.isArray(list) ? list : []); return (chainageM) => m.bermSpecAt(spans, chainageM); }; const crossStage = m.createCrossStage({ grid: () => ({ grid }) }); const pipe = m.createDesignPipeline({ stages: { route: m.createRouteStage({ minRadiusM: () => plan.min_radius_m ?? 0, savedLine: () => ({ line: savedLine(), curves: savedCurves() }), }), follow: m.createFollowStage({ savedLine, saved: () => ({ pipes, structures, alignment: edits }), // 페이지 배선(`design_pipeline_page` stationsAt) stationsAt: (route, patch, inputs) => m .buildStations( route.line, route.curves, patch.pipes ?? inputs.pipes ?? [], patch.structures ?? inputs.structures ?? [], { intervalM: interval, types: enabled, ...m.snapOptions(inputs.route, plan) }, ) .map((station) => station.chainageM), }), stations: m.createStationsStage({ intervalM: interval, types: () => enabled, saved: () => plan, }), ground: m.createGroundStage({ grid: () => ({ grid, design: "las" }), remote: () => Promise.reject(new Error("서버 없음")), }), planline: m.createPlanlineStage({ savedBase: (at) => m.plantedBase({ base: null, detail }, at, types), savedLine, storedPvi: () => m.toAlignmentBase(m.readAlignment(detail.longitudinal)).basePvi, }), profile: m.profileStage, cross: (ctx) => crossStage.stage({ ...ctx, inputs: { ...ctx.inputs, cross: m.crossWithContext( ctx.inputs.cross, ctx.out.ground?.stations ?? [], m.shapeStatus(ctx.inputs.pipes, ctx.inputs.structures, types), bermAt(ctx.inputs.structures), ), }, }), footprint: m.footprintStage, }, }); const record = await pipe.start({ route: null, alignment: edits, cross: crossSource?.input ?? null, pipes, structures, plusStations: plus, }); await pipe.settled(); if (record.outcome !== "done") throw new Error(`묶음 ${record.outcome}`); if (move.node != null) { const flat = m.flattenServerPlan(plan.nodes, plan.curves ?? [], plan.node_edits); const nodes = flat.planned.map(([x, y]) => [x, y]); nodes[move.node] = [nodes[move.node][0] + move.dx, nodes[move.node][1] + move.dy]; const moved = await pipe.update("route", { rev: 1, nodes, curveOn: flat.curveOn.map((on) => on !== false), curveRadius: flat.curveRadius.map((r) => r ?? null), curveLock: flat.curveLock ?? nodes.map(() => null), curveArc: flat.curveArc ?? nodes.map(() => null), stationSnap: plan.station_snap ?? false, stationSnapStep: plan.station_snap_step ?? null, terrain: null, }); await pipe.settled(); if (moved.outcome !== "done") throw new Error(`묶음(옮김) ${moved.outcome}`); } // 단면 모양 = 따라간 관 · 구조물(페이지 = follow 뒤 가게 값 · 457) const follow = pipe.output("follow"); const status = m.shapeStatus(follow?.pipes ?? pipes, follow?.structures ?? structures, types); const ground = pipe.output("ground"); const profile = pipe.output("profile"); const shown = m.draftSectionDetail( detail, ground, profile, pipe.output("cross"), crossSource?.specs ?? new Map(), interval, status, ); m.applyStructureAreaRows(shown.cross_sections, m.structureAreaRows(shown.cross_sections)); const haul = m.computeMassHaul( shown.cross_sections, context.earthwork_conversion, context.natural_spoil_min_ground_slope ?? undefined, ); process.stdout.write( JSON.stringify({ keys: ground.stations.map((station) => station.key), sections: shown.cross_sections, mass_haul: haul, warnings: profile.warnings ?? [], violations: profile.violations ?? [], footprint_records: pipe.output("footprint")?.records?.length ?? 0, skipped: record.skipped, route_line: pipe.output("route").line, draft: pipe.input("route"), }), ); process.exit(0);