라벨변경 #810
@ -31,6 +31,7 @@ import { usePlan } from '@/hooks/usePlan'
|
|||||||
import { roofsState } from '@/store/roofAtom'
|
import { roofsState } from '@/store/roofAtom'
|
||||||
import { useText } from '@/hooks/useText'
|
import { useText } from '@/hooks/useText'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
|
import { calcLineActualSize2 } from '@/util/qpolygon-utils'
|
||||||
|
|
||||||
export function useRoofAllocationSetting(id) {
|
export function useRoofAllocationSetting(id) {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
@ -405,7 +406,43 @@ export function useRoofAllocationSetting(id) {
|
|||||||
if (roof.separatePolygon.length === 0) {
|
if (roof.separatePolygon.length === 0) {
|
||||||
roof.innerLines.forEach((line) => {
|
roof.innerLines.forEach((line) => {
|
||||||
if ((!line.attributes.actualSize || line.attributes?.actualSize === 0) && line.length > 1) {
|
if ((!line.attributes.actualSize || line.attributes?.actualSize === 0) && line.length > 1) {
|
||||||
line.set({ attributes: { ...line.attributes, actualSize: line.attributes.planeSize } })
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
// [hip actualSize fallback 2026-04-30]
|
||||||
|
// 기존: actualSize 미설정/0 시 planeSize 로 fallback → diagonal(hip)
|
||||||
|
// 까지 actualSize=planeSize 가 되어 伏せ図/配置面 라벨 토글 무효화.
|
||||||
|
// 수정: diagonal 은 pitch 로 actualSize 산출(calcLineActualSize2),
|
||||||
|
// horizontal/vertical 만 planeSize fallback 유지.
|
||||||
|
// pitch 우선순위: line.attributes.pitch → roof.attributes.pitch
|
||||||
|
// → roof.roofMaterial.pitch → 0(편평).
|
||||||
|
// ─────────────────────────────────────────────────────────────────
|
||||||
|
const dx = Math.abs((line.x2 ?? 0) - (line.x1 ?? 0))
|
||||||
|
const dy = Math.abs((line.y2 ?? 0) - (line.y1 ?? 0))
|
||||||
|
const isDiagonal = dx > 0.5 && dy > 0.5
|
||||||
|
let newActualSize
|
||||||
|
if (isDiagonal) {
|
||||||
|
const pitch =
|
||||||
|
line.attributes?.pitch ??
|
||||||
|
roof.attributes?.pitch ??
|
||||||
|
roof.roofMaterial?.pitch ??
|
||||||
|
0
|
||||||
|
newActualSize = Number(
|
||||||
|
calcLineActualSize2(
|
||||||
|
{ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 },
|
||||||
|
getDegreeByChon(pitch),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
newActualSize = line.attributes.planeSize
|
||||||
|
}
|
||||||
|
line.set({ attributes: { ...line.attributes, actualSize: newActualSize } })
|
||||||
|
// lengthText 에 actualSize 즉시 propagate (없으면 모드 토글이 if(obj.actualSize)
|
||||||
|
// 가드에 막혀 토글 무효화).
|
||||||
|
const lengthText = canvas.getObjects().find(
|
||||||
|
(o) => o.name === 'lengthText' && o.parentId === line.id,
|
||||||
|
)
|
||||||
|
if (lengthText) {
|
||||||
|
lengthText.set({ actualSize: newActualSize })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -479,6 +516,10 @@ export function useRoofAllocationSetting(id) {
|
|||||||
|
|
||||||
const sk = roofBase.innerLines.find((sl) => {
|
const sk = roofBase.innerLines.find((sl) => {
|
||||||
if (removedSk.includes(sl)) return false
|
if (removedSk.includes(sl)) return false
|
||||||
|
// [auxiliaryLine 제외 2026-04-30] integrateExtensionLines 는 SK auto-build
|
||||||
|
// hip stub + ext 의 1:1 통합용. 사용자가 그린 auxiliaryLine 은 이미 완전한
|
||||||
|
// 분할선이므로 ext 와 병합하면 좌표가 어긋남(코너 끝점 손실).
|
||||||
|
if (sl.name === 'auxiliaryLine') return false
|
||||||
const skP1 = { x: sl.x1, y: sl.y1 }
|
const skP1 = { x: sl.x1, y: sl.y1 }
|
||||||
const skP2 = { x: sl.x2, y: sl.y2 }
|
const skP2 = { x: sl.x2, y: sl.y2 }
|
||||||
const sharesP1 = isSamePt(extP1, skP1) || isSamePt(extP1, skP2)
|
const sharesP1 = isSamePt(extP1, skP1) || isSamePt(extP1, skP2)
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
import { corridorDimensionSelector } from '@/store/settingAtom'
|
import { corridorDimensionSelector } from '@/store/settingAtom'
|
||||||
import { canvasState } from '@/store/canvasAtom'
|
import { canvasState, globalPitchState } from '@/store/canvasAtom'
|
||||||
|
import { calcLineActualSize2 } from '@/util/qpolygon-utils'
|
||||||
|
import { getDegreeByChon } from '@/util/canvas-util'
|
||||||
|
|
||||||
export function useText() {
|
export function useText() {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
const corridorDimension = useRecoilValue(corridorDimensionSelector)
|
const corridorDimension = useRecoilValue(corridorDimensionSelector)
|
||||||
|
const globalPitch = useRecoilValue(globalPitchState)
|
||||||
|
|
||||||
const changeCorridorDimensionText = (columnText) => {
|
const changeCorridorDimensionText = (columnText) => {
|
||||||
let { column } = corridorDimension
|
let { column } = corridorDimension
|
||||||
@ -21,18 +24,94 @@ export function useText() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
// [라벨 토글 fallback 2026-04-30]
|
||||||
|
// 기존: obj.planeSize / obj.actualSize 가 lengthText 에 set 되어있어야만 토글 적용.
|
||||||
|
// hip(대각선)을 비롯해 일부 경로(skeleton SK 빌더, split 후 재생성 등)는
|
||||||
|
// lengthText 의 attribute 동기화가 누락되어 토글 시 if(obj.xxxSize) 가드에 막힘.
|
||||||
|
// 수정: parent line(QLine) 의 attributes 에서 직접 fallback 으로 읽어오고,
|
||||||
|
// lengthText 에도 즉시 propagate 해서 다음 토글부터 정상 인식.
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
const resolveSize = (obj, key) => {
|
||||||
|
if (obj[key]) return obj[key]
|
||||||
|
const parent = obj.parent ?? canvas.getObjects().find((o) => o.id === obj.parentId)
|
||||||
|
const v = parent?.attributes?.[key]
|
||||||
|
if (v) {
|
||||||
|
obj.set({ [key]: v })
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// [hip actualSize 즉석 재계산 2026-04-30]
|
||||||
|
// 기존 SK 가 pitch=0 으로 빌드되어 hip 의 actualSize === planeSize 가 된 경우,
|
||||||
|
// realDimension 토글 시 pitch 폴백으로 즉석 재계산.
|
||||||
|
// parent line.attributes.pitch → roof.pitch → roof.roofMaterial.pitch.
|
||||||
|
// 재계산 결과는 obj.actualSize 와 line.attributes.actualSize 양쪽에 propagate.
|
||||||
|
const recomputeHipActualIfFlat = (obj) => {
|
||||||
|
const parent = obj.parent ?? canvas.getObjects().find((o) => o.id === obj.parentId)
|
||||||
|
if (!parent) return null
|
||||||
|
// [isDiagonal 판정 — parent 좌표 사용]
|
||||||
|
// QLine lengthText 의 minX/maxX/minY/maxY 는 this.length(라인길이) 로 설정되어
|
||||||
|
// 수평 ridge 도 dy>0 이 되는 버그. 반드시 parent.x1/y1/x2/y2 로 판정.
|
||||||
|
const px1 = Number(parent.x1)
|
||||||
|
const py1 = Number(parent.y1)
|
||||||
|
const px2 = Number(parent.x2)
|
||||||
|
const py2 = Number(parent.y2)
|
||||||
|
const dx = Number.isFinite(px1) && Number.isFinite(px2) ? Math.abs(px2 - px1) : 0
|
||||||
|
const dy = Number.isFinite(py1) && Number.isFinite(py2) ? Math.abs(py2 - py1) : 0
|
||||||
|
// 임계 5.0mm — addRawLine 의 HIP/RIDGE 분류 임계와 일치 (drift 흡수).
|
||||||
|
const isDiagonal = dx > 5.0 && dy > 5.0
|
||||||
|
if (!isDiagonal) return null
|
||||||
|
const plane = obj.planeSize ?? parent?.attributes?.planeSize
|
||||||
|
const actual = obj.actualSize ?? parent?.attributes?.actualSize
|
||||||
|
if (!plane) return null
|
||||||
|
if (actual && Math.abs(actual - plane) > 0.5) return null // 이미 정상
|
||||||
|
const roof = canvas.getObjects().find((o) => o.id === parent?.parentId)
|
||||||
|
const pitch =
|
||||||
|
(parent?.attributes?.pitch ? parent.attributes.pitch : null) ??
|
||||||
|
(roof?.pitch ? roof.pitch : null) ??
|
||||||
|
(roof?.roofMaterial?.pitch ? roof.roofMaterial.pitch : null) ??
|
||||||
|
globalPitch ??
|
||||||
|
4
|
||||||
|
if (!pitch) return null
|
||||||
|
// x1/x2/y1/y2 가 number 가 아니면 calcLinePlaneSize 의 Big() 호출에서 throw.
|
||||||
|
const x1 = Number(parent.x1)
|
||||||
|
const y1 = Number(parent.y1)
|
||||||
|
const x2 = Number(parent.x2)
|
||||||
|
const y2 = Number(parent.y2)
|
||||||
|
if (!Number.isFinite(x1) || !Number.isFinite(y1) || !Number.isFinite(x2) || !Number.isFinite(y2)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let newActual = null
|
||||||
|
try {
|
||||||
|
newActual = Number(calcLineActualSize2({ x1, y1, x2, y2 }, getDegreeByChon(pitch)))
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!newActual || Math.abs(newActual - plane) < 0.5) return null
|
||||||
|
// 표시용 obj.actualSize 만 갱신. parent.attributes 갱신 시 fabric set 사이드이펙트
|
||||||
|
// (Big.js 사슬) 가 다른 곳을 트리거할 수 있어 회피.
|
||||||
|
obj.set({ actualSize: newActual })
|
||||||
|
return newActual
|
||||||
|
}
|
||||||
|
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case 'corridorDimension':
|
case 'corridorDimension':
|
||||||
lengthTexts.forEach((obj) => {
|
lengthTexts.forEach((obj) => {
|
||||||
if (obj.planeSize) {
|
const v = resolveSize(obj, 'planeSize')
|
||||||
obj.set({ text: obj.planeSize.toString() })
|
if (v) {
|
||||||
|
obj.set({ text: v.toString() })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'realDimension':
|
case 'realDimension':
|
||||||
lengthTexts.forEach((obj) => {
|
lengthTexts.forEach((obj) => {
|
||||||
if (obj.actualSize) {
|
// hip 이고 actualSize===planeSize 면 pitch 로 즉석 재계산.
|
||||||
obj.set({ text: obj.actualSize.toString() })
|
recomputeHipActualIfFlat(obj)
|
||||||
|
const v = resolveSize(obj, 'actualSize')
|
||||||
|
if (v) {
|
||||||
|
obj.set({ text: v.toString() })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
@ -85,9 +164,21 @@ export function useText() {
|
|||||||
// lengthText 객체는 QPolygon.js:712-734 에서 minX/maxX/minY/maxY 로 부모 라인의
|
// lengthText 객체는 QPolygon.js:712-734 에서 minX/maxX/minY/maxY 로 부모 라인의
|
||||||
// 바운딩박스를 저장. 둘 다 양수 차이 = 대각선.
|
// 바운딩박스를 저장. 둘 다 양수 차이 = 대각선.
|
||||||
const hipTexts = lengthTexts.filter((obj) => {
|
const hipTexts = lengthTexts.filter((obj) => {
|
||||||
const dx = (obj.maxX ?? 0) - (obj.minX ?? 0)
|
// [parent 좌표 기반 isDiagonal — QLine lengthText min/max 부정확 회피]
|
||||||
const dy = (obj.maxY ?? 0) - (obj.minY ?? 0)
|
// QLine.js:122-125 가 minY/maxY 를 this.top/this.top+this.length 로 설정해
|
||||||
return dx > 0.5 && dy > 0.5
|
// 수평 ridge 도 maxY-minY>0 이 됨. 반드시 parent.x1/y1/x2/y2 로 판정.
|
||||||
|
const parent = obj.parent ?? canvas.getObjects().find((p) => p.id === obj.parentId)
|
||||||
|
if (!parent) return false
|
||||||
|
const px1 = Number(parent.x1)
|
||||||
|
const py1 = Number(parent.y1)
|
||||||
|
const px2 = Number(parent.x2)
|
||||||
|
const py2 = Number(parent.y2)
|
||||||
|
if (!Number.isFinite(px1) || !Number.isFinite(py1) || !Number.isFinite(px2) || !Number.isFinite(py2)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const dx = Math.abs(px2 - px1)
|
||||||
|
const dy = Math.abs(py2 - py1)
|
||||||
|
return dx > 5.0 && dy > 5.0
|
||||||
})
|
})
|
||||||
|
|
||||||
if (hipTexts.length >= 2) {
|
if (hipTexts.length >= 2) {
|
||||||
|
|||||||
@ -2912,7 +2912,12 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
|||||||
console.log('Has matching line:', outerLine);
|
console.log('Has matching line:', outerLine);
|
||||||
//if(outerLine === null) return
|
//if(outerLine === null) return
|
||||||
}
|
}
|
||||||
let pitch = outerLine?.attributes?.pitch??0
|
// [hip pitch fallback 2026-04-30]
|
||||||
|
// outerLine.attributes.pitch 가 없으면 0 → calcLineActualSize2(deg=0) 가
|
||||||
|
// calcLinePlaneSize 와 동치(tan0=0) → 모든 hip 의 planeSize === actualSize.
|
||||||
|
// 결과: 伏せ図(plane) ↔ 配置面(actual) 메뉴 토글해도 hip 라벨 변화 없음.
|
||||||
|
// roof.pitch 폴백(usePolygon.js:1649 패턴) 으로 진짜 pitch 확보.
|
||||||
|
let pitch = outerLine?.attributes?.pitch ?? roof?.pitch ?? 0
|
||||||
|
|
||||||
|
|
||||||
const convertedPolygon = edgeResult.Polygon?.map(point => ({
|
const convertedPolygon = edgeResult.Polygon?.map(point => ({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user