Files
Aislo/resources/tester/helper_266_detail_home.cjs
T
eomsangdonandClaude Opus 5.5 db8462c6d4 feat(B07 CAD): 337-2 마-1 오토캐드 마우스 — 누름 더함 · Shift 뺌 · 상자 더함/뺌 · 명령 중 끌기 상자 · 스침 · 그립 누르고-옮기고-누르기 · 그립 숫자 · 보기 전용 문 · 직교 강제 · Shift 임시 직교 · 휠 기본 밂 · 휠 두 번 범위
- 고르기: 누름 = 더함 · Shift = 뺌 · Ctrl = 넣고 빼기 · 상자 = 앞 고름 ∪ 상자 · Shift 상자 = 뺌(H4 · H5) · 명령 안 고르기 단계도 누를 때 첫 점 · 스침 강조(ic/selecting.ts)
- 그립: 쉴 때만 집음(N7) · 누르고 떼면 대기 → 다음 누름에 놓음 · 끌어 놓기(H3) · 거리 숫자(N8) · 확정 · 읽기 전용 도면은 그립 · Delete 막음(H2)
- 직교: F8 = 기준점에서 가로 · 세로 강제 · Shift 누른 동안 뒤집음 · 객체 스냅 점 · 좌표 · 각도 잠금은 안 덮음 · 클릭 사건 snapped 채움(ic/ortho.ts · H8) · 가이드는 F3 와 뗌 · 가이드 잡는 거리 = 화면 픽셀(H9)
- 휠: 기본 밂(앞으로 굴리면 확대 · 오토캐드 · 모든 화면 · ☰ 설정 뒤집기 그대로) · 가운데 단추 두 번 = 범위 줌 · ZOOM · ⛶ 앞 화면 기억(ic/zoom-prompt.ts)
- 시험: mouse-acad 새로 · 휠 방향 시험 손질(input-wheel · input-prefs-cad · input_prefs · app_settings · 164 · 220_1 · 221_1 · 244_8 기대값 뒤집음 · 266 pull 박음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
2026-10-15 23:41:46 +09:00

116 lines
4.6 KiB
JavaScript

/* PLAN 266-1 단위검증 헬퍼 — [상세] 처음 가로 범위 = 카드 반폭.
* 실제 TS(`_Cross_View_Metrics` 칸 맞춤 · `b_svg_zoom_pan` homeScale)를 Node 로 돌림.
* 처음 배율 = 뷰 `detailChart` 와 같은 식(base.ppm / fitted.ppm · 짝 시험이 원본 줄을 확인).
* 결과 = 표준출력 JSON 한 벌. 짝 = `test_266_detail_home.py`.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const B06 = path.join(ROOT, "B06_Section");
const common = fs.readFileSync(path.join(B06, "B06_Section_UI_Section_Common.ts"), "utf8");
const CROSS_PAD = Function(`return ${/export const CROSS_PAD = (\{[^}]*\})/.exec(common)[1]}`)();
const CROSS_HEIGHT = Number(/export const CROSS_HEIGHT = (\d+)/.exec(common)[1]);
const STUBS = {
"./B06_Section_UI_Section_Common": {
CROSS_PAD,
CROSS_HEIGHT,
validElevation: (s) => s.valid !== false && Number.isFinite(s.elevation_m),
},
};
function loadTs(full) {
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
const localRequire = (request) => STUBS[request] ?? {};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
return box.exports;
}
// 휠 방향과 무관한 시험(처음으로 · 범위) — 337 기본 밂 뒤에도 당김 수치 그대로(pull 박음)
globalThis.localStorage = {
getItem: (k) => (k === "aislo:pref:input-prefs" ? '{"wheelZoom":"pull"}' : null),
};
STUBS["./input_prefs"] = loadTs(path.join(ROOT, "A00_Common", "input_prefs.ts")); // 휠 방향(332)
const metrics = loadTs(path.join(B06, "B06_Section_UI_Cross_View_Metrics.ts"));
const { attachSvgZoomPan } = loadTs(path.join(ROOT, "A00_Common", "b_svg_zoom_pan.ts"));
function fakeSvg(width, height) {
const listeners = {};
const attrs = {};
return {
attrs,
setAttribute: (name, value) => (attrs[name] = String(value)),
getAttribute: (name) => attrs[name] ?? null,
classList: { add() {}, remove() {} },
addEventListener: (type, fn) => (listeners[type] ??= []).push(fn),
fire: (type, event) =>
(listeners[type] ?? []).forEach((fn) =>
fn({ preventDefault() {}, stopPropagation() {}, pointerId: 1, ...event }),
),
setPointerCapture() {},
releasePointerCapture() {},
getBoundingClientRect: () => ({ left: 0, top: 0, width, height }),
};
}
// 가파른 비탈 — ±30 m 샘플 · 1 m 마다 · 1 m 에 0.6 m 오름(칸 맞춤이 가로를 넓히게)
const section = {
samples: Array.from({ length: 61 }, (_, i) => ({
offset_m: i - 30,
elevation_m: 100 + 0.6 * (i - 30),
})),
};
/** 반폭 · 칸 크기로 상세 창을 연 셈 → 처음 · 줌 · 팬 · 원복 뒤 보이는 가로 범위(m). */
function open(halfWidth, W, H) {
const base = metrics.crossPlotBase(section, 1, W, halfWidth, 100);
const fitted = metrics.fitPlotBase(base, H);
const homeScale = base && fitted ? base.pixelsPerMeter / fitted.pixelsPerMeter : 1;
const svg = fakeSvg(W, H);
const layer = fakeSvg(W, H);
let state = null;
const handle = attachSvgZoomPan({
svg,
layer,
widthPx: W,
heightPx: H,
pad: CROSS_PAD,
maxScale: 16,
content: { x: -W, y: -H, width: 3 * W, height: 3 * H },
wheelZoom: true,
homeScale,
onChange: (next) => (state = next),
});
// 보이는 플롯 가로 끝(svg x) → 도형 층 x → 측점 거리(m)
const range = () => {
const s = state ?? { scale: 1, tx: 0, ty: 0 };
const toM = (x) =>
fitted.minOffset + ((x - s.tx) / s.scale - CROSS_PAD.left) / fitted.pixelsPerMeter;
return [toM(CROSS_PAD.left), toM(W - CROSS_PAD.right)].map((v) => Math.round(v * 1000) / 1000);
};
const out = { homeScale, widened: fitted !== base, first: range() };
svg.fire("wheel", { clientX: W / 2, clientY: H / 2, deltaY: 100 });
out.wheelScale = state.scale / homeScale;
svg.fire("pointerdown", { button: 1, clientX: 100, clientY: 100 });
svg.fire("pointermove", { clientX: 160, clientY: 100 });
svg.fire("pointerup", {});
out.panned = range()[0] !== out.first[0];
handle.reset(); // [⤢]
out.reset = range();
for (let i = 0; i < 40; i++) svg.fire("wheel", { clientX: W / 2, clientY: H / 2, deltaY: -100 });
out.minScale = state.scale; // 줄이면 칸 맞춤 전체(1배)까지
svg.fire("dblclick", {});
out.dblclick = range();
return out;
}
process.stdout.write(
JSON.stringify({
global15: open(15, 1200, 300),
station10: open(10, 1200, 300),
tall15: open(15, 1200, 900),
}),
);