From b28397c91a96363e1397202da52c9a2ee8e8d202 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Tue, 2 Jun 2026 09:45:20 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EC=9A=A9=EB=A7=88=EB=A3=A8(skeleton)?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=99=B8=EA=B3=BD=20=EC=A7=80=EB=B6=95?= =?UTF-8?q?=EC=84=A0=20=ED=8E=B8=EC=A7=91=20=EA=B8=B0=EB=8A=A5=20=EC=84=A4?= =?UTF-8?q?=EA=B3=84=20=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drawSkeletonRidgeRoof 경로에서 숨겨진 외곽 지붕선을 변별 경로와 동일한 selectable innerLine으로 생성해 기존 select+우클릭 편집 기능을 재사용하는 설계. splitRoofLinesToInnerLines 공용 추출 + 정렬버그 수정 방향 포함. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...026-06-01-skeleton-roofline-edit-design.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/plans/2026-06-01-skeleton-roofline-edit-design.md diff --git a/docs/plans/2026-06-01-skeleton-roofline-edit-design.md b/docs/plans/2026-06-01-skeleton-roofline-edit-design.md new file mode 100644 index 00000000..2efd354e --- /dev/null +++ b/docs/plans/2026-06-01-skeleton-roofline-edit-design.md @@ -0,0 +1,89 @@ +# 용마루(skeleton) 경로 외곽 지붕선 편집 기능 — 설계 + +- 작성일: 2026-06-01 +- 대상 브랜치: `feature/redo-undo` +- 관련 파일: `src/util/skeleton-utils.js`, `src/util/qpolygon-utils.js`, `src/hooks/useContextMenu.js` + +## 1. 배경 / 문제 + +지붕 그리기는 `QPolygon.drawHelpLine()` 에서 외벽선 type 패턴으로 분기한다. + +- **변별 경로** (`drawRoofByAttribute`, qpolygon-utils.js): 외곽선(`roof.lines`)을 내부선 끝점 기준으로 split 해서 추녀(hip)/지붕선을 `name='hip'` selectable QLine 으로 `roof.innerLines` 에 넣는다. 사용자는 이 라인을 select → 우클릭으로 편집할 수 있다. +- **용마루(skeleton) 경로** (`drawSkeletonRidgeRoof` → `skeletonBuilder` → `createInnerLinesFromSkeleton`): 외곽 처마선을 SK 빌더의 outer edge 로 만들되 `lineName='roofLine'` + `selectable:false` + `visible:false` 로 **숨겨버린다** (`skeleton-utils.js:1611-1643`). 그래서 외곽 지붕선을 편집할 수 없다. + +**목표**: 용마루 경로에서도 외곽 지붕선을 변별 경로와 **동일한 형태의 innerLine** 으로 생성하여, 기존 select + 우클릭 편집 기능을 그대로 재사용한다. + +## 2. 핵심 사실 (탐색 결과) + +### 2-1. innerLine 편집 기능의 정체 +사용자가 innerLine 을 편집하는 통로는 **select → 우클릭 컨텍스트 메뉴**다. `useContextMenu.js:309-312` 에서 `auxiliaryLine` / `hip` / `ridge` / `eaveHelpLine` 을 한 case 로 묶는다. + +| 메뉴 | 동작 | +|---|---| +| 사이즈 변경 | `AuxiliarySize` 모달 | +| 보조선 이동(M) / 복사(C) | `AuxiliaryEdit` 모달 | +| 보조선 삭제 / 전체삭제 | `roof.innerLines` 에서 제거 | +| 수직 이등분선 | `roof.innerLines.push` | + +이 분기는 **`currentObject.name` 기준**이다. 따라서 외곽 지붕선을 `name='hip'` (또는 `'ridge'`) 로 만들어 innerLine 에 넣기만 하면 이 메뉴가 **자동으로** 붙는다. 별도 바인딩 코드 불필요. + +### 2-2. 변별 경로는 이미 동작 중 (= 목표 동작의 레퍼런스) +변별 경로 split 은 외곽 지붕선을 `drawRoofLine` 으로 그리는데, 그 함수의 `name` 이 `SUBLINE.HIP` 이다 (의도적 재사용). 즉 변별 경로의 외곽 지붕선은 이미 `name='hip'` innerLine 으로 들어가 select+우클릭이 동작한다. **용마루도 이와 동일하게 동작하면 된다 (전용 차이 없음).** + +### 2-3. innerLine 이 편집 가능하려면 가져야 하는 속성 + +| 조건 | 값 | 안 맞으면 | +|---|---|---| +| `type` | `'QLine'` | 플랜 복원/필터 누락 (`useCanvasConfigInitialize.js:243`) | +| `name` | `'hip'` / `'ridge'` (SUBLINE 값) | 컨텍스트 메뉴·이동 분기에 안 걸림 | +| `attributes.roofId` / `parentId` | `roof.id` | roof 조회·자식 이동 실패 | +| `selectable` | `true` | 클릭 자체 불가 | +| **금지** | `name='roofLine'`, `selectable:false`, `evented:false` | 현재 숨긴 외곽선이 정확히 이 상태 | + +### 2-4. 재그리기 함정 +`drawHelpLine()` 은 호출될 때마다 `roof.innerLines` 를 비우고 SK 함수가 처음부터 다시 그린다 (`QPolygon.js:582-598`). 따라서 외곽 지붕선을 "한 번 추가" 하는 것으로 부족하고, **재그리기 때마다 skeleton 경로가 자동 재생성** 하도록 만들어야 살아남는다. + +## 3. 설계 (접근안 A: split 로직 공용 추출) + +### 3-1. 공용 함수 추출 +`qpolygon-utils.js:5432-5496` 의 "지붕선에 따라 라인추가" 블록을 함수로 추출: + +``` +splitRoofLinesToInnerLines(roof, canvas, textMode) → QLine[] + - innerLinesPoints 수집 (innerLines 끝점 unique) + - roof.lines 순회: + hasOverlapLine 가드 (innerLine 이 외곽선을 완전 포함하면 skip) + splitPoint 수집 (innerLinesPoints 중 currentLine 위 + 끝점 아닌 점) + 분류: 첫 구간 = drawHipLine(prevDegree) + 중간 구간 = drawRoofLine + 끝 구간 = (splitPoint 1개면 drawRoofLine, 아니면 drawHipLine(nextDegree)) + splitPoint 없으면 통째로 drawRoofLine + - 생성 라인을 innerLines 에 push 후 반환 +``` + +- **위치**: `drawHipLine` / `drawRoofLine` 이 있는 `qpolygon-utils.js` 에 두고 `export`. `skeleton-utils.js` 에서 import. +- **정렬 버그 동시 수정**: 현재 `splitPoint.sort((a, b) => a[1] - b[1])` 는 원소가 `{point, distance}` 객체라 `a[1]` 이 `undefined` → 정렬 no-op. `a.distance - b.distance` 로 수정. (분할점 2개 이상 케이스에서 라인이 거리순으로 그려지지 않던 잠재 버그) +- **`drawRoofByAttribute` 교체**: 인라인 블록을 함수 호출로 교체. 순수 추출이므로 변별 경로 동작 불변. + +### 3-2. skeleton 경로 변경 +- `createInnerLinesFromSkeleton` 의 `isOuterEdge` 숨김 처리(`:1636-1643` 의 `visible:false`, `:1624` 의 `selectable:false`) 제거. +- 내부선(용마루/추녀) 생성 완료 후 `splitRoofLinesToInnerLines(roof, canvas, textMode)` 호출 → 외곽 지붕선이 변별 경로와 동일한 형태로 `roof.innerLines` 에 합류. +- `drawHelpLine` 재호출 시마다 자동 재실행되어 편집 후 재그리기에도 외곽 지붕선 유지. + +## 4. 리스크 & 구현 전 검증 + +1. **SK 추녀 끝점 ↔ `roof.lines` 정합성**: split 은 "innerLine 끝점이 `roof.lines` 위에 있다"(`isPointOnLineNew`, tol 0.1)를 전제로 한다. SK 가 만든 hip 끝점이 처마 외곽선 위에 실제로 떨어지는지 **실측 확인 필요**. 안 맞으면 split 이 헛돌아 외곽선이 통째로 roofLine 1개로만 그려진다. → 구현 첫 단계에서 검증. +2. **순환 import**: `qpolygon-utils ↔ skeleton-utils` 상호 import 여부 점검. 순환이면 split 함수를 제3의 모듈(예: canvas-util)로 추출하거나 의존성 정리. +3. **기존 `isOuterEdge` roofLine 제거 영향**: 숨긴 라인을 참조하는 곳(치수 계산, 플랜 저장/복원 등)이 있는지 확인 후 제거. + +## 5. 성공 조건 (검증 기준) + +- 용마루 지붕에서 외곽 지붕선이 화면에 보이고 클릭 선택된다. +- 우클릭 시 hip/ridge 와 동일한 메뉴(사이즈변경/이동/복사/삭제/수직이등분선)가 노출·동작한다. +- 마루이동 등으로 재그리기 후에도 외곽 지붕선이 유지된다. +- 변별 경로 동작 회귀 없음 (외곽선 split 결과 동일, 정렬 버그 수정분 제외). + +## 6. 범위 밖 (YAGNI) + +- 외곽 지붕선 전용 우클릭 메뉴/가드 (변별 경로와 완전 동일하게 가므로 추가 분기 없음). +- 박공(`drawGableRoof`) / 한쪽흐름(`drawShedRoof`) 경로는 이번 변경 대상 아님.