- 하한 검사 = 모든 평면 조작이 applyEdit 에서 지난 걸음과 견줌 · 새로 깨면 그 걸음으로 되돌림 · 까닭 상태줄(끼우기 · 지우기 · 놓기 · 방향키 · 곡선 칸) · 손잡이 끌기도 걸음마다 검사 - 곡선 삭제 = 내각 155° 미만이면 막지 않고 경고(상태줄 · 알림) - 끌기 · 손잡이 놓을 때 스냅 켬이면 「함께 밀린 교점 · 못 맞춘 곡선」 보고 - 스냅 끔 · 간격 바꿈 = 계산 줄에 초안 바로(tellDraft) - R · 길이 고정 · 넣은 R = 곡선표 edits 칸(짝 B05_Profile_Route_NodeEdits.py ↔ flattenServerPlan) · 다시 열면 넣은 값으로 - 상태줄 = 겹친 노드(0.5 m 안) · 못 그린 곡선 표시 - 시험 test_430_route_edit_rules · 배선 바뀐 시험 둘(352-3 · 390-4) · 398-2 도우미 알림 가짜 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
138 lines
5.0 KiB
JavaScript
138 lines
5.0 KiB
JavaScript
/* 430 평면 형상 제어 규칙 한 곳 — `logic/B05_Profile_UI_RouteEdit_Edits.ts`(하한 검사 · 겹친 노드 ·
|
|
* 못 그린 곡선 · 넣은 값 되살림) · `_KeyNudge.fitNote` 를 Node 로 돌려 JSON 한 줄
|
|
* (`test_430_route_edit_rules.py` 가 판정).
|
|
* node helper_430_route_edit_rules.cjs <서버 곡선표 JSON 파일>
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
const LOGIC = path.join(ROOT, "B05_Profile", "logic");
|
|
const cache = new Map();
|
|
|
|
function loadTs(file) {
|
|
const full = path.resolve(file);
|
|
if (cache.has(full)) return cache.get(full);
|
|
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) =>
|
|
request.startsWith(".")
|
|
? loadTs(path.join(path.dirname(full), `${request}.ts`))
|
|
: require(request);
|
|
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
|
cache.set(full, box.exports);
|
|
return box.exports;
|
|
}
|
|
|
|
const edits = loadTs(path.join(LOGIC, "B05_Profile_UI_RouteEdit_Edits.ts"));
|
|
const nudge = loadTs(path.join(LOGIC, "B05_Profile_RouteEdit_KeyNudge.ts"));
|
|
|
|
const LIMITS = { minRadiusM: 30, limitRadiusM: 20, limitArcM: 0 };
|
|
// IP1 · IP2 · IP3 — 직선 넉넉(곡선 R 30 다 들어감)
|
|
const ROUTE = [
|
|
[0, 0],
|
|
[100, 0],
|
|
[200, 60],
|
|
[300, 0],
|
|
[400, 0],
|
|
];
|
|
|
|
function cols(planned) {
|
|
const n = planned.length;
|
|
return {
|
|
planned: planned.map((v) => [v[0], v[1]]),
|
|
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),
|
|
};
|
|
}
|
|
|
|
/** 지난 걸음 `before` 에서 `change` 로 고친 뒤 하한 검사 글. */
|
|
function stop(before, change) {
|
|
const now = cols(before.planned);
|
|
change(now);
|
|
const built = edits.rebuildLine(now, LIMITS);
|
|
return edits.floorStop(before, now.planned, built.nodes, LIMITS);
|
|
}
|
|
|
|
const base = cols(ROUTE);
|
|
const out = {
|
|
// 같은 노드 수 — IP2 를 IP1 쪽 8 m 앞으로(IP1 · IP2 접선 자리 모자람 → R 하한 아래)
|
|
moveBreak: stop(base, (c) => (c.planned[2] = [108, 4])),
|
|
moveOk: stop(base, (c) => (c.planned[2] = [200, 70])),
|
|
// 끼우기 — IP1 바로 뒤 4 m 에 새 노드(IP1 접선 자리 2 m)
|
|
insertBreak: stop(base, (c) => {
|
|
c.planned.splice(2, 0, [104, 1]);
|
|
for (const k of ["curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"])
|
|
c[k].splice(2, 0, k === "curveOn" ? true : k === "apexLock" ? false : null);
|
|
}),
|
|
// 지우기 — IP2 를 빼도 IP1 · IP3 넉넉
|
|
deleteOk: stop(base, (c) => {
|
|
for (const k of ["planned", "curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"])
|
|
c[k].splice(2, 1);
|
|
}),
|
|
// 둘 넘게 다르면 안 견줌
|
|
manyGap: stop(base, (c) => {
|
|
c.planned.splice(1, 0, [50, 0], [52, 0]);
|
|
for (const k of ["curveOn", "curveRadius", "curveLock", "curveArc", "apexLock"])
|
|
c[k].splice(
|
|
1,
|
|
0,
|
|
...(k === "curveOn" ? [true, true] : k === "apexLock" ? [false, false] : [null, null]),
|
|
);
|
|
}),
|
|
};
|
|
|
|
// 이미 하한을 밑돌던 자리는 「새로」가 아님 — 밑돈 노선에서 다시 조금 옮겨도 통과
|
|
const broken = cols(ROUTE);
|
|
broken.planned[2] = [108, 4];
|
|
out.alreadyBroken = stop(broken, (c) => (c.planned[2] = [108.5, 4.2]));
|
|
|
|
// 겹친 노드 · 못 그린 곡선
|
|
const overlap = [
|
|
[0, 0],
|
|
[100, 0],
|
|
[100.3, 0.2],
|
|
[200, 60],
|
|
[300, 0],
|
|
];
|
|
out.flags = edits.routeFlags(overlap, Array(5).fill(true), []);
|
|
out.flagsOff = edits.routeFlags(ROUTE, [true, false, false, false, true], []);
|
|
out.summary = edits.curveSummary({
|
|
nodeCount: 5,
|
|
curveOn: Array(5).fill(true),
|
|
curveRadius: Array(5).fill(null),
|
|
curveLock: Array(5).fill(null),
|
|
apexLock: [],
|
|
curveCount: 2,
|
|
violationCount: 0,
|
|
minRadiusM: 30,
|
|
fresh: false,
|
|
flags: out.flags,
|
|
});
|
|
|
|
// fitNote — 손으로 옮긴 고른 IP 는 「밀림」에 안 셈(놓을 때 · 430) · 입력 뒤는 셈(408-1)
|
|
const after = ROUTE.map((v, i) => (i === 2 ? [v[0] + 3, v[1]] : i === 3 ? [v[0] + 1, v[1]] : v));
|
|
out.noteHand = nudge.fitNote(ROUTE, after, 2, [], 20, 20, true);
|
|
out.noteInput = nudge.fitNote(ROUTE, after, 2, [], 20, 20);
|
|
|
|
// 짝 — 서버 곡선표(파이썬이 씀)를 그대로 펴서 넣은 값이 돌아오는가
|
|
const server = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
const flat = edits.flattenServerPlan(server.nodes, server.curves, server.edits);
|
|
out.flat = {
|
|
planned: flat.planned,
|
|
curveOn: flat.curveOn,
|
|
curveRadius: flat.curveRadius,
|
|
curveLock: flat.curveLock,
|
|
curveArc: flat.curveArc,
|
|
};
|
|
const old = edits.flattenServerPlan(server.nodes, server.curves, null);
|
|
out.flatOld = { curveRadius: old.curveRadius, curveLock: old.curveLock };
|
|
process.stdout.write(JSON.stringify(out));
|