From 1ceee943bc7763293a3fb7d94153b4bf15a5bb0e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 11:56:37 +0900 Subject: [PATCH] =?UTF-8?q?feat(b07):=20=ED=9A=A1=EB=8B=A8=20=EC=9E=A5=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=EC=84=A0=20=EB=B3=B4=EC=9D=B4=EA=B8=B0=20/?= =?UTF-8?q?=20=EC=88=A8=EA=B8=B0=EA=B8=B0=20=EB=8B=A8=EC=B6=94=20=E2=80=94?= =?UTF-8?q?=20=EC=A0=90=EC=84=A0=20=EC=9E=91=EB=8F=84=20=EC=98=81=EC=97=AD?= =?UTF-8?q?=20+=20=EB=B6=89=EC=9D=80=20=EB=84=98=EC=B9=A8=20=ED=8B=80=20?= =?UTF-8?q?=C2=B7=20=EC=88=A8=EA=B9=80=EC=9D=B4=EB=A9=B4=20=EB=84=98?= =?UTF-8?q?=EC=B9=A8=20=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EC=97=86=EC=9D=8C(18?= =?UTF-8?q?0-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz --- .../B07_DesignDetail_UI_GuideToggle.ts | 44 ++++++++ B07_DesignDetail/B07_DesignDetail_UI_Page.ts | 29 +++-- .../src/integration/aislo-drawing-bridge.ts | 7 +- .../src/integration/guide-area.test.ts | 41 ++++++- .../openwebcad/src/integration/guide-area.ts | 16 ++- .../tester/helper_180_3_b07_guide_toggle.cjs | 101 ++++++++++++++++++ .../tester/test_180_3_b07_guide_toggle.py | 76 +++++++++++++ ui_template/cad_host/cad_host.ts | 4 + 8 files changed, 303 insertions(+), 15 deletions(-) create mode 100644 B07_DesignDetail/B07_DesignDetail_UI_GuideToggle.ts create mode 100644 resources/tester/helper_180_3_b07_guide_toggle.cjs create mode 100644 resources/tester/test_180_3_b07_guide_toggle.py diff --git a/B07_DesignDetail/B07_DesignDetail_UI_GuideToggle.ts b/B07_DesignDetail/B07_DesignDetail_UI_GuideToggle.ts new file mode 100644 index 000000000..7fddc1c0c --- /dev/null +++ b/B07_DesignDetail/B07_DesignDetail_UI_GuideToggle.ts @@ -0,0 +1,44 @@ +/* ============================================================================= + * B07_DesignDetail_UI_GuideToggle.ts + * 횡단 장 안내선 보이기 / 숨기기 단추(PLAN 180-3) — 좌측 「횡단도」 묶음 배치 칸 줄에 붙음. + * + * 끄는 것 = 화면 전용 안내선 둘(붉은 넘침 칸 틀 + 파란 점선 작도 영역) · 인쇄되는 칸선은 그대로 + * 상태 = 이 화면을 쓰는 동안만(저장 안 함) · 장을 바꾸거나 화면을 다시 세워도 이어짐(모듈 값) + * 숨김이면 「n 칸 넘침」 토스트도 안 띄움 · CAD 에는 `cad.setGuideVisible` 로 알림 + * ========================================================================== */ + +import { createButton } from "@ui/ui_template_elements"; + +/** 안내선 보임 — 모듈 값(화면 다시 세워도 유지 · 새로 고침하면 보임으로) */ +let shown = true; + +export const guideShown = (): boolean => shown; + +const labelOf = (visible: boolean): string => (visible ? "안내선 숨기기" : "안내선 보이기"); + +/** + * 단추 하나 — 누르면 보임 ↔ 숨김 · `apply(보임)` 로 CAD 에 알림. + * 장이 아닌 도면이면 꺼짐 — 부르는 쪽이 `button.disabled` 로 정함. + */ +export function buildGuideToggle(apply: (visible: boolean) => void): HTMLButtonElement { + const button = createButton({ + label: labelOf(shown), + variant: "ghost", + size: "sm", + onClick: () => { + shown = !shown; + paint(); + apply(shown); + }, + }); + const paint = (): void => { + const label = button.children[button.children.length - 1]; + if (label) label.textContent = labelOf(shown); + button.setAttribute("aria-pressed", String(!shown)); + button.title = shown + ? "점선 작도 영역 · 붉은 넘침 틀을 화면에서 숨김(인쇄 칸선은 그대로)" + : "숨긴 점선 작도 영역 · 붉은 넘침 틀을 다시 보임"; + }; + paint(); + return button; +} diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Page.ts b/B07_DesignDetail/B07_DesignDetail_UI_Page.ts index c5dd63a11..bc17268eb 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Page.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_Page.ts @@ -53,6 +53,7 @@ import { type QuantityTable, } from "./B07_DesignDetail_Api_Fetch"; import { appendStructureEntities } from "./B07_DesignDetail_UI_Cad_Structures"; +import { buildGuideToggle, guideShown } from "./B07_DesignDetail_UI_GuideToggle"; // CAD iframe 부모 흐름 · 도각 편집은 M02 와 한 벌로 공용에 둔다(PLAN 10-3). import { createCadHost, type CadSaveResult } from "@ui/cad_host/cad_host"; import { createFrameTemplateEditor } from "@ui/cad_host/cad_host_frame_edit"; @@ -125,6 +126,10 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise { onGuideChange: (area) => frameEditor.guideChanged(area), }); + // 180-3 안내선 보이기 / 숨기기 — 숨김은 화면을 다시 세워도 이어 줌(`…_UI_GuideToggle` 모듈 값) + if (!guideShown()) cad.setGuideVisible(false); + const guideToggle = buildGuideToggle((visible) => cad.setGuideVisible(visible)); + let currentDrawing: DesignDrawingItem | undefined; let currentIndex = -1; let currentConfirmed = false; @@ -154,10 +159,16 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise { /** 장 배치 칸 자리(153-1) — 횡단도 묶음 맨 아래 · 정보 칸을 다시 그려도 남음. */ const crossLayoutSlot = document.createElement("div"); crossLayoutSlot.className = "b07-cross-layout-slot"; + // 배치 칸을 못 받아도 안내선 단추는 남음 · 받으면 같은 줄 끝에 붙음 + const guideOnly = document.createElement("div"); + guideOnly.className = "b07-cross-layout"; + guideOnly.append(guideToggle); + crossLayoutSlot.append(guideOnly); let crossLayoutRequest: Promise | undefined; /** 장 배치 칸(140-3) — 적용 = 서버 쓰기 → 목록 다시 · 같은 장 번호를 다시 엶. 장 아니면 기본값 · [이 장] 꺼짐. */ const fillCrossLayout = async (drawing: DesignDrawingItem) => { + guideToggle.disabled = !isCrossSheet(drawing); // 장의 안내선만 if (!projectId) return; let layout: CrossLayout; try { @@ -185,15 +196,15 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise { hideLoadingOverlay(); } }; - crossLayoutSlot.replaceChildren( - buildCrossLayoutControls( - (number && layout.sheets[String(number)]) || layout.default, - number - ? (grid) => void save({ ...layout, sheets: { ...layout.sheets, [String(number)]: grid } }) - : null, - (grid) => void save({ default: grid, sheets: {} }), - ), + const controls = buildCrossLayoutControls( + (number && layout.sheets[String(number)]) || layout.default, + number + ? (grid) => void save({ ...layout, sheets: { ...layout.sheets, [String(number)]: grid } }) + : null, + (grid) => void save({ default: grid, sheets: {} }), ); + controls.append(guideToggle); + crossLayoutSlot.replaceChildren(controls); }; /** @@ -335,7 +346,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise { const guide = response.guide ?? null; const meta = buildMeta(drawing, response, index); cad.load(stash ?? response.drawing, meta, false, {}, { guide }); - if (guide?.marks?.length) { + if (guideShown() && guide?.marks?.length) { showToast(`${guide.marks.length} 칸 넘침 — 열 · 행을 줄일 것`, "warning"); } if (response.drawing.stale_layout) { diff --git a/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts b/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts index 961021c98..cedc8c175 100644 --- a/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts +++ b/B07_DesignDetail/openwebcad/src/integration/aislo-drawing-bridge.ts @@ -35,7 +35,7 @@ import { runCommandInput } from '../commands/run-command.ts'; import { setRecoveryScope } from '../helpers/autosave.ts'; import { registerBoxResizeDrag } from './box-resize-drag.ts'; import { handleTableMessage } from './aislo-table-bridge.ts'; -import { registerGuideDrag, setGuide, setGuideArea } from './guide-area.ts'; +import { registerGuideDrag, setGuide, setGuideArea, setGuideVisible } from './guide-area.ts'; export const AISLO_DRAWING_LOAD_MESSAGE = 'aislo:b08:load-drawing'; export const AISLO_DRAWING_READY_MESSAGE = 'aislo:b08:drawing-ready'; @@ -50,6 +50,7 @@ export const AISLO_DRAWING_HOST_SAVE_MESSAGE = 'aislo:b08:host-save'; /** 작도 영역 가이드(169-1 · 169-3) — 부모 숫자 칸 → CAD 갈기 · CAD 끌기 → 부모 숫자 칸 */ export const AISLO_GUIDE_AREA_MESSAGE = 'aislo:b08:guide-area'; export const AISLO_GUIDE_CHANGED_MESSAGE = 'aislo:b08:guide-changed'; +export const AISLO_GUIDE_VISIBLE_MESSAGE = 'aislo:b08:guide-visible'; interface DrawingLoadMessage { type: typeof AISLO_DRAWING_LOAD_MESSAGE; @@ -201,8 +202,10 @@ export function registerAisloDrawingBridge() { window.addEventListener('message', async (event: MessageEvent) => { if (event.origin !== window.location.origin || event.source !== window.parent) return; if (await handleTableMessage(event.data, notifyParent)) return; // 표 놓기 · 갈기(M02 41장) - const guideMessage = event.data as { type?: string; area?: unknown }; + const guideMessage = event.data as { type?: string; area?: unknown; visible?: unknown }; if (guideMessage?.type === AISLO_GUIDE_AREA_MESSAGE) return setGuideArea(guideMessage.area); + if (guideMessage?.type === AISLO_GUIDE_VISIBLE_MESSAGE) + return setGuideVisible(guideMessage.visible); const message = event.data as Partial; if (message.type === AISLO_DRAWING_SAVE_REQUEST_MESSAGE) { try { diff --git a/B07_DesignDetail/openwebcad/src/integration/guide-area.test.ts b/B07_DesignDetail/openwebcad/src/integration/guide-area.test.ts index 366f4d1f2..9becd673b 100644 --- a/B07_DesignDetail/openwebcad/src/integration/guide-area.test.ts +++ b/B07_DesignDetail/openwebcad/src/integration/guide-area.test.ts @@ -1,13 +1,17 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { GUIDE_MIN_MM, type GuideArea, dragGuideArea, + drawGuide, getGuide, + isGuideVisible, pickGuideHandle, setGuide, setGuideArea, + setGuideVisible, } from './guide-area.ts'; +import type { ScreenCanvasDrawController } from '../drawControllers/screenCanvas.drawController.ts'; const AREA: GuideArea = [42, 47, 812, 567]; @@ -40,4 +44,39 @@ describe('작도 영역 가이드(169-1 · 169-3)', () => { expect(dragGuideArea(AREA, 's', { x: 0, y: 9999 })).toEqual([42, 567 - GUIDE_MIN_MM, 812, 567]); expect(dragGuideArea(AREA, 'e', { x: -100, y: 0 })).toEqual([42, 47, 42 + GUIDE_MIN_MM, 567]); }); + + it('안내선 보이기 / 숨기기(180-3) — 숨김이면 점선 영역 · 붉은 틀 안 그림 · 싣기를 거쳐도 이어짐', () => { + const lines = vi.fn(); + const controller = { + setOpacity: vi.fn(), + setLineStyles: vi.fn(), + worldToTarget: (point: unknown) => point, + drawLineScreen: lines, + fillRectScreen: vi.fn(), + } as unknown as ScreenCanvasDrawController; + const guide = { area: AREA, marks: [[0, 0, 5, 5] as GuideArea], editable: false }; + + setGuide(guide); + expect(isGuideVisible()).toBe(true); // 기본 = 보임 + drawGuide(controller); + expect(lines).toHaveBeenCalledTimes(8); // 점선 영역 4 변 + 붉은 틀 4 변 + + setGuideVisible(false); + lines.mockClear(); + drawGuide(controller); + expect(lines).not.toHaveBeenCalled(); + setGuide(guide); // 장을 다시 실어도 숨김 유지 + setGuideArea([0, 0, 100, 50]); + drawGuide(controller); + expect(isGuideVisible()).toBe(false); + expect(lines).not.toHaveBeenCalled(); + expect(getGuide()?.marks).toHaveLength(1); // 값은 그대로 — 그리기만 거름 + + setGuideVisible(true); + drawGuide(controller); + expect(lines).toHaveBeenCalledTimes(8); + setGuideVisible('x'); // 어긋난 값 = 보임 + expect(isGuideVisible()).toBe(true); + setGuide(null); + }); }); diff --git a/B07_DesignDetail/openwebcad/src/integration/guide-area.ts b/B07_DesignDetail/openwebcad/src/integration/guide-area.ts index 953678ac9..1a8db4040 100644 --- a/B07_DesignDetail/openwebcad/src/integration/guide-area.ts +++ b/B07_DesignDetail/openwebcad/src/integration/guide-area.ts @@ -7,7 +7,8 @@ import { getCanvas, getScreenCanvasDrawController } from '../state.ts'; * * 도면 개체가 아니라 그리기 겹판(기준점 십자와 같은 자리)이라 저장 · DXF · 인쇄에 안 섞인다. * `editable` 이면 모서리 · 변을 끌어 영역을 고치고, 고친 값은 부모(숫자 칸)에 알린다. - * 부모 짝 = `ui_template/cad_host/cad_host.ts` `guide` 곁값 · `setGuideArea` · `onGuideChange`. + * 부모 짝 = `ui_template/cad_host/cad_host.ts` `guide` 곁값 · `setGuideArea` · `onGuideChange` · + * `setGuideVisible`(180-3 — 점선 영역 + 붉은 넘침 틀 보이기 / 숨기기 · 도각 칸선은 도면 개체라 그대로). */ /** [왼쪽 x, 아래 y, 오른쪽 x, 위 y] — 도면 좌표 mm */ @@ -32,6 +33,8 @@ const GRAB_PIXELS = 9; export const GUIDE_MIN_MM = 10; let guide: Guide | null = null; +/** 안내선(점선 영역 + 붉은 틀) 보임 — 싣기 때 안 지움 · 숨김이면 그리지도 · 집지도 않음 */ +let guideVisible = true; const isArea = (value: unknown): value is GuideArea => Array.isArray(value) && @@ -42,6 +45,13 @@ const isArea = (value: unknown): value is GuideArea => export const getGuide = (): Guide | null => guide; +export const isGuideVisible = (): boolean => guideVisible; + +/** 부모 단추(180-3) — `true` 만 보임 · 그 밖 값은 보임으로 */ +export function setGuideVisible(visible: unknown): void { + guideVisible = visible !== false; +} + /** 싣기 때 — 어긋난 값이면 가이드 없음 */ export function setGuide(next: unknown): void { const candidate = next as Partial | null | undefined; @@ -103,7 +113,7 @@ function strokeRect(drawController: ScreenCanvasDrawController, area: GuideArea) /** 그리기 겹판 — 엔티티 장면 위에 매 장 그림(`helpers/draw.ts`) */ export function drawGuide(drawController: ScreenCanvasDrawController): void { - if (!guide) return; + if (!guide || !guideVisible) return; drawController.setOpacity(0.9); drawController.setLineStyles(false, false, GUIDE_COLOR, 1, [8, 5]); strokeRect(drawController, guide.area); @@ -142,7 +152,7 @@ export function registerGuideDrag(notify: (area: GuideArea) => void): void { window.addEventListener( 'mousedown', (event: MouseEvent) => { - if (event.button !== 0 || event.target !== canvas || !guide?.editable) return; + if (event.button !== 0 || event.target !== canvas || !guide?.editable || !guideVisible) return; const world = worldAt(event); if (!world) return; const tolerance = GRAB_PIXELS / getScreenCanvasDrawController().getScreenScale(); diff --git a/resources/tester/helper_180_3_b07_guide_toggle.cjs b/resources/tester/helper_180_3_b07_guide_toggle.cjs new file mode 100644 index 000000000..5dd7883f8 --- /dev/null +++ b/resources/tester/helper_180_3_b07_guide_toggle.cjs @@ -0,0 +1,101 @@ +/* 180-3 B07 안내선 보이기 / 숨기기 단추 헬퍼 — `…_UI_GuideToggle.ts` 를 Node 로 돌려 JSON 한 벌로. + * 공용 단추(`ui_template_elements`)는 진짜 · 화면 그릇만 흉내(helper_153 꼴). + */ +const fs = require("fs"); +const path = require("path"); +const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); + +const ROOT = path.join(__dirname, "..", ".."); +const cache = new Map(); + +class FakeNode { + constructor(tag) { + this.tagName = tag.toUpperCase(); + this.children = []; + this.dataset = {}; + this.attrs = {}; + this.listeners = {}; + this._text = ""; + this._classes = new Set(); + this.disabled = false; + this.title = ""; + const self = this; + this.classList = { + add: (...names) => names.forEach((n) => self._classes.add(n)), + contains: (name) => self._classes.has(name), + }; + } + set className(value) { + this._classes = new Set(String(value).split(/\s+/).filter(Boolean)); + } + get className() { + return [...this._classes].join(" "); + } + set textContent(value) { + this._text = String(value); + this.children = []; + } + get textContent() { + return this._text + this.children.map((c) => c.textContent).join(""); + } + setAttribute(key, value) { + this.attrs[key] = String(value); + } + append(...kids) { + kids.forEach((k) => this.children.push(k)); + } + addEventListener(type, handler) { + (this.listeners[type] ||= []).push(handler); + } + click() { + if (!this.disabled) (this.listeners.click || []).forEach((handler) => handler({})); + } +} +globalThis.document = { createElement: (tag) => new FakeNode(tag) }; + +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) => { + if (request === "@ui/ui_template_elements") + return loadTs(path.join(ROOT, "ui_template", "ui_template_elements.ts")); + if (request.startsWith(".")) { + const target = path.join(path.dirname(full), `${request}.ts`); + return fs.existsSync(target) ? loadTs(target) : {}; + } + return {}; + }; + new Function("exports", "module", "require", js)(box.exports, box, localRequire); + cache.set(full, box.exports); + return box.exports; +} + +const { buildGuideToggle, guideShown } = loadTs( + path.join(ROOT, "B07_DesignDetail", "B07_DesignDetail_UI_GuideToggle.ts"), +); + +const sent = []; +const button = buildGuideToggle((visible) => sent.push(visible)); +const view = () => ({ + text: button.textContent, + cls: button.className, + pressed: button.attrs["aria-pressed"], + shown: guideShown(), +}); +const out = { first: view() }; +button.click(); +out.hidden = view(); +button.click(); +out.again = view(); +button.click(); +out.last = view(); +// 화면을 다시 세워 새 단추를 만들어도 숨김 유지(모듈 값) +const rebuilt = buildGuideToggle((visible) => sent.push(visible)); +out.rebuilt = { text: rebuilt.textContent, pressed: rebuilt.attrs["aria-pressed"] }; +out.sent = sent; +process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_180_3_b07_guide_toggle.py b/resources/tester/test_180_3_b07_guide_toggle.py new file mode 100644 index 000000000..abbdef089 --- /dev/null +++ b/resources/tester/test_180_3_b07_guide_toggle.py @@ -0,0 +1,76 @@ +"""180-3 B07 횡단 장 안내선 보이기 / 숨기기 단추. + +- 끄는 것 = 점선 작도 영역 + 붉은 넘침 틀(화면 전용) · 인쇄 칸선 · 도면 개체는 그대로 +- 단추 = 배치 칸 줄 · ghost sm · 글 「안내선 숨기기」 ↔ 「안내선 보이기」 · 상태는 모듈 값(저장 안 함) +- 숨김이면 넘침 토스트도 안 띄움 · 메시지 한 줄(`aislo:b08:guide-visible`) · cad_host `setGuideVisible` +""" + +import json +import os +import re +import subprocess +from pathlib import Path + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = Path(HERE).parents[1] +B07 = ROOT / "B07_DesignDetail" + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_단추_글_누름_상태_이어짐(): + proc = subprocess.run( + ["node", os.path.join(HERE, "helper_180_3_b07_guide_toggle.cjs")], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert proc.returncode == 0, proc.stderr + out = json.loads(proc.stdout) + assert out["first"] == { + "text": "안내선 숨기기", + "cls": "ui-btn ui-btn--ghost ui-btn--sm", + "pressed": "false", + "shown": True, + } + assert out["hidden"]["text"] == "안내선 보이기" and out["hidden"]["pressed"] == "true" + assert out["hidden"]["shown"] is False + assert out["again"]["text"] == "안내선 숨기기" and out["again"]["shown"] is True + assert out["last"]["shown"] is False + assert out["sent"] == [False, True, False] # CAD 에 알린 값 + # 화면을 다시 세워 새 단추를 만들어도 숨김 유지 + assert out["rebuilt"] == {"text": "안내선 보이기", "pressed": "true"} + + +def test_메시지_짝_호스트_CAD(): + host = _read(ROOT / "ui_template" / "cad_host" / "cad_host.ts") + bridge = _read(B07 / "openwebcad" / "src" / "integration" / "aislo-drawing-bridge.ts") + guide = _read(B07 / "openwebcad" / "src" / "integration" / "guide-area.ts") + name = "aislo:b08:guide-visible" + assert f'"{name}"' in host and f"'{name}'" in bridge # 짝 메시지 이름 같음 + assert "setGuideVisible: (visible) => postAfterLoad(" in host + assert "setGuideVisible: (visible: boolean) => void;" in host + assert "return setGuideVisible(guideMessage.visible);" in bridge + assert "if (!guide || !guideVisible) return;" in guide # 그리기 거름 + assert "!guideVisible) return;" in guide # 끌기 집기도 거름 + # 싣기(setGuide)가 보임 값을 건드리지 않음 + body = guide[ + guide.index("export function setGuide(") : guide.index("export function setGuideArea") + ] + assert "guideVisible" not in body + + +def test_페이지_잇기_토스트_거름_장만(): + page = _read(B07 / "B07_DesignDetail_UI_Page.ts") + assert "if (guideShown() && guide?.marks?.length) {" in page # 숨김 = 넘침 토스트 없음 + assert "cad.setGuideVisible(visible)" in page + assert "if (!guideShown()) cad.setGuideVisible(false);" in page # 다시 세운 화면에 이어 줌 + assert "controls.append(guideToggle);" in page # 배치 칸 줄 끝 + assert "guideToggle.disabled = !isCrossSheet(drawing);" in page # 장만 + # 단추는 인쇄 칸선(도각 층) · 도면 개체를 건드리지 않음 — 파이썬 쪽 변경 없음 + assert not re.search( + r"guide.?visible", _read(B07 / "B07_DesignDetail_Engine_Cad_Sheet.py"), re.I + ) diff --git a/ui_template/cad_host/cad_host.ts b/ui_template/cad_host/cad_host.ts index 07801f0c7..51ea8b6f3 100644 --- a/ui_template/cad_host/cad_host.ts +++ b/ui_template/cad_host/cad_host.ts @@ -36,6 +36,7 @@ const CAD_PLACE_CANCELLED_MESSAGE = "aislo:b08:place-cancelled"; /** 작도 영역 가이드(169-1 · 169-3) — 짝 = CAD `integration/guide-area.ts` */ const CAD_GUIDE_AREA_MESSAGE = "aislo:b08:guide-area"; const CAD_GUIDE_CHANGED_MESSAGE = "aislo:b08:guide-changed"; +const CAD_GUIDE_VISIBLE_MESSAGE = "aislo:b08:guide-visible"; /** 작도 영역 [왼쪽 x, 아래 y, 오른쪽 x, 위 y] — 도면 좌표 mm */ export type CadGuideArea = [number, number, number, number]; @@ -128,6 +129,8 @@ export interface CadHost { updateTable: (link: string, cells: unknown[][], preview?: boolean) => Promise; /** 가이드 사각형만 갈기(숫자 칸 고침 · 169-3) — 도면은 다시 안 실음. */ setGuideArea: (area: CadGuideArea) => void; + /** 안내선(점선 작도 영역 + 붉은 넘침 틀) 보이기 / 숨기기(180-3) — 싣기를 거쳐도 이어짐 · 도면에 안 섞임. */ + setGuideVisible: (visible: boolean) => void; /** 메시지 듣기를 멈춘다 — 페이지를 떠날 때. */ destroy: () => void; } @@ -360,6 +363,7 @@ export function createCadHost postAfterLoad({ type: CAD_GUIDE_AREA_MESSAGE, area }), + setGuideVisible: (visible) => postAfterLoad({ type: CAD_GUIDE_VISIBLE_MESSAGE, visible }), destroy: () => { stopLoadWatchdog(); window.removeEventListener("message", onMessage);