feat(825-1): B07 내장 캐드 그리드 — 도면 좌표에 붙임 · 1·2·5 적응 간격 · 주선 · 한 칸 크기 표시

- 그리드를 화면 고정 24px 에서 원점 기준 도면 좌표 선으로 바꿈(팬·줌을 따라감 · 보이는 범위만 계산)
- 간격 단계 1·2·5 × 10^k · 보조선 사이 12~30px · 한 방향 선 수 상한 200 · 주선 5·10 × 10^k
- 순수 함수 helpers/grid-steps.ts + 시험 · zoom-out-perf 에 그리드 켠 clear() 선 수 · 시간 시험 추가
- 주선 색 --draw-grid-major 를 공통 테마(밝음 · 어두움)에 더함 · 상태줄에 「그리드 1 칸 = ○ m」 표시

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 18:21:38 +09:00
co-authored by Claude Sonnet 5.5
parent c367ab5f56
commit 2ac6c9808a
7 changed files with 173 additions and 15 deletions
@@ -1,5 +1,6 @@
import { Point, type Vector } from '@flatten-js/core';
import { WHEEL_ZOOM_EXPONENT } from '../App.consts';
import { gridLines, pickGridStep } from '../helpers/grid-steps.ts';
import { getAngleWithXAxis } from '../helpers/get-angle-with-x-axis.ts';
import { mapNumberRange } from '../helpers/map-number-range.ts';
import { StateVariable } from '../helpers/undo-stack.ts';
@@ -338,21 +339,40 @@ export class ScreenCanvasDrawController implements DrawController {
this.context.fillStyle = themeColor('--cad-canvas', CANVAS_BACKGROUND_FALLBACK);
this.context.fillRect(0, 0, this.canvasSize?.x, this.canvasSize?.y);
if (getGridEnabled()) {
this.context.strokeStyle = themeColor('--draw-grid', '#242b35');
this.context.lineWidth = 1;
this.context.setLineDash([]);
this.context.beginPath();
for (let x = 0.5; x < this.canvasSize.x; x += 24) {
this.context.moveTo(x, 0);
this.context.lineTo(x, this.canvasSize.y);
}
for (let y = 0.5; y < this.canvasSize.y; y += 24) {
this.context.moveTo(0, y);
this.context.lineTo(this.canvasSize.x, y);
}
this.context.stroke();
if (getGridEnabled()) this.drawGrid();
}
/** 도면 좌표에 붙은 그리드(825-1) — 팬 · 줌을 따라가고 확대 정도에 간격이 바뀜. 보이는 범위의 선만 긋는다. */
private drawGrid() {
const { x: width, y: height } = this.canvasSize;
const scale = this.screenScale;
const step = pickGridStep(scale, Math.max(width, height));
const minorPath = new Path2D();
const majorPath = new Path2D();
for (const { value, major } of gridLines(
this.screenOffset.x,
this.screenOffset.x + width / scale,
step
)) {
const x = Math.round((value - this.screenOffset.x) * scale) + 0.5;
(major ? majorPath : minorPath).moveTo(x, 0);
(major ? majorPath : minorPath).lineTo(x, height);
}
for (const { value, major } of gridLines(
this.screenOffset.y,
this.screenOffset.y + height / scale,
step
)) {
const y = Math.round(height - (value - this.screenOffset.y) * scale) + 0.5;
(major ? majorPath : minorPath).moveTo(0, y);
(major ? majorPath : minorPath).lineTo(width, y);
}
this.context.lineWidth = 1;
this.context.setLineDash([]);
this.context.strokeStyle = themeColor('--draw-grid', '#242b35');
this.context.stroke(minorPath);
this.context.strokeStyle = themeColor('--draw-grid-major', '#3a4352');
this.context.stroke(majorPath);
}
/**