diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 30c98824..77b84742 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -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