Merge branch 'dev' of https://git.hanasys.jp/qcast3/qcast-front into feature/redo-undo
# Conflicts: # src/hooks/module/useModuleBasicSetting.js
This commit is contained in:
commit
55e0528c26
@ -1,25 +0,0 @@
|
|||||||
- [잘 되는 코드 건들지 않기](feedback_no_touch_working_code.md) — 버그 수정 시 정상 코드 수정 금지, 최소 지점만 수정
|
|
||||||
- [코드 수정 시 주석 + 커밋 메시지 둘 다 제시](feedback_inline_change_tag.md) — 인라인 `[태그명 YYYY-MM-DD]` + 작업 단락 끝에 git commit 메시지 제안 (사용자가 직접 입력)
|
|
||||||
- [작업 완료 = 코드+인수인계서/메모리, 커밋은 2단계](feedback_handover_sync.md) — 같은 응답에 코드+docs+memory 모두 작성. 단 커밋은 1차 코드만→해시확보→[TBD-CODE] 치환→2차 docs/memory. 커밋 메시지 2개 제시.
|
|
||||||
- [커밋 로그 파일 위치](reference_commit_log.md) — qcast-front 는 post-commit 훅으로 `memory/commit_log.txt` 에 매 커밋 자동 append
|
|
||||||
- [오버 이동은 평행 위치까지 클램핑](feedback_over_move_silent_skip.md) — useMovementSetting handleSave OVER_GUARD. 정책 flag `OVER_MOVE_POLICY` (clamp/reject/passthrough) 상단 상수로 전환 가능
|
|
||||||
- [moveLine + offset 이중 적용 방지](feedback_moveline_offset.md) — drawHelpLine 호출 전 roof.moveFlowLine/moveUpDown 설정 금지. 순서: baseLines 복원 → drawHelpLine(moveLine 없이) → moveLine 속성 복원
|
|
||||||
- [OVER 클램프 후 빨강 보조선 미해결](project_over_move_red_helper_deferred.md) — 평행 직전까지 이동하면 eaveHelpLine(빨강) 이 계속 그려지는 별건 이슈, 2026-04-27 이후 재개 예정
|
|
||||||
- [평행 이동 SK 붕괴 근본원인/수정](project_parallel_collapse_fix.md) — dy 적용 경로 2곳(moving/buildRaw)을 인덱스 직접 매핑 + Set 합집합으로 전환
|
|
||||||
- [페어링 인덱스 master = roofLines](project_index_master_roofLines.md) — skeleton-utils 정렬 master 를 wallLines→roofLines 로 변경, leftTop 최저좌표→CCW. 2026-05-06 ensureCounterClockwiseLines graph 키도 0.1 round 통일 (drift 보강)
|
|
||||||
- [SHOULDER_ABSORBED 임계 < 10 통일](project_shoulder_absorbed_thresholds.md) — skip/HIP_SKIP/_out 4경로/SK-VERTEX-PROXIMITY 가드 임계·범위, OVER_EPS=0.5→planeSize=5 대응
|
|
||||||
- [SK 이동 로직 회귀 체크리스트](project_skeleton_regression_checklist.md) — V1 트랙 회귀 검증 5 시나리오
|
|
||||||
- [v2.0 설계 방향 (휴면)](project_v2_design_direction.md) — 2026-05-06 휴면. 사용자 명령 전까지 참조 금지
|
|
||||||
- [v2.0 핵심 불변식 (휴면)](project_v2_invariant_no_reset.md) — 2026-05-06 휴면. 사용자 명령 전까지 참조 금지
|
|
||||||
- [디버그 라벨 QPolygon 인라인](feedback_debug_labels_local_only.md) — 별도 util 파일 금지. 라벨 코드는 QPolygon.js 인라인, NEXT_PUBLIC_RUN_MODE==='local' 가드.
|
|
||||||
- [좌표 매칭 tolerance 는 UI drift 고려해 넉넉히](feedback_same_point_tolerance.md) — 매칭용 isSamePoint 는 0.5, 기본 0.1 은 UI/Big.js drift 로 매칭 실패 빈발
|
|
||||||
- [V1 단일 트랙 (V2 휴면)](feedback_v1_v2_dual_track.md) — 2026-05-06 V2 휴면. 사용자 명령 전까지 V2 언급/참조 금지
|
|
||||||
- [화면 표시 vs 내부 실질값 분리](feedback_display_vs_internal_values.md) — 라벨 흔들림 수정은 표시 단계 보정 우선, 좌표 round 추적은 별건. setActualSize axis 평균이 첫 적용 케이스.
|
|
||||||
- [명령 받으면 관련 파일 전체 확인 후 이행](feedback_full_analysis_before_action.md) — 단일 파일 추측 수정 금지. 흐름 전체 추적 후 수정 지점 결정.
|
|
||||||
- [2차 마루이동 ridge 클릭불가 보강](project_ridge_2nd_move_fix.md) — useMovementSetting [type] effect 에 canvas 레벨 ridge selectable=true fallback 추가
|
|
||||||
- [phantom ridge 다운스트림 cull](project_phantom_ridge_cull.md) — createInnerLinesFromSkeleton 종료 직전 zero-length+dead-end ridge 2단계 cull. SK builder drift phantom 흡수.
|
|
||||||
- [HIP/RIDGE 분류 임계 5.0mm](project_hip_ridge_threshold.md) — addRawLine isDiagonal 임계 0.1→5.0. 벽이동 drift 로 ridge 가 hip 로 변하는 문제 차단.
|
|
||||||
- [마루이동 x/y-overlap 사전필터](project_marudo_xoverlap_prefilter.md) — useMovementSetting FLOW_LINE 거리최소필터 이전 overlap 사전필터. 2차 이동 반대측 wall 오선택 차단.
|
|
||||||
- [hip pitch 폴백](project_hip_pitch_fallback.md) — processEavesEdge:2915 outerLine.pitch ?? roof.pitch ?? 0. hip 의 plane===actual 메뉴토글 무효 버그 수정.
|
|
||||||
- [auxiliaryLine integrate 제외](project_aux_line_integrate_exclusion.md) — integrateExtensionLines sk-finder 에서 aux 제외. ext+aux 코너끝점 손실 → split 실패 차단.
|
|
||||||
- [저구배+특정기와 안내 alert](project_low_pitch_warning.md) — 6종 기와(和瓦A/B,平板瓦C/D,S瓦,セメント瓦)+寸 2~2.5미만 시 시공 매뉴얼 안내 alert(OK만). util `roof-pitch-warning.js`
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
name: 디버그 라벨은 QPolygon 인라인 + local 전용
|
|
||||||
description: debugLabels 는 별도 util 파일 없이 QPolygon.js 안에 인라인. NEXT_PUBLIC_RUN_MODE==='local' 에서만 동작.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
**핵심 규칙 (2개)**:
|
|
||||||
|
|
||||||
1. 가드: 캔버스 라벨은 항상 **`process.env.NEXT_PUBLIC_RUN_MODE === 'local'`** 에서만 그려져야 한다. 'local.dev', 'development', 'production' 에서 라벨 보이면 안 됨.
|
|
||||||
|
|
||||||
2. **별도 util 파일 만들지 말 것**. 사용자 명시: "util 에 debug 파일 자체가 없다.. 라벨은 다른 qline 이런곳에 올려라". 라벨 코드는 호출처(`src/components/fabric/QPolygon.js`) 안에 인라인 함수(`__attachDebugLabels`, `__classifyLineForLabel`, `__isDebugLabelsEnabled`) 로 둔다.
|
|
||||||
|
|
||||||
**Why**:
|
|
||||||
- 사용자 명시 반복: "local 일때만 실행, dev/prd 에 사용되면 안 된다."
|
|
||||||
- `src/util/debugLabels.js` (+ stub, + next.config alias) 구조를 만들었더니 사용자가 "util 에 debug 파일 자체가 없다" 고 거부. 별도 파일 = 관리 부담 + dev/prd 동기화 이슈.
|
|
||||||
- 인라인 두면 외부 의존 0, 가드는 빌드 타임 dead-code 제거 가능 (Next.js 가 NEXT_PUBLIC_* 를 리터럴 치환).
|
|
||||||
- 캔버스에 노란 배경 라벨(B-7, SK-3 등) 이 운영 환경에서 보이면 사용자/고객 혼란.
|
|
||||||
- `local.dev` 는 "로컬에서 dev API 붙이는" 모드라 이름은 local 이지만 OFF 대상.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- 가드 함수 `__isDebugLabelsEnabled()` 의 비교 문자열 변경/완화 금지. `===` 등호 유지.
|
|
||||||
- 라벨 관련 새 헬퍼 함수가 필요해도 `src/util/` 등 외부에 새 파일 만들지 말 것. QPolygon.js (또는 사용자가 지정한 기존 파일) 안에 인라인.
|
|
||||||
- 다른 디버그 시각화 추가 시에도 같은 인라인 + 가드 패턴 재사용.
|
|
||||||
- ENV 정의: `.env.localhost`=local / `.env.local.dev`=local.dev / `.env.development`=development / `.env.production`=production.
|
|
||||||
|
|
||||||
**현재 상태 (2026-04-27, 최종)**:
|
|
||||||
- `src/util/debugLabels.js` 삭제, `src/util/debugLabels.stub.js` 삭제, `next.config.mjs` alias 설정 원복.
|
|
||||||
- 모든 라벨 코드는 `src/components/fabric/QPolygon.js` 상단 (~line 11~80) 에 인라인:
|
|
||||||
- `DEBUG_LABEL_NAME` 상수
|
|
||||||
- `__isDebugLabelsEnabled()` env 가드
|
|
||||||
- `__classifyLineForLabel(obj)` prefix 분류
|
|
||||||
- `__attachDebugLabels(canvas, parentId)` 부착
|
|
||||||
- 호출은 `drawHelpLine` 끝 (~line 600): `__attachDebugLabels(this.canvas, this.id)`
|
|
||||||
- dev/prd 빌드: 가드가 false → fabric.Text 생성/추가 없음. 외부 파일 의존 없으므로 빌드 안전.
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
name: 화면 표시값과 내부 실질값 분리
|
|
||||||
description: 라벨 흔들림(좌우대칭 1mm 차 등) 수정 시 화면 일관성 우선, 내부 정확도는 별도 보관 가정
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
화면에 보여주는 라벨이 좌우대칭/같은 라인에서 흔들릴 때, 가장 쉬운 단발 보정 (평균 axis, ratio 통일 등) 으로 화면만 맞추는 방향을 사용자가 선호함. 내부 attribute (planeSize/actualSize 등) 의 절대 정확도는 별도 보관/계산 경로에서 보장된다고 가정.
|
|
||||||
|
|
||||||
**Why:** 사용자 표현 "화면에 보여주는 것이라.. 쉬운 방법을 사용하세요.. 실질적인 라인값은 따로 잘 보관하고 있으면 된다.." (2026-04-29). 좌표 정수화·FP 노이즈로 발생하는 라벨 비대칭은 근본(좌표 round 지점) 추적보다 표시 단계 보정이 ROI 가 높다는 판단.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 같은 라인이 두 face 에서 다른 라벨이 나오는 류의 비대칭은, face-direction 별 분기에서 axis 를 통일 (예: setActualSize 의 dx/dy 평균 사용) 하거나 표시 캐싱으로 1차 처리
|
|
||||||
- 내부 attribute 정밀도(0.01) 와 화면 round(정수) 는 분리 유지
|
|
||||||
- 좌표 round 지점 자체를 추적·수정하는 것은 영향 범위가 크므로 별도 이슈로 분리
|
|
||||||
- v1 트랙 setActualSize (useLine.js) 비대칭 보정이 적용된 첫 케이스 — 비슷한 케이스에 동일 패턴 재사용
|
|
||||||
- hip 간 비대칭 (좌우대칭 hip 0.5mm 차, 예: 5048 vs 5048.5) 해결 (2026-04-30):
|
|
||||||
- 1차 시도: useLine.js axis 정수 round → 효과 없어 롤백 (좌표 drift 가 ×10 증폭 후 sqrt fp 에서 발생, 단일 라인 round 로 안 잡힘)
|
|
||||||
- **근본 원인:** QLine.js:23 toFixed(1) 0.1mm 절단 + setActualSize sqrt fp + Math.round(*2)/2 boundary
|
|
||||||
- **다운스트림 read 확인:** actualSize 가 단순 라벨 아님. usePolygon.js 분할 비율 / useRoofAllocationSetting.js 확장라인 병합 / usePlacementShapeDrawing.js shape 상속 / JSON 저장(DB) 모두 read. → attribute 직접 수정 위험.
|
|
||||||
- **최종 수정:** useText.js changeCorridorDimensionText 에 cross-hip display 통일 post-pass 추가. 대각선 lengthText 만 추출, ±2mm 이내 그룹화, 정수 round 평균값으로 .text 만 통일. line.attributes 는 안 건드림 → 다운스트림 영향 0.
|
|
||||||
- 추후 근본수정 (QLine.js 정밀도 상향 등) 시 이 보정 제거 가능
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: 명령 받으면 관련 파일 전체 확인 후 이행
|
|
||||||
description: 단일 파일/지점 추측 수정 금지. 작업 시작 전 관련 파일/흐름 전체 추적 필수
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
명령을 받으면 관련 파일을 다 확인하고 명령을 이행한다. 단일 파일만 보고 추측으로 수정하지 않는다.
|
|
||||||
|
|
||||||
**Why:** 2026-04-30 hip 라벨 5048/5048.5 비대칭 이슈 진행 중, useLine.js 만 보고 axis 정수 round 수정했으나 효과 없었음. 사용자 지적: "그 파일만 보지 말고 수정전에 전체 분석을 해야 되는거 아냐?" 이후 Explore 서브에이전트로 전체 흐름 추적하니 좌표 drift 출처가 QLine.js:23 toFixed(1), 표시 경로가 useText.js, 모드 분기가 settingAtom.js 인 게 드러남. 단일 파일 추측 수정은 효과 없는 작업 + 사용자 시간 낭비.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 작업 시작 전: 관련 파일/흐름 전체 추적 (좌표 → 산출 → 저장 → 표시 → 모드 전환 등 end-to-end)
|
|
||||||
- 복잡한 흐름이면 Explore 서브에이전트로 분석 리포트 먼저 받기
|
|
||||||
- "어디서 만들어지고 어디서 표시되는지" 명확히 한 후에 수정 지점 결정
|
|
||||||
- 수정 후보가 여러 곳일 때 사용자에게 옵션 제시
|
|
||||||
- 단일 파일 보고 "이거 같다" 추측 금지
|
|
||||||
- 적용 가능 범위: 모든 작업 (버그 fix, 기능 추가, 리팩터)
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
---
|
|
||||||
name: 작업 완료 시 인수인계서 동시 갱신 + 2단계 커밋 (선코드 후문서) + 커밋 해시는 자동 로그 참조
|
|
||||||
description: 코드 변경이 끝나면 같은 응답에서 인수인계서/메모리도 작성. 단, 커밋은 2단계 — 1차 코드만 커밋 → 해시 확인 → 인수인계서/메모리에 해시 명시 → 2차 커밋. 해시 자리는 [TBD-CODE] placeholder.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 커밋 메시지 제안 (2단계)
|
|
||||||
|
|
||||||
작업이 완료되면 git 커밋 메시지를 **2개** 제시한다 — 1차 코드용 / 2차 문서·메모리용.
|
|
||||||
|
|
||||||
**Why:** 사용자가 직접 커밋한다 (Claude 가 `git commit` 하지 말 것). 또한 인수인계서/메모리에 자기참조 해시를 쓰려면 코드 커밋이 먼저 이뤄져야 한다 (§3 참조).
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 작업 완료 응답 마지막에 **커밋 메시지 2개** 별도 섹션으로 제시
|
|
||||||
- **1차 (코드)**: 한국어 타이틀 한 줄. 변경 핵심 간결히.
|
|
||||||
- **2차 (문서/메모리)**: `docs: <태그> 인수인계서/메모리 갱신 (<해시>)` 형식. `<해시>` 자리는 사용자가 1차 커밋 후 알려주면 채움.
|
|
||||||
- **티켓번호(`[1845]` 등) prefix 는 임의로 붙이지 말 것** — 사용자가 직접 추가
|
|
||||||
|
|
||||||
## 2. 작업 완료 시 인수인계서 동시 갱신 (커밋 신호 기다리지 말 것)
|
|
||||||
|
|
||||||
작업이 끝나면 **별도 지시 없어도 관련 인수인계서까지 같은 응답에서 함께 갱신**한다.
|
|
||||||
|
|
||||||
**Why:** 사용자 피드백 (2026-05-06): "내가 커밋하면 자동으로 여기까지 해줘야 한다." 코드만 변경하고 인수인계서를 빼먹으면 사용자가 커밋 직전에 다시 갱신을 요청해야 하므로 흐름이 끊김. 인수인계서는 후임/리뷰의 1차 자료라 코드 변경과 동시 반영이 필수.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 응답 구성 순서: ① 코드 변경 요약 → ② 인수인계서 갱신 → ③ 커밋 메시지 제안.
|
|
||||||
- **갱신 대상 인수인계서 매핑** (qcast-front `docs/`):
|
|
||||||
- Straight Skeleton 라인 이동, 외벽선/처마선/내부선/오버 이동/phantom ridge/hip vs ridge → `SK_MOVEMENT_HANDOVER.md`
|
|
||||||
- 모달·다이얼로그·라인 표시·계산기 패드·라벨/색/인덱스 → `UI_MODAL_HANDOVER.md`
|
|
||||||
- API 안정화·인증/세션·동시 UPDATE 데드락·errSave·재시도·dev 스크립트 → `API_STABILITY_HANDOVER.md`
|
|
||||||
- 모듈/배치/회로(PCS·접속함)/처마/가대/[1xxx] 티켓 시리즈 → `MODULE_CIRCUIT_HANDOVER.md`
|
|
||||||
- 트랙이 새로 생기면 `HANDOVER_INDEX.md` 에도 항목 추가
|
|
||||||
- 갱신 방식: **기존 §번호 보존**, 새 항목은 **마지막 번호 다음에 신규 섹션으로 append** (기존 §번호를 미루지 말 것 — diff 부담↑)
|
|
||||||
- 새 섹션에는 **작성일(YYYY-MM-DD)**, 배경(무슨 일이 있었나), 한 일(파일·라인·태그), 후속 확인 포인트
|
|
||||||
- 사소한 변경(오타·포맷팅 등) 인수인계서 정책에 영향 없으면 갱신 생략 가능. 의심되면 갱신 쪽으로 기울 것.
|
|
||||||
- **커밋 해시 자리는 `[TBD-CODE]` placeholder** 로 두고 §3 의 2단계 커밋 흐름에 맡긴다.
|
|
||||||
|
|
||||||
## 3. 2단계 커밋 — 선(先)코드, 후(後)문서/메모리 (2026-05-06 신규)
|
|
||||||
|
|
||||||
**Why:** UI_MODAL_HANDOVER §1.1 표처럼 "어떤 작업이 어느 커밋인지" 인수인계서/메모리에 정확한 해시로 추적하려면, 코드 커밋이 먼저 이뤄지고 그 해시를 사후에 인수인계서/메모리에 적어야 함. 한 번에 커밋하면 자기참조 해시를 쓸 수 없음.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 응답 구성: 코드 + 인수인계서/메모리 모두 같은 응답에서 작성. 해시가 들어갈 자리는 `[TBD-CODE]` placeholder 표시.
|
|
||||||
- 커밋 가이드 (사용자가 직접):
|
|
||||||
1. **1차 — 코드 파일만 stage 후 커밋** → 해시 확보 (`git rev-parse --short HEAD`)
|
|
||||||
2. 사용자가 해시를 Claude 에게 알려줌
|
|
||||||
3. Claude 가 인수인계서/메모리의 `[TBD-CODE]` → 실제 해시로 교체
|
|
||||||
4. **2차 — 인수인계서 + 메모리 stage 후 커밋**
|
|
||||||
- post-commit 훅의 `commit_log.txt` 에는 두 커밋 모두 자동 append (§4 참조)
|
|
||||||
- 인수인계서/메모리 변경만 단독으로 있는 작업(코드 변경 없음)은 1단계로 끝.
|
|
||||||
|
|
||||||
## 4. 커밋 해시는 자동 로그에서 참조 (수동 기록 X)
|
|
||||||
|
|
||||||
2026-05-06 부터 qcast-front 저장소에 git `post-commit` 훅이 설치되어, 커밋 직후 자동으로 `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-front/memory/commit_log.txt` 에 한 줄 append 된다.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 인수인계서/리뷰/리포트에 해시를 인용해야 할 때 이 로그 파일을 먼저 참조 (또는 `git log -1 --format='%h %s'`)
|
|
||||||
- Claude 가 메모리 파일에 해시를 수동으로 누적하지 말 것 (이중 기록 금지)
|
|
||||||
- 푸시 여부는 로그에 기록되지 않으므로, 푸시 확인이 필요하면 `git log origin/<branch>..HEAD` 등으로 별도 확인
|
|
||||||
- 자세한 위치/형식은 `reference_commit_log.md` 참조
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: 코드 수정 시 주석 + 커밋 메시지 둘 다 제시
|
|
||||||
description: 파일 수정 시 (1) 변경 지점에 `[태그명 날짜]` 인라인 주석 추가, (2) 작업 단락 끝에 사용자가 git push 할 때 쓸 commit 메시지 제안 명시. 둘 다 응답에 보여줄 것.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
**규칙** (사용자 강조, 2026-05-06):
|
|
||||||
1. 코드 수정/생성 시 변경 지점 직전에 `[태그명 YYYY-MM-DD]` 형식의 한국어 주석으로 작업 의도를 인라인 기록
|
|
||||||
2. 작업 단락 끝에 **git commit 용 메시지 제안** 을 별도 블록으로 제시 — 사용자가 그대로 복사해서 commit/push 할 수 있게
|
|
||||||
|
|
||||||
**Why**:
|
|
||||||
- 인라인 주석: 사용자가 코드베이스를 빠르게 grep 해서 "이건 언제, 왜 바꾼 거지?" 추적함. 메모리는 휘발성, 주석은 코드 옆에 영구 보존.
|
|
||||||
- 커밋 메시지: 사용자가 작업 후 직접 git commit / push 함. 매번 변경 내용을 다시 정리해 입력하는 부담을 덜기 위해 제가 제안 메시지를 함께 줘야 함. 사용자가 입력자, 제가 작성자.
|
|
||||||
|
|
||||||
**기존 코드베이스 관례 예시** (skeleton-utils.js):
|
|
||||||
- `// [v1 helper 2026-04-29] B 경로: ...`
|
|
||||||
- `// [정렬 정책 — 인덱스가 생명] (2026-04-27)`
|
|
||||||
- `// [PAIR-DIAG] local only — ... (2026-04-30)`
|
|
||||||
- `// [hip pitch fallback 2026-04-30]`
|
|
||||||
- `// [HIP/RIDGE 임계 2026-04-30] 0.1 → 5.0 상향.`
|
|
||||||
- `// [graph drift fix 2026-05-06]`
|
|
||||||
|
|
||||||
**How to apply (인라인 주석)**:
|
|
||||||
- 형식: `[짧은 태그명 YYYY-MM-DD]` 또는 `[태그명] (YYYY-MM-DD)` (둘 다 허용). 첫 줄은 짧게, 필요 시 다음 줄들에 상세 설명/원인/영향 범위.
|
|
||||||
- 위치: 변경된 라인 또는 블록의 **바로 위**. 함수 헤더가 아니라 실제 변경 지점.
|
|
||||||
- 기존 주석이 있으면 충돌 안 나게 추가 (덮어쓰지 말 것). 한 변경 지점에 여러 태그 누적 가능.
|
|
||||||
- 사소한 변경 (오타/포맷팅) 은 생략 가능. 로직/임계/정책 변경은 필수.
|
|
||||||
- **응답에서도 명시적으로 보고**: "어디 라인에 어떤 태그/내용으로 주석 넣었는지" 사용자에게 알려줄 것.
|
|
||||||
|
|
||||||
**How to apply (커밋 메시지)**:
|
|
||||||
- 작업이 한 단락 마무리된 시점마다 응답 끝에 **한 줄짜리 commit 메시지** 만 제시. 본문/bullet 추가 금지.
|
|
||||||
- 형식: `주제 — 핵심 변경` (한국어/일본어). `git log --oneline` 의 기존 관례를 따름. 예: "라벨변경", "동시 UPDATE 데드락 방어 — abort+latest, X-Idempotency-Key, 직렬화 - api 통신에러", "SK 인덱스 어긋남 보강 — ensureCounterClockwiseLines graph 키 0.1 round 통일".
|
|
||||||
- 한 줄에 안 들어가면 더 압축. 사용자가 본문 원할 때만 별도 요청.
|
|
||||||
- 제가 직접 `git commit` 을 실행하지 않는다 — 사용자가 직접 입력. 단, 사용자가 "커밋해줘" 라고 명시 요청하면 실행.
|
|
||||||
|
|
||||||
**관련**:
|
|
||||||
- `feedback_no_touch_working_code.md` — 정상 코드 건들지 않기 (이 규칙은 변경된 부분에만 주석)
|
|
||||||
- `feedback_full_analysis_before_action.md` — 변경 전 흐름 추적
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: moveLine + offset 변경 시 이중 적용 방지
|
|
||||||
description: offset 변경 시 baseLines 복원 후 drawHelpLine에서 moveLine을 다시 적용하면 이중 이동 발생 — drawHelpLine 전에는 moveLine 속성을 설정하지 말 것
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
offset 변경 후 moveLine 상태를 유지할 때, baseLines 좌표를 복원하고 drawHelpLine을 호출하면 skeletonBuilder에서 movingLineFromSkeleton이 또 실행되어 이중 이동이 발생한다.
|
|
||||||
|
|
||||||
**Why:** baseLines에 이미 moveLine 이동이 반영되어 있으므로, skeletonBuilder가 moveFlowLine으로 다시 이동하면 두 번 적용됨.
|
|
||||||
|
|
||||||
**How to apply:** drawHelpLine 호출 전에는 roof에 moveFlowLine/moveUpDown을 설정하지 않고, drawHelpLine 완료 후에 Object.assign으로 복원해야 한다. 순서: baseLines 복원 → drawHelpLine(moveLine 없이) → moveLine 속성 복원.
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: 잘 되는 코드 건들지 않기
|
|
||||||
description: 버그 수정이나 예외 처리 시 기존에 잘 동작하는 코드를 수정하지 말 것. 별도 함수나 분기로 처리.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
버그 수정이나 예외 처리 시 기존에 정상 동작하는 코드를 절대 건들지 말 것.
|
|
||||||
|
|
||||||
**Why:** 문제를 추적하다가 관련 없는 정상 코드까지 수정하면 오히려 다른 기능이 깨진다. skeleton-utils.js에서 세로 라인 버그를 추적하면서 processEavesEdge, createInnerLinesFromSkeleton 등 정상 동작하는 부분에 필터링 로직을 추가했다가 용마루 라인이 사라지는 등 부작용이 발생했다. 오버 체크 같은 예외 기능도 메인 변수(changRoofLinePoints)를 직접 수정하면 안된다.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 기존 함수 내부를 수정하지 말 것 — 새로운 함수를 만들거나 분기(if/else)로 분리할 것
|
|
||||||
- 기존 함수에 조건문/필터 로직을 끼워넣으면 기존에 맞춰진 동작이 틀어진다
|
|
||||||
- 예외 처리(오버 체크 등)는 메인 로직과 완전히 분리 — 별도 함수/별도 변수 사용
|
|
||||||
- 로그 추가는 OK, 하지만 기존 로직 변경은 절대 금지
|
|
||||||
- 추측으로 여러 곳을 동시에 수정하지 말 것
|
|
||||||
- 수정 전에 반드시 사용자와 방향을 확인할 것
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
---
|
|
||||||
name: 오버 이동은 평행 위치까지 클램핑 (mutation 거부 아님)
|
|
||||||
description: useMovementSetting.handleSave 의 mutation 직전에 OVER_GUARD 를 두어 인접 라인을 0 길이로 흡수하는 평행 위치까지 deltaX/deltaY 를 잘라 적용한다. 사용자가 입력한 큰 값도 평행 한계까지는 그대로 반영되고, 그 이상은 흡수 위치에서 멈춘다. UI 알림은 띄우지 않고 콘솔 경고만.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
**현행 정책 (2026-04-24, 최종):**
|
|
||||||
사용자가 입력한 이동량이 인접 라인을 뒤집어 폴리곤 self-cross 를 일으키는 수준이면, **mutation 을 거부하지 않고 평행 위치(인접 라인이 정확히 0 길이가 되는 한계)까지로 클램핑** 한다. wall.baseLines 는 절대 오버 좌표로 갱신되지 않으므로 SK 빌드도 항상 정상 가정 하에 동작.
|
|
||||||
|
|
||||||
**정책 전환 flag (2026-04-24 추가):**
|
|
||||||
- 위치: `src/hooks/roofcover/useMovementSetting.js` 파일 상단 `const OVER_MOVE_POLICY = 'clamp'`.
|
|
||||||
- 값: `'clamp'` (기본) / `'reject'` (silent skip) / `'passthrough'` (가드 전면 해제).
|
|
||||||
- 교체 방법: 이 상수 하나만 바꾸면 handleSave 내 OVER_GUARD 분기가 자동 반응.
|
|
||||||
- Why: 사용자가 정책을 과거로 되돌리거나 임시 실험하고 싶을 때 분기 코드 수정 없이 전환.
|
|
||||||
|
|
||||||
**OVER_GUARD 위치 & 판정:**
|
|
||||||
- 파일: `src/hooks/roofcover/useMovementSetting.js`, handleSave 의 `targetBaseLines.forEach((target) => { ... })` 안.
|
|
||||||
- 위치: deltaX/deltaY 결정 직후, `currentLine.set({...})` 직전.
|
|
||||||
- 클램핑 식 (currentLine 이 수평이면 deltaY, 수직이면 deltaX 가 유일 성분):
|
|
||||||
```
|
|
||||||
raw = isHoriz ? deltaY : deltaX
|
|
||||||
sgn = Math.sign(raw)
|
|
||||||
nLimit = isHoriz ? (nextLine.y2 - currentLine.y2) : (nextLine.x2 - currentLine.x2)
|
|
||||||
pLimit = isHoriz ? (prevLine.y1 - currentLine.y1) : (prevLine.x1 - currentLine.x1)
|
|
||||||
|
|
||||||
// sgn 과 같은 부호 한계만 후보 (반대 부호는 인접 라인이 길어지는 방향이므로 오버 아님)
|
|
||||||
absLimit = min of |nLimit|, |pLimit| (sgn 일치하는 것만)
|
|
||||||
|
|
||||||
// [EPS 필수] 정확한 평행은 인접 baseLine zero-length → polygon 중복 꼭짓점 → SK 빌더가 헛선 추가.
|
|
||||||
// OVER_EPS 만큼 빼서 인접 baseLine 이 0.5 길이로 남게 → 기존 SHOULDER_ABSORBED 가드가 자연 처리.
|
|
||||||
safeAbsLimit = max(0, absLimit - OVER_EPS) // OVER_EPS = 0.5
|
|
||||||
limited = sgn * min(|raw|, safeAbsLimit)
|
|
||||||
```
|
|
||||||
- `|limited| < |raw|` 이면 클램핑 발생 → 콘솔에 `[handleSave] OVER 클램핑: deltaX|deltaY raw→limited (nLimit=... pLimit=..., EPS=0.5)`.
|
|
||||||
- 토스트/모달 알림 없음.
|
|
||||||
|
|
||||||
**[필수] roof.moveUpDown 동시 클램핑:**
|
|
||||||
- 클램핑 블록 안에서 `roof.moveUpDown = Math.round(Math.abs(__limited) * 10)` 을 같이 해줘야 한다.
|
|
||||||
- 이유: `movingLineFromSkeleton` (skeleton-utils.js) 은 wall.baseLines 가 아니라 `roof.moveUpDown` + `canvas.skeleton.lastPoints`(또는 `orgRoofPoints`) 로부터 **roof.points 확장량**을 계산한다. wall.baseLines delta 만 줄이고 `roof.moveUpDown` 을 방치하면:
|
|
||||||
- wall.baseLines 는 평행 위치까지만 이동 (클램핑 됨)
|
|
||||||
- roof.points 는 원본 입력값(예: 1525) 기반 확장 적용
|
|
||||||
- 두 좌표계 충돌 → polygon 자기교차 → `removeNonOrthogonalPoints` 가 꼭짓점 축소 → "SK 가 wall.baseLines 안에서 멈추고 확장 안 됨" 증상.
|
|
||||||
- 단위: `__limited` 는 cm (value 가 mm→cm 변환된 뒤 delta 로 쓰임). `roof.moveUpDown` 은 mm (movingLineFromSkeleton 내부에서 `Big(moveUpDown).div(10)` 으로 cm 환산). 그래서 `*10`.
|
|
||||||
|
|
||||||
**Why:**
|
|
||||||
- 거부(reject)로 시도했더니 사용자가 평행 위치까지 정확히 가는 정상 케이스에서도 wall 이 안 움직인다고 함 → 거부 정책 폐기.
|
|
||||||
- 클램핑은 사용자 의도에 가장 근접: "큰 값을 입력해도 알아서 평행까지 멈춘다" 는 UX. 사용자 입장에서 다시 정확한 값을 계산할 필요 없음.
|
|
||||||
- 사후 처리(silent skip / 오버 좌표로 SK 재빌드 / 오버 전용 함수) 는 invalid geometry 를 시각화하는 한계 때문에 매번 추가 요구가 발생.
|
|
||||||
- 사전 클램핑으로 wall.baseLines invariant(인접 라인 방향 유지)를 강제하면 하류(SK 빌드, innerLines 생성, lastPoints) 모두 정상 가정 하에 동작.
|
|
||||||
|
|
||||||
**가정:**
|
|
||||||
- currentLine 은 수평(y1==y2) 또는 수직(x1==x2). 직각 polygon 가정.
|
|
||||||
- nextLine/prevLine 은 currentLine 과 직각이 아니어도 동작은 하지만, 한계 식의 정밀성은 직각일 때 최적.
|
|
||||||
- 대각선 라인 polygon 에서 정밀도 이슈 발생 시 한계 식을 일반 벡터 투영으로 확장 필요.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- handleSave 외 다른 wall.baseLines mutation 경로(드래그 이동 등)에도 동일 OVER_GUARD 추가가 일관됨. 현재는 handleSave 에만 적용.
|
|
||||||
- OVER_GUARD 통과 후 평행이 정확히 일치한 인접 라인은 zero-length baseLine 이 됨 → 기존 `SHOULDER_ABSORBED` 가드(planeSize < 1)로 SK 빌드에서 자연 처리.
|
|
||||||
|
|
||||||
**Dead code 보존:**
|
|
||||||
- 이전 정책으로 추가된 아래 코드는 남겨둔다 (안전망 + 다른 mutation 경로 추가 가능성):
|
|
||||||
- `skeleton-utils.js: drawSkeletonRidgeRoofFromBaseLines` 함수 (오버 전용 SK 빌드).
|
|
||||||
- `QPolygon.drawHelpLine` 의 verdict==='crossed' → `drawSkeletonRidgeRoofFromBaseLines` 분기.
|
|
||||||
- `skeleton-utils.js: drawSkeletonRidgeRoof` 내부 crossed 경고 로그.
|
|
||||||
- `redrawHelpLineForced` 래퍼.
|
|
||||||
- 현행 정책에서는 OVER_GUARD 가 막아주므로 위 분기들은 호출되지 않음. 사용자가 명시적으로 제거 요청할 때까지 보존.
|
|
||||||
|
|
||||||
**규칙 교체 이력:**
|
|
||||||
- 2026-04-22: "리셋 없이 조용히 스킵" (silent skip). Why: 사용자 추가 라인 보존, UI 깜빡임 방지.
|
|
||||||
- 2026-04-24 (1차): "crossed 가드 제거 + 오버 좌표 그대로 일반 경로 통과". 결과: 일반 경로 확장/클램핑이 좌표 왜곡.
|
|
||||||
- 2026-04-24 (2차): "crossed 시 오버 전용 함수로 분기". 결과: SK 가 roofLine 바깥으로 삐져나감 → 추가 클립 요구.
|
|
||||||
- 2026-04-24 (3차): "OVER_GUARD 로 mutation 거부". 결과: 평행 위치 정확히 도달하는 정상 케이스도 거부됨 → 폐기.
|
|
||||||
- 2026-04-24 (4차): "OVER_GUARD 로 평행 위치 정확히 도달하게 클램핑". 결과: 정확한 평행이 polygon 중복 꼭짓점을 만들어 SK 빌더가 헛선을 추가. 이전 평행 이동이 멀쩡했던 이유는 fabric floating point 노이즈 덕이었음.
|
|
||||||
- 2026-04-24 (최종 1차): **"OVER_GUARD 클램핑 + OVER_EPS=0.5 여유"**. 평행보다 0.5 모자란 위치까지 이동 → 인접 baseLine 이 0.5 길이로 남고 SHOULDER_ABSORBED(planeSize<1) 가드가 자연 처리. 사용자 체감은 거의 정확한 평행.
|
|
||||||
- 2026-04-24 (최종 2차 버그 fix): 위 상태에서 "SK 가 확장을 안하고 wall.baseLines 안에서 멈춤" 증상 발생. 원인: `roof.moveUpDown` 을 원본 입력값(parseInt) 그대로 두어 movingLineFromSkeleton 이 확장을 원본 크기로 시도 → polygon 자기교차. fix: 클램핑 블록 안에서 `roof.moveUpDown = Math.round(Math.abs(__limited) * 10)` 동시 갱신.
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
name: 좌표 매칭 tolerance 는 UI drift 고려해 넉넉히
|
|
||||||
description: 평행 이동 등 baseLines / selectLine 좌표 매칭용 isSamePoint 는 기본 0.1 이 아니라 0.5 사용. UI 클릭 시점과 저장값 차이 + Big.js 누적 연산 drift 를 포용해야 "동일 선으로 보이는데 매칭 0개" 로 dy 미적용되는 undershoot 회피.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
매칭용 `isSamePoint` 호출에는 `tolerance=0.5` 를 명시적으로 넘긴다 (기본 0.1 은 너무 빡빡, 1.0 은 과하게 관대).
|
|
||||||
|
|
||||||
**Why:**
|
|
||||||
- 사용자 입력은 소수 2자리(0.01)까지, 마우스 hover 로 "정확히 잡았다"고 보여도 클릭 순간 좌표가 미세하게 달라짐 (UI 라운딩/이벤트 시점 차이)
|
|
||||||
- `selectLine` 좌표(UI)와 `wall.baseLines`(Big.js 누적 연산: /10, offset 덧셈, 재생성)가 서로 다른 경로로 오기 때문에 실측 drift 상한 ~0.3~0.4
|
|
||||||
- 기본 0.1 tolerance 로는 matchingLines 가 간헐적으로 0개 → dy 미적용 → 같은 입력인데도 "어떤 때는 못 미치고 어떤 때는 정상" 불안정 체감이 발생했음
|
|
||||||
- 1.0 은 실측 10mm 가까이라 과하게 관대 — 사용자가 "같은 선이 아닌데 잡혔다" 느낄 여지
|
|
||||||
- **0.5 는 드리프트 상한(~0.4) 여유 포용 + 이웃 edge 오매칭 위험 사실상 0 의 균형점**
|
|
||||||
- 전역 `isSamePoint` 기본값은 건드리지 않고 **매칭 필터 site 에만 명시 전달** (다른 호출처 영향 차단)
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- `skeleton-utils.js` 의 `movingLineFromSkeleton` matchingLines 필터, `buildRawMovedPoints` matchingLines 필터 등 "UI 좌표 vs 내부 계산 좌표" 비교 site 에는 전부 0.5 지정
|
|
||||||
- 내부 계산 좌표끼리 비교(예: polygon 내 인접 edge 연결 판정)는 기본 0.1 유지 — drift 원인이 UI 아니면 필요 없음
|
|
||||||
- 새로운 매칭 filter 를 추가할 때도 UI 경유 좌표가 끼면 tolerance=0.5 기본 고려
|
|
||||||
- 변경 이력: 1차로 1.0 도입 → "과하게 관대" 피드백으로 0.5 로 축소 (2026-04-23)
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: V1 단일 트랙 (V2 휴면)
|
|
||||||
description: 2026-05-06 부로 V2 는 휴면. V1 이 V2 의 목표 상당 부분을 흡수. 사용자 명시 명령 전까지 V2 작업/언급 금지.
|
|
||||||
type: feedback
|
|
||||||
---
|
|
||||||
|
|
||||||
**규칙** (사용자, 2026-05-06): "이번 V1에서 V2가 진행하려던 것이 많이 수용되었다. 그래서 진행할 이유가 없어졌다. 이제까지 기록은 남겨두고, V2는 내가 명령하기 전까지 잠자고 있으면 된다."
|
|
||||||
|
|
||||||
**Why**: V1 에서 V2 가 노리던 설계 목표 상당 부분이 이미 흡수되어 V2 별도 트랙 유지 명분이 사라짐. 다만 과거 시도 기록은 참조용으로 보존.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- 모든 신규 작업은 V1 트랙 (`hotfix/dev_yscha` 등 운영 브랜치) 에서 진행
|
|
||||||
- V2 관련 메모리 (`project_v2_design_direction.md`, `project_v2_invariant_no_reset.md`) 는 보존하되 **읽지도 인용하지도 말 것** — 사용자가 명시적으로 "V2" 언급할 때만 참조
|
|
||||||
- `sk-v2.0` 브랜치는 commit 된 상태로 휴면. 머지/삭제 금지
|
|
||||||
- V1 회귀 체크리스트 (`project_skeleton_regression_checklist.md`) 는 계속 적용
|
|
||||||
- 응답에서 V2 비교/제안 자제 — 사용자가 깨우기 전까지는 V2 존재하지 않는 것처럼 작업
|
|
||||||
|
|
||||||
**휴면 진입 시점 상태** (2026-05-06):
|
|
||||||
- V2 Phase 1, 2 완료된 상태로 sk-v2.0 브랜치에 commit 후 정지
|
|
||||||
- V1 이 동등 또는 유사 기능을 흡수 완료
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: auxiliaryLine integrateExtensionLines 제외
|
|
||||||
description: 사용자가 그린 auxiliaryLine 은 ext 와 collinear 병합 대상에서 제외해야 split 정상 동작
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
useRoofAllocationSetting.js `integrateExtensionLines` sk-finder 에
|
|
||||||
`if (sl.name === 'auxiliaryLine') return false` 가드 필요.
|
|
||||||
|
|
||||||
**Why:**
|
|
||||||
- integrateExtensionLines 는 SK auto-build 시 만들어지는 hip stub + extensionLine 의
|
|
||||||
1:1 통합용. 두 라인이 코너 끝점 공유 + collinear 일 때 단일 mergedLine 으로 합침.
|
|
||||||
- 사용자가 그리는 auxiliaryLine 은 CanvasFrame.jsx 102-107 에서 이미
|
|
||||||
`roof.innerLines` 로 push 됨 (완전한 분할선 상태).
|
|
||||||
- aux 가 ext 와 코너 끝점 공유 + 45° collinear 케이스에 sk-finder 가 aux 를
|
|
||||||
병합 후보로 잡음 → ext+aux 가 하나의 mergedLine 으로 합쳐지면서 코너 끝점 소실
|
|
||||||
- 예) ext (992.1,394.4)→(1042.1,444.4) + aux (1042.1,444.4)→(792.1,194.4)
|
|
||||||
= mergedLine (992.1,394.4)→(792.1,194.4) (코너 1042.1,444.4 손실)
|
|
||||||
- 결과: splitPolygonWithLines 가 "경로 존재하나 3개 미만 좌표" 로 실패 → alloc 안 됨
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- aux 는 이미 완성된 분할선이므로 ext 와 합치면 안 됨.
|
|
||||||
- sk 후보 필터에서 `sl.name === 'auxiliaryLine'` 를 명시적으로 제외.
|
|
||||||
- ext 와 SK auto-build hip stub 간 1:1 통합 로직만 살리고 aux 는 그대로 split 에 사용.
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
---
|
|
||||||
name: hip pitch 폴백 (planeSize===actualSize 버그 수정)
|
|
||||||
description: skeleton-utils.js processEavesEdge 의 pitch 폴백 누락으로 hip 의 planeSize===actualSize 가 되어 메뉴 토글이 무효였던 버그
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
skeleton-utils.js:2915 `let pitch = outerLine?.attributes?.pitch ?? roof?.pitch ?? 0`
|
|
||||||
|
|
||||||
**Why:**
|
|
||||||
- 기존: `outerLine?.attributes?.pitch ?? 0`
|
|
||||||
- outerLine.attributes.pitch 누락 시 pitch=0 → addRawLine(line 3396) 의 calcLineActualSize2(deg=0)
|
|
||||||
은 tan(0)=0 으로 인해 planeSize 와 동일값 반환
|
|
||||||
- 결과: SK builder 가 만든 모든 hip 의 attributes.planeSize === attributes.actualSize
|
|
||||||
- DIAG 로그 증거: `[v1 ext] hip 연장 ... (plane 2615→3322.14, actual 2615→3322.14)`
|
|
||||||
- 사용자 증상: 伏せ図入력(corridorDimension/planeSize) ↔ 配置面(realDimension/actualSize)
|
|
||||||
메뉴 토글 시 hip 라벨 숫자 변화 없음
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- usePolygon.js:1649 패턴 (line.pitch ?? represent.pitch ?? globalPitch) 과 동일한 폴백
|
|
||||||
- skeleton-utils 는 hook 이 아니라서 globalPitch 직접 접근 불가, 대신 roof.pitch 사용
|
|
||||||
- roof 객체는 usePolygon.js:1651-1664 에서 `pitch: pitch` 로 생성됨
|
|
||||||
- 추가 보정 (useRoofAllocationSetting.js handleAlloc) 은 actualSize=0 케이스만 잡으므로
|
|
||||||
이 근본 수정 후에도 보존 (안전망 역할)
|
|
||||||
- useText.js parent.attributes 폴백, useMenu.js 직접 호출, useText DIAG 로그 도 모두 유지
|
|
||||||
- 기존 SK 가 pitch=0 으로 빌드된 경우용 즉석 재계산: useText.js `recomputeHipActualIfFlat`
|
|
||||||
- pitch 폴백 체인: line.attributes.pitch → roof.pitch → roof.roofMaterial.pitch → globalPitch ?? 4
|
|
||||||
- obj.actualSize 만 갱신 (parent.attributes 갱신 시 fabric set 사이드이펙트 위험)
|
|
||||||
|
|
||||||
**중요 함정 — lengthText min/max 는 isDiagonal 판정 불가:**
|
|
||||||
- QLine.js:122-125 가 lengthText 의 `minY: this.top, maxY: this.top + this.length` 로 설정
|
|
||||||
- `this.length` 는 라인 길이라서 수평 ridge 도 `maxY - minY = length > 0`
|
|
||||||
- 즉 lengthText 의 minX/maxX/minY/maxY 로 dx,dy 구해서 `dx>0 && dy>0` 으로 판정하면
|
|
||||||
수평/수직 ridge 까지 hip 으로 잘못 분류됨 → ridge 가 재계산/그룹통일 대상이 되어
|
|
||||||
토글 시 값 변경 / 다른 라인 값으로 덮어쓰기 발생
|
|
||||||
- **반드시 parent QLine 의 `x1/y1/x2/y2` 로 직접 dx,dy 계산** (임계 5.0 mm)
|
|
||||||
- QPolygon.js:712-734 path (Math.min/max(start,end)) 는 정확하지만 QLine path 는 부정확
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
name: HIP/RIDGE 분류 임계 5.0mm
|
|
||||||
description: addRawLine 의 isDiagonal 임계를 0.1→5.0 으로 상향. 벽라인 이동 누적 drift 로 ridge 가 hip 로 잘못 재분류되는 문제 차단.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
증상: 벽라인 이동 후 수직/수평 ridge (RG-N) 가 hip (H-N) 으로 변함. 화면상 색/이름이 바뀌어 사용자 인지.
|
|
||||||
|
|
||||||
원인: `skeleton-utils.js addRawLine` line 3375 의 `isDiagonal = dx > 0.1 && dy > 0.1` 임계가 너무 작음. 정상 vertical/horizontal ridge 도 누적 drift 로 작은 축에 0.1mm 초과 drift 가 생기면 즉시 HIP 으로 normalize 됨.
|
|
||||||
|
|
||||||
수정: 임계 0.1 → 5.0. 진짜 45° hip 은 dx,dy 모두 수십 mm 이상이라 영향 없음. drift ridge 는 작은 축이 수 mm 이내 → ridge 유지.
|
|
||||||
|
|
||||||
**Why:** 사용자 보고 "RG 라인이 벽라인이동이후 H라인으로 바뀐다, 수평수직 깨지면서 대각선으로 보이는 듯". 사용자 확인 후 변화 없음 = 회귀 없음 검증됨.
|
|
||||||
|
|
||||||
**How to apply:** ridge↔hip 분류 이슈 재발 시 `addRawLine` 의 `isDiagonal` 임계 우선 확인. 5.0 으로도 부족하면 상향 또는 비율 기반(min/max ratio) 전환. 진짜 hip 의 최소 dx/dy 가 5mm 이상이라는 전제가 깨지는 케이스(매우 좁은 hip)가 나오면 비율 기반 필수.
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
name: 페어링 인덱스 master = roofLines 정책
|
|
||||||
description: skeleton-utils.js 라인 페어링 정렬 master 를 wallLines→roofLines 로 변경한 정책 (2026-04-27)
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**정책**: `src/util/skeleton-utils.js` (~1342~1359) 의 라인 페어링 정렬에서 **roofLines 를 단일 master** 로 사용. wall.lines / wall.baseLines 는 roofLines 의 raw 인덱스 1:1 매핑으로 정렬.
|
|
||||||
|
|
||||||
**Why**:
|
|
||||||
- 사용자 원칙: "인덱스가 여기에는 생명이다. leftTop(최저 좌표) 꼭짓점에서 시작해 CCW 로 그려 나간다."
|
|
||||||
- **사용자가 다각형을 시계방향(CW)으로 그릴 수도 있음** → 그래서 인덱스를 다시 세워야 한다. 항상 **최저 좌표(leftTop, min-Y → min-X)** 를 #1 로 놓고 CCW 로 재정렬해 정규화. 사용자의 그리는 방향(CW/CCW)에 무관하게 고유한 시작점/순서를 보장.
|
|
||||||
- 종전 master 였던 `wallLines (= wall.lines)` 는 `useMode.js:1880`, `usePropertiesSetting.js:165`, `useRoofShapePassivitySetting.js:242` 에서 재할당 가능 → 시작점 산정이 흔들릴 위험.
|
|
||||||
- `roofLines` 는 외곽(처마) 경계라 사용자 mutation 영향 없음 → 시작점 안정.
|
|
||||||
- B-7 OVER 시 보조라인이 B-5/B-3 위치에 그려진 간헐 버그가 정렬 master 흔들림에서 비롯됐을 가능성.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- ensureCounterClockwiseLines 의 입력은 roofLines (master). leftTop = min-Y → tiebreak min-X 꼭짓점에서 CCW.
|
|
||||||
- wall.lines, wall.baseLines 는 raw 인덱스 보존이 전제 (코드상 페어링 1:1 가정).
|
|
||||||
- `_keyOfEdge` 는 좌표를 0.1 단위로 라운드 후 양 끝점 정렬 키 생성 → 부동소수점 drift 흡수.
|
|
||||||
- `ensureCounterClockwiseLines` 내부 graph 키도 0.1 단위 round (2026-05-06 추가) — caller `_keyOfEdge` 와 동일 정책으로 통일.
|
|
||||||
- 매핑 실패 시 fallback 으로 sortRoofLines 항목 자체를 사용 (안전망).
|
|
||||||
- 추후 wall.* 가 수정될 때 raw 인덱스가 보존되는지 검증 필요. 만약 깨진다면 매핑 키를 좌표 대신 라인 ID 기반으로 변경 검토.
|
|
||||||
|
|
||||||
**ensureCounterClockwiseLines drift 보강 (2026-05-06)**:
|
|
||||||
- 증상: 같은 도형인데 간헐적으로 인덱스 시작점/순서가 바뀌는 현상 (사용자가 다시 그리면 정상화).
|
|
||||||
- 원인: 함수 내부 `graph` Map 키가 raw 좌표 (`${line.x1},${line.y1}`) 라서 drift (e.g. 100.0 vs 100.0000001) 로 같은 점이 별개 노드로 등록 → graph 끊어짐 → traversal 중도 break → result.length < lines.length → 페어링 어긋남.
|
|
||||||
- 수정 위치 (`skeleton-utils.js:3463`):
|
|
||||||
1. `:3469-3480` graph 빌드 시 endpoint 좌표 `Math.round(v*10)/10` 로 round, 이웃 객체 `{x,y}` 도 rounded 저장 (후속 `${n.x},${n.y}` 키가 graph 키와 일치)
|
|
||||||
2. `:3528` `isReversed` 비교를 정확비교 → `Math.abs(line.x1 - next.x) > 0.05` approx (round 절반 임계). raw line.x1 vs rounded next.x 불일치 false-positive 차단
|
|
||||||
- 영역 외 보존: atan2 / area 부호 / result 라인 좌표는 raw 그대로 (다운스트림 정확도 유지).
|
|
||||||
- 정상 케이스 (drift 없음) 동작 변화 없음. 100.0 round = 100.0.
|
|
||||||
|
|
||||||
**관련**:
|
|
||||||
- `feedback_over_move_silent_skip.md` (OVER 클램핑 정책)
|
|
||||||
- `project_over_move_red_helper_deferred.md` (빨강 보조선 별건 이슈)
|
|
||||||
- `project_parallel_collapse_fix.md` (평행 이동 SK 붕괴)
|
|
||||||
- `feedback_same_point_tolerance.md` (좌표 매칭 tolerance)
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
---
|
|
||||||
name: 저구배+특정기와 안내 alert
|
|
||||||
description: 6종 기와(和瓦A/B, 平板瓦C/D, S瓦, セメント瓦) + 寸 2~2.5 미만 저장 시 시공 매뉴얼 안내 alert (OK 만)
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
저구배(2 ≤ pitch < 2.5 寸 OR 11.31 ≤ angle < 14.04°) 에서 6종 기와 사용 시 저장 직전에 안내 alert 띄움 (OK 만, 중단 흐름 없음). (도입 커밋 `a5d2964e`)
|
|
||||||
|
|
||||||
대상 6종 (`getRoofMaterialList` 응답 기준):
|
|
||||||
- `ROOF_ID_WA_53A`, `ROOF_ID_WA_53B`
|
|
||||||
- `ROOF_ID_HIRA_C`, `ROOF_ID_HIRA_D`, `ROOF_ID_HIRA_S`, `ROOF_ID_HIRA_SEME`
|
|
||||||
|
|
||||||
트리거 3 곳:
|
|
||||||
- `PlacementShapeSetting.handleSaveBtn` (배치면 초기설정 저장)
|
|
||||||
- `useRoofAllocationSetting.handleSave` (屋根面の割り当て)
|
|
||||||
- `useRoofAllocationSetting.handleSaveContext` (屋根材変更 후 할당)
|
|
||||||
|
|
||||||
판정/confirm 헬퍼: `src/util/roof-pitch-warning.js`
|
|
||||||
i18n 키: `modal.roof.material.low.pitch.warning`
|
|
||||||
|
|
||||||
**Why:** 저구배에서 이 6종 기와 제품군은 적용 가능한 부속/구조물에 제한이 있어, 사용자에게 시공 매뉴얼 사전 확인을 강제할 필요. 일본어 메시지 "2寸以上2.5寸未満の瓦を使用する場合…" 그대로 번역 사용.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- 6종 변경 시 `LOW_PITCH_RESTRICTED_ROOF_IDS` Set 만 수정 (3 트리거 핸들러는 수정 불필요)
|
|
||||||
- 임계값 변경 시 `PITCH_LOW_INCLUSIVE` / `PITCH_HIGH_EXCLUSIVE` / `ANGLE_*` 상수 수정
|
|
||||||
- 寸 / 度 둘 다 OR 조건 — `roofAngleSet` 이 `'slope'` 면 pitch 만, `'flat'` 이면 angle 만 갱신될 수 있어서. 한쪽만 검사하면 빠뜨림.
|
|
||||||
- 등록된 지붕재마다 순차 alert. OK 만 있고 중단 흐름 없음 — OK 누르면 저장 진행.
|
|
||||||
- 세션 캐시 없음, 매번 표시.
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
name: 마루이동 x/y-overlap 사전필터
|
|
||||||
description: useMovementSetting FLOW_LINE 분기에서 거리최소필터 이전에 ridge 좌표범위와 겹치는 baseLine 만 추리는 사전필터. 2차 이동 시 반대측 wall 오선택 차단.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
useMovementSetting.js 686-730 라인. 거리최소필터(sort+filter by distance) **이전** 에 ridge 좌표범위 overlap 사전필터 추가.
|
|
||||||
|
|
||||||
**Why:** 1차 이동(예: down)으로 대상 wall 이 멀어지면 2차 이동 시 ridge x-range 무관한 반대측 wall 이 y-distance 만으로 "가까움" 차지해 잘못 선택됨. 기존 동률필터는 length>1 에서만 동작해 길이=1 케이스 보강 안 됨.
|
|
||||||
|
|
||||||
**How to apply:** FLOW_LINE 마루이동 관련 회귀 시 이 사전필터 블록부터 점검. target 가로/세로 자동 판정(`Math.abs(y1-y2)<0.5`) 으로 4방향(up/down/left/right) + 가로·세로 마루 모두 커버. UP_DOWN 은 1개라 영향 없음. overlap 0 개면 fallback 으로 전체 후보 유지.
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
name: OVER 클램프 후 빨강 보조선 미해결 이슈
|
|
||||||
description: 오버 클램핑 시 인접 baseLine 은 SHOULDER_ABSORBED 되지만 이동된 currentLine 의 빨강 eaveHelpLine 은 계속 그려지는 별건 이슈 (2026-04-24 연기)
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**상태**: 연기 (2026-04-24 대화에서 사용자가 "별건" 으로 분류, 당장 급하지 않음)
|
|
||||||
|
|
||||||
**증상**: OVER_MOVE_POLICY='clamp' 로 오버 입력이 평행 위치-EPS(0.5) 까지 잘려 들어갈 때, 시각적으로는 거의 평행인데 **빨강 eaveHelpLine 연장선** 이 여전히 그려진다. 사용자 체감 이물감.
|
|
||||||
|
|
||||||
**원인 분석** (2026-04-24 조사 완료):
|
|
||||||
- `src/util/skeleton-utils.js:1453` `getAddLine(newPStart, newPEnd, 'red')` — eaveHelpLine 연장선. 이동된 wall.baseLine 과 원본 roofLine 사이 gap 을 시각화하는 용도. 이동량 > 0 이면 항상 그려짐.
|
|
||||||
- `src/util/skeleton-utils.js:1505` `stroke: '#FF0000'` — HIP 대각 extensionLine.
|
|
||||||
- 클램프 후 인접 baseLine 은 planeSize=0.5 (< 1) → SHOULDER_ABSORBED 가 흡수. 하지만 currentLine 본인의 gap(이동량) 은 여전히 크기 때문에 1453 빨강선은 그려짐.
|
|
||||||
|
|
||||||
**Why**: 원래 이 빨강 연장선은 "평행하기 전까지 움직였을 때" 시각화 용도. 클램프 정책 상 '사용자 체감 평행 = 실제로는 EPS 직전' 이므로 시각적으로 모순. 사용자 피드백: "이 보조선은 평행하기전까지 움직였을때 그리는 것인데.."
|
|
||||||
|
|
||||||
**How to apply**: 월요일(2026-04-27) 이후 재개 시:
|
|
||||||
- 수정 위치 후보: `skeleton-utils.js:1453` 호출 직전에 "currentLine 이 평행 한계에 EPS 이내로 근접" 조건 추가해서 스킵.
|
|
||||||
- 판정 기준 후보: 인접 baseLine 의 planeSize < 1 (= SHOULDER_ABSORBED) 이면 이 currentLine 도 평행 도달로 간주, 빨강선 생략.
|
|
||||||
- 주의: 일반 이동(정상 범위) 에서는 빨강선이 의도된 동작이므로 조건 범위를 정확히 좁혀야 함. `feedback_no_touch_working_code.md` 원칙에 따라 최소 지점만 수정.
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
---
|
|
||||||
name: 평행 이동 SK 붕괴 근본 원인 및 수정 (2차)
|
|
||||||
description: 평행 이동 시 roof.basePoints[index] 좌표 매칭이 wall 오프셋 붕괴(degenerate baseLines)·좌표 우연 일치에 취약해 엉뚱한 꼭짓점에 dy 적용 → SK 축직교 깨짐 → SkeletonBuilder 보조선 난립/붕괴. movingLineFromSkeleton 평행 분기와 buildRawMovedPoints를 인덱스 직접 매핑 + Set 합집합으로 전환.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**사실:** `skeleton-utils.js`의 평행 이동 dy 적용 경로 2곳을 좌표 매칭 → 인덱스 직접 매핑으로 전환 (2026-04-23 오후).
|
|
||||||
- `movingLineFromSkeleton` 평행 분기: `matchingLines.forEach + newPoints.forEach((point, index) => isSamePoint(roof.basePoints[index], originalStart/End))` 제거. `line.findIndex = k` 기반 `affected = {k, (k+1)%n}` Set 합집합 → `affected.forEach(i => apply dy)` 로 교체.
|
|
||||||
- `buildRawMovedPoints`: 동일 패턴으로 교체. `matchingLines` 는 LP-TRACE 로그용으로 `{line, idx}` 쌍 형태 유지.
|
|
||||||
|
|
||||||
**Why:** `createOrderedBasePoints`가 오전 커밋(`0d3ede96 보조라인 맞춤`)에서 `baseLines[i].startPoint` 직접 인덱스 매핑으로 고쳐졌지만, **wall 오프셋 계산 단계에서 이미 `baseLines[k]`가 degenerate(zero-length 또는 인접 edge와 start 좌표 공유)** 가 된 형상(L자 shoulder, ㅗ의 shoulder 등)에서는 `roof.basePoints[k]` 가 다른 edge 꼭짓점과 좌표 일치하게 되어 좌표 매칭이 오염됨. polygon topology 로 정해지는 인덱스 `k` 는 이런 좌표 붕괴에 독립적.
|
|
||||||
|
|
||||||
**Why (Set 합집합):** `matchingLines ≥ 2` 케이스(보강 라인 중복 push, coincident edges)에서 같은 꼭짓점에 dy 가 중복 적용되는 잠재 버그도 동시에 제거.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- `moveFlowLine`(단일 라인 이동) 경로와 무관. 오전에 안정화된 단일 라인 이동 로직은 건드리지 않음.
|
|
||||||
- `createOrderedBasePoints` / `roof.basePoints` 는 다른 경로가 참조할 수 있어 유지. 이 두 지점만 우회.
|
|
||||||
- 평행 누적/붕괴 이동 증상(예: 첫 top_out 에서 `verifyMoveBoundary moved=[0, k-1, k]` 처럼 의도하지 않은 인접 꼭짓점이 함께 이동) 재현 시 이 패치가 적용되어 있는지 먼저 확인.
|
|
||||||
|
|
||||||
**관련 커밋 히스토리:**
|
|
||||||
- `4d9e2fc0 top 평행수정` (오전): `verifyMoveBoundary` 로깅만 추가.
|
|
||||||
- `0d3ede96 보조라인 맞춤` (오전): `createOrderedBasePoints` graph traversal → 인덱스 직접 매핑. 단, dy 적용 경로는 여전히 좌표 매칭이라 이 패치(2차)로 보완.
|
|
||||||
|
|
||||||
**죽은 코드 주의:** `skeleton-utils.js`의 `correctedPoints` 교정 블록(skeletonBuilder 내)은 `0d3ede96`에서 블록 주석 처리된 죽은 코드. 건드리지 않는다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**3차 후속 수정 (2026-04-23 저녁): SHOULDER_ABSORBED guard**
|
|
||||||
|
|
||||||
**사실:** `skeleton-utils.js:1482` pair forEach 엔트리에 zero-length `wallBaseLine` 스킵 가드 추가 (`planeSize < 1` 체크).
|
|
||||||
|
|
||||||
**Why:** `useMovementSetting.js:handleSave` 의 이웃 흡수 로직 (line 766-782) 이 shoulder 가 이동량에 의해 흡수되는 경우 `nextLine`/`prevLine` 을 `(x,y)→(x,y)` zero-length 로 mutate 하고 `planeSize=0` 으로 저장. 이는 의도된 geometry 표현 (흡수된 edge 는 "없어진" 상태). 다만 downstream `skeleton-utils.js` pair 매칭에서 이를 그대로 받아 `⚠️DIR_MISMATCH` 로그 + 잘못된 eaveHelpLine 생성 → SK 보조선 난립.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- `skeleton-utils.js` pair 생성부 `sortWallLines.forEach((wallLine, index) => { ... })` 최상단에 `if ((wallBaseLine.attributes?.planeSize ?? Infinity) < 1) return` 가드.
|
|
||||||
- 같은 파일 line 666 의 `wall.baseLines.filter(line => line.attributes.planeSize > 0)` 선례와 동일 패턴.
|
|
||||||
- 생산지(`useMovementSetting.js`) 는 의도대로 동작 중이라 건드리지 않음 (잘 되는 코드 건들지 않기 원칙).
|
|
||||||
- 대안이었던 "sortWallBaseLines 배열 자체 필터" 는 `sortRoofLines[index]`/`sortWallLines[index]` 와 인덱스 align 이 깨져 불가. per-iteration guard 가 최소 변경.
|
|
||||||
|
|
||||||
**재현 체크:** `⏭️ [pair#N] SHOULDER_ABSORBED skip` 로그가 찍히면 가드가 걸린 것.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**4차 후속 수정 (2026-04-23 저녁): HIP_SKIP adj_absorbed guard**
|
|
||||||
|
|
||||||
**사실:** `skeleton-utils.js:processInBoth` 진입부에 인접 pair 흡수 감지 시 HIP extensionLine 생성 스킵 가드 추가.
|
|
||||||
|
|
||||||
**Why:** SHOULDER_ABSORBED 가드로 pair forEach 의 DIR_MISMATCH 는 해결됐지만, 남은 인접 pair 의 `processInBoth` 가 원본 `roof.points` 기반 꼭짓점 (흡수로 사라진 shoulder 위치) 으로 HIP 빗변 끝점을 설정해 **"공중에 뜬 orange 대각선"** 을 만들어냄. 화면상 blue 사각형(simplified ridge) 외곽 코너에 길이 70.7 짜리 빗변이 orphan 으로 남는 증상.
|
|
||||||
|
|
||||||
**How to apply:**
|
|
||||||
- `processInBoth` 함수 최상단에 `sortWallBaseLines[(index±1)%n].attributes.planeSize < 1` 체크, 인접 중 하나라도 흡수된 경우 early return.
|
|
||||||
- processInBoth 는 getMoveUpDownLine 스코프 내부 정의라 sortWallBaseLines 클로저 접근 가능.
|
|
||||||
- 호출처 2곳 (line 1675, 1930) 에 중복 가드 안 넣고 함수 진입부 한 곳에서 처리.
|
|
||||||
- processInStartEnd (blue/green/pink/red 보조선) 은 건드리지 않음 — HIP 빗변 전용 문제.
|
|
||||||
|
|
||||||
**재현 체크:** `⏭️ [pair#N] X_in HIP_SKIP adj_absorbed prev=T/F next=T/F` 로그로 확인.
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: phantom ridge (RG-N) 다운스트림 cull
|
|
||||||
description: createInnerLinesFromSkeleton 종료 직전 zero-length + dead-end ridge 2단계 cull 로 SK builder drift phantom 제거
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
증상: 정확한 수치 입력 폴리곤(예: 12-vertex 계단형)에서 SK builder 가 0.0001 좌표 drift 로 phantom skeleton edge 를 emit. 화면에 length=0 또는 한쪽 끝이 매달린 ridge (사용자 표현 RG-8) 로 표시.
|
|
||||||
|
|
||||||
원인:
|
|
||||||
1. SK builder 가 zero-length ridge 를 face 처리 중 emit (예: `ridge (61,-526)→(61,-526)`).
|
|
||||||
2. 이 zero-length 라인이 동일 endpoint 에 degree +2 를 부여 → 진짜 dead-end ridge 의 degree 를 1→3 으로 부풀려 위장.
|
|
||||||
3. dedup(line 1583) 으로는 단일 entry 라 못 잡음.
|
|
||||||
4. logDeadEndLines (line 2919) 는 로그만 출력, 삭제 안 함.
|
|
||||||
|
|
||||||
수정 (B안):
|
|
||||||
- `createInnerLinesFromSkeleton` 의 두 return 직전(현 line 2780~) 에 IIFE cull 블록 추가.
|
|
||||||
- 1단계: `lineName === 'ridge'` 이고 dx<0.5 && dy<0.5 인 zero-length 라인 toRemove 등록.
|
|
||||||
- 2단계: toRemove 제외한 라인들로 degree 재계산. ridge 한쪽 endpoint 가 degree=1 && roof corner 와 1mm 이상 떨어진 경우 toRemove.
|
|
||||||
- canvas.remove + innerLines.splice 동기.
|
|
||||||
- hip / eaves / roofLine 은 절대 cull 안 함 (lineName 가드).
|
|
||||||
|
|
||||||
**Why:** 사용자 보고 "정확한 수치로 그리면 SK 무너지는 케이스 재발". cd5eebd0 이전 SK builder 내부 수정이 이 케이스에는 안 통했고, builder 추가 수정 위험 높아 다운스트림 cull 로 흡수.
|
|
||||||
|
|
||||||
**How to apply:** RG-N 가짜 ridge 가 다시 나오면 콘솔의 `[B안 cull] dead-end ridge 제거 N개` 로그로 cull 작동 확인. 안 잡히면 1) 해당 라인 lineName 이 'ridge' 가 아닌지, 2) 인접 phantom 이 degree 를 더 부풀려 우회하는지 진단. cull 블록은 createInnerLinesFromSkeleton 끝(`if (findPoints.length > 0)` 직전).
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
name: 2차 마루이동 ridge 클릭불가 보강
|
|
||||||
description: useMovementSetting [type] effect 에서 roof.innerLines 외에 canvas 레벨 ridge 직접 조회로 selectable=true 보강
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
증상: 1차 마루이동 성공 → 2차 팝업 오픈 시 ridge 클릭해도 선택 안됨. mouseMove 마다 `[MV-TRACE] selectedObject.current=null → skip` 무한 발생.
|
|
||||||
|
|
||||||
원인: useMovementSetting `useEffect [type]` 에서 `roof.innerLines.forEach` 로 ridge selectable=true 설정. drawHelpLine 으로 ridge 재생성된 후 innerLines 가 stale 인 케이스에서 새 ridge 가 selectable=false 로 남음 → fabric selection:created 미발화 → currentObjectState/selectedObject.current null 유지.
|
|
||||||
|
|
||||||
수정: 기존 roof.innerLines 반복 경로는 그대로 두고(정상 케이스 유지), 그 직후 `canvas.getObjects().filter(name===RIDGE)` 로 ridge 직접 조회 → selectable=true 보강. 누락 시만 효과, 정상 시 동일 set 재호출로 부작용 없음.
|
|
||||||
|
|
||||||
**Why:** 사용자 보고 "두번째는 마우스로 아무리 움직여도 마루이동이 안되는 경우가 있다". roof.innerLines 동기화 타이밍이 깨지는 케이스를 canvas 레벨 fallback 으로 흡수.
|
|
||||||
|
|
||||||
**How to apply:** ridge 클릭→선택 동작이 다시 안되면 `useMovementSetting.js [type] useEffect` 의 canvas 레벨 ridge selectable 보강 블록 확인. 추가 라인 (onesideFlowRidge/yosemune) 도 마루이동 대상이라면 같은 자리에 추가.
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: SHOULDER_ABSORBED 임계는 모든 가드에서 동일하게 < 10
|
|
||||||
description: skeleton-utils.js 의 흡수 판정 임계는 OVER_EPS=0.5 → planeSize=5 대응 위해 < 10. 가드 추가 시 동일 값.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**규칙**: `wallBaseLine.attributes.planeSize` 로 흡수 판정하는 모든 가드는 임계 **`< 10`** 사용.
|
|
||||||
|
|
||||||
**관련 가드 (2026-04-27 기준)**:
|
|
||||||
- `skeleton-utils.js:1608` SHOULDER_ABSORBED skip — `< 10`
|
|
||||||
- `skeleton-utils.js:1475-1481` SHOULDER-ADJACENT processInBoth HIP_SKIP — `< 10` (이전 `< 1` 이었으나 통일)
|
|
||||||
- `skeleton-utils.js:1797` SHOULDER-ADJACENT `left_out` OUT_SKIP — `< 10`
|
|
||||||
- `skeleton-utils.js:1923` SHOULDER-ADJACENT `right_out` OUT_SKIP — `< 10`
|
|
||||||
- `skeleton-utils.js:2084` SHOULDER-ADJACENT `top_out` OUT_SKIP — `< 10`
|
|
||||||
- `skeleton-utils.js:2207` SHOULDER-ADJACENT `bottom_out` OUT_SKIP — `< 10`
|
|
||||||
- `skeleton-utils.js:1517-1546` (processInBoth) SK-VERTEX-PROXIMITY HIP_SKIP_VERTEX_MISS — target 이 `skPolygonPoints` (createInnerLinesFromSkeleton 6번째 인자, drawSkeletonRidgeRoof 에서 `changRoofLinePoints`, drawSkeletonRidgeRoofFromBaseLines 에서 `rawPoints` 전달) 의 vertex 5px 이내 아니면 skip. **2-hop 흡수** (인접 pair 가 stretched 되어 prev/next planeSize 가드를 통과하는 케이스) 도 차단. ※ closure 가 아닌 인자로 전달 — processInBoth 는 createInnerLinesFromSkeleton 안, changRoofLinePoints 는 skeletonBuilder 스코프라 직접 참조 시 ReferenceError.
|
|
||||||
|
|
||||||
**Why**:
|
|
||||||
- `useMovementSetting.js` 의 `OVER_EPS=0.5` 캔버스 단위 → `calcLinePlaneSize` 가 `× 10` 곱하므로 잔여 길이=0.5 시 planeSize=5.
|
|
||||||
- 가드 임계가 `< 1` 이면 planeSize=5 흡수가 가드 통과 → phantom HIP/extensionLine.
|
|
||||||
- 정상 baseLine 은 통상 50+ → `< 10` 이어도 false positive 없음.
|
|
||||||
- 임계가 두 곳에서 다르면 한쪽만 흡수 인식하고 다른 쪽은 정상 처리 → 직사각형으로 합쳐진 폴리곤에 공중에 뜬 HIP 빗변(예: H-6) 출현.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- 흡수 판정 가드를 새로 추가할 때 임계는 항상 `< 10` (또는 SHOULDER_ABSORBED skip 라인의 임계와 명시적으로 동기화).
|
|
||||||
- `OVER_EPS` 가 변경되면 임계도 재계산: 임계 ≥ `OVER_EPS × 10 + 안전마진(5)`.
|
|
||||||
- 가드들의 임계는 코드 가까이에 비교용 주석으로 cross-reference 유지.
|
|
||||||
|
|
||||||
**증상 사례**:
|
|
||||||
- (1) ㅗ 폴리곤에서 B-2 (bottom-left horizontal) 를 위로 이동해 B-4 (bottom-right horizontal) 와 평행/연속시킴.
|
|
||||||
- 직사각형으로 흡수돼야 하는데 (700,548)→(750,598) 위치에 H-6 (HIP extensionLine) 가 phantom 으로 그려짐.
|
|
||||||
- 원인: pair#3 vertical step 흡수(planeSize=5) → pair#4 인접 가드 `< 1` 통과 → processInBoth HIP 생성.
|
|
||||||
- 수정: processInBoth HIP_SKIP 가드 임계 `< 10` 으로 통일.
|
|
||||||
- (2) bump (돌출부) 폴리곤에서 left wall 을 바깥으로 이동해 인접 pair 흡수 → roofline 밖에 H-12 출현 + `updateAndAddLine NOT FOUND`.
|
|
||||||
- 원인 1: pair#6 흡수 후 pair#7 의 `_out` 분기가 dissolved 된 옛 roof.points 코너 참조.
|
|
||||||
- 원인 2 (1차 수정 후 잔류): pair#0 (top_in, processInBoth) 의 END 가 흡수된 pair#6 의 옛 roof 코너 (587,-108) 를 참조 → target=(587,-31) 이 새 SK 외부에 phantom HIP 생성.
|
|
||||||
- 수정 1: `_out` 4 경로에 인접 흡수 가드 추가.
|
|
||||||
- 수정 2: processInBoth 의 createHipLine 호출 전 SK-VERTEX-PROXIMITY 검사 추가. target 이 `changRoofLinePoints` vertex 5px 이내 아니면 skip.
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
---
|
|
||||||
name: skeleton-utils 이동 로직 회귀 테스트 체크리스트
|
|
||||||
description: skeleton-utils.js 의 SK 이동/HIP/eaveHelpLine 가드 변경 후 반드시 검증해야 할 시나리오. 사용자가 회귀 두려워 함 → 변경 후 이 목록 확인 권장.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**규칙**: `skeleton-utils.js` 의 movement/HIP/_out/processInBoth 영역 변경 시 아래 시나리오 모두 통과해야 함. 하나라도 깨지면 즉시 사용자에게 보고 후 진행 정지.
|
|
||||||
|
|
||||||
**검증 시나리오 (2026-04-27 기준 통과 상태)**:
|
|
||||||
|
|
||||||
1. **ㅁ 단순 사각형** — 어떤 이동이든 phantom HIP 없이 정상 처마/지붕 라인만 그려져야 함. (sanity check)
|
|
||||||
|
|
||||||
2. **ㅗ 폴리곤 단독 이동 (4 방향)** — top/bottom/left/right 각각 단독 in/out 이동. 모든 corner 의 HIP extensionLine 이 새 SK vertex 와 일치해야 함.
|
|
||||||
|
|
||||||
3. **ㅗ B-2 위로 이동 → B-4 평행 (H-6 phantom 시나리오)**
|
|
||||||
- 직사각형으로 흡수돼야 하는 합쳐진 폴리곤에 (700,548)→(750,598) 같은 phantom HIP 가 **없어야** 함.
|
|
||||||
- 가드: `skeleton-utils.js:1475-1481` HIP_SKIP `< 10`.
|
|
||||||
|
|
||||||
4. **bump 폴리곤 top-in + left-out 클램프 (H-12 phantom 시나리오)**
|
|
||||||
- left wall 을 평행 직전까지 이동(OVER 클램프, planeSize=5).
|
|
||||||
- roofline **밖에** H-12 가 **없어야** 함. `updateAndAddLine NOT FOUND` 도 안 떠야 함.
|
|
||||||
- 콘솔에서 `HIP_SKIP_VERTEX_MISS end target=(587.4,1.2)` 같은 skip 로그가 찍혀야 정상.
|
|
||||||
- 가드: `_out` 4 경로 OUT_SKIP + processInBoth SK-VERTEX-PROXIMITY.
|
|
||||||
|
|
||||||
5. **bump 폴리곤 top-in + left-out + right-out (양쪽 흡수 → 사각형)**
|
|
||||||
- 결과: 단순 사각형 SK. phantom HIP 0개.
|
|
||||||
- pair#0 양쪽 모두 `HIP_SKIP_VERTEX_MISS`, pair#1 right_out OUT_SKIP, pair#7 left_out OUT_SKIP, pair#2/pair#6 SHOULDER_ABSORBED skip 모두 찍혀야 함.
|
|
||||||
|
|
||||||
**Why**:
|
|
||||||
- 도형 가드는 한 케이스 고치면 다른 케이스 깨지기 쉬움 (false positive/negative). 사용자가 회귀에 매우 민감.
|
|
||||||
- 위 5 케이스가 흡수/평행/단독이동 패턴 전반을 커버 → 하나라도 깨지면 가드 임계나 적용 범위가 잘못된 것.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- skeleton-utils.js 1475/1797/1923/2084/2207/1517 부근 또는 `processInBoth` 본체 변경 시: 사용자에게 "위 5 케이스 다시 테스트 부탁" 이라고 명시적으로 요청.
|
|
||||||
- 새 가드 추가 시 임계는 항상 `< 10` (project_shoulder_absorbed_thresholds.md 참고).
|
|
||||||
- 가드의 위치 정보는 검증 시 grep 으로 위치 변동 확인 후 메모리 업데이트.
|
|
||||||
|
|
||||||
**관련 가드 위치** (변경 시 함께 검증):
|
|
||||||
- `skeleton-utils.js:1610` SHOULDER_ABSORBED skip
|
|
||||||
- `skeleton-utils.js:1475-1481` processInBoth HIP_SKIP (인접 흡수)
|
|
||||||
- `skeleton-utils.js:1517-1546` processInBoth SK-VERTEX-PROXIMITY (2-hop 흡수)
|
|
||||||
- `skeleton-utils.js:1797/1923/2084/2207` `_out` 4 경로 OUT_SKIP
|
|
||||||
- `createInnerLinesFromSkeleton` 6번째 인자 `skPolygonPoints` — closure 미가능, 인자 전달 필수.
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
name: v2.0 (sk-v2.0 브랜치) 설계 방향 — wall master, v1 호환 잊기
|
|
||||||
description: sk-v2.0 브랜치에서는 v1 시각/동작 호환을 신경쓰지 말고 v2.0 자체 설계로만 진행. v1 은 별도 트랙.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**규칙**: `sk-v2.0` 브랜치 작업 시 v1 (tag `v1.0`, commit `9a73fce3`) 와의 호환/시각 동등성 신경쓰지 말 것. v1 은 별도 트랙으로 계속 진행됨. v2.0 은 v2.0 자체 설계 목표만 보고 진행.
|
|
||||||
|
|
||||||
**v2.0 최종 그림**:
|
|
||||||
- `wall.baseLines` = master 좌표 (사용자 이동 가능)
|
|
||||||
- `roof.points` = wallLine + offset 확장으로 도출 (사용자 이동 가능, 독립적으로)
|
|
||||||
- SkeletonBuilder 입력 = wall master 기반
|
|
||||||
- 보조선(eaveHelpLine, HIP, 잔여선 등) 모두 이 구조 위에서 재구성
|
|
||||||
|
|
||||||
**Phase 분할** (사용자 결정, 2026-04-28):
|
|
||||||
- **Phase 1** ✅ (2026-04-28 사각형 + 복잡한 도형 통과): SkeletonBuilder 입력을 `roof.points` 가 아닌 `wall.baseLines` 좌표로 변경. offset/roof 참조 제거. SK 폴리곤 = wall 폴리곤 그 자체. 구현: `skeleton-utils.js:709-720` (offset 확장 로직 70줄 제거, 직접 매핑 10줄로 대체).
|
|
||||||
- **Phase 2** ✅ (2026-04-28 사각형 + 복잡한 도형 통과, offset 변경 케이스 포함):
|
|
||||||
- **최종 방식 (사용자 통찰 반영)**: SK 그래프 mutate 안 함. wall corner 마다 magenta 단일 45° extension 라인을 별도 QLine 으로 추가. SK 자체는 wall 좌표 그대로 보존 → wallbaseLine 안 변하면 SK 도 안 변함, 라인이동/offset/보조선 셋이 서로 리셋 안 됨.
|
|
||||||
- 사용자 통찰 (2026-04-28): "wallbaseLine 안 변하면 SK 자체도 안 변해야 한다. mutate 하면 한 vertex 가 여러 edge 의 begin/end 로 공유되어 의도치 않은 endpoint 변경. 미리 통합하지 말고 마지막에 통합하자".
|
|
||||||
- extension 끝점 공식: bisector dir = `n1 + n2`, λ = **max(λA, λB)** (작은 offset 쪽 roofLine 통과해서 큰 offset 쪽까지 뻗음). 휘지 않는 단일 직선.
|
|
||||||
- Polygon 밖으로 나간 부분은 `clipLineToRoofBoundary(wp, bp, roof.lines)` 로 절삭. clip 후 길이 < 0.5 → skip.
|
|
||||||
- 구현: `computeWallCornerExtensions(baseLines, wall)`, `skeleton-utils.js:556` 부근. 호출부 `skeleton-utils.js:980` 부근에서 magenta(`#FF00FF`) QLine 으로 추가.
|
|
||||||
- `processInBoth` (skeleton-utils.js:1517 부근) v2.0 에서 early-return. v1 의 HIP extension 생성 로직은 vestigial — Phase 2 가 동일 역할.
|
|
||||||
- **버려진 시도들** (디버깅 흔적): SK vertex mutate (eave 가 slanted 됨), L자 extension (휘는 라인), min(λ) 끝점 (한쪽 offset~0 corner extension 짧아짐), 선형방정식 교점 (roof corner 까지) — 모두 사용자 의도와 안 맞음.
|
|
||||||
- **Phase 3**: wall/roof 독립 이동, 보조선(eaveHelpLine 등) 전면 재설계.
|
|
||||||
|
|
||||||
**Why**:
|
|
||||||
- 고객이 roofLine 도 wallLine 처럼 움직이고 싶어함 → 두 축이 독립적이어야 → 처음부터 설계 다시 (사용자 표현: "처음부터 설계를 다시 해야 할것 같다").
|
|
||||||
- v1 호환을 유지하려 하면 "wallLine→roofLine derivation" 이 양방향 dependency 가 되어 설계가 꼬임.
|
|
||||||
- v1 은 v1 트랙(main 또는 dev) 에서 별개로 진행됨.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- `sk-v2.0` 브랜치에서 작업 시: v1 회귀 체크리스트(project_skeleton_regression_checklist.md) 무시 가능. 그건 v1 트랙 전용.
|
|
||||||
- "시각적으로 동일해야 하나?" 같은 질문 자체를 하지 말 것. v2.0 은 자체 시각으로 진행.
|
|
||||||
- 단 v2.0 내에서는 사용자가 "한단계 한단계" 를 강조했으므로 Phase 1→2→3 순서 엄수, Phase 안에서도 사각형→복잡한 도형 순으로.
|
|
||||||
|
|
||||||
**관련 메모**:
|
|
||||||
- `project_skeleton_regression_checklist.md` — v1 트랙용 회귀 시나리오. v2.0 작업에는 적용 안 함.
|
|
||||||
- `project_shoulder_absorbed_thresholds.md` — v1 흡수 가드. v2.0 에서는 wall master 라 흡수 자체가 다른 의미가 됨, 재검토 필요.
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
name: v2.0 핵심 불변식 — 누적 보존, 리셋 금지
|
|
||||||
description: sk-v2.0 에서 라인이동/offset변경/보조선추가 가 서로 리셋시키면 안 됨. 최종 그림 = wall.baseLines 현재 좌표 + Phase 2 offset 확장.
|
|
||||||
type: project
|
|
||||||
---
|
|
||||||
|
|
||||||
**불변식 (사용자 명시, 2026-04-28)**:
|
|
||||||
> "최종 그림 = `wall.baseLines` 좌표점 + offset 만큼 확장된 그림"
|
|
||||||
> 어떤 작업이 일어나도 다른 작업의 결과를 리셋시키면 안 됨.
|
|
||||||
|
|
||||||
**구체적으로**:
|
|
||||||
- 사용자가 **라인 이동** → `wall.baseLines` 좌표만 갱신, offset/보조선 보존
|
|
||||||
- 사용자가 **offset 변경** → `wall.baseLines[i].attributes.offset` 만 갱신, 라인이동/보조선 보존
|
|
||||||
- 사용자가 **보조선 추가** → 별도 컬렉션에 추가, 라인이동/offset 보존
|
|
||||||
|
|
||||||
**v1 의 문제** (사용자 표현): "v1은 리셋이 되는 문제가 있다."
|
|
||||||
한 작업이 다른 작업의 결과를 덮어쓰는 케이스가 있었음. v2.0 에서는 발생 금지.
|
|
||||||
|
|
||||||
**v2.0 설계상 자동 보장되는 부분**:
|
|
||||||
- SK 입력은 `wall.baseLines` 현재 좌표 매번 읽음 (`skeleton-utils.js:715`) — 누적 이동 자동 반영
|
|
||||||
- Phase 2 확장은 `bl.attributes?.offset` 매번 dynamic 읽음 — 누적 offset 자동 반영
|
|
||||||
- SK 재빌드는 SK-derived 라인만 다시 만듦
|
|
||||||
|
|
||||||
**주의 지점 (리셋 위험)**:
|
|
||||||
- `roof.innerLines` 통째 교체 (`skeleton-utils.js:882-883`):
|
|
||||||
```js
|
|
||||||
roof.innerLines = createInnerLinesFromSkeleton(...)
|
|
||||||
```
|
|
||||||
사용자가 별도로 그린 보조선이 `roof.innerLines` 에 섞여있으면 SK 재빌드 시 사라짐. 보조선은 별도 name/컬렉션으로 보관해야 함.
|
|
||||||
- `canvas.skeleton = cleanSkeleton` 교체 (`skeleton-utils.js:915-917`) — `lastPoints` 만 명시적으로 보존하고 있음. 다른 누적 상태 (e.g. 사용자 보조선) 도 동일하게 보존 필요할 수 있음.
|
|
||||||
|
|
||||||
**How to apply**:
|
|
||||||
- v2.0 에서 새 기능 작업 시 항상 이 불변식 만족하는지 확인.
|
|
||||||
- "한 작업이 다른 결과를 지우는가?" 자체질문 필수.
|
|
||||||
- 리셋 위험 지점 (위 주의 지점) 수정 시 누적 보존 코드 추가.
|
|
||||||
- offset/이동/보조선 셋 모두 동시에 발생하는 시나리오로 회귀 테스트.
|
|
||||||
|
|
||||||
**관련**:
|
|
||||||
- `project_v2_design_direction.md` — v2.0 phase 분할
|
|
||||||
- `feedback_no_touch_working_code.md` — 정상 코드 건들지 않기
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
---
|
|
||||||
name: 커밋 로그 파일 위치 (post-commit hook 자동 누적, qcast-front)
|
|
||||||
description: qcast-front 저장소의 .git/hooks/post-commit 훅이 매 커밋 직후 누적 기록하는 plain text 로그 위치와 형식
|
|
||||||
type: reference
|
|
||||||
---
|
|
||||||
|
|
||||||
qcast-front 저장소는 로컬 git `post-commit` 훅으로 매 커밋 직후 다음 파일에 한 줄을 append 한다.
|
|
||||||
|
|
||||||
- **경로**: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-front/memory/commit_log.txt`
|
|
||||||
- **형식**: `YYYY-MM-DD HH:MM:SS [브랜치] short(fullhash) 커밋제목`
|
|
||||||
- **훅 위치**: `<repo>/.git/hooks/post-commit` (qcast-front 로컬 전용, 팀 공유 X)
|
|
||||||
- **작성일**: 2026-05-06
|
|
||||||
|
|
||||||
## 사용 시점
|
|
||||||
- 사용자가 "최근 커밋 뭐 있었지?", "어제 커밋한 해시 알려줘" 같은 질문을 할 때 이 로그 파일을 먼저 확인한다.
|
|
||||||
- `git log` 와 병행 활용: 로그 파일은 누적 타임스탬프(작업 시각) 기준, `git log` 는 커밋 메타데이터 기준이라 둘이 보완적이다.
|
|
||||||
- 푸시 여부는 기록되지 않음 (post-commit 만 trigger). push 성공 여부가 필요하면 별도 확인.
|
|
||||||
|
|
||||||
## 주의
|
|
||||||
- 로그 파일이 없을 수도 있음 (훅 설치 후 첫 커밋 전). 이 경우 `git log` 로 대체.
|
|
||||||
- 다른 저장소에서는 동작하지 않음. 이 hook 은 qcast-front 로컬에만 설치됨 (자매 프로젝트 qcast-api 도 동일 형식의 훅을 별도 보유).
|
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -49,10 +49,12 @@ certificates
|
|||||||
.ai
|
.ai
|
||||||
|
|
||||||
# [auto-untracked 2026-05-06] 머신별 자동 산출물 / 알 수 없는 파일
|
# [auto-untracked 2026-05-06] 머신별 자동 산출물 / 알 수 없는 파일
|
||||||
.claude/memory/commit_log.txt
|
|
||||||
.claude/settings.local.json
|
|
||||||
.claude/.commit-notice
|
|
||||||
last_updated.log
|
last_updated.log
|
||||||
claude-501/
|
claude-501/
|
||||||
sl
|
sl
|
||||||
|
|
||||||
|
# [local-only 2026-05-08] 모든 markdown 문서 + Claude 설정은 git 추적 제외 (로컬 전용)
|
||||||
|
# 단, 루트 README.md 만 추적 유지
|
||||||
|
*.md
|
||||||
|
!/README.md
|
||||||
|
.claude/
|
||||||
|
|||||||
53
CLAUDE.md
53
CLAUDE.md
@ -1,53 +0,0 @@
|
|||||||
# Claude Code 설정
|
|
||||||
|
|
||||||
## 신규 컴퓨터 셋업
|
|
||||||
|
|
||||||
클론 후 아래 명령을 한 번 실행하면 (1) Claude 메모리 연결 + (2) git 훅 활성화 가 끝납니다.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PROJ=$(pwd)
|
|
||||||
MEM_DIR="$HOME/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-front"
|
|
||||||
mkdir -p "$MEM_DIR"
|
|
||||||
rm -rf "$MEM_DIR/memory"
|
|
||||||
ln -s "$PROJ/.claude/memory" "$MEM_DIR/memory"
|
|
||||||
|
|
||||||
# git hooks (post-commit: 커밋 로그 + [TBD-CODE] 자동 치환 + .commit-notice 마커)
|
|
||||||
git config core.hooksPath .githooks
|
|
||||||
```
|
|
||||||
|
|
||||||
> **주의**: 위 경로(`-Users-cha-Works-Projects-qcast3-qcast-front`)는 이 프로젝트의 절대경로 인코딩입니다.
|
|
||||||
> 프로젝트를 `/Users/cha/Works/Projects/qcast3/qcast-front` 에 clone 했을 때 그대로 사용하면 됩니다.
|
|
||||||
|
|
||||||
## git hooks (`.githooks/`)
|
|
||||||
|
|
||||||
`.githooks/` 는 git 으로 추적되는 훅 디렉토리입니다. `git config core.hooksPath .githooks` 가 한 번 설정되면 자동 실행됩니다.
|
|
||||||
|
|
||||||
- `post-commit` — 커밋 로그 append, `[TBD-CODE]` placeholder 를 새 short hash 로 자동 치환, `.claude/.commit-notice` 마커 작성 (Claude 의 UserPromptSubmit hook 이 다음 프롬프트에 컨텍스트로 주입)
|
|
||||||
|
|
||||||
## 메모리 구조
|
|
||||||
|
|
||||||
`.claude/memory/` 폴더가 Claude 의 프로젝트 메모리입니다. git 으로 관리됩니다.
|
|
||||||
|
|
||||||
- `MEMORY.md` — 메모리 인덱스 (항상 로드됨)
|
|
||||||
- `feedback_*.md` — 작업 방식 피드백
|
|
||||||
- `project_*.md` — 프로젝트 상태 및 결정 사항
|
|
||||||
- `reference_*.md` — 외부 시스템 참조
|
|
||||||
|
|
||||||
## 인수인계서
|
|
||||||
|
|
||||||
`docs/` 폴더의 `*_HANDOVER.md` 파일들을 참고하세요.
|
|
||||||
|
|
||||||
- `docs/HANDOVER_INDEX.md` — 진입점 인덱스 (먼저 읽기)
|
|
||||||
- `docs/SK_MOVEMENT_HANDOVER.md` — SK 라인 이동 (최우선)
|
|
||||||
- `docs/UI_MODAL_HANDOVER.md` — 모달/UI
|
|
||||||
- `docs/API_STABILITY_HANDOVER.md` — API 안정화
|
|
||||||
- `docs/MODULE_CIRCUIT_HANDOVER.md` — 모듈/회로
|
|
||||||
|
|
||||||
## 로컬 개발
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev # 기본 (dev API)
|
|
||||||
npm run dev:local-api # localhost:8080 API
|
|
||||||
npm run dev:log # 기본 + 서버 로그 파일
|
|
||||||
npm run dev:log:local-api # localhost:8080 + 서버 로그 파일
|
|
||||||
```
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
# API 안정화 / 인증 / 세션 인수인계서
|
|
||||||
|
|
||||||
> 작성일: 2026-05-06 (옵션 B 분리)
|
|
||||||
> 대상: 후임 AI 엔지니어 또는 개발 관리자
|
|
||||||
> 작업 브랜치: `hotfix/dev_yscha`
|
|
||||||
|
|
||||||
> **이 문서의 범위**: 백엔드 API 통신, 재시도, 인증/세션 추적, 로컬 개발 환경 트랙만 다룹니다.
|
|
||||||
> 다른 트랙은 `HANDOVER_INDEX.md` 참조.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 한 줄 요약
|
|
||||||
|
|
||||||
API 통신이 **동시 호출로 데드락**나거나 **간헐적 실패**로 사용자 작업이 날아가는 일을 막기 위해, 클라이언트 측 직렬화 / 재시도 / 에러 저장 / stale closure 제거 / 인증 추적을 차례로 보강한 트랙입니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 동시 UPDATE 데드락 방어 (가장 중요)
|
|
||||||
|
|
||||||
### 1.1 무슨 일이 있었나
|
|
||||||
- 같은 데이터를 짧은 시간에 여러 번 저장하면 **백엔드에서 동시 UPDATE 가 부딪혀 데드락**이 났습니다.
|
|
||||||
- 사용자 입장에서는 "저장 안 됨" / "타임아웃" / "데이터 깨짐" 으로 보였습니다.
|
|
||||||
|
|
||||||
### 1.2 어떻게 풀었나 (커밋 `3000de9c`)
|
|
||||||
|
|
||||||
세 단계로 구성:
|
|
||||||
|
|
||||||
| 단계 | 무엇을 하나 |
|
|
||||||
|------|------------|
|
|
||||||
| **abort + latest** | 진행 중인 동일 요청은 즉시 abort 하고, **가장 최근 요청 1개만** 살림 |
|
|
||||||
| **`X-Idempotency-Key` 헤더** | 같은 의도의 요청이 중복 도달해도 백엔드가 한 번만 처리하도록 멱등키 부여 |
|
|
||||||
| **클라이언트 큐 직렬화** | 같은 리소스에 대한 호출은 클라이언트 단에서 줄을 세워 1개씩만 보냄 |
|
|
||||||
|
|
||||||
### 1.3 핵심 파일
|
|
||||||
- `src/hooks/useAxios.js` — 메인 구현 위치
|
|
||||||
- 호출부 여러 곳
|
|
||||||
|
|
||||||
### 1.4 주의
|
|
||||||
- abort 가 너무 공격적이면 **사용자가 의도한 마지막 요청도 취소될 수 있습니다**. 새 호출 추가 시 abort 정책을 검토하세요.
|
|
||||||
- 멱등키는 **요청 단위로 유니크** 해야 합니다. 같은 키로 다른 의미의 요청을 보내면 이전 결과가 캐시처럼 돌아옵니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 에러 저장 API (`errSave`)
|
|
||||||
|
|
||||||
- 커밋 `bb775b28`
|
|
||||||
- 클라이언트에서 발생한 예외/에러를 **백엔드로 자동 전송**해 추적할 수 있게 한 API.
|
|
||||||
- 운영 중에 사용자가 못 알아채는 에러를 잡기 위함.
|
|
||||||
|
|
||||||
### 사용처
|
|
||||||
- 글로벌 에러 핸들러
|
|
||||||
- 주요 fetch 실패 catch 블록
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. API 재시도 — `getRoofMaterialList` 10회
|
|
||||||
|
|
||||||
- 커밋 `910974f8`
|
|
||||||
- `getRoofMaterialList` API 가 간헐적으로 실패하는 이슈에 대해 **최대 10회 재시도**.
|
|
||||||
- 지붕재 목록은 화면 진입 시 반드시 필요한 데이터라 실패하면 사용자가 아무것도 못 함.
|
|
||||||
|
|
||||||
### 주의
|
|
||||||
- 10회 재시도는 **이 한 API 한정** 정책. 다른 API 에 그대로 적용하지 마세요. 일반화하면 백엔드 부하 증가.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. `fetchBasicSettings` Stale Closure 수정
|
|
||||||
|
|
||||||
- 커밋 `63578276`
|
|
||||||
- **문제**: `fetchBasicSettings` 가 `correntObjectNo` 를 Recoil 상태에서 직접 읽고 있어, 새 오브젝트가 생성될 때 **이전 값(stale)** 이 그대로 남아 있는 클로저 문제 발생.
|
|
||||||
- **수정**: 호출 시점의 최신 값을 인자로 받아 처리하도록 변경.
|
|
||||||
|
|
||||||
### 일반화 가능한 교훈
|
|
||||||
- React + Recoil 환경에서 비동기 함수 안에서 atom 을 읽을 때 항상 stale closure 가능성을 의심하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 인증 / 세션 추적
|
|
||||||
|
|
||||||
자동 로그인 / 로그아웃 동작이 의도대로 안 되는 케이스가 보고돼 단계적으로 추적 로그를 넣었습니다.
|
|
||||||
|
|
||||||
| 단계 | 커밋 |
|
|
||||||
|------|------|
|
|
||||||
| 로그인·로그아웃 추적 (1차) | `138bdbdd` |
|
|
||||||
| 추적 강화 (반복) | `ccf60ee7`, `c6da437a` |
|
|
||||||
| 자동 로그인·로그아웃 추적 1 | `c6da437a` |
|
|
||||||
| 자동 로그인·로그아웃 추적 2 | `afe0a377` |
|
|
||||||
| 로그아웃 결과 없이 (이슈 케이스) | `b021b7be` |
|
|
||||||
|
|
||||||
### 추적 로그의 목적
|
|
||||||
- 어디서 토큰이 사라지는지
|
|
||||||
- 어디서 자동 로그인 트리거가 안 잡히는지
|
|
||||||
- 어디서 로그아웃 결과가 누락되는지
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 로컬 개발 환경 — dev 스크립트
|
|
||||||
|
|
||||||
- 커밋 `27ee53c3`
|
|
||||||
- `package.json` 에 다음 추가:
|
|
||||||
- `dev:local-api` — 로컬 백엔드(`localhost:8080`) 에 붙여서 실행
|
|
||||||
- `dev:log:local-api` — 위와 동일 + 로그 강화 모드
|
|
||||||
- `.env.localhost` 와 페어로 사용. 환경변수에서 API base URL 분기.
|
|
||||||
|
|
||||||
### 사용 시나리오
|
|
||||||
- 백엔드 개발자와 동시 디버깅 시
|
|
||||||
- API 응답 형태가 stage / production 과 다른 경우
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 미해결 / 추적 중
|
|
||||||
|
|
||||||
- 동시 UPDATE 데드락의 **완전 재발 방지**는 여전히 모니터링 필요. abort 정책이 새로운 호출 패턴에서 깨질 수 있음.
|
|
||||||
- 인증 추적 로그는 **운영에서 떼어낼지** 결정 필요. 현재는 디버깅 목적 유지.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 핵심 파일
|
|
||||||
|
|
||||||
| 파일 | 역할 |
|
|
||||||
|------|------|
|
|
||||||
| `src/hooks/useAxios.js` | API 호출 hook + abort/멱등키/직렬화 |
|
|
||||||
| `package.json` | dev 스크립트 |
|
|
||||||
| `.env.localhost` | 로컬 환경 변수 |
|
|
||||||
| `src/...` (Recoil atoms 의 fetch wrapper) | stale closure 회피 패턴 적용 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 인수 직후 권장 절차
|
|
||||||
|
|
||||||
1. 이 문서 통독.
|
|
||||||
2. `useAxios.js` 의 abort/직렬화 흐름 코드 한 번 따라가 보기.
|
|
||||||
3. `npm run dev:local-api` 로 로컬 백엔드와 붙어 동작 확인.
|
|
||||||
4. 인증 추적 로그를 운영에서 유지/제거 여부 사용자에게 확인.
|
|
||||||
|
|
||||||
끝.
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
# 인수인계 문서 인덱스
|
|
||||||
|
|
||||||
> 작성일: 2026-05-06
|
|
||||||
> 대상: 후임 AI 엔지니어 또는 개발 관리자
|
|
||||||
> 작업 브랜치: `hotfix/dev_yscha`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 이 폴더는 뭔가요?
|
|
||||||
|
|
||||||
Qcast Front 프로젝트의 **트랙별 인수인계 문서**가 모여 있습니다. 한 파일에 다 넣으면 너무 길어지고 영역이 섞여서 보기 어렵기 때문에 4개 트랙으로 분리했습니다.
|
|
||||||
|
|
||||||
**읽는 순서**: 위에서부터 차례로 읽으면 됩니다. SK 라인 트랙이 가장 위험하고 우선순위가 높습니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 트랙별 문서
|
|
||||||
|
|
||||||
### [SK_MOVEMENT_HANDOVER.md](./SK_MOVEMENT_HANDOVER.md) ★ 최우선
|
|
||||||
**Straight Skeleton 라인 이동 트랙.**
|
|
||||||
지붕 외곽선을 움직였을 때 내부 선이 깨지는 문제를 어떻게 풀어왔는지 정리. 이 프로젝트에서 가장 위험하고 손대기 까다로운 영역. **반드시 가장 먼저 읽으세요.**
|
|
||||||
|
|
||||||
핵심 키워드: 외벽선 / 처마선 / 내부선 / SK 알고리즘 / 오버 이동 / clamp 정책 / 골짜기 판정 / phantom ridge / hip vs ridge
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### [UI_MODAL_HANDOVER.md](./UI_MODAL_HANDOVER.md)
|
|
||||||
**모달 / 다이얼로그 / 라인 표시 트랙.**
|
|
||||||
계산기 패드 토글 추가, 외벽선/평면도 확정 다이얼로그, 라인 라벨/색/인덱스 표시 정돈.
|
|
||||||
|
|
||||||
핵심 키워드: 계산기 토글 / 補助線 / 確定 다이얼로그 / Enter 키 / 라인 라벨 / 디버그 라벨 인라인
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### [API_STABILITY_HANDOVER.md](./API_STABILITY_HANDOVER.md)
|
|
||||||
**백엔드 API 안정화 / 인증 / 세션 / 로컬 개발 환경 트랙.**
|
|
||||||
동시 UPDATE 데드락 방어, 에러 저장, 재시도, stale closure, 자동 로그인/로그아웃 추적, dev 스크립트.
|
|
||||||
|
|
||||||
핵심 키워드: abort+latest / X-Idempotency-Key / 직렬화 / errSave / 10회 재시도 / Recoil stale closure / 자동 로그인 추적 / dev:local-api
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### [MODULE_CIRCUIT_HANDOVER.md](./MODULE_CIRCUIT_HANDOVER.md)
|
|
||||||
**모듈 / 배치 / 회로 / 처마 / 가대 / 티켓 시리즈 트랙.**
|
|
||||||
태양광 모듈 배치, PCS·접속함 회로, 처마 커버, 가대 표시 등의 도메인 특화 작업과 [1xxx] 티켓 시리즈.
|
|
||||||
|
|
||||||
핵심 키워드: PCS / 접속함 / Cross Mix / 모듈 배치 / 거치대 / 처마 커버 / 팔작지붕 / 사의 각도 / [1xxx] 티켓
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 같이 봐야 하는 자료
|
|
||||||
|
|
||||||
### 메모리 인덱스
|
|
||||||
- 위치: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-front/memory/MEMORY.md`
|
|
||||||
- 모든 정책 결정·임계값·"왜 이렇게 했는지" 가 항목별로 들어 있습니다.
|
|
||||||
- **작업 시작 전에 반드시 한 번 통독하세요.**
|
|
||||||
|
|
||||||
### 기존 문서 (참고 자료)
|
|
||||||
- `docs/Qcast coding convention.pdf`
|
|
||||||
- `docs/Qcast development guilde.pdf`
|
|
||||||
- `docs/git commit message Convention.pdf`
|
|
||||||
- `docs/메세지 처리 가이드.pdf`
|
|
||||||
- `docs/Canvas Status.md`
|
|
||||||
- `docs/dictionary.txt`
|
|
||||||
- `docs/2025-02-XX_Qcell_회의.txt` (회의록 시리즈)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 인수받은 직후 추천 절차
|
|
||||||
|
|
||||||
1. **이 인덱스 통독** (5분).
|
|
||||||
2. **메모리 `MEMORY.md` 인덱스 통독** — 9개 원칙 + 각 트랙 메모리 항목 (10~15분).
|
|
||||||
3. **`SK_MOVEMENT_HANDOVER.md` 통독** — 가장 위험한 영역 먼저 (30~60분).
|
|
||||||
4. 나머지 3개 트랙 문서는 **필요할 때 펼쳐 보는 레퍼런스** 로 사용.
|
|
||||||
5. **회귀 체크리스트** (`SK_MOVEMENT_HANDOVER.md §8.10`) 5 시나리오 직접 실행해 현재 동작 확인.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 핵심 원칙 3줄 요약
|
|
||||||
|
|
||||||
이 프로젝트에서 일을 한다면 적어도 이 3개는 머리에 두세요. (자세한 9개 원칙은 `SK_MOVEMENT_HANDOVER.md §6` 참조)
|
|
||||||
|
|
||||||
1. **잘 되는 코드는 건들지 말 것.** 새 함수로 추가하세요.
|
|
||||||
2. **오버 이동은 평행 위치까지만** (clamp 정책).
|
|
||||||
3. **사용자가 추가한 라인은 절대 사라지지 않게** 하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
끝.
|
|
||||||
@ -1,210 +0,0 @@
|
|||||||
# 모듈 / 배치 / 회로 / 처마 / 티켓 시리즈 인수인계서
|
|
||||||
|
|
||||||
> 작성일: 2026-05-06 (옵션 B 분리)
|
|
||||||
> 대상: 후임 AI 엔지니어 또는 개발 관리자
|
|
||||||
> 작업 브랜치: `hotfix/dev_yscha`
|
|
||||||
|
|
||||||
> **이 문서의 범위**: 태양광 모듈 배치, 회로/PCS/접속함, 처마/가대, 그리고 [1xxx] 티켓 시리즈 같은 **도메인 특화 작업**을 다룹니다.
|
|
||||||
> 다른 트랙은 `HANDOVER_INDEX.md` 참조.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 한 줄 요약
|
|
||||||
|
|
||||||
지붕 형상이 결정된 다음 단계 — **태양광 모듈을 어디에 어떻게 배치하고**, **모듈을 어느 PCS·접속함에 묶고**, **처마/가대를 어떻게 잡을지** — 의 자잘한 버그/개선 트랙입니다. 티켓([1xxx]) 단위로 들어와 처리된 케이스가 많아 항목이 많지만, 각각은 **국소적인 수정**입니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 회로 / PCS / 접속함 트랙
|
|
||||||
|
|
||||||
태양광 시스템에서 모듈 → 접속함 → PCS(전력변환기) → 분전반 으로 이어지는 회로 구성 영역.
|
|
||||||
|
|
||||||
### 1.1 Cross Mix (DC3) 접속함 통합 (커밋 `0eb888fb`)
|
|
||||||
- **문제**: PCS 담당 모듈 타입에 따라 접속함이 여러 건 내려가던 것을 1건으로 합쳐야 했음.
|
|
||||||
- **수정**: PCS 담당 모듈 타입 기준으로 필터링해서 **1건만 내려가도록 통합**.
|
|
||||||
|
|
||||||
### 1.2 `subModuleTpCd` 추가 (커밋 `f022956c`)
|
|
||||||
- `trestles[]` 에 Cross Mix 식별용 필드 추가.
|
|
||||||
- 단일/동계열은 빈 값.
|
|
||||||
|
|
||||||
### 1.3 `moduleTpCd` 보존 + dedupe 키 변경 (커밋 `795c9789`)
|
|
||||||
- PCS 접속함 응답의 새 필드 `moduleTpCd` 를 프론트 상태/제출 파라미터까지 보존.
|
|
||||||
- 중복 제거 키를 `(itemId, moduleTpCd)` 조합으로 변경.
|
|
||||||
|
|
||||||
### 1.4 `PassivityCircuitAllocation` ref prop 제거
|
|
||||||
- forwardRef 미사용 함수형 컴포넌트에 ref 를 prop 으로 넘겨 React warning 이 발생 → 제거.
|
|
||||||
|
|
||||||
### 1.5 [1491] 실내 집중형 PCS 접속함 산출 이상 (커밋 `d0590f51`, `471093eb`)
|
|
||||||
- 실내 집중형 PCS 사용 시 접속함이 정상 산출 안 되던 현상 + cvrLmtRow 처마 커버 단수 보정.
|
|
||||||
|
|
||||||
### 1.6 [1749] 모듈 배치 후 회로 설정 오류 (커밋 `a11010c0`)
|
|
||||||
- 배치 후 회로 단계에서 할당 실패하던 케이스 수정.
|
|
||||||
|
|
||||||
### 1.7 [1845] 실내집중형 PCS 수동 회로분할 동일 방위/구배 강제 (커밋 `ba92e340`)
|
|
||||||
- **작성일**: 2026-05-06
|
|
||||||
- **배경 (HANASYS 보고)**: HANASYS DESIGN 에서 屋内集中PCS(`pcsTpCd === 'INDFCS'`) + **수동** 회로분할 시, 다른 방위 또는 다른 구배의 지붕면 모듈을 **같은 직렬회로**로 묶을 수 있는 사례. 屋外マルチPCS(`OUTDMULTI`) 또는 자동분할 경로에서는 정상.
|
|
||||||
- **원인**: `PassivityCircuitAllocation.jsx > handleCircuitNumberFix` 의 `switch (pcsTpCd)` 분기에서 OUTDMULTI 는 `surfaceType` (방위-구배 키) 검증이 있었으나, INDFCS 는 `northModuleYn` 만 검증하고 방위/구배 검증 누락.
|
|
||||||
- **수정**: INDFCS 분기에 `surfaceType` 누적 검증 추가. 검증 대상은 **이번 선택 모듈 + 같은 PCS 의 기존 할당 모듈 합집합** (북면 검사와 동일한 누적 패턴). 그룹 2개 이상이면 alert + return.
|
|
||||||
- **신규 메시지 키**: `module.circuit.fix.not.same.direction.pitch.error` (ko / ja). 기존 `module.circuit.fix.not.same.roof.error` 의 4개 호출지점은 의미가 달라 그대로 유지.
|
|
||||||
- **태그**: `[INDFCS-SAME-ROOF 2026-05-06]`
|
|
||||||
- **회귀 체크**: ① INDFCS 동일 방위/구배 누적 ② INDFCS 1차 동향 후 2차 남향 추가 차단 ③ 북면 혼합 차단(기존) ④ OUTDMULTI 다방위 차단(기존) ⑤ 자동분할 영향 없음.
|
|
||||||
|
|
||||||
### 1.8 [1845] 보정 — 검증 단위를 PCS → 회로번호로 좁힘 (커밋 `4ebe4cb4`)
|
|
||||||
- **작성일**: 2026-05-06
|
|
||||||
- **배경**: 1.7 수정 직후 회귀에서 **자동분할 후 수동분할 이어가는 시나리오** 차단 발견. 자동으로 회로 1=남(south-5.5) 4장이 부여된 같은 PCS 에, 수동으로 회로 2=서(west-5.5) 4장을 묶으려 하면 alert 차단됨. 회로 1 과 회로 2 는 서로 다른 직렬회로이므로 통과돼야 정상.
|
|
||||||
- **원인**: 1.7 의 (b) 누적 패턴이 같은 PCS 의 **모든** 기존 모듈을 합집합 대상으로 잡아, 다른 회로번호까지 검증에 포함됨. HANASYS 보고 본문 "**同一の直列回路として**構成できてしまう" 에 비춰 보면 검증 단위는 PCS 가 아니라 **회로번호** 가 맞음.
|
|
||||||
- **수정**: `originHaveThisPcsModules` 를 `getCircuitNumber()` 결과 (이번 부여하려는 회로번호 문자열) 와 일치하는 모듈로 필터링한 `sameCircuitExisting` 으로 교체. 이번 선택 모듈 + 같은 회로번호 기존 모듈만 합집합 검증.
|
|
||||||
- **진단 경로**: `debugCapture.log('INDFCS-VALIDATION', ...)` 임시 삽입 → `NEXT_PUBLIC_ENABLE_LOGGING=true` 로 재현 → `debug/debug.log` 에서 `uniqueKeys` 와 `existingSameCircuitCount` 확인 후 정식화 시 제거.
|
|
||||||
- **태그**: `[INDFCS-SAME-ROOF 2026-05-06]` (1.7 과 동일 태그, 주석에 "보정 2026-05-06" 명시)
|
|
||||||
- **회귀 체크 추가**: ⑥ 자동(회로 1 = 남) → 수동(회로 2 = 서) 통과 ⑦ 수동에서 같은 회로번호로 다른 방위 묶기는 여전히 차단.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 모듈 배치 / 설치 영역
|
|
||||||
|
|
||||||
### 2.1 [1956] 모듈 설치면 제대로 설치 안 되는 현상 (커밋 `aa16a45b`)
|
|
||||||
- 특정 지붕 형태에서 모듈이 자리잡지 못하던 케이스 수정.
|
|
||||||
|
|
||||||
### 2.2 [1830] 모듈 설치영역 대각 발생 (커밋 `2101d30d`)
|
|
||||||
- 모듈 설치 영역 외곽이 대각으로 잘못 잡히던 현상 수정.
|
|
||||||
|
|
||||||
### 2.3 [1805] 설치 영역 / 겹치는 선 (커밋 `7b8b995e`, `fdfa8d52`)
|
|
||||||
- 설치 영역이 잘못 잡히는 케이스.
|
|
||||||
- 겹치는 선이 있을 때 모듈 설치 영역에 이상 발생 → 수정.
|
|
||||||
|
|
||||||
### 2.4 [1798] 배치면 그리기 오류 (커밋 `76d3637a`)
|
|
||||||
- 배치면 그리는 단계에서 발생하던 오류 수정.
|
|
||||||
|
|
||||||
### 2.5 [1807] 거치대 / 낙설 방지 금구 (커밋 `1681fd85`, `6cf55600`)
|
|
||||||
- 거치대 선택 시 "낙설 방지 금구 설치" 가 자동으로 체크되는 현상 수정 (1, 2 차).
|
|
||||||
|
|
||||||
### 2.6 [1485] 오브젝트 복사 기능 권한 설정 (커밋 `b9bb4708`)
|
|
||||||
- 복사 기능을 권한 그룹에 따라 제어.
|
|
||||||
|
|
||||||
### 2.7 top / bottom = in 할당 (커밋 `13cd9211`, `f2b527b7`)
|
|
||||||
- 모듈 할당 정책에서 top·bottom 방향이 in 일 때의 처리.
|
|
||||||
- 추가/제외 두 단계로 처리.
|
|
||||||
|
|
||||||
### 2.8 수동 배치 시 자동 물떼세 배치 (커밋 `b1c35376`, revert `13f10fd1`)
|
|
||||||
- 한 번 적용했다가 **revert** 됨. 향후 재적용 시 revert 사유 확인 필요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 처마 / 가대 / 지붕재
|
|
||||||
|
|
||||||
### 3.1 [1455] 처마 커버 표시 (커밋 `0e70c3af`)
|
|
||||||
- 처마 커버 표시 로직 보강.
|
|
||||||
|
|
||||||
### 3.2 처마 커버 설치 이상 (커밋 `18083f89`)
|
|
||||||
- 일부 케이스에서 처마 커버가 잘못 설치되던 현상 수정.
|
|
||||||
|
|
||||||
### 3.3 [1751] 기와 형태 처마 돌출 치수 변경 (커밋 `1dd4e0cf`)
|
|
||||||
- 기와 지붕에서 처마 돌출 치수를 바꿨을 때 동작 보정.
|
|
||||||
|
|
||||||
### 3.4 [1840] 팔작지붕 도면 사례 (커밋 `cd5eebd0`)
|
|
||||||
- 팔작지붕 형태가 생성되지 않던 케이스.
|
|
||||||
|
|
||||||
### 3.5 [1480] 사의 각도 설정 "각도" (커밋 `3eaf3f2c`, `90228121`, `dffb8c0d`, `4a270ba5`, `2514fc92`)
|
|
||||||
- 사(斜) 각도를 "각도" 단위로 설정했을 때 발생하는 문제. **다회 수정** — 한 번에 안 잡혀서 여러 차례 들어감.
|
|
||||||
|
|
||||||
### 3.6 [1471] (관련) 플랜별 지붕재 변경 저장 (커밋 `64ba5ad3`)
|
|
||||||
- 플랜마다 지붕재를 다르게 저장.
|
|
||||||
|
|
||||||
### 3.7 [1456] 배치면 초기 설정에서 지붕재 / 촌 위치 변경 (커밋 `07ce6b6f`)
|
|
||||||
|
|
||||||
### 3.8 [1960] 배치도·계통도에 가대 표시 (커밋 `4d91da5f`, `9c25d3ed`)
|
|
||||||
- 출력된 배치도/계통도에 가대가 잘못 표시되던 케이스.
|
|
||||||
|
|
||||||
### 3.9 [1971] 외벽선 작성 방향 "반시계 방향" 표기 삭제 (커밋 `d15c9e69`)
|
|
||||||
- UI 표기 정리.
|
|
||||||
|
|
||||||
### 3.10 [1806] 서까래 높이 조정 (커밋 `caab0d9d`, `01b9625a`)
|
|
||||||
- 서까래 높이 조정에 따른 지붕 형상 불일치 + 라인 오버 체크 1, 2 차.
|
|
||||||
|
|
||||||
### 3.11 [1961][1969][2118] (커밋 `3f5f4908`)
|
|
||||||
- 묶음 수정.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 견적서 / 도면 표기
|
|
||||||
|
|
||||||
### 4.1 [1525] 면형상 배치 치수 표기 이상 (커밋 `f77794ce`)
|
|
||||||
|
|
||||||
### 4.2 [1482] 도면 흐름 방향 좌우 표시 잘림 (커밋 `4b098fa5`)
|
|
||||||
- 흐름 방향이 좌우로 설정된 경우 도면에서 일부가 잘려 보이던 현상.
|
|
||||||
|
|
||||||
### 4.3 [1482] 플랜 복사 시 견적서에 [하제피치] 표시 (커밋 `e912d084`)
|
|
||||||
- 플랜 복사 시 발생.
|
|
||||||
|
|
||||||
### 4.4 [1470] 배치도·계통도 화질 개선 (커밋 `be95b1b3`)
|
|
||||||
- cell 이미지 추가로 출력 화질 개선.
|
|
||||||
|
|
||||||
### 4.5 [1766] 방위 변경 체크박스 견적서 표기 (커밋 `f3bddbea`)
|
|
||||||
|
|
||||||
### 4.6 [1423] 설계 가져오기 기능 (커밋 `f87bfb90`)
|
|
||||||
- 다른 설계에서 가져오기.
|
|
||||||
|
|
||||||
### 4.7 [카톡] 견적서 / 물건검색 1차점 숨김 (커밋 `a10196f9`, `44aca56d`)
|
|
||||||
- 1차점 사용자에게는 일부 정보 숨기고, 2차점 로그인 사용자에게만 노출.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 캔버스 / 좌표 / 일반 버그
|
|
||||||
|
|
||||||
### 5.1 확대 축소 100% 가운데 정렬 (커밋 `98440188`, `8d9379c3`)
|
|
||||||
|
|
||||||
### 5.2 회전 시 길이 계산 반영 안 됨 (커밋 `3c702cf5`)
|
|
||||||
|
|
||||||
### 5.3 배치면 다수 시 길이 계산 안 됨 (커밋 `606acf91`)
|
|
||||||
|
|
||||||
### 5.4 혹장 계산 roofLine (커밋 `0e4b0b41`)
|
|
||||||
|
|
||||||
### 5.5 `roofWidth` / `roofHeight` null 문제 (커밋 `2cc28147`)
|
|
||||||
|
|
||||||
### 5.6 임시저장 폼 — 물건명 50자 제한 (커밋 `71da4d07`)
|
|
||||||
|
|
||||||
### 5.7 dev 관련 링크 수정 (커밋 `a2bbb035`, `31b01132`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 작업 패턴 — 티켓 시리즈를 다룰 때
|
|
||||||
|
|
||||||
이 트랙은 거의 **티켓([1xxx]) 단위 국소 수정**으로 구성됩니다. 새 티켓이 들어왔을 때:
|
|
||||||
|
|
||||||
1. **재현 시나리오 확보** — 어떤 데이터/플로우에서 발생하는지 명확히 합의.
|
|
||||||
2. **유사 티켓 검색** — 같은 영역에서 비슷한 [1xxx] 가 이미 처리됐는지 git log + 본 문서로 확인.
|
|
||||||
3. **국소 수정** — `SK_MOVEMENT_HANDOVER.md §6 #1` 원칙 (잘 되는 코드 안 건드리기) 엄수.
|
|
||||||
4. **회귀 검증** — 가까운 시나리오 1~2개 추가로 돌려 보기.
|
|
||||||
5. **태그 + 커밋 메시지** — 인라인 `[Tagname YYYY-MM-DD]` + git commit 메시지 제안 (사용자가 직접 커밋).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 미해결 / 추적
|
|
||||||
|
|
||||||
- [1480] 사의 각도 — 5번 수정이 들어간 만큼 완전히 잡혔는지 추적 필요.
|
|
||||||
- [1805] 모듈 설치영역 — 겹치는 선 케이스가 다양해 추가 보고 가능성 있음.
|
|
||||||
- 수동 물떼세 배치 — revert 후 미적용 상태. 재적용 시 revert 사유부터.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 핵심 파일 (영역별 진입점)
|
|
||||||
|
|
||||||
| 영역 | 핵심 파일/디렉터리 |
|
|
||||||
|------|---------------------|
|
|
||||||
| 회로/PCS/접속함 | `src/...PassivityCircuitAllocation*`, 회로 hook |
|
|
||||||
| 모듈 배치 | 모듈 배치/설치 영역 컴포넌트 |
|
|
||||||
| 처마/가대 | `processEavesEdge` 인근 (`skeleton-utils.js`) — SK 트랙과 겹침 주의 |
|
|
||||||
| 견적서/도면 | 출력 컴포넌트 |
|
|
||||||
| dev/링크 | `package.json`, `.env.localhost` |
|
|
||||||
|
|
||||||
(파일 위치는 영역별로 흩어져 있어 git log + 커밋 해시 확인이 가장 빠릅니다.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 인수 직후 권장 절차
|
|
||||||
|
|
||||||
1. 이 문서 통독 → 어느 영역의 티켓이 많은지 감 잡기.
|
|
||||||
2. 가장 최근 [1xxx] 커밋 1~2개를 골라 변경 디프 직접 확인.
|
|
||||||
3. 인접 영역 티켓이 들어오면 본 문서 + git log 로 유사 사례 먼저 검색.
|
|
||||||
|
|
||||||
끝.
|
|
||||||
@ -1,346 +0,0 @@
|
|||||||
# SK 라인 이동 인수인계서
|
|
||||||
|
|
||||||
> 최초 작성일: 2026-04-22
|
|
||||||
> 최근 갱신일: 2026-05-06 (옵션 B 분리 — SK 라인 전용으로 축소)
|
|
||||||
> 대상: 후임 AI 엔지니어 또는 개발 관리자
|
|
||||||
> 작업 브랜치: `hotfix/dev_yscha`
|
|
||||||
|
|
||||||
> **이 문서의 범위**: Straight Skeleton(SK) 라인 이동·계산 트랙만 다룹니다.
|
|
||||||
> SK 외 작업은 다음 별도 문서를 보세요:
|
|
||||||
> - `UI_MODAL_HANDOVER.md` — 모달/다이얼로그/계산기 토글
|
|
||||||
> - `API_STABILITY_HANDOVER.md` — API 안정화 / 인증 / 세션
|
|
||||||
> - `MODULE_CIRCUIT_HANDOVER.md` — 모듈/배치/회로/처마/티켓 시리즈
|
|
||||||
> - `HANDOVER_INDEX.md` — 진입점 인덱스
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 이 문서가 뭔가요?
|
|
||||||
|
|
||||||
이 프로젝트(Qcast Front)에서 가장 까다롭고 위험한 부분인 **"지붕 모양을 바꾸면 내부 선이 깨지는 문제"** 를 어떻게 풀어왔는지 정리한 인수인계 문서입니다.
|
|
||||||
|
|
||||||
이 문서를 읽고 나면:
|
|
||||||
1. 지붕 도면이 어떤 부품으로 구성돼 있는지 알 수 있고,
|
|
||||||
2. 어떤 버그가 있었고 어떻게 풀었는지 따라갈 수 있고,
|
|
||||||
3. 다음에 손대면 안 되는 코드와, 손대도 되는 코드를 구분할 수 있게 됩니다.
|
|
||||||
|
|
||||||
### 같이 봐야 하는 자료
|
|
||||||
- 메모리 인덱스: `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-front/memory/MEMORY.md`
|
|
||||||
- 모든 정책 결정·임계값·"왜 이렇게 했는지" 가 항목별로 들어 있습니다.
|
|
||||||
- **작업을 시작하기 전에 반드시 한 번 통독하세요.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 도메인 1분 설명 — 지붕 도면은 어떻게 구성되나
|
|
||||||
|
|
||||||
이 앱은 **건물 지붕을 그리는 CAD 같은 도구**입니다. 사용자가 건물 외곽선을 그리면, 시스템이 자동으로 지붕(처마)과 그 안쪽 선(용마루 등)을 계산해 그려줍니다.
|
|
||||||
|
|
||||||
### 1.1 화면에 나오는 선 3종류
|
|
||||||
|
|
||||||
| 선 이름 | 색깔 | 누가 만드나? | 어디에 저장되나? |
|
|
||||||
|---------|------|--------------|------------------|
|
|
||||||
| **wallLine (외벽선)** | 초록색 | **사용자가 직접** 그리고 움직임 | `wall.lines`, `wall.baseLines` |
|
|
||||||
| **roofLine (처마선)** | 검정색 | 외벽선에서 일정 거리(offset)만큼 바깥쪽으로 자동 계산됨 | `roof.points` |
|
|
||||||
| **innerLine (내부선)** | 빨강 등 | 자동 계산 + 사용자가 추가한 용마루/추녀 | `roof.innerLines`, `canvas.skeletonLines` |
|
|
||||||
|
|
||||||
> 핵심: 사용자가 만지는 건 **외벽선** 뿐이고, 나머지는 자동 계산입니다. 그래서 외벽선이 움직이면 처마와 내부선이 다 같이 다시 계산됩니다.
|
|
||||||
|
|
||||||
### 1.2 "Straight Skeleton" (줄여서 SK) 가 뭔가요?
|
|
||||||
|
|
||||||
지붕 안쪽의 용마루·추녀 같은 선을 자동으로 그어주는 **수학 알고리즘**입니다. 다각형(외벽선)을 입력으로 주면, 안쪽으로 똑같은 속도로 줄어드는 가상의 선들이 만나는 자취가 나옵니다. 이게 곧 용마루 라인이 됩니다.
|
|
||||||
|
|
||||||
이 프로젝트의 가장 큰 어려움은 **이 SK 알고리즘이 입력 값에 매우 예민**해서, 외벽선을 조금만 잘못 움직여도 결과가 무너진다는 점입니다.
|
|
||||||
|
|
||||||
### 1.3 외벽선 이동의 4가지 방향
|
|
||||||
|
|
||||||
사용자가 외벽선을 잡고 끌면, 시스템은 그걸 다음 4가지로 분류합니다:
|
|
||||||
|
|
||||||
- 위치: `top` (위) / `bottom` (아래) / `left` (왼쪽) / `right` (오른쪽)
|
|
||||||
- 방향: `in` (안쪽으로) / `out` (바깥쪽으로)
|
|
||||||
|
|
||||||
이 8개 조합을 다 처리해야 합니다.
|
|
||||||
|
|
||||||
### 1.4 "오버 이동" 이란?
|
|
||||||
|
|
||||||
외벽선을 너무 많이 바깥쪽으로 끌면, 안쪽 도형이 뒤집혀 버립니다(예: ㄴ자가 ㄱ자가 됨). 이걸 **"경계를 넘었다(crossed)"** 고 부릅니다.
|
|
||||||
|
|
||||||
이 프로젝트의 핵심 골칫거리가 바로 이겁니다:
|
|
||||||
- 사용자가 실수로/일부러 너무 많이 끌었을 때
|
|
||||||
- 그동안 사용자가 힘들게 추가한 보조선들이 사라지면 안 됨
|
|
||||||
- 동시에 SK 자동 계산은 정상적으로 다시 동작해야 함
|
|
||||||
|
|
||||||
### 1.5 4가지 판정 상태
|
|
||||||
|
|
||||||
이동이 들어오면 시스템이 다음 중 하나로 판정합니다:
|
|
||||||
|
|
||||||
| 판정 | 의미 |
|
|
||||||
|------|------|
|
|
||||||
| `ok` | 안전한 이동, 정상 처리 |
|
|
||||||
| `on-boundary` | 평행한 위치까지 딱 맞게 도달, 정상 처리 |
|
|
||||||
| `crossed` | 경계를 넘었음(뒤집힘), **특별 경로**로 처리 |
|
|
||||||
| `unknown` | 판정 불가, 일단 기존 흐름 진행 |
|
|
||||||
|
|
||||||
판정 함수: `verifyMoveBoundary()` (`src/util/skeleton-utils.js:510`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 어떤 문제가 있었나 (수정 전 증상)
|
|
||||||
|
|
||||||
1. 외벽선을 우측 상단으로 강하게 끌면, 화면에 **있지도 않은 가짜 수직선**이 그려졌습니다.
|
|
||||||
2. 외벽선을 **두 번 연속 이동**하면, 첫 번째 이동 결과가 사라지고 도형이 무너졌습니다.
|
|
||||||
3. 경계를 넘는 이동을 하면 **사용자가 그동안 추가한 보조선들이 다 사라졌습니다**. — 이게 사용자가 가장 싫어하던 문제입니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 왜 이런 문제가 났나 (근본 원인)
|
|
||||||
|
|
||||||
### 3.1 좌표 기록이 짧게 잘려서 저장됐음
|
|
||||||
|
|
||||||
이동 후에 도형의 좌표를 `lastPoints` 라는 변수에 저장하는데, 이 저장 과정에서 **점 일부가 빠졌습니다**. 그래서 그다음 이동이 들어왔을 때 원본(`roof.points`) 과 **점 개수가 안 맞아서** 매칭이 깨졌습니다.
|
|
||||||
|
|
||||||
### 3.2 보정 함수가 항상 "원본"만 기준으로 비교했음
|
|
||||||
|
|
||||||
오버 이동을 감지하는 함수(`calcOverCorrectedPoints`) 가 1차 이동 결과를 무시하고 항상 원래 도형하고만 비교했습니다. 그래서 2차 이동에서 오버가 감지되면 **1차 이동도 같이 되돌려버리는** 부작용이 있었습니다.
|
|
||||||
|
|
||||||
### 3.3 화면을 먼저 지운 뒤 다시 그렸음
|
|
||||||
|
|
||||||
`drawHelpLine` 함수가 SK 재계산을 하기 **전에** 캔버스를 먼저 다 지웁니다. 그래서 SK 재계산 도중에 문제가 생겨도 **화면은 이미 텅 빈 상태**가 됩니다.
|
|
||||||
|
|
||||||
> **(2026-05-06 현재)** 위 분석은 옛 정책 기준입니다. 지금은 `crossed` 일 때 `drawSkeletonRidgeRoofFromBaseLines` 라는 **오버 전용 그리기 함수**로 갈아타서, 화면이 비어 있어도 곧바로 다시 채워줍니다. 또 §6.1 의 클램프 정책 덕분에 애초에 `crossed` 자체가 잘 발생하지 않습니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 어떻게 풀었나 (2026-04-22 시점 작업)
|
|
||||||
|
|
||||||
### 4.1 새로 추가한 함수들 (기존 함수는 절대 안 건드림)
|
|
||||||
|
|
||||||
| 함수 | 위치 | 한마디로 |
|
|
||||||
|------|------|---------|
|
|
||||||
| `buildRawMovedPoints(roofId, canvas)` | `skeleton-utils.js:385` | 점을 가공/축소 없이 **원본 그대로** 이동시킨 결과를 만들어 줌 |
|
|
||||||
| `verifyMoveBoundary(roofId, canvas)` | `skeleton-utils.js:510` | "이 이동이 안전한가?" 를 4단계 (`ok`/`on-boundary`/`crossed`/`unknown`)로 판정해 줌 |
|
|
||||||
| `calcOverCorrectedPointsSafe(...)` | `skeleton-utils.js:3222` | 기존 보정 함수가 "이전 이동 결과"를 잊어버리는 문제를 우회. 이전 결과를 합쳐서 가짜 원본을 만들어 넘김 |
|
|
||||||
|
|
||||||
### 4.2 안전장치(가드) 3곳
|
|
||||||
|
|
||||||
옛 정책에서는 "위험 감지 → 즉시 멈춤" 이었습니다. **현재(2026-05-06)는** 멈추는 대신 **다른 경로로 가게** 바뀌었습니다.
|
|
||||||
|
|
||||||
| 어디 | 무슨 일을 하나 |
|
|
||||||
|------|---------------|
|
|
||||||
| `skeleton-utils.js:1071` (skeletonBuilder 안) | `verifyMoveBoundary` 결과 확인 → `crossed` 면 **경고만 찍고 그대로 진행** |
|
|
||||||
| `skeleton-utils.js:1331` (catch 블록) | 에러가 나도 전체 상태를 리셋하지 않음. 해당 지붕만 false 처리 |
|
|
||||||
| `QPolygon.js:467` (drawHelpLine 진입부) | 결과를 `__verdict` 변수에 기록만. 용마루 분기에서 `crossed` 면 **오버 전용 함수**로 분기 |
|
|
||||||
|
|
||||||
### 4.3 좌표 저장을 풀 길이로 보존
|
|
||||||
|
|
||||||
`skeletonBuilder` 끝부분(`skeleton-utils.js:1315`)에서:
|
|
||||||
```js
|
|
||||||
const rawMovedFull = buildRawMovedPoints(roofId, canvas)
|
|
||||||
if (rawMovedFull && rawMovedFull.length === (roof.points?.length ?? 0)) {
|
|
||||||
canvas.skeleton.lastPoints = rawMovedFull // 점 개수 보존
|
|
||||||
} else {
|
|
||||||
canvas.skeleton.lastPoints = roofLineContactPoints // 안 맞으면 기존 방식
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4 처음 검증 결과 (2026-04-22)
|
|
||||||
- 1차 이동 820 → 정상.
|
|
||||||
- 2차 이동 1912 (경계 넘는 이동) → 옛 정책에서는 화면이 그대로 유지됐음.
|
|
||||||
- **현재는** 같은 입력에서 오버 전용 함수로 분기해서 SK 가 정상 재계산됩니다. 그리고 §6.1 의 클램프 정책이 들어와서 실서비스에서는 애초에 `crossed` 가 잘 안 납니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 아직 안 끝난 일 / 추적 중
|
|
||||||
|
|
||||||
### 5.1 사용자가 추가한 라인을 따로 보관하기 (Step 5, **미완료**)
|
|
||||||
|
|
||||||
**문제:** SK 가 다시 계산될 때마다 `roof.innerLines` 를 통째로 덮어씁니다. 사용자가 손으로 추가한 용마루/추녀 같은 라인은 SK 결과에 안 들어가니까 **사라질 위험**이 있습니다.
|
|
||||||
|
|
||||||
**제안:**
|
|
||||||
- `roof.userAddedLines` 라는 별도 보관함을 만들고
|
|
||||||
- 라인 추가 UI 에서 거기에 저장한 다음
|
|
||||||
- SK 결과 + 사용자 추가분을 합쳐서 `innerLines` 에 넣기
|
|
||||||
|
|
||||||
```js
|
|
||||||
roof.innerLines = [
|
|
||||||
...createInnerLinesFromSkeleton(...), // 자동 계산
|
|
||||||
...(roof.userAddedLines ?? []), // 사용자 추가
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 용마루 이동(`moveFlowLine`) 경로 판정 (**미완료**)
|
|
||||||
|
|
||||||
지금 `verifyMoveBoundary` 는 외벽 이동만 판정합니다. 용마루를 직접 잡고 끄는 경우는 항상 `unknown` 이 나옵니다. 이 경로에서도 경계 판정이 필요해지면 `buildRawMovedPoints` 를 확장해야 합니다.
|
|
||||||
|
|
||||||
### 5.3 빨강 처마 보조선 잔존 (**추적 중, 휴면**)
|
|
||||||
|
|
||||||
오버 이동을 클램프하면 빨강 색의 처마 보조선(`eaveHelpLine`) 이 화면에 남는 별건 이슈가 있습니다. 2026-04-27 이후 재개 예정 (메모리: `project_over_move_red_helper_deferred.md`).
|
|
||||||
|
|
||||||
### 5.4 v2.0 설계 (**휴면**)
|
|
||||||
|
|
||||||
v2.0 으로 가는 새 설계 방향이 있었지만 **2026-05-06 자로 휴면**입니다. 사용자 명령 전까지는 v2 관련 메모리(`project_v2_*`) 를 참조하지 마세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 꼭 지켜야 할 9가지 원칙
|
|
||||||
|
|
||||||
이 프로젝트에서 이 원칙들을 어기면 거의 반드시 새로운 버그가 생깁니다.
|
|
||||||
|
|
||||||
| # | 원칙 | 한 줄 설명 |
|
|
||||||
|---|------|-----------|
|
|
||||||
| 1 | **잘 되는 코드는 안 건드리기** | 버그 수정은 **최소 지점만**. 기존 함수 수정 금지. 필요하면 새 함수를 만드세요. |
|
|
||||||
| 2 | **고치기 전에 흐름부터 확인하기** | 한 파일만 보고 추측해서 고치지 마세요. 호출 체인 전체를 따라가서 어디를 고칠지 정한 다음 작업하세요. |
|
|
||||||
| 3 | **수정한 곳에는 태그를 남기기** | 수정한 줄 위에 `// [태그명 YYYY-MM-DD]` 주석을 달고, 작업 끝나면 git commit 메시지를 사용자에게 제시하세요 (커밋은 사용자가 직접 함). |
|
|
||||||
| 4 | **오버 이동은 평행 위치까지만 (clamp)** | 사용자가 너무 많이 끌면 평행한 위치까지만 잘라줍니다. 정책은 `OVER_MOVE_POLICY` 상수로 바꿀 수 있어요. |
|
|
||||||
| 5 | **화면 표시값과 내부 좌표는 따로** | 라벨이 흔들려 보이면 표시 단계에서 평균을 내는 식으로 보정하세요. 내부 좌표를 함부로 round 하면 곳곳에서 깨집니다. |
|
|
||||||
| 6 | **좌표 비교 허용오차는 0.5** | UI/Big.js 계산 오차 때문에 0.1 로 비교하면 자주 매칭 실패합니다. |
|
|
||||||
| 7 | **디버그 라벨은 별도 파일 만들지 말기** | `QPolygon.js` 안에 인라인으로 두고, `NEXT_PUBLIC_RUN_MODE === 'local'` 일 때만 표시되도록 가드. |
|
|
||||||
| 8 | **V1 한 트랙만** | v2.0 은 휴면 중. 사용자가 직접 명령하기 전까지 v2 관련 코드/메모리 언급 금지. |
|
|
||||||
| 9 | **moveLine + offset 이중 적용 방지** | offset 변경 후 baseLines 복원 → `drawHelpLine` 호출 시 `roof.moveFlowLine` / `roof.moveUpDown` 을 미리 설정하면 SK 빌더가 또 한 번 이동시켜 **이중 이동**이 발생합니다. 순서: `baseLines 복원 → drawHelpLine(moveLine 없이) → moveLine 속성 복원`. (메모리: `feedback_moveline_offset.md`) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 전체 흐름 (사용자가 외벽선을 끌었을 때)
|
|
||||||
|
|
||||||
```
|
|
||||||
사용자가 마우스로 외벽선을 끌어다 놓음
|
|
||||||
↓
|
|
||||||
useMovementSetting.js __onMouseDown 핸들러
|
|
||||||
↓
|
|
||||||
handleSave() (line 514)
|
|
||||||
├─ roof.moveUpDown / moveSelectLine / moveDirect / movePosition 세팅
|
|
||||||
│ = "어느 라인을 어느 방향으로 얼마나 옮기는가" 정보를 저장
|
|
||||||
│
|
|
||||||
├─ ★ 안전장치: OVER_GUARD (line 895~940)
|
|
||||||
│ 너무 많이 끌었으면 평행 직전까지 잘라줌 (clamp 정책)
|
|
||||||
│ - 'clamp' (기본): 평행까지만 자르고 진행
|
|
||||||
│ - 'reject' : 아무것도 안 함
|
|
||||||
│ - 'passthrough' : 옛 동작 (위험)
|
|
||||||
│
|
|
||||||
├─ wall.baseLines 좌표를 실제로 변경
|
|
||||||
│
|
|
||||||
└─ roof.drawHelpLine() 호출
|
|
||||||
↓
|
|
||||||
QPolygon.js drawHelpLine (line 467)
|
|
||||||
├─ ★ verifyMoveBoundary 로 판정 → __verdict 변수에 보관
|
|
||||||
├─ 캔버스에서 내부 그림 다 지움 + innerLines = []
|
|
||||||
├─ 지붕 종류별로 분기:
|
|
||||||
│ - 용마루형(Eaves) + crossed → drawSkeletonRidgeRoofFromBaseLines (오버 전용)
|
|
||||||
│ - 용마루형 그 외 → drawSkeletonRidgeRoof (일반)
|
|
||||||
│ - 박공형(Gable) / 외쪽(Shed) / 패턴 → 각각 전용 함수
|
|
||||||
↓
|
|
||||||
skeletonBuilder (skeleton-utils.js)
|
|
||||||
├─ ★ 안전장치: verifyMoveBoundary (line 1071)
|
|
||||||
│ crossed 여도 멈추지 않고 경고만 찍음
|
|
||||||
├─ safeMovedPointsWithFallback → movingLineFromSkeleton
|
|
||||||
├─ wall.baseLines 직접 사용 (V1 정책)
|
|
||||||
├─ SkeletonBuilder.BuildFromGeoJSON ← 진짜 SK 계산
|
|
||||||
├─ createInnerLinesFromSkeleton (line 937 부근)
|
|
||||||
│ 끝에 가짜 ridge 제거(cull) 2단계 추가됨
|
|
||||||
│ ⚠ 사용자 추가 라인 보존(Step 5) 미완료
|
|
||||||
└─ canvas.skeleton.lastPoints 저장 (line 1315, 풀 길이 보장)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. SK 라인 — 그동안 누적된 작업
|
|
||||||
|
|
||||||
> 모든 항목은 §6 의 원칙(특히 #1, #4)을 만족합니다. **기존 함수를 고치지 않고**, 진입부에 안전장치를 추가하거나 임계값을 조정하거나 끝부분에서 정리하는 식으로 처리했습니다.
|
|
||||||
|
|
||||||
### 8.1 오버 이동 정책 — 평행 직전까지 자동 클램프
|
|
||||||
- **장소**: `useMovementSetting.handleSave` 의 `OVER_GUARD` 블록 (line 895~940)
|
|
||||||
- **상수**: 파일 맨 위 `OVER_MOVE_POLICY = 'clamp' | 'reject' | 'passthrough'` (기본 `clamp`)
|
|
||||||
- **메모리**: `feedback_over_move_silent_skip.md`
|
|
||||||
|
|
||||||
### 8.2 평행 이동 시 SK 가 무너지는 문제
|
|
||||||
- **증상**: 외벽선을 평행 이동했을 때 점 개수 매칭이 깨졌습니다.
|
|
||||||
- **수정**: 좌표 변경 경로 2곳을 **인덱스 직접 매핑** 방식으로 통일.
|
|
||||||
- **메모리**: `project_parallel_collapse_fix.md`
|
|
||||||
|
|
||||||
### 8.3 정렬 기준을 `wallLines` 에서 `roofLines` 로 변경
|
|
||||||
- **이유**: wallLines 기준으로 정렬하니까 후처리에서 자꾸 어긋났습니다.
|
|
||||||
- **수정**: 정렬 master 를 `roofLines` 로. **2026-05-06**: `ensureCounterClockwiseLines` 의 그래프 키도 `0.1 round` 로 통일 (커밋 `6f41e127`).
|
|
||||||
- **메모리**: `project_index_master_roofLines.md`
|
|
||||||
|
|
||||||
### 8.4 SHOULDER_ABSORBED 임계값 통일
|
|
||||||
- 4개 경로에서 같은 임계값(< 10) 을 쓰도록 정리.
|
|
||||||
- **메모리**: `project_shoulder_absorbed_thresholds.md`
|
|
||||||
|
|
||||||
### 8.5 가짜 ridge 제거 (Phantom ridge cull)
|
|
||||||
- **증상**: SK 계산 오차로 길이 0인 가짜 ridge 가 생겼습니다.
|
|
||||||
- **수정**: `createInnerLinesFromSkeleton` 끝부분에서 **2단계로 청소** (zero-length → dead-end).
|
|
||||||
- **메모리**: `project_phantom_ridge_cull.md`
|
|
||||||
|
|
||||||
### 8.6 HIP 와 RIDGE 구분 임계값
|
|
||||||
- **증상**: 외벽 이동 오차로 ridge 가 hip 으로 잘못 분류됐습니다.
|
|
||||||
- **수정**: `addRawLine` 의 대각 판정 임계 `0.1 → 5.0mm`.
|
|
||||||
- **메모리**: `project_hip_ridge_threshold.md`
|
|
||||||
|
|
||||||
### 8.7 Hip pitch 기본값
|
|
||||||
- **증상**: hip 의 평면도/실측 토글이 동작 안 했습니다.
|
|
||||||
- **수정**: `processEavesEdge:2915` 에서 `outerLine.pitch ?? roof.pitch ?? 0` 폴백.
|
|
||||||
- **메모리**: `project_hip_pitch_fallback.md`
|
|
||||||
|
|
||||||
### 8.8 보조선(auxiliaryLine) 통합 단계 제외
|
|
||||||
- **이유**: 보조선이 통합에 끼면 코너 끝점이 사라져서 split 이 실패.
|
|
||||||
- **수정**: `integrateExtensionLines` 에서 보조선 제외.
|
|
||||||
- **메모리**: `project_aux_line_integrate_exclusion.md`
|
|
||||||
|
|
||||||
### 8.9 마루(ridge) 이동 보강
|
|
||||||
- **2차 이동에서 ridge 클릭 안 됨** → `useMovementSetting` 의 `[type]` effect 에 `ridge.selectable = true` fallback.
|
|
||||||
- **반대측 wall 오선택** → 거리 필터 전에 x/y 겹침 사전필터 추가.
|
|
||||||
- **메모리**: `project_ridge_2nd_move_fix.md`, `project_marudo_xoverlap_prefilter.md`
|
|
||||||
|
|
||||||
### 8.10 회귀 검증 5 시나리오
|
|
||||||
- 어떤 SK 수정이든 끝나면 5개 시나리오로 동작 확인 필수.
|
|
||||||
- **메모리**: `project_skeleton_regression_checklist.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 메모리 인덱스 (SK 관련, 전부 `~/.claude/projects/-Users-cha-Works-Projects-qcast3-qcast-front/memory/`)
|
|
||||||
|
|
||||||
### 원칙(`feedback_*`)
|
|
||||||
- `feedback_no_touch_working_code.md` — 잘 되는 코드 안 건드리기
|
|
||||||
- `feedback_full_analysis_before_action.md` — 흐름 먼저 확인
|
|
||||||
- `feedback_inline_change_tag.md` — 수정 태그 + 커밋 메시지
|
|
||||||
- `feedback_over_move_silent_skip.md` — 오버 이동 클램프 정책
|
|
||||||
- `feedback_moveline_offset.md` — moveLine + offset 이중 적용 방지 ★
|
|
||||||
- `feedback_display_vs_internal_values.md` — 표시값 vs 내부값 분리
|
|
||||||
- `feedback_same_point_tolerance.md` — 좌표 tolerance 0.5
|
|
||||||
- `feedback_debug_labels_local_only.md` — 디버그 라벨 인라인
|
|
||||||
- `feedback_v1_v2_dual_track.md` — V1 단일 트랙
|
|
||||||
|
|
||||||
### SK / 이동 (`project_*`)
|
|
||||||
- `project_parallel_collapse_fix.md`
|
|
||||||
- `project_index_master_roofLines.md`
|
|
||||||
- `project_shoulder_absorbed_thresholds.md`
|
|
||||||
- `project_phantom_ridge_cull.md`
|
|
||||||
- `project_hip_ridge_threshold.md`
|
|
||||||
- `project_hip_pitch_fallback.md`
|
|
||||||
- `project_aux_line_integrate_exclusion.md`
|
|
||||||
- `project_ridge_2nd_move_fix.md`
|
|
||||||
- `project_marudo_xoverlap_prefilter.md`
|
|
||||||
- `project_skeleton_regression_checklist.md`
|
|
||||||
- `project_over_move_red_helper_deferred.md` (빨강 보조선 잔존, 추적 중)
|
|
||||||
|
|
||||||
### 휴면 (참조 금지)
|
|
||||||
- `project_v2_design_direction.md`
|
|
||||||
- `project_v2_invariant_no_reset.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 인수받은 직후 추천 순서
|
|
||||||
|
|
||||||
1. **이 문서 §1~§3** 읽기 — 도메인 감 잡기.
|
|
||||||
2. **§6 의 원칙 9개** 머리에 박기.
|
|
||||||
3. **메모리 `MEMORY.md` 인덱스** 통독.
|
|
||||||
4. **§8.10 의 회귀 체크리스트 5 시나리오** 직접 실행해서 현재 동작 확인.
|
|
||||||
5. §5 의 미완료 항목 중 사용자 우선순위에 따라 착수.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 마지막 한마디
|
|
||||||
|
|
||||||
이 프로젝트는 **수학적으로 예민한 SK 계산** 위에 사용자가 **수많은 수동 작업을 쌓아 올린** 구조입니다. 그래서 새 기능보다는 **회귀 방지가 항상 우선**입니다.
|
|
||||||
|
|
||||||
"잘 되는 코드는 건들지 말고", "오버 이동은 평행까지만 잘라주고", "사용자가 추가한 라인은 절대 사라지지 않게" — 이 세 줄만 머리에 두고 가시면 됩니다.
|
|
||||||
|
|
||||||
끝 (2026-05-06 갱신, SK 전용으로 분리).
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
# UI / 모달 / 다이얼로그 인수인계서
|
|
||||||
|
|
||||||
> 작성일: 2026-05-06 (옵션 B 분리)
|
|
||||||
> 대상: 후임 AI 엔지니어 또는 개발 관리자
|
|
||||||
> 작업 브랜치: `hotfix/dev_yscha`
|
|
||||||
|
|
||||||
> **이 문서의 범위**: 모달/다이얼로그/계산기 토글/라인 표시 같은 UI 트랙만 다룹니다.
|
|
||||||
> 다른 트랙은 `HANDOVER_INDEX.md` 참조.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. 한 줄 요약
|
|
||||||
|
|
||||||
여러 입력 모달에 **계산기 패드 토글 버튼**을 추가하고, 외벽선/평면도 같은 중요한 단계에 **확정 다이얼로그**를 붙이고, 라인 라벨/색/인덱스 표시를 정돈한 트랙입니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 계산기 패드 토글 — 대규모 시리즈
|
|
||||||
|
|
||||||
사용자가 모달의 숫자 입력 칸을 누르면 계산기 패드가 떠야 하는 요구사항이 있어, 거의 모든 입력 모달에 토글 버튼을 추가했습니다.
|
|
||||||
|
|
||||||
### 1.1 적용된 모달
|
|
||||||
|
|
||||||
| 모달 | 커밋 |
|
|
||||||
|------|------|
|
|
||||||
| 補助線 사이즈변경 / 이동 / 복사 | `60cb8691` |
|
|
||||||
| サイズ変更 (사이즈 변경) | `bc389e3a` |
|
|
||||||
| モジュール / 架台設定 (모듈/거치대) | `b10fc270` |
|
|
||||||
| 配置面初期設定 (배치면 초기 설정) | `025b7ede` |
|
|
||||||
| 屋根形状の設定 (지붕 형상 설정) | `ebc6181b` |
|
|
||||||
| オブジェクト配置 (오브젝트 배치) | `678b9442` |
|
|
||||||
| 물건 등록 | `3365000b`, `662a4653` |
|
|
||||||
| 견적서 | `ea849ee5` |
|
|
||||||
| 일반 (공통) | `1d4d1ab3` |
|
|
||||||
|
|
||||||
### 1.2 동작
|
|
||||||
- 입력 칸 옆 **계산기 아이콘** 클릭 → 키패드 표시/숨김 토글
|
|
||||||
- 키패드는 `+ - × ÷` 사칙 연산 + 즉시 반영
|
|
||||||
|
|
||||||
### 1.3 주의
|
|
||||||
- 모달마다 입력 컴포넌트 구조가 조금씩 달라서 **공통 컴포넌트 한 개로 통합돼 있지 않습니다**. 같은 패턴이 여러 모달에 반복 구현돼 있어요.
|
|
||||||
- 새 모달에 추가할 때는 가장 비슷한 모달의 코드를 베이스로 잡으세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 일본어 모달 토글 추가 시리즈
|
|
||||||
|
|
||||||
UI 옵션을 켜고 끄는 토글 스위치를 일본어 모달들에 추가한 별도 시리즈입니다.
|
|
||||||
|
|
||||||
| 모달 | 커밋 |
|
|
||||||
|------|------|
|
|
||||||
| 外壁の編集とオフセット (외벽 편집/오프셋) | `6967b8cd` |
|
|
||||||
| 棟線移動・桁上げ下げ (마루 이동/도리 올리기 내리기) | `84ac6d50` |
|
|
||||||
| 軒/ケラバ変更 (처마/박공 변경) | `94f050d9` |
|
|
||||||
| 補助線の作成 (보조선 작성) | `2c19a208`, `a0cac795` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 확정 다이얼로그 — 외벽선 / 평면도
|
|
||||||
|
|
||||||
### 3.1 외벽선 확정
|
|
||||||
- 커밋 `7d1e49a1`
|
|
||||||
- 외벽선 작성을 끝낼 때 **"확정하시겠습니까?"** 다이얼로그.
|
|
||||||
- **버튼 클릭** + **Enter 키** 양쪽 다 지원.
|
|
||||||
|
|
||||||
### 3.2 평면도 확정
|
|
||||||
- 커밋 `0e3acedf`
|
|
||||||
- 평면도 단계 종료 시 같은 패턴.
|
|
||||||
|
|
||||||
### 3.3 왜 추가했나
|
|
||||||
- 사용자가 실수로 다음 단계로 넘어가면 **앞 단계로 되돌리기 어려운 작업**이 있습니다. 그래서 명시적으로 확정 단계를 둠.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 라인 오버 → 평행 라인 자동 전환
|
|
||||||
|
|
||||||
- 커밋 `26b583e2`
|
|
||||||
- 사용자가 외벽선을 너무 많이 끌면 **평행 라인까지만 잘라서 자동 변환**.
|
|
||||||
- SK 트랙의 **clamp 정책**(`SK_MOVEMENT_HANDOVER.md §8.1`)과 같은 사용자 경험을 만들어주는 UI 측 동작.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 라인 표시·관리 정리 (작은 작업 모음)
|
|
||||||
|
|
||||||
| 항목 | 커밋 |
|
|
||||||
|------|------|
|
|
||||||
| 잔여선 삭제 + 라벨 표시 (local) | `1d00e000` |
|
|
||||||
| 라인 줄 표시 / 인덱스 재설치 | `8fef6c9f`, `428ff36a` |
|
|
||||||
| 라인 색 정리 | `02c8422e` |
|
|
||||||
| 동일 라벨 처리 | `c2215157` |
|
|
||||||
| innerLine 정리 | `c2eba138` |
|
|
||||||
| 라벨 변경 | `376ccab8` |
|
|
||||||
| 빌드 오류 수정 — `debugLabels import` 제거 (정책 정착 계기) | `6b02b2ca` |
|
|
||||||
|
|
||||||
> `debugLabels` 별도 파일 금지 정책이 여기서 정착됐습니다 (`SK_MOVEMENT_HANDOVER.md §6 #7` 원칙). 디버그 라벨은 반드시 `QPolygon.js` 인라인 + `NEXT_PUBLIC_RUN_MODE === 'local'` 가드.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 보조라인 / SK 라인 기록 보존
|
|
||||||
|
|
||||||
- `3c1a1f89` 보조라인 맞춤
|
|
||||||
- `7a987116` SK 라인 기록 보존
|
|
||||||
- 사용자가 추가한 라인이 SK 재계산에서 사라지면 안 된다는 정책의 연장선 (SK 트랙 §5.1 미완료와 연계).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 배치 / 지붕 이동 보강
|
|
||||||
|
|
||||||
| 항목 | 커밋 |
|
|
||||||
|------|------|
|
|
||||||
| 배치면 이동 시 화살표 위치 수정 | `8eca824c` |
|
|
||||||
| 지붕 이동 시 보조선 길이 안 따라가는 현상 | `21f25995` |
|
|
||||||
| offset ↔ moveline 벽라인 정상 이동 | `20235ac2` |
|
|
||||||
| 움직인 라인 그대로 유지 offset | `94f18d15` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 핵심 파일
|
|
||||||
|
|
||||||
| 파일 | 역할 |
|
|
||||||
|------|------|
|
|
||||||
| 補助線 모달 컴포넌트 | 계산기 토글 (커밋 `60cb8691` 으로 정확 위치 확인) |
|
|
||||||
| `src/components/fabric/QPolygon.js` | 라인 표시·라벨 (인라인 디버그 라벨 포함) |
|
|
||||||
| `src/hooks/roofcover/useMovementSetting.js` | 라인 오버 시 평행 변환 등 이동 UX |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 저구배 + 특정 기와 시공 매뉴얼 안내 (2026-05-06, 커밋 `a5d2964e`)
|
|
||||||
|
|
||||||
### 9.1 무엇을
|
|
||||||
6종 기와 제품을 **저구배 (2 ≤ 寸 < 2.5 또는 11.31° ≤ 角度 < 14.04°)** 로 사용하려 할 때, 저장 직전에 시공 매뉴얼 확인 안내 alert 을 띄움 (OK 만 — 중단 흐름 없음).
|
|
||||||
|
|
||||||
### 9.2 트리거 3 곳
|
|
||||||
| # | 트리거 | 핸들러 |
|
|
||||||
|---|---|---|
|
|
||||||
| 1 | 配置面 초기설정 → 「保存」 | `PlacementShapeSetting.jsx` `handleSaveBtn` |
|
|
||||||
| 2 | 平面図入力 → 「屋根面の割り当て」 | `useRoofAllocationSetting.js` `handleSave` |
|
|
||||||
| 3 | [配置面]屋根材変更 → 「屋根面の割り当て」 | `useRoofAllocationSetting.js` `handleSaveContext` |
|
|
||||||
|
|
||||||
### 9.3 대상 屋根材 6종 (`getRoofMaterialList` 응답 기준)
|
|
||||||
`ROOF_ID_WA_53A` (和瓦A), `ROOF_ID_WA_53B` (和瓦B), `ROOF_ID_HIRA_C` (平板瓦C), `ROOF_ID_HIRA_D` (平板瓦D), `ROOF_ID_HIRA_S` (S瓦), `ROOF_ID_HIRA_SEME` (セメント瓦)
|
|
||||||
|
|
||||||
### 9.4 동작
|
|
||||||
- 안내 alert (OK 만). **중단 흐름 없음** — OK 누르면 저장 진행.
|
|
||||||
- 등록된 지붕재가 여러 개일 경우, 해당 항목마다 **순차로** alert.
|
|
||||||
- 세션 캐시 없음 — 트리거마다 매번 표시.
|
|
||||||
|
|
||||||
### 9.5 핵심 파일
|
|
||||||
| 파일 | 역할 |
|
|
||||||
|------|------|
|
|
||||||
| `src/util/roof-pitch-warning.js` | `LOW_PITCH_RESTRICTED_ROOF_IDS` Set + `isLowPitchRestricted` + `notifyLowPitchRestrictionForRoofs` |
|
|
||||||
| `src/locales/{ko,ja}.json` | i18n 키 `modal.roof.material.low.pitch.warning` |
|
|
||||||
|
|
||||||
### 9.6 임계 변경 시 주의
|
|
||||||
- 寸 / 度 둘 다 OR 조건. `roofAngleSet` 이 `'slope'` 면 `pitch` 만, `'flat'` 이면 `angle` 만 갱신될 수 있어서.
|
|
||||||
- 6종 ID 변경은 `LOW_PITCH_RESTRICTED_ROOF_IDS` 만 수정하면 됨. 트리거 3개 핸들러는 수정 불필요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. 인수 직후 권장 절차
|
|
||||||
|
|
||||||
1. 이 문서 통독.
|
|
||||||
2. 계산기 토글 모달 1개 (예: 보조선 사이즈변경) 직접 띄워서 동작 확인.
|
|
||||||
3. 외벽선 확정 다이얼로그 동작 확인 (Enter 키 포함).
|
|
||||||
4. 라인 오버 → 평행 자동 전환 확인 (SK 트랙 §8.1 와 함께).
|
|
||||||
5. 저구배 + 6종 기와 confirm 동작 확인 (배치면 초기설정에서 和瓦A + 寸=2 저장 → 팝업 확인).
|
|
||||||
|
|
||||||
끝.
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# 점 갯수 별 타입
|
|
||||||
|
|
||||||
## 점 6개
|
|
||||||
|
|
||||||
### type1
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### type2
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### type3
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### type4
|
|
||||||
|
|
||||||

|
|
||||||
@ -30,9 +30,26 @@ export default function ObjectSetting({ id, pos = { x: 50, y: 230 } }) {
|
|||||||
//오브젝트 배치로 넘어오면 면형상 선택 불가
|
//오브젝트 배치로 넘어오면 면형상 선택 불가
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
canvas.discardActiveObject()
|
canvas.discardActiveObject()
|
||||||
|
|
||||||
|
// [ROOF-SELECTABLE-RESTORE 2026-05-08] 모달 닫힘 시 roof selectable 원복.
|
||||||
|
// 누락되어 있어 한 번 진입 후엔 지붕 선택 불가 → 자동배치 단계에서 다른 hook 이 다시
|
||||||
|
// selectable=true 로 set 해주기 전까진 클릭 무반응 상태가 지속됐음.
|
||||||
|
// 무조건 true 로 복원하면 동시에 열린 다른 모드(roof.selectable=false 의도) 와 충돌 가능 →
|
||||||
|
// 진입 시점의 원래 값을 snapshot 해두고 cleanup 시 그 값으로 복원(다른 모드의 의도 보존).
|
||||||
|
const prevSelectable = surfaceShapePolygons.map((obj) => ({ obj, selectable: obj.selectable }))
|
||||||
|
|
||||||
surfaceShapePolygons.forEach((obj) => {
|
surfaceShapePolygons.forEach((obj) => {
|
||||||
obj.set({ selectable: false })
|
obj.set({ selectable: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
prevSelectable.forEach(({ obj, selectable }) => {
|
||||||
|
// 객체가 이미 캔버스에서 제거됐을 수도 있으므로 canvas 멤버십 확인 후 복원.
|
||||||
|
if (canvas?.getObjects().includes(obj)) {
|
||||||
|
obj.set({ selectable })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { useObjectBatch } from '@/hooks/object/useObjectBatch'
|
|||||||
import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common'
|
import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||||
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch'
|
||||||
import { CalculatorInput } from '@/components/common/input/CalcInput'
|
import { CalculatorInput } from '@/components/common/input/CalcInput'
|
||||||
|
import { useSwal } from '@/hooks/useSwal'
|
||||||
|
|
||||||
export default function SizeSetting(props) {
|
export default function SizeSetting(props) {
|
||||||
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
const contextPopupPosition = useRecoilValue(contextPopupPositionState)
|
||||||
@ -20,6 +21,7 @@ export default function SizeSetting(props) {
|
|||||||
const { closePopup } = usePopup()
|
const { closePopup } = usePopup()
|
||||||
const { resizeObjectBatch } = useObjectBatch({})
|
const { resizeObjectBatch } = useObjectBatch({})
|
||||||
const { resizeSurfaceShapeBatch } = useSurfaceShapeBatch({})
|
const { resizeSurfaceShapeBatch } = useSurfaceShapeBatch({})
|
||||||
|
const { swalFire } = useSwal()
|
||||||
const widthRef = useRef(null)
|
const widthRef = useRef(null)
|
||||||
const heightRef = useRef(null)
|
const heightRef = useRef(null)
|
||||||
const [width, setWidth] = useState(target?.width ? (target.width * 10).toFixed() : 0)
|
const [width, setWidth] = useState(target?.width ? (target.width * 10).toFixed() : 0)
|
||||||
@ -46,6 +48,15 @@ export default function SizeSetting(props) {
|
|||||||
const width = widthRef.current.value
|
const width = widthRef.current.value
|
||||||
const height = heightRef.current.value
|
const height = heightRef.current.value
|
||||||
|
|
||||||
|
// [OPENING-MIN-SIZE 2026-05-08] 0/빈값 입력 시 적용 차단. 0,0 으로 확정되면 4정점 동일
|
||||||
|
// 0-dim 객체가 plan 에 저장되어 모듈배치 단계에서 turf throw → 화면 크래시.
|
||||||
|
if (target.name === BATCH_TYPE.OPENING || target.name === BATCH_TYPE.SHADOW) {
|
||||||
|
if (width === '' || height === '' || Number(width) <= 0 || Number(height) <= 0) {
|
||||||
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (target.name === BATCH_TYPE.OPENING || target.name === BATCH_TYPE.SHADOW) {
|
if (target.name === BATCH_TYPE.OPENING || target.name === BATCH_TYPE.SHADOW) {
|
||||||
resizeObjectBatch(settingTarget, target, width, height, id)
|
resizeObjectBatch(settingTarget, target, width, height, id)
|
||||||
} else if (target.name === POLYGON_TYPE.ROOF) {
|
} else if (target.name === POLYGON_TYPE.ROOF) {
|
||||||
|
|||||||
@ -50,6 +50,18 @@ export function useRoofFn() {
|
|||||||
roofMaterial = polygon.roofMaterial ?? selectedRoofMaterial
|
roofMaterial = polygon.roofMaterial ?? selectedRoofMaterial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// [ROOF-PATTERN-GUARD 2026-05-08] polygon bbox 0 또는 roofMaterial 결손 시
|
||||||
|
// 0×0 patternSourceCanvas 가 만들어져 drawImage InvalidStateError 로 페이지 500 진입.
|
||||||
|
// 손상 plan(예 S203X460260507001) 로드 케이스 차단.
|
||||||
|
if (!polygon.width || !polygon.height) {
|
||||||
|
console.warn('[ROOF-PATTERN-GUARD] skip pattern, 0-dim polygon:', polygon.id, polygon.name, polygon.width, polygon.height)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!roofMaterial || !roofMaterial.layout) {
|
||||||
|
console.warn('[ROOF-PATTERN-GUARD] skip pattern, invalid roofMaterial:', polygon.id, roofMaterial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const ratio = window.devicePixelRatio || 1
|
const ratio = window.devicePixelRatio || 1
|
||||||
const layout = roofMaterial.layout
|
const layout = roofMaterial.layout
|
||||||
|
|
||||||
|
|||||||
@ -329,6 +329,15 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
|
|
||||||
//도머등 오브젝트 객체가 있으면 아웃라인 낸다
|
//도머등 오브젝트 객체가 있으면 아웃라인 낸다
|
||||||
batchObjects.forEach((obj) => {
|
batchObjects.forEach((obj) => {
|
||||||
|
// [OPENING-MIN-SIZE 2026-05-08] 기존 plan 에 저장된 0-면적 개구/그림자/도머 보호:
|
||||||
|
// 모든 정점이 동일 좌표면 offsetPolygon → cleanSelfIntersectingPolygon → turf.polygon throw 발생.
|
||||||
|
// 신규는 SizeSetting 가드로 막지만, 이미 저장된 데이터는 여기서 skip.
|
||||||
|
const _pts = obj.getCurrentPoints?.() || obj.points || []
|
||||||
|
if (_pts.length >= 2 && _pts.every((p) => p.x === _pts[0].x && p.y === _pts[0].y)) {
|
||||||
|
console.warn('[OPENING-MIN-SIZE 2026-05-08] 0-dim batch object skipped (outline)', { id: obj.id, name: obj.name, parentId: obj.parentId })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//도머일때
|
//도머일때
|
||||||
if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) {
|
if (obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER) {
|
||||||
const groupPoints = obj.getCurrentPoints()
|
const groupPoints = obj.getCurrentPoints()
|
||||||
@ -963,11 +972,16 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
|
|
||||||
tempModule.set({ points: rectPoints })
|
tempModule.set({ points: rectPoints })
|
||||||
const tempTurfModule = polygonToTurfPolygon(tempModule)
|
const tempTurfModule = polygonToTurfPolygon(tempModule)
|
||||||
|
|
||||||
//도머 객체를 가져옴 (undo 후 캔버스 객체가 교체될 수 있으므로 다시 가져옴)
|
//도머 객체를 가져옴 (undo 후 캔버스 객체가 교체될 수 있으므로 다시 가져옴)
|
||||||
const currentBatchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE)
|
const currentBatchObjects = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.OBJECT_SURFACE)
|
||||||
if (currentBatchObjects) {
|
if (currentBatchObjects) {
|
||||||
currentBatchObjects.forEach((object) => {
|
currentBatchObjects.forEach((object) => {
|
||||||
|
// [OPENING-MIN-SIZE 2026-05-08] 0-면적 객체는 turf 변환 시 throw 위험 → skip
|
||||||
|
const _pts = object.getCurrentPoints?.() || object.points || []
|
||||||
|
if (_pts.length >= 2 && _pts.every((p) => p.x === _pts[0].x && p.y === _pts[0].y)) {
|
||||||
|
console.warn('[OPENING-MIN-SIZE 2026-05-08] 0-dim batch object skipped (intersect)', { id: object.id, name: object.name })
|
||||||
|
return
|
||||||
|
}
|
||||||
let dormerTurfPolygon = polygonToTurfPolygon(object, true)
|
let dormerTurfPolygon = polygonToTurfPolygon(object, true)
|
||||||
const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인
|
const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인
|
||||||
//겹치면 안됨
|
//겹치면 안됨
|
||||||
@ -3732,6 +3746,12 @@ export function useModuleBasicSetting(tabNum) {
|
|||||||
//도머 객체를 가져옴
|
//도머 객체를 가져옴
|
||||||
if (batchObjects) {
|
if (batchObjects) {
|
||||||
batchObjects.forEach((object) => {
|
batchObjects.forEach((object) => {
|
||||||
|
// [OPENING-MIN-SIZE 2026-05-08] 0-면적 객체는 turf 변환 시 throw 위험 → skip
|
||||||
|
const _pts = object.getCurrentPoints?.() || object.points || []
|
||||||
|
if (_pts.length >= 2 && _pts.every((p) => p.x === _pts[0].x && p.y === _pts[0].y)) {
|
||||||
|
console.warn('[OPENING-MIN-SIZE 2026-05-08] 0-dim batch object skipped (intersect)', { id: object.id, name: object.name })
|
||||||
|
return
|
||||||
|
}
|
||||||
let dormerTurfPolygon
|
let dormerTurfPolygon
|
||||||
|
|
||||||
if (object.type === 'group') {
|
if (object.type === 'group') {
|
||||||
|
|||||||
@ -291,6 +291,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDown = false
|
isDown = false
|
||||||
|
// [OPENING-COLLAPSE-DIAG 2026-05-08] 치수입력 mouse:up 시점 width/height 0 검출
|
||||||
|
if (!rect.width || !rect.height) {
|
||||||
|
console.warn('[OPENING-COLLAPSE-DIAG] dimension-input mouse:up rect collapsed:', { name: objName, w: rect.width, h: rect.height, sx: rect.scaleX, sy: rect.scaleY, left: rect.left, top: rect.top })
|
||||||
|
}
|
||||||
rect.set({ name: objName, parentId: selectedSurface.id, points: rectToPolygon(rect) })
|
rect.set({ name: objName, parentId: selectedSurface.id, points: rectToPolygon(rect) })
|
||||||
rect.setCoords()
|
rect.setCoords()
|
||||||
initEvent()
|
initEvent()
|
||||||
@ -1438,6 +1442,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
addCanvasMouseEventListener('mouse:up', (e) => {
|
addCanvasMouseEventListener('mouse:up', (e) => {
|
||||||
//개구, 그림자 타입일 경우 폴리곤 타입 변경
|
//개구, 그림자 타입일 경우 폴리곤 타입 변경
|
||||||
if (BATCH_TYPE.OPENING === obj.name || BATCH_TYPE.SHADOW === obj.name) {
|
if (BATCH_TYPE.OPENING === obj.name || BATCH_TYPE.SHADOW === obj.name) {
|
||||||
|
// [OPENING-COLLAPSE-DIAG 2026-05-08] 객체 이동 mouse:up 시점 width/height 0 검출
|
||||||
|
if (!obj.width || !obj.height || !obj.scaleX || !obj.scaleY) {
|
||||||
|
console.warn('[OPENING-COLLAPSE-DIAG] move mouse:up obj collapsed:', { id: obj.id, name: obj.name, w: obj.width, h: obj.height, sx: obj.scaleX, sy: obj.scaleY, left: obj.left, top: obj.top })
|
||||||
|
}
|
||||||
obj.set({
|
obj.set({
|
||||||
points: rectToPolygon(obj),
|
points: rectToPolygon(obj),
|
||||||
})
|
})
|
||||||
@ -1541,6 +1549,10 @@ export function useObjectBatch({ isHidden, setIsHidden }) {
|
|||||||
addCanvasMouseEventListener('mouse:up', (e) => {
|
addCanvasMouseEventListener('mouse:up', (e) => {
|
||||||
//개구, 그림자 타입일 경우 폴리곤 타입 변경
|
//개구, 그림자 타입일 경우 폴리곤 타입 변경
|
||||||
if (BATCH_TYPE.OPENING == obj.name || BATCH_TYPE.SHADOW == obj.name) {
|
if (BATCH_TYPE.OPENING == obj.name || BATCH_TYPE.SHADOW == obj.name) {
|
||||||
|
// [OPENING-COLLAPSE-DIAG 2026-05-08] 복제 mouse:up 시점 width/height 0 검출
|
||||||
|
if (!clonedObj.width || !clonedObj.height || !clonedObj.scaleX || !clonedObj.scaleY) {
|
||||||
|
console.warn('[OPENING-COLLAPSE-DIAG] copy mouse:up clonedObj collapsed:', { name: obj.name, w: clonedObj.width, h: clonedObj.height, sx: clonedObj.scaleX, sy: clonedObj.scaleY, left: clonedObj.left, top: clonedObj.top })
|
||||||
|
}
|
||||||
clonedObj.set({
|
clonedObj.set({
|
||||||
points: rectToPolygon(clonedObj),
|
points: rectToPolygon(clonedObj),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -139,6 +139,30 @@ export function useCanvas(id) {
|
|||||||
|
|
||||||
const initialize = () => {
|
const initialize = () => {
|
||||||
canvas.getObjects().length > 0 && canvas?.clear()
|
canvas.getObjects().length > 0 && canvas?.clear()
|
||||||
|
// [BG-CACHE-DIAG 2026-05-08] drawCacheOnCanvas 0-dim cache 차단 + 식별
|
||||||
|
// 운영 S203X460260507001 등 손상된 plan 로드 시 InvalidStateError 방지.
|
||||||
|
// 객체 width/height 가 정상이어도 zoom/scale/bbox 조합으로 _cacheCanvas 가
|
||||||
|
// 0×0 이 될 수 있어 객체 단위 가드로는 부족. drawCacheOnCanvas 직전에
|
||||||
|
// 캐시 캔버스 dim 검사 + 로그 + 캐시 비활성으로 차단.
|
||||||
|
if (!fabric.Object.prototype.__bgCacheDiagPatched) {
|
||||||
|
const origDrawCache = fabric.Object.prototype.drawCacheOnCanvas
|
||||||
|
fabric.Object.prototype.drawCacheOnCanvas = function (ctx) {
|
||||||
|
const c = this._cacheCanvas
|
||||||
|
if (!c || !c.width || !c.height) {
|
||||||
|
console.warn('[BG-CACHE-DIAG] skip 0-dim cache:', {
|
||||||
|
type: this.type, name: this.name, id: this.id,
|
||||||
|
w: this.width, h: this.height,
|
||||||
|
sx: this.scaleX, sy: this.scaleY,
|
||||||
|
visible: this.visible, opacity: this.opacity,
|
||||||
|
cacheW: c?.width, cacheH: c?.height,
|
||||||
|
})
|
||||||
|
this.objectCaching = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return origDrawCache.call(this, ctx)
|
||||||
|
}
|
||||||
|
fabric.Object.prototype.__bgCacheDiagPatched = true
|
||||||
|
}
|
||||||
// settings for all canvas in the app
|
// settings for all canvas in the app
|
||||||
fabric.Object.prototype.transparentCorners = false
|
fabric.Object.prototype.transparentCorners = false
|
||||||
fabric.Object.prototype.selectable = true
|
fabric.Object.prototype.selectable = true
|
||||||
|
|||||||
@ -180,6 +180,24 @@ export function usePlan(params = {}) {
|
|||||||
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
|
* @param {boolean} saveAlert - 저장 완료 알림 표시 여부
|
||||||
*/
|
*/
|
||||||
const saveCanvas = async (saveAlert = true) => {
|
const saveCanvas = async (saveAlert = true) => {
|
||||||
|
// [ROOF-MATERIAL-SAVE-GUARD 2026-05-08] 면(roof) 에 시각적 지붕재 패턴은 칠해져 있는데
|
||||||
|
// 데이터(roofMaterial) 가 비어있는 "불일치" 케이스만 저장 차단.
|
||||||
|
// - fill 비어있고 roofMaterial 도 없음 → 미선택 상태, 저장 허용
|
||||||
|
// - fill 이 fabric.Pattern 인데 roofMaterial 누락 → 저장 시 누락 JSON 이 DB 로 가고
|
||||||
|
// 재로드 시 setSurfaceShapePattern 0×0 patternCanvas → drawImage InvalidStateError
|
||||||
|
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && !obj.wall)
|
||||||
|
const inconsistent = roofs.filter((r) => {
|
||||||
|
const f = r.fill
|
||||||
|
const hasPatternFill = f && typeof f === 'object' && (f.source || f.type === 'pattern')
|
||||||
|
return hasPatternFill && !r.roofMaterial
|
||||||
|
})
|
||||||
|
if (inconsistent.length > 0) {
|
||||||
|
console.warn('[ROOF-MATERIAL-SAVE-GUARD] pattern drawn but roofMaterial missing:',
|
||||||
|
inconsistent.map((r) => ({ id: r.id, name: r.name, fillType: r.fill?.type })))
|
||||||
|
swalFire({ text: '面に屋根材パターンが描画されていますが、屋根材データが保存されていません。\n屋根材を再設定してから保存してください。', icon: 'warning' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 저장 전 선택되어 있는 object 제거
|
// 저장 전 선택되어 있는 object 제거
|
||||||
const setupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
const setupSurfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user