diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index c1b5524c..4bb91ba8 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1730,10 +1730,25 @@ export function useMode() { // edge[i] 가 tiny 이면 시작 정점 vertex[i] 를 제거해 앞쪽 edge 와 병합한다. // 이렇게 하면 남는 cleaned edge 의 line 매핑(cleanedLines[k]=lines[kept[k]]) 이 // edge[i+1] (실제 긴 edge) 의 line 을 쓰게 되므로 offset 계산이 안정해진다. + // [tiny edge axis guard 2026-05-13] 단, 제거 후 새로 생기는 엣지가 axis-aligned 인 경우에만 안전. + // 직각 노치(예: H 91mm + V 24.5mm 코너) 의 24.5mm edge 를 단순 제거하면 v[i-1]→v[i+1] 가 + // 15° 대각선이 되어 inset+clipOffsetToOriginal 후 mm 단위 zigzag 토막으로 보임 (RACK-TILT). + // 원본 데이터가 wallBaseLine 의 axis-aligned 직각 노치를 보존해야 하므로, 대각선 결과가 되면 skip. const TINY_EDGE_THRESHOLD = 30 + const AXIS_TOL = 0.5 for (let i = 0; i < n; i++) { if (edges[i].length < TINY_EDGE_THRESHOLD) { - removeIndices.add(i) + const prev = (i + n - 1) % n + const nxt = (i + 1) % n + const vPrev = vertices[prev] + const vNext = vertices[nxt] + // 새 엣지 v[prev]→v[nxt] 가 수평 또는 수직인 경우만 머지 (drift artifact 흡수). + // 그 외(직각 노치 사이의 short step) 는 진짜 geometry 이므로 보존. + const isAxisAligned = + Math.abs(vPrev.y - vNext.y) < AXIS_TOL || Math.abs(vPrev.x - vNext.x) < AXIS_TOL + if (isAxisAligned) { + removeIndices.add(i) + } } }