Merge pull request 'dev' (#945) from dev into prd-deploy

Reviewed-on: #945
This commit is contained in:
ysCha 2026-07-02 13:11:47 +09:00
commit 9c7584da60

View File

@ -6,7 +6,7 @@ import { QPolygon } from '@/components/fabric/QPolygon'
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import Big from 'big.js'
import * as turf from '@turf/turf'
// import { debugCapture } from '@/util/debugCapture' // [KERAB-GABLE-DIAG 2026-06-29] 박공 마루 미생성 추적용
const TWO_PI = Math.PI * 2
const EPSILON = 1e-10 //좌표계산 시 최소 차이값
@ -3596,14 +3596,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
const prevIndex = baseLines.findIndex((line) => line === prevLine)
const nextIndex = baseLines.findIndex((line) => line === nextLine)
// [KERAB-GABLE-DIAG 2026-06-29] ① 이벤트: 박공 변 처리 진입
// const __gcoord = `(${Math.round(currentLine.x1)},${Math.round(currentLine.y1)})-(${Math.round(currentLine.x2)},${Math.round(currentLine.y2)})`
// debugCapture.log('KERAB-GABLE-DIAG ①이벤트:gable-enter', { gable: __gcoord, prevType: prevLine?.attributes?.type, nextType: nextLine?.attributes?.type })
//양옆이 처마가 아닐때 패스
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
// [KERAB-GABLE-DIAG 2026-06-29] ②원인→④결과: 양옆이 처마가 아님 → 마루 미생성
// debugCapture.log('KERAB-GABLE-DIAG ②원인:neighbor-not-eaves → ④결과:no-ridge', { gable: __gcoord })
return
}
@ -3615,11 +3609,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1),
}
// [KERAB-GABLE-DIAG 2026-06-29] ②원인: 본처리 진입 게이트(vector 차이 + inPolygon) 통과 여부
// const __vecDiff = prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y
// const __inPoly = checkWallPolygon.inPolygon(inPolygonPoint)
// debugCapture.log('KERAB-GABLE-DIAG ②원인:gate-vec+inPoly', { gable: __gcoord, vecDiff: __vecDiff, inPoly: __inPoly, inPolygonPoint })
//좌우 라인이 서로 다른방향일때
if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) {
const analyze = analyzeLine(currentLine)
@ -3933,13 +3922,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
}
})
// [KERAB-GABLE-DIAG 2026-06-29] ②원인: 반대편 평행변(oppositeLine) 매칭 — 0이면 마루 미생성
// debugCapture.log('KERAB-GABLE-DIAG ②원인:oppositeLine-match', {
// gable: __gcoord,
// count: oppositeLine.length,
// lines: oppositeLine.map((o) => `(${Math.round(o.line.x1)},${Math.round(o.line.y1)})-(${Math.round(o.line.x2)},${Math.round(o.line.y2)})`),
// })
if (oppositeLine.length > 0) {
const ridgePoints = []
@ -4201,14 +4183,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
if (stdAnalyze.isHorizontal) {
if (overlapMinX > overlapMaxX) {
// [KERAB-GABLE-DIAG 2026-06-29] ③분기→④결과: 수평 박공 X구간 겹침 없음 → 이 oppositeLine skip(no-ridge)
// debugCapture.log('KERAB-GABLE-DIAG ③분기:horizontal-no-overlap → ④결과:no-ridge', {
// gable: __gcoord,
// overlapMinX,
// overlapMaxX,
// stdRange: [stdMinX, stdMaxX],
// ridgeRange: [rMinX, rMaxX],
// })
return
}
const dist1 = Math.abs(ridgePoint[0] - overlapMinX)
@ -4226,14 +4200,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
}
if (stdAnalyze.isVertical) {
if (overlapMinY > overlapMaxY) {
// [KERAB-GABLE-DIAG 2026-06-29] ③분기→④결과: 수직 박공 Y구간 겹침 없음 → 이 oppositeLine skip(no-ridge)
// debugCapture.log('KERAB-GABLE-DIAG ③분기:vertical-no-overlap → ④결과:no-ridge', {
// gable: __gcoord,
// overlapMinY,
// overlapMaxY,
// stdRange: [stdMinY, stdMaxY],
// ridgeRange: [rMinY, rMaxY],
// })
return
}
const dist1 = Math.abs(ridgePoint[1] - overlapMinY)
@ -4312,6 +4278,53 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
}
}
// [KERAB-GABLE-DIAG-FIX 2026-07-02] 박공 마루 inner 끝점이 roofLine 에도 힙 seed 에도 안 닿고 폴리곤 내부에 떠 있으면
// (ㅗ/ㄱ 등 비대칭 팔에서 overlap-clamp 가 마루를 짧게 끊어 메인 루프 교점 상대가 없어지는 문제)
// 마루 진행 방향으로 연장해 가장 가까운 힙 seed 교점까지 끝점을 이동시켜, 이후 교점 루프가 마루를 처리하도록 한다.
{
const onRoofLine = (px, py) => roof.lines.find((line) => isPointOnLineNew(line, { x: px, y: py })) !== undefined
const onSeedHip = (px, py) =>
linesAnalysis.some(
(l) => l.type === TYPES.HIP && isPointOnLineNew({ x1: l.start.x, y1: l.start.y, x2: l.end.x, y2: l.end.y }, { x: px, y: py }),
)
const p1Floating = !onRoofLine(point[0], point[1]) && !onSeedHip(point[0], point[1])
const p2Floating = !onRoofLine(point[2], point[3]) && !onSeedHip(point[2], point[3])
// 박공 마루는 한쪽 끝이 항상 처마출폭(roofLine) 위에 있으므로 한쪽만 떠 있을 때만 연장한다.
if (p1Floating !== p2Floating) {
const fx = p1Floating ? 0 : 2
const fy = p1Floating ? 1 : 3
const bx = p1Floating ? 2 : 0
const by = p1Floating ? 3 : 1
const dir = { x: Math.sign(point[fx] - point[bx]), y: Math.sign(point[fy] - point[by]) }
const rayEdge = {
vertex1: { x: point[bx], y: point[by] },
vertex2: { x: point[fx] + dir.x * 100000, y: point[fy] + dir.y * 100000 },
}
const floatDist = Math.hypot(point[fx] - point[bx], point[fy] - point[by])
let best = null
let bestDist = Infinity
linesAnalysis
.filter((l) => l.type === TYPES.HIP)
.forEach((l) => {
const hipEdge = { vertex1: { x: l.start.x, y: l.start.y }, vertex2: { x: l.end.x, y: l.end.y } }
const is = edgesIntersection(rayEdge, hipEdge)
if (!is) return
const isVec = { x: Math.sign(is.x - point[bx]), y: Math.sign(is.y - point[by]) }
if (isVec.x !== dir.x || isVec.y !== dir.y) return
const d = Math.hypot(is.x - point[bx], is.y - point[by])
if (d <= floatDist) return
if (d < bestDist) {
bestDist = d
best = is
}
})
if (best) {
point[fx] = best.x
point[fy] = best.y
}
}
}
const ridgeLength = Math.sqrt(Math.pow(point[2] - point[0], 2) + Math.pow(point[3] - point[1], 2))
if (ridgeLength > EPSILON) {
@ -4349,20 +4362,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
right: nextIndex,
type: TYPES.RIDGE,
degree: 0,
gableRidge: true, // [KERAB-GABLE-JOG-FIX 2026-07-02] 박공 파이프라인 생성 마루 식별
})
// [KERAB-GABLE-DIAG 2026-06-29] ④결과: 박공 마루 가선분 생성 성공 — left/right 는 이후 교점 매칭의 핵심 단서
// debugCapture.log('KERAB-GABLE-DIAG ④결과:ridge-pushed', {
// gable: __gcoord,
// ridgeLength,
// left: prevIndex,
// right: nextIndex,
// start: startPoint,
// end: endPoint,
// })
}
} else {
// [KERAB-GABLE-DIAG 2026-06-29] ③분기→④결과: 마루 길이 ~0 → 가선분 미생성
// debugCapture.log('KERAB-GABLE-DIAG ③분기:ridge-too-short → ④결과:no-ridge', { gable: __gcoord, ridgeLength, point })
}
})
}
@ -4370,8 +4372,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
}
//반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
// [KERAB-GABLE-DIAG 2026-06-29] ③분기: 양옆 벡터 동일(U자) → 반절마루 불가, GABLE_LINE 분기로
// debugCapture.log('KERAB-GABLE-DIAG ③분기:U-shape-gableline', { gable: __gcoord })
const analyze = analyzeLine(currentLine)
const roofLine = analyze.roofLine
@ -4427,6 +4427,44 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
canvas.renderAll()
})
// [KERAB-GABLE-JOG-FIX 2026-07-02] 박공 마루(gableRidge)가 생기면 그 팔 안의 중심선 마루 하나만 유효하다.
// 기반 스켈레톤(proceedRidges)이 뱉는, 박공 마루와 평행·index공유·같은 팔에 겹치는 중복 세로/가로 마루는 제거한다.
// (처마일 때는 apex 로 흡수되던 마루가, 박공 마루 추가로 index 를 공유하며 이중마루로 남는 것을 막는다.)
{
const gableRidges = linesAnalysis.filter((l) => l.type === TYPES.RIDGE && l.gableRidge)
if (gableRidges.length > 0) {
const isVert = (l) => Math.abs(l.start.x - l.end.x) < Math.abs(l.start.y - l.end.y)
const removeSet = new Set()
linesAnalysis.forEach((base) => {
if (base.type !== TYPES.RIDGE || base.gableRidge) return
gableRidges.forEach((g) => {
if (isVert(base) !== isVert(g)) return // 평행만
const shareIdx = base.left === g.left || base.left === g.right || base.right === g.left || base.right === g.right
if (!shareIdx) return
const gLen = Math.hypot(g.end.x - g.start.x, g.end.y - g.start.y)
if (isVert(g)) {
const bMin = Math.min(base.start.y, base.end.y)
const bMax = Math.max(base.start.y, base.end.y)
const gMin = Math.min(g.start.y, g.end.y)
const gMax = Math.max(g.start.y, g.end.y)
const overlap = Math.min(bMax, gMax) - Math.max(bMin, gMin) > EPSILON
if (overlap && Math.abs(base.start.x - g.start.x) < gLen) removeSet.add(base)
} else {
const bMin = Math.min(base.start.x, base.end.x)
const bMax = Math.max(base.start.x, base.end.x)
const gMin = Math.min(g.start.x, g.end.x)
const gMax = Math.max(g.start.x, g.end.x)
const overlap = Math.min(bMax, gMax) - Math.max(bMin, gMin) > EPSILON
if (overlap && Math.abs(base.start.y - g.start.y) < gLen) removeSet.add(base)
}
})
})
if (removeSet.size > 0) {
linesAnalysis = linesAnalysis.filter((l) => !removeSet.has(l))
}
}
}
//각 선분의 교차점을 찾아 선을 그린다.
const MAX_ITERATIONS = 1000 //무한루프 방지
let iterations = 0
@ -4434,24 +4472,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) {
iterations++
// [KERAB-GABLE-DIAG 2026-06-29] ①이벤트: 교점 루프 iteration 시작 — RIDGE 현재 상태(붕괴 추적)
// {
// const __r = linesAnalysis.find((l) => l.type === TYPES.RIDGE)
// debugCapture.log('KERAB-GABLE-DIAG ①이벤트:loop-iter-start', {
// iter: iterations,
// total: linesAnalysis.length,
// ridge: __r
// ? {
// start: { x: Math.round(__r.start.x * 10) / 10, y: Math.round(__r.start.y * 10) / 10 },
// end: { x: Math.round(__r.end.x * 10) / 10, y: Math.round(__r.end.y * 10) / 10 },
// len: Math.round(Math.hypot(__r.end.x - __r.start.x, __r.end.y - __r.start.y) * 10) / 10,
// left: __r.left,
// right: __r.right,
// }
// : null,
// })
// }
const intersections = []
linesAnalysis.forEach((currLine, i) => {
let minDistance = Infinity
@ -4459,6 +4479,14 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
let linePoint = null
let partner = null
// [KERAB-GABLE-JOG-FIX 2026-07-02] 인덱스를 공유하는(=정당한 접합 가능) 최근접 파트너를 별도 추적.
// ㅗ 노치의 계단형 마루(offset 평행)처럼 인덱스 미공유 교점이 최근접으로 끼어들어 상호최단을 가로채면,
// 마루↔힙의 정당한 접합이 막혀 마루가 소멸한다. 전역 최근접이 인덱스 미공유일 때만 이 후보로 폴백한다.
let sameMinDistance = Infinity
let sameIntersectPoint = null
let sameLinePoint = null
let samePartner = null
const cLength = Math.sqrt(Math.pow(currLine.end.x - currLine.start.x, 2) + Math.pow(currLine.end.y - currLine.start.y, 2))
//남은 길이가 0이면 무시
if (cLength < EPSILON) return
@ -4496,31 +4524,34 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
partner = j
}
}
// [KERAB-GABLE-JOG-FIX 2026-07-02] 인덱스 공유(정당한 접합) 최근접 후보를 별도로 추적
const shareIdx =
currLine.left === nextLine.left || currLine.left === nextLine.right || currLine.right === nextLine.left || currLine.right === nextLine.right
if (shareIdx && distance1 < sameMinDistance && !almostEqual(distance1, sameMinDistance) && !(distance1 < EPSILON && distance2 < EPSILON)) {
sameMinDistance = distance1
sameIntersectPoint = intersect
sameLinePoint = point
samePartner = j
}
}
})
// [KERAB-GABLE-JOG-FIX 2026-07-02] 전역 최근접 파트너가 인덱스 미공유(접합 불가)이면, 인덱스 공유 최근접으로 폴백
if (partner !== null) {
const pLine = linesAnalysis[partner]
const chosenSame =
currLine.left === pLine.left || currLine.left === pLine.right || currLine.right === pLine.left || currLine.right === pLine.right
if (!chosenSame && samePartner !== null) {
minDistance = sameMinDistance
intersectPoint = sameIntersectPoint
linePoint = sameLinePoint
partner = samePartner
}
}
if (intersectPoint) {
intersections.push({ index: i, intersect: intersectPoint, linePoint, partner })
}
})
// [KERAB-GABLE-DIAG 2026-06-29] ②원인: 이번 iter 에서 각 RIDGE 가 교점쌍(partner)을 찾았는지 — 못 찾으면 루프 미처리되어 잔여로 떨어진다
// linesAnalysis.forEach((l, li) => {
// if (l.type !== TYPES.RIDGE) return
// const found = intersections.find((it) => it.index === li)
// const partnerLine = found ? linesAnalysis[found.partner] : null
// debugCapture.log('KERAB-GABLE-DIAG ②원인:loop-ridge-intersect-scan', {
// iter: iterations,
// ridge: {
// start: { x: Math.round(l.start.x * 10) / 10, y: Math.round(l.start.y * 10) / 10 },
// end: { x: Math.round(l.end.x * 10) / 10, y: Math.round(l.end.y * 10) / 10 },
// share: [l.left, l.right],
// },
// hasIntersect: !!found,
// partner: partnerLine ? { type: partnerLine.type, share: [partnerLine.left, partnerLine.right] } : null,
// intersect: found ? { x: Math.round(found.intersect.x * 10) / 10, y: Math.round(found.intersect.y * 10) / 10 } : null,
// })
// })
const intersectPoints = intersections
.map((item) => item.intersect)
.filter((point, index, self) => self.findIndex((p) => almostEqual(p.x, point.x) && almostEqual(p.y, point.y)) === index)
@ -4536,78 +4567,31 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
for (const { index, intersect, linePoint, partner } of intersections) {
const cLine = linesAnalysis[index]
const pLine = linesAnalysis[partner]
// [KERAB-GABLE-DIAG 2026-06-29] RIDGE 가 낀 교점쌍이 어느 게이트에서 탈락하는지 추적용
// const __ridgePair = cLine?.type === TYPES.RIDGE || pLine?.type === TYPES.RIDGE
//교점이 없거나, 이미 처리된 선분이면 처리하지 않는다.
if (!intersect || processed.has(index)) {
// if (__ridgePair)
// debugCapture.log('KERAB-GABLE-DIAG ③분기:loop-skip → ④결과:skipped', {
// iter: iterations,
// reason: !intersect ? 'no-intersect' : 'already-processed',
// cType: cLine?.type,
// pType: pLine?.type,
// })
continue
}
//교점이 없거나, 교점 선분이 없으면 처리하지 않는다.
const pIntersection = intersections.find((p) => p.index === partner)
if (!pIntersection || !pIntersection.intersect) {
// if (__ridgePair)
// debugCapture.log('KERAB-GABLE-DIAG ③분기:loop-skip → ④결과:skipped', {
// iter: iterations,
// reason: 'partner-no-intersect',
// cType: cLine?.type,
// pType: pLine?.type,
// })
continue
}
//상호 최단 교점이 아닐경우 처리하지 않는다.
if (pIntersection.partner !== index) {
// if (__ridgePair)
// debugCapture.log('KERAB-GABLE-DIAG ③분기:loop-skip → ④결과:skipped', {
// iter: iterations,
// reason: 'not-mutual-shortest',
// cType: cLine?.type,
// pType: pLine?.type,
// pPartner: pIntersection.partner,
// index,
// })
continue
}
//공통선분이 없으면 처리하지 않는다.
const isSameLine = cLine.left === pLine.left || cLine.left === pLine.right || cLine.right === pLine.left || cLine.right === pLine.right
if (!isSameLine) {
// if (__ridgePair)
// debugCapture.log('KERAB-GABLE-DIAG ③분기:loop-skip → ④결과:skipped', {
// iter: iterations,
// reason: 'no-common-line',
// cType: cLine?.type,
// pType: pLine?.type,
// cShare: [cLine.left, cLine.right],
// pShare: [pLine.left, pLine.right],
// })
continue
}
//처리 된 라인으로 설정
processed.add(index).add(partner)
// [KERAB-GABLE-DIAG 2026-06-29] ②원인: 이번 교점쌍에 RIDGE 포함 — 어떤 partner 와 어디서 만났나
if (cLine.type === TYPES.RIDGE || pLine.type === TYPES.RIDGE) {
// debugCapture.log('KERAB-GABLE-DIAG ②원인:loop-pair-has-ridge', {
// iter: iterations,
// cType: cLine.type,
// pType: pLine.type,
// intersect: { x: Math.round(intersect.x * 10) / 10, y: Math.round(intersect.y * 10) / 10 },
// cShare: [cLine.left, cLine.right],
// pShare: [pLine.left, pLine.right],
// })
}
let point1 = linePoint
let point2 = pIntersection.linePoint
let length1 = Math.sqrt(Math.pow(point1[2] - point1[0], 2) + Math.pow(point1[3] - point1[1], 2))
@ -4623,22 +4607,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
}
// [KERAB-GABLE-DIAG 2026-06-29] ③분기: RIDGE(c) 그리기 게이트 판정(length1>0 && !중복)
if (cLine.type === TYPES.RIDGE) {
// debugCapture.log('KERAB-GABLE-DIAG ③분기:loop-ridge(c)-draw-gate', {
// iter: iterations,
// length1: Math.round(length1 * 10) / 10,
// already: alreadyPoints(innerLines, point1),
// willDraw: length1 > 0 && !alreadyPoints(innerLines, point1),
// point: point1.map((v) => Math.round(v * 10) / 10),
// })
}
if (length1 > 0 && !alreadyPoints(innerLines, point1)) {
if (cLine.type === TYPES.HIP) {
innerLines.push(drawHipLine(point1, canvas, roof, textMode, cLine.degree, cLine.degree))
} else if (cLine.type === TYPES.RIDGE) {
// [KERAB-GABLE-DIAG 2026-06-29] ④결과: RIDGE(c) 실제로 그려짐
// debugCapture.log('KERAB-GABLE-DIAG ④결과:loop-ridge(c)-drawn', { iter: iterations, point: point1.map((v) => Math.round(v * 10) / 10) })
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
} else if (cLine.type === TYPES.NEW) {
const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1
@ -4656,22 +4628,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
}
}
// [KERAB-GABLE-DIAG 2026-06-29] ③분기: RIDGE(p) 그리기 게이트 판정(length2>0 && !중복)
if (pLine.type === TYPES.RIDGE) {
// debugCapture.log('KERAB-GABLE-DIAG ③분기:loop-ridge(p)-draw-gate', {
// iter: iterations,
// length2: Math.round(length2 * 10) / 10,
// already: alreadyPoints(innerLines, point2),
// willDraw: length2 > 0 && !alreadyPoints(innerLines, point2),
// point: point2.map((v) => Math.round(v * 10) / 10),
// })
}
if (length2 > 0 && !alreadyPoints(innerLines, point2)) {
if (pLine.type === TYPES.HIP) {
innerLines.push(drawHipLine(point2, canvas, roof, textMode, pLine.degree, pLine.degree))
} else if (pLine.type === TYPES.RIDGE) {
// [KERAB-GABLE-DIAG 2026-06-29] ④결과: RIDGE(p) 실제로 그려짐
// debugCapture.log('KERAB-GABLE-DIAG ④결과:loop-ridge(p)-drawn', { iter: iterations, point: point2.map((v) => Math.round(v * 10) / 10) })
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
} else if (pLine.type === TYPES.NEW) {
const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1
@ -4908,23 +4868,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
// 처리된 가선분 제외
linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index)))
// [KERAB-GABLE-DIAG 2026-06-29] ④결과: iteration 종료 후 RIDGE 상태(붕괴/소멸 여부)
// {
// const __r = linesAnalysis.find((l) => l.type === TYPES.RIDGE)
// debugCapture.log('KERAB-GABLE-DIAG ④결과:loop-iter-end', {
// iter: iterations,
// newAnalysisCount: newAnalysis.length,
// processedCount: processed.size,
// ridge: __r
// ? {
// start: { x: Math.round(__r.start.x * 10) / 10, y: Math.round(__r.start.y * 10) / 10 },
// end: { x: Math.round(__r.end.x * 10) / 10, y: Math.round(__r.end.y * 10) / 10 },
// len: Math.round(Math.hypot(__r.end.x - __r.start.x, __r.end.y - __r.start.y) * 10) / 10,
// }
// : 'GONE',
// })
// }
canvas
.getObjects()
.filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
@ -4933,21 +4876,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => {
if (newAnalysis.length === 0) break
}
// [KERAB-GABLE-DIAG 2026-06-29] ①이벤트: 교점 루프 종료 — 잔여 linesAnalysis 덤프(이 시점에 RIDGE 가 남아있는지가 핵심)
// debugCapture.log('KERAB-GABLE-DIAG ①이벤트:after-loop-residual', {
// baseLinesLen: baseLines.length,
// iterations,
// count: linesAnalysis.length,
// lines: linesAnalysis.map((l) => ({
// type: l.type,
// degree: l.degree,
// left: l.left,
// right: l.right,
// start: { x: Math.round(l.start.x * 10) / 10, y: Math.round(l.start.y * 10) / 10 },
// end: { x: Math.round(l.end.x * 10) / 10, y: Math.round(l.end.y * 10) / 10 },
// })),
// })
// 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리.
const proceedAnalysis = []
linesAnalysis