136 lines
6.0 KiB
JavaScript
136 lines
6.0 KiB
JavaScript
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,
|
||
}
|
||
}
|