라인줄표시, 인덱스 재설치 #794

Merged
ysCha merged 1 commits from dev into dev-deploy 2026-04-27 13:10:12 +09:00
2 changed files with 23 additions and 13 deletions
Showing only changes of commit 8fef6c9f28 - Show all commits

View File

@ -7,6 +7,7 @@ import * as turf from '@turf/turf'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import Big from 'big.js'
import { drawSkeletonRidgeRoof, drawSkeletonRidgeRoofFromBaseLines, verifyMoveBoundary } from '@/util/skeleton-utils'
import { attachDebugLabels } from '@/util/debugLabels'
export const QPolygon = fabric.util.createClass(fabric.Polygon, {
type: 'QPolygon',
@ -514,6 +515,9 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
// console.log('변별로 설정')
drawRoofByAttribute(this.id, this.canvas, textMode)
}
// [DEBUG] URL ?debug=labels 일 때만 라벨 부착. 운영 빌드/평소엔 noop.
attachDebugLabels(this.canvas, this.id)
},
/**

View File

@ -1339,24 +1339,29 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode, isOver
})
}
// 각 라인 집합 정렬
const sortWallLines = ensureCounterClockwiseLines(wallLines)
// wall.baseLines를 독립적으로 CCW 정렬하면 ㅗ→붕괴 등으로 꼭짓점 집합이 달라질 때
// 시작점(min-Y-then-min-X)이 wallLines와 어긋나 배열이 shift된다.
// wallLines[i]와 wall.baseLines[i]는 초기 생성 시부터 raw-index 1:1 페어링이 유지되므로,
// sortWallLines의 CCW 순서에 대응하는 원 인덱스를 복원하여 같은 순서로 wall.baseLines를 매핑한다.
// [정렬 정책 — 인덱스가 생명] (2026-04-27)
// 1) master = roofLines. roofLine 은 외곽(처마) 경계라 사용자 mutation 영향 없음 → 시작점 안정.
// 2) ensureCounterClockwiseLines 가 master 의 leftTop(min-Y → min-X) 꼭짓점에서 시작해 CCW 정렬.
// 3) wall.lines, wall.baseLines 는 raw 인덱스 1:1 페어링 유지된다는 전제로
// master 의 sorted 순서 → 원 인덱스 → wall.lines[idx] / wall.baseLines[idx] 매핑.
// wall 쪽을 master 로 쓰던 구현은 OVER 흡수/usePropertiesSetting 등에서 wall.lines 가
// 재할당되며 시작점이 어긋나 페어 인덱스가 회전되던 문제(2026-04-27 ㅗ 좌측오버 케이스) 가 있어 변경.
const _keyOfEdge = (l) => {
const a = `${Math.round(l.x1 * 10) / 10},${Math.round(l.y1 * 10) / 10}`
const b = `${Math.round(l.x2 * 10) / 10},${Math.round(l.y2 * 10) / 10}`
return a < b ? `${a}|${b}` : `${b}|${a}`
}
const _rawIdxByKey = new Map()
wallLines.forEach((l, i) => _rawIdxByKey.set(_keyOfEdge(l), i))
const sortWallBaseLines = sortWallLines.map((sl) => {
roofLines.forEach((l, i) => _rawIdxByKey.set(_keyOfEdge(l), i))
const sortRoofLines = ensureCounterClockwiseLines(roofLines)
const sortWallLines = sortRoofLines.map((sl) => {
const origIdx = _rawIdxByKey.get(_keyOfEdge(sl))
return origIdx != null && wallLines[origIdx] ? wallLines[origIdx] : sl
})
const sortWallBaseLines = sortRoofLines.map((sl) => {
const origIdx = _rawIdxByKey.get(_keyOfEdge(sl))
return origIdx != null && wall.baseLines[origIdx] ? wall.baseLines[origIdx] : sl
})
const sortRoofLines = ensureCounterClockwiseLines(roofLines)
// [MOVE-TRACE] 진입 스냅샷 (필요 시 주석 해제)
// console.log('[MOVE-TRACE] === getMoveUpDownLine entry ===',
@ -1596,10 +1601,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode, isOver
const roofLine = sortRoofLines[index]
const wallBaseLine = sortWallBaseLines[index]
// [SHOULDER-ABSORBED] wall.baseLines[k] 가 이동 흡수로 zero-length(planeSize≈0)면
// 해당 edge 는 흡수되어 사라진 상태. pair 생성 자체가 불필요(DIR_MISMATCH + eaveHelpLine 노이즈 유발).
// 참고: 같은 파일 line 666 의 filter(planeSize > 0) 선례와 동일 패턴.
if (!wallBaseLine || (wallBaseLine.attributes?.planeSize ?? Infinity) < 1) {
// [SHOULDER-ABSORBED] wall.baseLines[k] 가 이동 흡수/OVER_EPS 클램핑으로 거의 zero-length 이면
// 해당 edge 는 흡수된 상태. pair 생성 자체가 불필요(DIR_MISMATCH + eaveHelpLine 노이즈 유발).
// 임계 = 10: useMovementSetting OVER_EPS=0.5 (canvas unit) 시 잔여 길이=0.5 → planeSize=5 (calcLinePlaneSize × 10).
// 안전마진 두고 < 10 으로 차단. 정상 baseLine 은 보통 50+ 이므로 false positive 없음.
if (!wallBaseLine || (wallBaseLine.attributes?.planeSize ?? Infinity) < 10) {
console.log(`⏭️ [pair#${index}] SHOULDER_ABSORBED skip: wb=(${Math.round(wallBaseLine?.x1)},${Math.round(wallBaseLine?.y1)})→(${Math.round(wallBaseLine?.x2)},${Math.round(wallBaseLine?.y2)}) planeSize=${wallBaseLine?.attributes?.planeSize}`)
return
}