From ee065091c1e8ef5d4d0be4d695ccafa60f25ee79 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 31 Aug 2026 14:38:48 +0900 Subject: [PATCH] =?UTF-8?q?fix(B07):=20=EB=8F=84=EA=B0=81=20=EA=B0=99?= =?UTF-8?q?=EC=9D=80=20=EC=9E=A0=EA=B8=88=20=EB=8F=84=EB=A9=B4=EC=B8=B5?= =?UTF-8?q?=EC=9D=84=20=EC=84=A4=EA=B3=84=20=EC=A1=B0=EC=9E=91=EC=9D=B4=20?= =?UTF-8?q?=EC=A7=91=EC=A7=80=20=EC=95=8A=EA=B2=8C=20=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 잠금 필터가 공용 함수 없이 호출부마다 들어가 있어 넣은 곳은 막히고 안 넣은 곳은 샜다. Ctrl+A는 도각 103개·원지반 16개까지 전부 선택했고, 선택 삭제에는 잠금 검사가 없어 Ctrl+A → Delete 한 번에 도각이 사라졌다. 자르기와 트림·모따기 계열, 커서 강조도 도각을 집었다. state.ts에 getPickableEntities()(잠금 도면층 제외)를 두고 집기 경로가 그걸 쓴다. 선택은 setSelectedEntityIds() 한 곳에서 막아, 앞으로 생길 선택 경로도 자동으로 잠금 객체를 담지 못한다. 화면맞춤(zoomToFitScreen)도 같은 목록에 맞춘다 — 도각까지 넣으면 A1 한 장 전체가 잡혀 화면의 40%가 여백으로 갔다. Co-Authored-By: Claude Opus 5 (1M context) --- .../screenCanvas.drawController.ts | 7 ++-- .../src/inputController/input-controller.ts | 6 ++-- B07_DesignDetail/openwebcad/src/state.ts | 13 ++++++++ .../openwebcad/src/tools/eraser-tool.ts | 32 +++++++++++-------- .../src/tools/factories/sequence-tool.ts | 6 +++- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts b/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts index a75f9ae8..753a1803 100644 --- a/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts +++ b/B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts @@ -4,7 +4,7 @@ import { getAngleWithXAxis } from '../helpers/get-angle-with-x-axis.ts'; import { getBoundingBoxOfMultipleEntities } from '../helpers/get-bounding-box-of-multiple-entities.ts'; import { mapNumberRange } from '../helpers/map-number-range.ts'; import { StateVariable } from '../helpers/undo-stack.ts'; -import { getEntities, getGridEnabled, triggerReactUpdate } from '../state.ts'; +import { getEntities, getGridEnabled, getPickableEntities, triggerReactUpdate } from '../state.ts'; import { paintColor, themeColor } from '../theme.ts'; import { DEFAULT_TEXT_OPTIONS, type DrawController } from './DrawController'; @@ -142,7 +142,10 @@ export class ScreenCanvasDrawController implements DrawController { * 월드 좌표 offset에 그대로 대입해 중심 배치가 어긋나는 문제가 있었다.) */ public zoomToFitScreen() { - const entities = getEntities(); + // 도각·원지반 같은 잠금 도면층은 빼고 설계 콘텐츠에만 맞춘다 (2026-08-31 사용자 + // 확정). 도각까지 넣으면 A1 한 장 전체가 잡혀 화면의 40%가 여백으로 간다. + const pickable = getPickableEntities(); + const entities = pickable.length ? pickable : getEntities(); if (!entities.length) return; const boundingBox = getBoundingBoxOfMultipleEntities(entities); const boundingWidth = boundingBox.maxX - boundingBox.minX; diff --git a/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts b/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts index 308ace26..ac38c81d 100644 --- a/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts +++ b/B07_DesignDetail/openwebcad/src/inputController/input-controller.ts @@ -31,7 +31,7 @@ import { getActiveToolActor, getAngleStep, getCanvas, - getEntities, + getPickableEntities, getGridEnabled, getLastStateInstructions, getPanStartLocation, @@ -271,7 +271,7 @@ export class InputController { if (getActiveToolActor()?.getSnapshot()?.context.type === Tool.SELECT) { const closestEntityInfo = findClosestEntity( screenCanvasDrawController.targetToWorld(newScreenMouseLocation), - getEntities() + getPickableEntities() ); if (closestEntityInfo.distance < pickRadius()) { setHighlightedEntityIds([closestEntityInfo.entity.id]); @@ -428,7 +428,7 @@ export class InputController { this.handleRedo(evt); } else if (evt.ctrlKey && evt.key === 'a') { // User wants to select everything - setSelectedEntityIds(getEntities().map((entity) => entity.id)); + setSelectedEntityIds(getPickableEntities().map((entity) => entity.id)); } else if (evt.key === 'Backspace') { // Remove the last character from the input field evt.preventDefault(); diff --git a/B07_DesignDetail/openwebcad/src/state.ts b/B07_DesignDetail/openwebcad/src/state.ts index 124f2778..3ff2f069 100644 --- a/B07_DesignDetail/openwebcad/src/state.ts +++ b/B07_DesignDetail/openwebcad/src/state.ts @@ -189,6 +189,13 @@ export const getCanvas = () => canvas; export const getActiveToolActor = () => activeToolActor; export const getLastStateInstructions = () => lastStateInstructions; export const getEntities = (): Entity[] => entities; +/** + * 집을 수 있는 객체 — 잠금 도면층(도각 b08-frame·원지반 b08-ground 등 참조용)을 뺀다. + * 선택·지우기·수정 도구의 대상 찾기와 화면맞춤은 이 목록을 쓴다. AutoCAD가 도각을 + * 배치(도면공간)에 두어 모형공간 작업에 끼지 않게 하는 것과 같은 자리다. + */ +export const getPickableEntities = (): Entity[] => + entities.filter((entity) => !layersById.get(entity.layerId)?.isLocked); export const getSelectedEntityIds = () => selectedEntityIds; export const getShouldDrawCursor = () => shouldDrawCursor; export const getAngleGuideEntities = () => angleGuideEntities; @@ -305,6 +312,12 @@ export const setHighlightedEntityIds = (newEntityIds: string[]) => { highlightedEntityIdSet = new Set(newEntityIds); }; export const setSelectedEntityIds = (newEntityIds: string[]) => { + // 잠금 도면층 객체는 어느 경로로도 선택되지 않는다 — 선택이 곧 삭제·이동 대상이라 + // 호출부(Ctrl+A 등)마다 거르지 않고 여기 한 곳에서 막는다. + const locked = new Set( + entities.filter((entity) => layersById.get(entity.layerId)?.isLocked).map((entity) => entity.id) + ); + if (locked.size) newEntityIds = newEntityIds.filter((id) => !locked.has(id)); selectedEntityIds = newEntityIds; selectedEntityIdSet = new Set(newEntityIds); bumpSceneVersion(); // selection style (dashed) is baked into the scene cache diff --git a/B07_DesignDetail/openwebcad/src/tools/eraser-tool.ts b/B07_DesignDetail/openwebcad/src/tools/eraser-tool.ts index 1354de9e..be0e5fb6 100644 --- a/B07_DesignDetail/openwebcad/src/tools/eraser-tool.ts +++ b/B07_DesignDetail/openwebcad/src/tools/eraser-tool.ts @@ -1,24 +1,30 @@ -import type {Point, Polygon} from '@flatten-js/core'; -import {assign, createMachine} from 'xstate'; -import type {ArcEntity} from '../entities/ArcEntity'; -import type {CircleEntity} from '../entities/CircleEntity'; -import {EntityName} from '../entities/Entity'; -import {LineEntity} from '../entities/LineEntity'; -import type {RectangleEntity} from '../entities/RectangleEntity'; -import {findClosestEntity} from '../helpers/find-closest-entity'; -import {polygonToSegments} from '../helpers/polygon-to-segments'; +import type { Point, Polygon } from '@flatten-js/core'; +import { assign, createMachine } from 'xstate'; +import type { ArcEntity } from '../entities/ArcEntity'; +import type { CircleEntity } from '../entities/CircleEntity'; +import { EntityName } from '../entities/Entity'; +import { LineEntity } from '../entities/LineEntity'; +import type { RectangleEntity } from '../entities/RectangleEntity'; +import { findClosestEntity } from '../helpers/find-closest-entity'; +import { polygonToSegments } from '../helpers/polygon-to-segments'; import { addEntities, deleteEntities, getActiveLayerId, getEntities, + getPickableEntities, setEntities, setGhostHelperEntities, setShouldDrawHelpers, } from '../state'; -import {Tool} from '../tools'; -import {eraseArcSegment, eraseCircleSegment, eraseLineSegment, getAllIntersectionPoints,} from './eraser-tool.helpers'; -import type {MouseClickEvent, StateEvent, ToolContext} from './tool.types'; +import { Tool } from '../tools'; +import { + eraseArcSegment, + eraseCircleSegment, + eraseLineSegment, + getAllIntersectionPoints, +} from './eraser-tool.helpers'; +import type { MouseClickEvent, StateEvent, ToolContext } from './tool.types'; export interface EraserContext extends ToolContext { startPoint: Point | null; @@ -86,7 +92,7 @@ export const eraserToolStateMachine = createMachine( ); export function handleMouseClick(worldMouseLocation: Point) { - const closestEntity = findClosestEntity(worldMouseLocation, getEntities()); + const closestEntity = findClosestEntity(worldMouseLocation, getPickableEntities()); if (!closestEntity) { return; } diff --git a/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts b/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts index 5ef75cb3..3e5f6385 100644 --- a/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts +++ b/B07_DesignDetail/openwebcad/src/tools/factories/sequence-tool.ts @@ -13,6 +13,7 @@ import { getPointFromEvent } from '../../helpers/get-point-from-event'; import { pickRadius } from '../../helpers/pick-radius'; import { queryEntitiesNearPoint } from '../../helpers/spatial-index'; import { + getLayerById, getScreenCanvasDrawController, getSelectedEntities, setActiveToolActor, @@ -147,7 +148,10 @@ function cursorPoint(event: StateEvent | undefined): Point { /** 클릭 지점에서 가장 가까운 엔티티 (선택 반경 안에 있을 때만) */ function pickEntityAt(worldPoint: Point): Entity | null { const radius = pickRadius(); - const candidates = queryEntitiesNearPoint(worldPoint.x, worldPoint.y, radius); + // 잠금 도면층(도각 등)은 집지 않는다 — 수정 도구가 참조 선을 잡으면 안 된다. + const candidates = queryEntitiesNearPoint(worldPoint.x, worldPoint.y, radius).filter( + (entity) => !getLayerById(entity.layerId)?.isLocked + ); const { distance, entity } = findClosestEntity(worldPoint, candidates); return entity && distance <= radius ? entity : null; }