drawSkeletonRidgeRoof 경로에서 숨겨진 외곽 지붕선을 변별 경로와 동일한 selectable innerLine으로 생성해 기존 select+우클릭 편집 기능을 재사용하는 설계. splitRoofLinesToInnerLines 공용 추출 + 정렬버그 수정 방향 포함. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.5 KiB
용마루(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. 리스크 & 구현 전 검증
- SK 추녀 끝점 ↔
roof.lines정합성: split 은 "innerLine 끝점이roof.lines위에 있다"(isPointOnLineNew, tol 0.1)를 전제로 한다. SK 가 만든 hip 끝점이 처마 외곽선 위에 실제로 떨어지는지 실측 확인 필요. 안 맞으면 split 이 헛돌아 외곽선이 통째로 roofLine 1개로만 그려진다. → 구현 첫 단계에서 검증. - 순환 import:
qpolygon-utils ↔ skeleton-utils상호 import 여부 점검. 순환이면 split 함수를 제3의 모듈(예: canvas-util)로 추출하거나 의존성 정리. - 기존
isOuterEdgeroofLine 제거 영향: 숨긴 라인을 참조하는 곳(치수 계산, 플랜 저장/복원 등)이 있는지 확인 후 제거.
5. 성공 조건 (검증 기준)
- 용마루 지붕에서 외곽 지붕선이 화면에 보이고 클릭 선택된다.
- 우클릭 시 hip/ridge 와 동일한 메뉴(사이즈변경/이동/복사/삭제/수직이등분선)가 노출·동작한다.
- 마루이동 등으로 재그리기 후에도 외곽 지붕선이 유지된다.
- 변별 경로 동작 회귀 없음 (외곽선 split 결과 동일, 정렬 버그 수정분 제외).
6. 범위 밖 (YAGNI)
- 외곽 지붕선 전용 우클릭 메뉴/가드 (변별 경로와 완전 동일하게 가므로 추가 분기 없음).
- 박공(
drawGableRoof) / 한쪽흐름(drawShedRoof) 경로는 이번 변경 대상 아님.