- 54_9 알약: 구조물 표 불러오기(322-1) 못 찾음 → makeLoader 기본 풀이 - 398_2 · 408: 곡선 판 가둠(505-3) ResizeObserver · 판 찾기 가짜 더함 → makeLoader - 323_9: 라우터 draft 길 import.meta.glob(302-1) 가짜 · 옛 풀이 그대로 requireFor · 기대 = 노선 그대로 [저장] 가림막 없음(498-3 D1) - test_app_settings: CAD 명령 153 → 154(488-3 IMPORT) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
191 lines
6.1 KiB
JavaScript
191 lines
6.1 KiB
JavaScript
/* 398-2 꺾임점 창 반지름 · 곡선 길이 넣기(요구 249) — `_CurveBar` 배선을 Node 로 돌려 JSON 한 줄
|
|
* (`test_398_2_curve_input.py` 가 판정). 창(`_Label`)은 손잡이만 잡는 가짜 · 편집 창 `applyEdit` 는
|
|
* 같은 길(스냅 켬 = `fitCurveStations` → 길이 고정 R · 하한 → `buildEditedPolyline`)로 흉내.
|
|
* node helper_398_2_curve_input.cjs
|
|
*/
|
|
const path = require("path");
|
|
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const B05 = path.join(ROOT, "B05_Profile");
|
|
let handlers = null;
|
|
const shows = [];
|
|
const STUBS = {
|
|
"./B05_Profile_UI_RouteEdit_Label": {
|
|
createCurveLabel: (given) => {
|
|
handlers = given;
|
|
return { show: (state) => shows.push(state), hide: () => {}, destroy: () => {} };
|
|
},
|
|
centerDirectionOf: () => null,
|
|
},
|
|
"@ui/ui_template_elements": { showToast: () => {} }, // 곡선 삭제 경고(430)
|
|
};
|
|
|
|
// 곡선 판 가둠(505-3) — 판 크기 감시 · 가짜 그릇엔 판이 없어 빈 감시
|
|
globalThis.ResizeObserver = class {
|
|
observe() {}
|
|
disconnect() {}
|
|
};
|
|
|
|
const { loadTs } = makeLoader({ root: ROOT, stubs: STUBS });
|
|
|
|
const curve = loadTs(path.join(B05, "logic", "B05_Profile_UI_RouteEdit_Curve.ts"));
|
|
const edits = loadTs(path.join(B05, "logic", "B05_Profile_UI_RouteEdit_Edits.ts"));
|
|
const fit = loadTs(path.join(B05, "logic", "B05_Profile_RouteEdit_StationFit.ts"));
|
|
const bar = loadTs(path.join(B05, "B05_Profile_UI_RouteEdit_CurveBar.ts"));
|
|
|
|
// IP1 = 앞뒤 직선 길어 R 60 들어감 · IP2 = 뒤 직선 16 m 라 접선 8 m 까지(큰 R 눌림)
|
|
const ROUTE = [
|
|
[0, 0],
|
|
[120, 0],
|
|
[220, 70],
|
|
[236, 70],
|
|
[340, 0],
|
|
];
|
|
const LIMITS = { minRadiusM: 12, limitRadiusM: 12, limitArcM: 5, intervalM: 20 };
|
|
|
|
function editor(snap) {
|
|
const n = ROUTE.length;
|
|
const state = {
|
|
picked: -1,
|
|
planned: ROUTE.map((v) => [v[0], v[1]]),
|
|
nodeInfo: [],
|
|
curveInfo: [],
|
|
curveOn: Array(n).fill(true),
|
|
curveRadius: Array(n).fill(null),
|
|
curveLock: Array(n).fill(null),
|
|
curveArc: Array(n).fill(null),
|
|
apexLock: Array(n).fill(false),
|
|
limitRadiusM: LIMITS.limitRadiusM,
|
|
limitArcM: LIMITS.limitArcM,
|
|
line: [],
|
|
snap: snap ? { stepM: LIMITS.intervalM, stationM: LIMITS.intervalM } : null,
|
|
};
|
|
const status = { textContent: "" };
|
|
const markEdited = () => {
|
|
edits.applyArcLocks(state.planned, state.curveLock, state.curveArc, state.curveRadius);
|
|
edits.applyCurveLimits(
|
|
state.planned,
|
|
state.curveOn,
|
|
state.curveRadius,
|
|
LIMITS.limitRadiusM,
|
|
LIMITS.limitArcM,
|
|
);
|
|
const built = curve.buildEditedPolyline(
|
|
state.planned,
|
|
state.curveOn,
|
|
state.curveRadius,
|
|
LIMITS.minRadiusM,
|
|
);
|
|
state.line = built.vertices;
|
|
state.curveInfo = built.curves;
|
|
state.nodeInfo = built.nodes;
|
|
};
|
|
let made = null;
|
|
const applyEdit = (message) => {
|
|
if (snap) state.planned = fit.fitCurveStations(state, LIMITS, 1);
|
|
markEdited();
|
|
made.sync();
|
|
status.textContent = `예상노선 — ${message} 꼬리`;
|
|
};
|
|
made = bar.createCurveBar({
|
|
canvas: {
|
|
getBoundingClientRect: () => ({ left: 0, top: 0, right: 800, bottom: 600 }),
|
|
closest: () => null,
|
|
parentElement: null,
|
|
isConnected: true,
|
|
},
|
|
container: { querySelector: (sel) => (sel === ".b05-routeedit__status" ? status : null) },
|
|
state: () => state,
|
|
toScreen: (v) => [v[0], v[1]],
|
|
applyEdit,
|
|
onUnselect: () => {},
|
|
});
|
|
markEdited();
|
|
const drawn = (k) => state.curveInfo.find((c) => c.node_first === k) ?? null;
|
|
const arcOf = (k) => {
|
|
const c = drawn(k);
|
|
return c ? c.radius_m * edits.deflectionRad(c.inner_angle_deg) : null;
|
|
};
|
|
const mc = (k) => {
|
|
const c = drawn(k);
|
|
if (!c) return null;
|
|
const [[a, b]] = loadTs(
|
|
path.join(B05, "logic", "B05_Profile_UI_RouteEdit_Frame.ts"),
|
|
).curveSpans(state.line, [c]);
|
|
return (a + b) / 2;
|
|
};
|
|
const pick = (k) => {
|
|
state.picked = k;
|
|
made.sync();
|
|
};
|
|
const lastShow = () => shows[shows.length - 1];
|
|
return { state, status, handlers: () => handlers, drawn, arcOf, mc, pick, lastShow, markEdited };
|
|
}
|
|
|
|
const round = (v) => (v === null ? null : Math.round(v * 1000) / 1000);
|
|
const out = {};
|
|
|
|
for (const snap of [false, true]) {
|
|
const key = snap ? "snapOn" : "snapOff";
|
|
const ed = editor(snap);
|
|
const h = ed.handlers();
|
|
const row = {};
|
|
// IP1 — 반지름 40 → 60 · 곡선 길이 18 → 25
|
|
ed.pick(1);
|
|
h.onRadius(40);
|
|
row.r40 = round(ed.drawn(1).radius_m);
|
|
const later = () => [2, 3].map((k) => round(ed.mc(k))); // 뒤쪽 곡선 MC(408-1)
|
|
row.r40Later = later();
|
|
h.onRadius(60);
|
|
row.r60 = round(ed.drawn(1).radius_m);
|
|
row.r60Shown = round(ed.lastShow().radiusShown);
|
|
row.r60Status = ed.status.textContent;
|
|
row.r60Mc = round(ed.mc(1));
|
|
row.r60Later = later();
|
|
h.onArcLength(18);
|
|
row.l18 = round(ed.arcOf(1));
|
|
h.onArcLength(25);
|
|
row.l25 = round(ed.arcOf(1));
|
|
row.l25Shown = round(ed.lastShow().arcLengthShown);
|
|
row.l25Status = ed.status.textContent;
|
|
// IP2 — 접선 자리 모자람(넣은 값 · 그린 값 · 까닭)
|
|
ed.pick(2);
|
|
h.onRadius(60);
|
|
row.capR = round(ed.drawn(2).radius_m);
|
|
row.capShown = round(ed.lastShow().radiusShown);
|
|
row.capStatus = ed.status.textContent;
|
|
h.onArcLength(40);
|
|
row.capArcStatus = ed.status.textContent;
|
|
h.onRadius(null);
|
|
row.autoStatus = ed.status.textContent;
|
|
out[key] = row;
|
|
}
|
|
|
|
// 「고정」 — 길이 고정 켬 = 교각이 바뀌어도 그 길이 · 끔 = 반지름이 남고 길이가 따라 바뀜
|
|
for (const lock of [true, false]) {
|
|
const ed = editor(false);
|
|
const h = ed.handlers();
|
|
ed.pick(1);
|
|
h.onArcLength(25);
|
|
if (lock) h.onLock("arc");
|
|
ed.state.planned[2] = [220, 50]; // IP1 교각이 바뀜
|
|
ed.markEdited();
|
|
out[lock ? "arcLocked" : "arcFree"] = {
|
|
arc: round(ed.arcOf(1)),
|
|
radius: round(ed.drawn(1).radius_m),
|
|
};
|
|
}
|
|
{
|
|
const ed = editor(false);
|
|
const h = ed.handlers();
|
|
ed.pick(1);
|
|
h.onRadius(60);
|
|
h.onLock("radius");
|
|
ed.state.planned[2] = [220, 50];
|
|
ed.markEdited();
|
|
out.radiusLocked = { radius: round(ed.drawn(1).radius_m), lock: ed.state.curveLock[1] };
|
|
}
|
|
|
|
process.stdout.write(JSON.stringify(out));
|