From 126789ef46c9e817c775c01825ad56fc62c06da5 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 11 Jun 2026 15:39:28 +0900 Subject: [PATCH] =?UTF-8?q?[=EA=B3=A8=EC=A7=9C=EA=B8=B0=EB=9D=BC=EC=9D=B8]?= =?UTF-8?q?=20=EA=B3=A8=EC=A7=9C=EA=B8=B0=E2=86=92=EC=BC=80=EB=9D=BC?= =?UTF-8?q?=EB=B0=94=20=EB=82=B4=EB=B6=80=ED=99=95=EC=9E=A5=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8(rLine)=20=EC=A0=95=EC=A7=80=EC=A0=90=20=EC=A0=88?= =?UTF-8?q?=EB=B0=98=20=EA=B7=9C=EC=B9=99=EC=9C=BC=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 등구배 지붕에서 마루는 양쪽 처마에서 등거리(절반) — 이 불변식을 적용. 기존 ridge-stop 우선/없으면 wall 끝까지 휴리스틱은 방향(가로/세로)에 따라 결과가 들쭉날쭉했다. (start + wallHit) / 2 중점으로 고정, 방향 무관 통일. wLine(wallBase) 도 vEnd(절반) 파생으로 '-extend' 라인 제거. Co-Authored-By: Claude Opus 4 --- src/hooks/roofcover/useEavesGableEdit.js | 96 +++++++++++++++--------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/src/hooks/roofcover/useEavesGableEdit.js b/src/hooks/roofcover/useEavesGableEdit.js index d4a6ee22..a6d2b86a 100644 --- a/src/hooks/roofcover/useEavesGableEdit.js +++ b/src/hooks/roofcover/useEavesGableEdit.js @@ -368,6 +368,53 @@ export function useEavesGableEdit(id) { break } + // [KERAB-STATE-DUMP 2026-06-11] A/B타입(가로/세로 케라바) 토글 진입 시점 지붕 상태 진단. + // 벽 type 지정은 있는데 내부 skLine 이 0인 "무에서 유" 케이스 파악용 — 사용자 설명 전 사실 수집. + // 읽기 전용(좌표/visible/type 만 덤프), 동작 변경 없음. logger 게이트(local 만 출력). + { + const _dr = canvas + .getObjects() + .find((o) => o.name === POLYGON_TYPE.ROOF && !o.isFixed && o.id === target.attributes?.roofId) + const _r1 = (v) => (typeof v === 'number' ? Math.round(v * 10) / 10 : v) + logger.log( + '[KERAB-STATE-DUMP] ' + + JSON.stringify({ + uiType: typeRef.current, + radio: radioTypeRef.current, + target: { + type: target.attributes?.type, + offset: target.attributes?.offset, + x1: _r1(target.x1), + y1: _r1(target.y1), + x2: _r1(target.x2), + y2: _r1(target.y2), + }, + willBecome: attributes?.type, + roofId: target.attributes?.roofId, + roofFound: !!_dr, + points: _dr?.points?.map((p) => ({ x: _r1(p.x), y: _r1(p.y) })), + lines: _dr?.lines?.map((l) => ({ + type: l.attributes?.type, + offset: l.attributes?.offset, + x1: _r1(l.x1), + y1: _r1(l.y1), + x2: _r1(l.x2), + y2: _r1(l.y2), + })), + innerCount: (_dr?.innerLines || []).length, + innerLines: (_dr?.innerLines || []).map((il) => ({ + lineName: il.lineName, + type: il.attributes?.type, + visible: il.visible !== false, + x1: _r1(il.x1), + y1: _r1(il.y1), + x2: _r1(il.x2), + y2: _r1(il.y2), + })), + }), + ) + } + // [2240 KERAB-NOOP-REKLICK 2026-05-19] 같은 type 으로의 재클릭은 무동작. // - 케라바→케라바, 처마→처마 등. 기존 rebuild 흐름이 다시 돌면 패턴 상태 // (ridge/half-label/orphan ext 정리)를 망가뜨림. @@ -1854,36 +1901,17 @@ export function useEavesGableEdit(id) { wallHit = ip } } - let ridgeStop = null - let ridgeT = Infinity - // [KERAB-VALLEY-EXT-RIDGE-STOP 2026-05-29] 룰 1: 원래 ridge 만 stop (roof 측 post 경로). - for (const il of roof.innerLines || []) { - if (!il || il.visible === false) continue - if (il.name !== LINE_TYPE.SUBLINE.RIDGE) continue - if (il.lineName !== 'ridge') continue - const ip = lineLineIntersection(start, rayEnd, { x: il.x1, y: il.y1 }, { x: il.x2, y: il.y2 }) - if (!ip) continue - if (!isOnSegV(ip, il.x1, il.y1, il.x2, il.y2)) continue - const t = (ip.x - start.x) * ux + (ip.y - start.y) * uy - if (t < 0.5) continue - if (wallT !== Infinity && t > wallT + 0.5) continue - if (t < ridgeT) { - ridgeT = t - ridgeStop = ip - } - } - if (ridgeStop) { - bestStop = ridgeStop + // [KERAB-VALLEY-HALF 2026-06-11] A/B 타입 표준 규칙(방향 무관): + // 내부확장라인은 맞은편 roofLine 까지 거리의 "절반"에서 멈춘다. + // 기존 ridge-stop / wall-끝까지 휴리스틱은 ridge 가 우연히 중간에 있으면 절반처럼, + // 없으면 끝까지 가버려 방향(세로/가로)에 따라 결과가 들쭉날쭉했다 → 항상 절반으로 통일. + // wallHit = 이 ray 가 만나는 맞은편 roof 외곽선(roofLine). 그 중점이 stop. + if (wallHit) { + bestStop = { x: (start.x + wallHit.x) / 2, y: (start.y + wallHit.y) / 2 } logger.log( - '[KERAB-VALLEY-EXT-RIDGE-STOP] post label=' + ep.label + - ' ridgeT=' + Math.round(ridgeT * 100) / 100 + - ' stop={' + Math.round(bestStop.x * 100) / 100 + ',' + Math.round(bestStop.y * 100) / 100 + '}', - ) - } else if (wallHit) { - bestStop = wallHit - logger.log( - '[KERAB-VALLEY-EXT-WALL] post label=' + ep.label + - ' end={' + Math.round(bestStop.x * 100) / 100 + ',' + Math.round(bestStop.y * 100) / 100 + '}', + '[KERAB-VALLEY-HALF] post label=' + ep.label + + ' wallHit={' + Math.round(wallHit.x * 100) / 100 + ',' + Math.round(wallHit.y * 100) / 100 + '}' + + ' halfStop={' + Math.round(bestStop.x * 100) / 100 + ',' + Math.round(bestStop.y * 100) / 100 + '}', ) } if (bestStop) { @@ -2049,15 +2077,11 @@ export function useEavesGableEdit(id) { const offX = newWStart.x - vStart.x const offY = newWStart.y - vStart.y const wEndProj = { x: vEnd.x + offX, y: vEnd.y + offY } - // wEnd(원 ray hit) 가 wEndProj 보다 ray 방향으로 더 멀리 있는지 — newWStart 기준 투영 비교. - const tProj = (wEndProj.x - newWStart.x) * wUx + (wEndProj.y - newWStart.y) * wUy - const tEndR = (wEnd.x - newWStart.x) * wUx + (wEnd.y - newWStart.y) * wUy buildOverlapLine(newWStart, wEndProj, '-wall') buildOverlapLine(vStart, newWStart, '-bridge-start') buildOverlapLine(vEnd, wEndProj, '-bridge-end') - if (tEndR > tProj + 0.5) { - buildOverlapLine(wEndProj, wEnd, '-extend') - } + // [KERAB-VALLEY-HALF 2026-06-11] 절반 규칙: wLine(wallBase) 도 vEnd(절반) 까지만. + // wEndProj 는 절반인 vEnd 에서 파생되므로, wEnd(원 ray full hit) 너머 확장은 금지. logger.log( '[KERAB-VALLEY-OVERLAP] drawn ' + JSON.stringify({ @@ -2068,7 +2092,7 @@ export function useEavesGableEdit(id) { wEnd: { x: Math.round(wEnd.x * 100) / 100, y: Math.round(wEnd.y * 100) / 100 }, vStart: { x: Math.round(vStart.x * 100) / 100, y: Math.round(vStart.y * 100) / 100 }, vEnd: { x: Math.round(vEnd.x * 100) / 100, y: Math.round(vEnd.y * 100) / 100 }, - extended: tEndR > tProj + 0.5, + extended: false, }), )