fix(B08·M02): 구조물 도면 처음 맞춤을 도각 기준으로 · 보조 회색 글자 대비 4.5 이상(73-7)

- CAD 화면맞춤 — 얹은 도각(m02-structure-frame)이 있으면 도각 전체에 맞춤(옛 DXF 도곽에 안 끌림) · 시험 추가
- B08 설명 줄 · 홍수량 보조 글 · 판정 글자와 진행 단계 머리의 프로젝트 이름 글자를 보조 글자 토큰으로

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
2026-10-07 08:19:03 +09:00
co-authored by Claude Sonnet 5.5
parent dc4679d329
commit 527ce32fb3
5 changed files with 52 additions and 5 deletions
@@ -1,5 +1,8 @@
import { Point } from '@flatten-js/core';
import { describe, expect, it } from 'vitest';
import { beforeAll, describe, expect, it } from 'vitest';
import { LineEntity } from '../entities/LineEntity';
import type { Layer } from '../App.types';
import { setEntities, setLayers } from '../state';
import { ScreenCanvasDrawController } from './screenCanvas.drawController';
/** translate·drawImage 인자만 기록하는 최소 2d 컨텍스트. */
@@ -37,3 +40,34 @@ describe('ScreenCanvasDrawController.drawImage', () => {
expect(calls.rotate).toEqual([-0.5, 0.5]);
});
});
describe('ScreenCanvasDrawController.zoomToFitScreen', () => {
beforeAll(() => {
(globalThis as { window?: unknown }).window = { dispatchEvent: () => true };
});
const layer = (id: string, isLocked: boolean) =>
({ id, name: id, isVisible: true, isLocked }) as unknown as Layer;
it('얹은 도각(m02-structure-frame)이 있으면 도각 전체에 맞춘다 — 도각 밖 먼 그림에 안 끌린다', () => {
setLayers([layer('m02-structure-frame', true), layer('기본', false)]);
setEntities(
[
new LineEntity('m02-structure-frame', new Point(0, 0), new Point(800, 600)),
new LineEntity('기본', new Point(-8000, -8000), new Point(0, 0)),
],
false
);
const controller = new ScreenCanvasDrawController(recordingContext().context);
controller.setCanvasSize(new Point(800, 600));
controller.zoomToFitScreen();
const scale = controller.getScreenScale();
expect(scale).toBeCloseTo(0.9, 5); // 800×600 도각 → 캔버스 90 %
for (const corner of [new Point(0, 0), new Point(800, 600)]) {
const t = controller.worldToTarget(corner);
expect(t.x).toBeGreaterThanOrEqual(0);
expect(t.x).toBeLessThanOrEqual(800);
expect(t.y).toBeGreaterThanOrEqual(0);
expect(t.y).toBeLessThanOrEqual(600);
}
});
});
@@ -31,6 +31,8 @@ const CANVAS_BACKGROUND_FALLBACK = '#111';
*
* To convert between the 2 coordinate systems, you need the screenOffset and screenScale
*/
/** M02 가 구조물 도면에 얹는 도각 층 — 서버 `STRUCTURE_FRAME_LAYER` 와 같음 */
const STRUCTURE_FRAME_LAYER_ID = 'm02-structure-frame';
// Batch-mode stroke decimation: skip chain segments shorter than this (screen px)
const BATCH_LOD_PX = 0.5;
// Batch-mode text smaller than this (screen px) is unreadable — skip drawing it
@@ -150,8 +152,15 @@ export class ScreenCanvasDrawController implements DrawController {
public zoomToFitScreen() {
// 도각·원지반 같은 잠금 도면층은 빼고 설계 콘텐츠에만 맞춘다 (2026-08-31 사용자
// 확정). 도각까지 넣으면 A1 한 장 전체가 잡혀 화면의 40%가 여백으로 간다.
// 구조물 도면(M02·B08)은 얹은 도각(작도 틀)이 있으면 도각 전체에 맞춘다 — 옛 DXF 가 자기 도곽·
// 표제 띠를 품어 범위가 크면 도각이 화면 밖으로 밀린다.
const structureFrame = getEntities().filter((e) => e.layerId === STRUCTURE_FRAME_LAYER_ID);
const pickable = getPickableEntities();
const entities = pickable.length ? pickable : getEntities();
const entities = structureFrame.length
? structureFrame
: pickable.length
? pickable
: getEntities();
if (!entities.length) {
// 빈 도면 — 원점(0,0)을 화면 가운데로. 안 옮기면 기준점 십자가 왼쪽 아래 귀퉁이에 잘린다.
const canvasSize = this.getCanvasSize();
+2 -2
View File
@@ -97,7 +97,7 @@
}
.b08-hydro__hint {
color: var(--color-text-muted);
color: var(--color-text-secondary);
font-size: var(--text-caption);
}
@@ -185,7 +185,7 @@
.b08-hydro__verdict {
padding: var(--spacing-8) 0;
color: var(--color-text-muted);
color: var(--color-text-secondary);
font-size: var(--text-body);
text-align: center;
}
+4
View File
@@ -6,6 +6,10 @@
gap: var(--spacing-16);
}
.b08-quantity .ui-general-block__empty {
color: var(--color-text-secondary); /* muted 는 밝은 테마에서 대비 3:1 — 4.5:1 로 */
}
.b08-quantity__tabs {
display: flex;
flex-wrap: wrap;
+1 -1
View File
@@ -134,7 +134,7 @@
max-width: 60%;
min-width: 0;
overflow: hidden;
color: var(--color-text-muted, var(--color-text));
color: var(--color-text-secondary, var(--color-text));
font-size: var(--text-body-sm);
text-align: right;
text-overflow: ellipsis;