import { useRecoilValue } from 'recoil' import { corridorDimensionSelector } from '@/store/settingAtom' import { canvasState } from '@/store/canvasAtom' export function useText() { const canvas = useRecoilValue(canvasState) const corridorDimension = useRecoilValue(corridorDimensionSelector) const changeCorridorDimensionText = (columnText) => { let { column } = corridorDimension if (columnText) { column = columnText } const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText') const group = canvas.getObjects().filter((obj) => obj.type === 'group') group.forEach((obj) => { obj._objects .filter((obj2) => obj2.name === 'lengthText') .forEach((obj3) => { lengthTexts.push(obj3) }) }) switch (column) { case 'corridorDimension': lengthTexts.forEach((obj) => { if (obj.planeSize) { obj.set({ text: obj.planeSize.toString() }) } }) break case 'realDimension': lengthTexts.forEach((obj) => { if (obj.actualSize) { obj.set({ text: obj.actualSize.toString() }) } }) break case 'noneDimension': lengthTexts.forEach((obj) => { obj.set({ text: '' }) }) break } // ───────────────────────────────────────────────────────────────────────── // [hip 라벨 cross-display 통일 2026-04-30 by Claude] // // 배경: // 좌우대칭 hip(대각선) 라인의 라벨이 0.5mm 차이로 갈리는 케이스 발생 // (예: 좌측 5048.5 / 우측 5048). // // 원인: // 1) polygon split / ridge intersection 단계 부동소수점 연산 // → QLine.js:23 toFixed(1) 0.1mm 단위 절단 → 좌우 hip 좌표가 0.1mm 차 // 2) useLine.js setActualSize 의 axisLength * tan + sqrt 계산에서 // 좌표 0.1mm 차가 ×10 으로 1mm 차로 증폭 // 3) 마지막 `Math.round(actualSize * 2) / 2` (0.5mm 단위) boundary 에서 갈림 // → 한쪽은 5048.0, 한쪽은 5048.5 로 결정 // 4) planeSize 도 usePolygon.js 의 _equalizeIfNearSymmetric([avg, avg]) // 경로에서 0.5mm 단위 값이 만들어질 수 있음 (corridorDimension 모드에 영향) // // 수정 방침: // - **표시 레이어 (lengthText.text) 만 보정**. // - line.attributes.actualSize / line.attributes.planeSize 는 절대 안 건드림. // → 다운스트림 read 경로 (라인 분할 비율 / 확장라인 병합 / 모듈배치 shape // 상속 / JSON 저장 / DB / 복제 플랜) 영향 0. // // 처리: // - 대각선 hip 라벨끼리 묶어서, 표시값이 ±2mm 이내이면 정수 round 한 // 공통값으로 스냅 (5048.5/5048 → 둘 다 5049 또는 5048). // - corridorDimension / realDimension 모드 모두 적용. // - noneDimension 모드는 빈 문자열이라 패스. // // 추후 근본 수정 (다른 개발자 검토 필요): // - QLine.js:23 toFixed(1) → toFixed(2) 정밀도 상향 (영향 범위 넓어 별건) // - skeleton-utils.js getLineIntersection 결과 좌표 round // - useLine.js setActualSize 마지막 round 단위 변경 (0.5 → 1.0) // 위 근본수정이 들어가면 이 보정 코드는 제거 가능. // // 메모리: feedback_display_vs_internal_values.md 참고. // ───────────────────────────────────────────────────────────────────────── if (column === 'corridorDimension' || column === 'realDimension') { // 대각선(hip) lengthText 추출. // lengthText 객체는 QPolygon.js:712-734 에서 minX/maxX/minY/maxY 로 부모 라인의 // 바운딩박스를 저장. 둘 다 양수 차이 = 대각선. const hipTexts = lengthTexts.filter((obj) => { const dx = (obj.maxX ?? 0) - (obj.minX ?? 0) const dy = (obj.maxY ?? 0) - (obj.minY ?? 0) return dx > 0.5 && dy > 0.5 }) if (hipTexts.length >= 2) { // 현재 표시 텍스트를 숫자로 파싱. const items = hipTexts .map((obj) => ({ obj, value: parseFloat(obj.text) })) .filter((item) => !isNaN(item.value)) // 그룹화: 표시값이 ±TOLERANCE mm 이내면 동일 그룹. // TOLERANCE=2 는 0.5 단위 round drift + sqrt fp 노이즈 흡수. // 대칭 hip 들의 정상 길이차 (예: 다른 지붕면의 hip) 보다는 작아야 함. const TOLERANCE = 2 const groups = [] for (const item of items) { const matched = groups.find((g) => Math.abs(g.representative - item.value) <= TOLERANCE) if (matched) { matched.items.push(item) // 그룹 대표값은 첫 항목 값 유지 (안정적인 클러스터링). } else { groups.push({ representative: item.value, items: [item] }) } } // 각 그룹의 멤버가 2개 이상이면 정수 round 평균값으로 통일. // 1개 그룹은 단독 hip 이므로 보정 불필요. for (const group of groups) { if (group.items.length < 2) continue const avg = group.items.reduce((s, i) => s + i.value, 0) / group.items.length const unified = Math.round(avg).toString() for (const item of group.items) { // text 만 set. obj.actualSize / obj.planeSize 는 그대로. item.obj.set({ text: unified }) } } } } // ───────────────────────────────────────────────────────────────────────── canvas?.renderAll() } return { changeCorridorDimensionText, } }