보조선 기준으로 지붕 나누기 추가
This commit is contained in:
parent
591c828bcf
commit
d0493287ec
@ -4,7 +4,7 @@ import { fabric } from 'fabric'
|
|||||||
import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, isPointOnLine } from '@/util/canvas-util'
|
import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, isPointOnLine } from '@/util/canvas-util'
|
||||||
import { QPolygon } from '@/components/fabric/QPolygon'
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
||||||
import { calcLinePlaneSize, equalizeSymmetricHips, isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils'
|
import { calcLinePlaneSize, equalizeSymmetricHips, isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils'
|
||||||
import { basicSettingState, flowDisplaySelector, corridorDimensionSelector } from '@/store/settingAtom'
|
import { basicSettingState, corridorDimensionSelector, flowDisplaySelector } from '@/store/settingAtom'
|
||||||
import { fontSelector } from '@/store/fontAtom'
|
import { fontSelector } from '@/store/fontAtom'
|
||||||
import { QLine } from '@/components/fabric/QLine'
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
||||||
@ -97,30 +97,27 @@ export const usePolygon = () => {
|
|||||||
// 기존엔 planeSize 만 표시해, 실제치수 모드에서도 이동 후 평면길이(예: 8000)가 나오던 문제.
|
// 기존엔 planeSize 만 표시해, 실제치수 모드에서도 이동 후 평면길이(예: 8000)가 나오던 문제.
|
||||||
const __isCorridorDimension = corridorDimension?.column === 'corridorDimension'
|
const __isCorridorDimension = corridorDimension?.column === 'corridorDimension'
|
||||||
const __display = (__isCorridorDimension ? planeSize : +roofSizeSet === 1 ? actualSize : planeSize) ?? planeSize ?? length
|
const __display = (__isCorridorDimension ? planeSize : +roofSizeSet === 1 ? actualSize : planeSize) ?? planeSize ?? length
|
||||||
const text = new fabric.Textbox(
|
const text = new fabric.Textbox(Number(__display).toFixed(1).replace(/\.0$/, ''), {
|
||||||
Number(__display).toFixed(1).replace(/\.0$/, ''),
|
left: left,
|
||||||
{
|
top: top,
|
||||||
left: left,
|
fontSize: lengthTextFontOptions.fontSize.value,
|
||||||
top: top,
|
minX,
|
||||||
fontSize: lengthTextFontOptions.fontSize.value,
|
maxX,
|
||||||
minX,
|
minY,
|
||||||
maxX,
|
maxY,
|
||||||
minY,
|
parentDirection: line.direction,
|
||||||
maxY,
|
parentDegree: degree,
|
||||||
parentDirection: line.direction,
|
parentId: polygon.id,
|
||||||
parentDegree: degree,
|
planeSize: planeSize ?? length,
|
||||||
parentId: polygon.id,
|
actualSize: actualSize ?? length,
|
||||||
planeSize: planeSize ?? length,
|
editable: false,
|
||||||
actualSize: actualSize ?? length,
|
selectable: true,
|
||||||
editable: false,
|
lockRotation: true,
|
||||||
selectable: true,
|
lockScalingX: true,
|
||||||
lockRotation: true,
|
lockScalingY: true,
|
||||||
lockScalingX: true,
|
parent: polygon,
|
||||||
lockScalingY: true,
|
name: 'lengthText',
|
||||||
parent: polygon,
|
})
|
||||||
name: 'lengthText',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
polygon.texts.push(text)
|
polygon.texts.push(text)
|
||||||
canvas.add(text)
|
canvas.add(text)
|
||||||
})
|
})
|
||||||
@ -357,24 +354,27 @@ export const usePolygon = () => {
|
|||||||
const drawDirectionStringToArrow2 = (polygon, showDirectionText) => {
|
const drawDirectionStringToArrow2 = (polygon, showDirectionText) => {
|
||||||
let { direction, surfaceCompass, moduleCompass, arrow } = polygon
|
let { direction, surfaceCompass, moduleCompass, arrow } = polygon
|
||||||
if (moduleCompass === null || moduleCompass === undefined) {
|
if (moduleCompass === null || moduleCompass === undefined) {
|
||||||
const textObj = new fabric.Text(`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : (arrow.roofAngle ?? getDegreeByChon(arrow.pitch))}${pitchText}`, {
|
const textObj = new fabric.Text(
|
||||||
fontFamily: flowFontOptions.fontFamily.value,
|
`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : (arrow.roofAngle ?? getDegreeByChon(arrow.pitch))}${pitchText}`,
|
||||||
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
|
{
|
||||||
fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
|
fontFamily: flowFontOptions.fontFamily.value,
|
||||||
fontSize: flowFontOptions.fontSize.value,
|
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
|
||||||
fill: flowFontOptions.fontColor.value,
|
fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
|
||||||
originX: 'center',
|
fontSize: flowFontOptions.fontSize.value,
|
||||||
originY: 'center',
|
fill: flowFontOptions.fontColor.value,
|
||||||
pitch: arrow.pitch,
|
originX: 'center',
|
||||||
roofAngle: arrow.roofAngle,
|
originY: 'center',
|
||||||
name: 'flowText',
|
pitch: arrow.pitch,
|
||||||
selectable: false,
|
roofAngle: arrow.roofAngle,
|
||||||
left: arrow.stickeyPoint.x,
|
name: 'flowText',
|
||||||
top: arrow.stickeyPoint.y,
|
selectable: false,
|
||||||
parent: arrow,
|
left: arrow.stickeyPoint.x,
|
||||||
parentId: arrow.id,
|
top: arrow.stickeyPoint.y,
|
||||||
visible: isFlowDisplay,
|
parent: arrow,
|
||||||
})
|
parentId: arrow.id,
|
||||||
|
visible: isFlowDisplay,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
polygon.canvas.add(textObj)
|
polygon.canvas.add(textObj)
|
||||||
return
|
return
|
||||||
@ -753,7 +753,9 @@ export const usePolygon = () => {
|
|||||||
// [SPLIT-ALLOC-DIAG 2026-06-01] 분할 graph 진입 전 innerLines 전수 dump.
|
// [SPLIT-ALLOC-DIAG 2026-06-01] 분할 graph 진입 전 innerLines 전수 dump.
|
||||||
// 케라바 패턴 라인(kerabPatternRidge/ExtRidge/Hip/ExtHip)과 원래 ridge(RG-2 등) 가
|
// 케라바 패턴 라인(kerabPatternRidge/ExtRidge/Hip/ExtHip)과 원래 ridge(RG-2 등) 가
|
||||||
// type='default' / isStart 미설정으로 graph 시드/유효점 후보에서 제외되는지 추적용.
|
// type='default' / isStart 미설정으로 graph 시드/유효점 후보에서 제외되는지 추적용.
|
||||||
logger.log(`[SPLIT-ALLOC-DIAG] polygon.id=${polygon.id?.slice(0, 8)} innerLines.visible=${innerLines.length} polygonLines=${polygon.lines?.length ?? 0}`)
|
logger.log(
|
||||||
|
`[SPLIT-ALLOC-DIAG] polygon.id=${polygon.id?.slice(0, 8)} innerLines.visible=${innerLines.length} polygonLines=${polygon.lines?.length ?? 0}`,
|
||||||
|
)
|
||||||
innerLines.forEach((l, i) => {
|
innerLines.forEach((l, i) => {
|
||||||
logger.log(
|
logger.log(
|
||||||
` [SPLIT-ALLOC-DIAG ${i}] lineName=${l.lineName ?? 'none'} name=${l.name} ` +
|
` [SPLIT-ALLOC-DIAG ${i}] lineName=${l.lineName ?? 'none'} name=${l.name} ` +
|
||||||
@ -1503,6 +1505,34 @@ export const usePolygon = () => {
|
|||||||
return Math.abs(line.startPoint.x - line.endPoint.x) > 2 || Math.abs(line.startPoint.y - line.endPoint.y) > 2
|
return Math.abs(line.startPoint.x - line.endPoint.x) > 2 || Math.abs(line.startPoint.y - line.endPoint.y) > 2
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// [SUPERSEDED-EAVES 2026-06-15] 처마선을 바깥으로 옮겨 처마(연·軒)를 만들면, 옮긴 처마와 양끝 연결선
|
||||||
|
// (모두 auxiliaryLine)·원래 처마선(eaves)이 닫힌 "띠 사각형"을 이룬다. 이때 원래 처마선이 면분할
|
||||||
|
// 입력에 남아 있으면 바깥 면을 [띠 + 사다리꼴]로 갈라, 사다리꼴을 닫는 시작선이 사라져 면이 통째로
|
||||||
|
// 누락된다(GETSPLIT-IO 진단: 위 사다리꼴 미생성). 원래 처마선은 이미 면 내부의 가상선이므로 면분할
|
||||||
|
// 입력에서 제외 → 띠+사다리꼴이 하나의 면(예: 6각형)으로 병합된다. 띠 패턴이 없으면 아무 영향 없음.
|
||||||
|
const auxBandLines = allLines.filter((l) => l.name === 'auxiliaryLine')
|
||||||
|
if (auxBandLines.length > 0) {
|
||||||
|
const sharesPt = (l, p) => isSamePoint(l.startPoint, p) || isSamePoint(l.endPoint, p)
|
||||||
|
const otherEnd = (l, p) => (isSamePoint(l.startPoint, p) ? l.endPoint : l.startPoint)
|
||||||
|
const supersededEaves = allLines.filter((E) => {
|
||||||
|
if (E.attributes?.type !== 'eaves') return false
|
||||||
|
const A = E.startPoint
|
||||||
|
const B = E.endPoint
|
||||||
|
// A·B 각각에서 바깥으로 뻗는 연결 보조선(한쪽 끝점만 공유)
|
||||||
|
const CA = auxBandLines.find((c) => c !== E && sharesPt(c, A) && !sharesPt(c, B))
|
||||||
|
const CB = auxBandLines.find((c) => c !== E && c !== CA && sharesPt(c, B) && !sharesPt(c, A))
|
||||||
|
if (!CA || !CB) return false
|
||||||
|
// 두 연결선의 바깥 끝점을 잇는 평행 보조선(옮긴 처마)이 있어야 띠가 닫힌다
|
||||||
|
const Ao = otherEnd(CA, A)
|
||||||
|
const Bo = otherEnd(CB, B)
|
||||||
|
return auxBandLines.some((c) => c !== CA && c !== CB && sharesPt(c, Ao) && sharesPt(c, Bo))
|
||||||
|
})
|
||||||
|
if (supersededEaves.length > 0) {
|
||||||
|
logger.log(`[SUPERSEDED-EAVES] 띠로 대체된 원래 처마선 ${supersededEaves.length}개 면분할 입력에서 제외`)
|
||||||
|
allLines = allLines.filter((l) => !supersededEaves.includes(l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 나눠서 중복 제거된 roof return
|
// 나눠서 중복 제거된 roof return
|
||||||
let newRoofs = getSplitRoofsPoints(allLines)
|
let newRoofs = getSplitRoofsPoints(allLines)
|
||||||
|
|
||||||
@ -1755,9 +1785,12 @@ export const usePolygon = () => {
|
|||||||
|
|
||||||
// 2차 매칭 (옵션 3 + α): 대각선이 외벽 중간을 분할한 sub-segment 처리
|
// 2차 매칭 (옵션 3 + α): 대각선이 외벽 중간을 분할한 sub-segment 처리
|
||||||
const _isCollinearWithin = (parent, p) => {
|
const _isCollinearWithin = (parent, p) => {
|
||||||
const ax = parent.startPoint.x, ay = parent.startPoint.y
|
const ax = parent.startPoint.x,
|
||||||
const bx = parent.endPoint.x, by = parent.endPoint.y
|
ay = parent.startPoint.y
|
||||||
const dx = bx - ax, dy = by - ay
|
const bx = parent.endPoint.x,
|
||||||
|
by = parent.endPoint.y
|
||||||
|
const dx = bx - ax,
|
||||||
|
dy = by - ay
|
||||||
const segLenSq = dx * dx + dy * dy
|
const segLenSq = dx * dx + dy * dy
|
||||||
if (segLenSq < 1) return false
|
if (segLenSq < 1) return false
|
||||||
const cross = (p.x - ax) * dy - (p.y - ay) * dx
|
const cross = (p.x - ax) * dy - (p.y - ay) * dx
|
||||||
@ -2091,6 +2124,22 @@ export const usePolygon = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// [GETSPLIT-IO 2026-06-15] getSplitRoofsPoints 의 정확한 입력(allLines, 좌표스냅·교차분할·중복제거 후)과
|
||||||
|
// 출력(roofs 면 polygon)을 debug/debug.log 로 영속화. 면분할 오할당 진단용 — 로컬 전용.
|
||||||
|
debugCapture.log('GETSPLIT-IO', {
|
||||||
|
allLines: allLines.map((l) => ({
|
||||||
|
name: l.name,
|
||||||
|
lineName: l.lineName ?? 'none',
|
||||||
|
type: l.attributes?.type ?? 'none',
|
||||||
|
isStart: l.attributes?.isStart ?? false,
|
||||||
|
sx: Math.round(l.startPoint.x * 100) / 100,
|
||||||
|
sy: Math.round(l.startPoint.y * 100) / 100,
|
||||||
|
ex: Math.round(l.endPoint.x * 100) / 100,
|
||||||
|
ey: Math.round(l.endPoint.y * 100) / 100,
|
||||||
|
})),
|
||||||
|
roofs: roofs.map((r) => r.map((p) => ({ x: Math.round(p.x * 100) / 100, y: Math.round(p.y * 100) / 100 }))),
|
||||||
|
})
|
||||||
|
|
||||||
return removeDuplicatePolygons(
|
return removeDuplicatePolygons(
|
||||||
roofs.filter((roof) => roof.length < 100),
|
roofs.filter((roof) => roof.length < 100),
|
||||||
allLines.some((line) => line.name === 'auxiliaryLine'),
|
allLines.some((line) => line.name === 'auxiliaryLine'),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user