From dbb6f0af81c1153c97ed16d8110f1891a5be3b0e Mon Sep 17 00:00:00 2001 From: yscha Date: Sun, 7 Dec 2025 14:48:05 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8F=99=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20fabri?= =?UTF-8?q?c=20=EB=B2=84=EC=A0=84=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/fabric/QPolygon.js | 4 +- .../roofcover/useRoofAllocationSetting.js | 18 ++ src/hooks/usePolygon.js | 170 +++++++++++++----- src/util/skeleton-utils.js | 4 +- 5 files changed, 152 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 7be9b3f3..676d8f4f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "chart.js": "^4.4.6", "dayjs": "^1.11.13", "env-cmd": "^10.1.0", - "fabric": "^5.3.0", + "fabric": "^5.5.2", "framer-motion": "^11.2.13", "fs": "^0.0.1-security", "iron-session": "^8.0.2", diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 8186b449..3ca095a8 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -336,8 +336,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) { // 용마루 -- straight-skeleton console.log('용마루 지붕') - drawRidgeRoof(this.id, this.canvas, textMode) - //drawSkeletonRidgeRoof(this.id, this.canvas, textMode); + //drawRidgeRoof(this.id, this.canvas, textMode) + drawSkeletonRidgeRoof(this.id, this.canvas, textMode); } else if (isGableRoof(types)) { // A형, B형 박공 지붕 console.log('패턴 지붕') diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 799e3a72..811d6fd6 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -29,6 +29,8 @@ import { QcastContext } from '@/app/QcastProvider' import { usePlan } from '@/hooks/usePlan' import { roofsState } from '@/store/roofAtom' import { useText } from '@/hooks/useText' +import { processEaveHelpLines } from '@/util/skeleton-utils' +import { QLine } from '@/components/fabric/QLine' export function useRoofAllocationSetting(id) { const canvas = useRecoilValue(canvasState) @@ -404,6 +406,22 @@ export function useRoofAllocationSetting(id) { const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) roofBases.forEach((roofBase) => { try { + + const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id) + if (roofEaveHelpLines.length > 0) { + if (roofBase.lines) { + // Filter out any eaveHelpLines that are already in lines to avoid duplicates + const existingEaveLineIds = new Set(roofBase.lines.map((line) => line.id)) + const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id)) + roofBase.lines = [...newEaveLines] + } else { + roofBase.lines = [...roofEaveHelpLines] + } + if (!roofBase.innerLines) { + roofBase.innerLines = [] + } + } + if (roofBase.separatePolygon.length > 0) { splitPolygonWithSeparate(roofBase.separatePolygon) } else { diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 2ae37440..5e21e902 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -845,6 +845,8 @@ export const usePolygon = () => { polygonLines.forEach((line) => { line.need = true }) + // 순서에 의존하지 않도록 모든 조합을 먼저 확인한 후 처리 + const innerLineMapping = new Map() // innerLine -> polygonLine 매핑 저장 // innerLines와 polygonLines의 겹침을 확인하고 type 변경 innerLines.forEach((innerLine) => { @@ -854,14 +856,28 @@ export const usePolygon = () => { if (innerLine.attributes && polygonLine.attributes.type) { // innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경 if (polygonLine.length < innerLine.length) { - polygonLine.need = false + if(polygonLine.lineName !== 'eaveHelpLine'){ + polygonLine.need = false + } } - innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize - innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize - innerLine.attributes.type = polygonLine.attributes.type - innerLine.direction = polygonLine.direction - innerLine.attributes.isStart = true - innerLine.parentLine = polygonLine + // innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize + // innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize + // innerLine.attributes.type = polygonLine.attributes.type + // innerLine.direction = polygonLine.direction + // innerLine.attributes.isStart = true + // innerLine.parentLine = polygonLine + + + // 매핑된 innerLine의 attributes를 변경 (교차점 계산 전에 적용) + innerLineMapping.forEach((polygonLine, innerLine) => { + innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize + innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize + innerLine.attributes.type = polygonLine.attributes.type + innerLine.direction = polygonLine.direction + innerLine.attributes.isStart = true + innerLine.parentLine = polygonLine + }) + } } }) @@ -1371,7 +1387,7 @@ export const usePolygon = () => { let representLine // 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다. - ;[...polygonLines, ...innerLines].forEach((line) => { + [...polygonLines, ...innerLines].forEach((line) => { let startFlag = false let endFlag = false const startPoint = line.startPoint @@ -1567,52 +1583,126 @@ export const usePolygon = () => { // ==== Dijkstra pathfinding ==== + // function findShortestPath(start, end, graph, epsilon = 1) { + // const startKey = pointToKey(start, epsilon) + // const endKey = pointToKey(end, epsilon) + // + // const distances = {} + // const previous = {} + // const visited = new Set() + // const queue = [{ key: startKey, dist: 0 }] + // + // for (const key in graph) distances[key] = Infinity + // distances[startKey] = 0 + // + // while (queue.length > 0) { + // queue.sort((a, b) => a.dist - b.dist) + // const { key } = queue.shift() + // if (visited.has(key)) continue + // visited.add(key) + // + // for (const neighbor of graph[key] || []) { + // const neighborKey = pointToKey(neighbor.point, epsilon) + // const alt = distances[key] + neighbor.distance + // if (alt < distances[neighborKey]) { + // distances[neighborKey] = alt + // previous[neighborKey] = key + // queue.push({ key: neighborKey, dist: alt }) + // } + // } + // } + // + // const path = [] + // let currentKey = endKey + // + // if (!previous[currentKey]) return null + // + // while (currentKey !== startKey) { + // const [x, y] = currentKey.split(',').map(Number) + // path.unshift({ x, y }) + // currentKey = previous[currentKey] + // } + // + // const [sx, sy] = startKey.split(',').map(Number) + // path.unshift({ x: sx, y: sy }) + // + // return path + // } + function findShortestPath(start, end, graph, epsilon = 1) { - const startKey = pointToKey(start, epsilon) - const endKey = pointToKey(end, epsilon) + const startKey = pointToKey(start, epsilon); + const endKey = pointToKey(end, epsilon); - const distances = {} - const previous = {} - const visited = new Set() - const queue = [{ key: startKey, dist: 0 }] + // 거리와 이전 노드 추적 + const distances = { [startKey]: 0 }; + const previous = {}; + const visited = new Set(); - for (const key in graph) distances[key] = Infinity - distances[startKey] = 0 + // 우선순위 큐 (거리가 짧은 순으로 정렬) + const queue = [{ key: startKey, dist: 0 }]; - while (queue.length > 0) { - queue.sort((a, b) => a.dist - b.dist) - const { key } = queue.shift() - if (visited.has(key)) continue - visited.add(key) + // 모든 노드 초기화 + for (const key in graph) { + if (key !== startKey) { + distances[key] = Infinity; + } + } - for (const neighbor of graph[key] || []) { - const neighborKey = pointToKey(neighbor.point, epsilon) - const alt = distances[key] + neighbor.distance - if (alt < distances[neighborKey]) { - distances[neighborKey] = alt - previous[neighborKey] = key - queue.push({ key: neighborKey, dist: alt }) + // 우선순위 큐에서 다음 노드 선택 + const getNextNode = () => { + if (queue.length === 0) return null; + queue.sort((a, b) => a.dist - b.dist); + return queue.shift(); + }; + + let current; + while ((current = getNextNode())) { + const currentKey = current.key; + + // 목적지에 도달하면 종료 + if (currentKey === endKey) break; + + // 이미 방문한 노드는 건너뜀 + if (visited.has(currentKey)) continue; + visited.add(currentKey); + + // 인접 노드 탐색 + for (const neighbor of graph[currentKey] || []) { + const neighborKey = pointToKey(neighbor.point, epsilon); + if (visited.has(neighborKey)) continue; + + const alt = distances[currentKey] + neighbor.distance; + + // 더 짧은 경로를 찾은 경우 업데이트 + if (alt < (distances[neighborKey] || Infinity)) { + distances[neighborKey] = alt; + previous[neighborKey] = currentKey; + + // 우선순위 큐에 추가 + queue.push({ key: neighborKey, dist: alt }); } } } - const path = [] - let currentKey = endKey + // 경로 재구성 + const path = []; + let currentKey = endKey; - if (!previous[currentKey]) return null - - while (currentKey !== startKey) { - const [x, y] = currentKey.split(',').map(Number) - path.unshift({ x, y }) - currentKey = previous[currentKey] + // 시작점에 도달할 때까지 역추적 + while (previous[currentKey] !== undefined) { + const [x, y] = currentKey.split(',').map(Number); + path.unshift({ x, y }); + currentKey = previous[currentKey]; } - const [sx, sy] = startKey.split(',').map(Number) - path.unshift({ x: sx, y: sy }) + // 시작점 추가 + if (path.length > 0) { + const [sx, sy] = startKey.split(',').map(Number); + path.unshift({ x: sx, y: sy }); + } - return path + return path.length > 0 ? path : null; } - // 최종 함수 function getPath(start, end, graph, epsilon = 1) { // startPoint와 arrivalPoint가 될 수 있는 점은 line.attributes.type이 'default' 혹은 null이 아닌 line인 경우에만 가능 diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 80763cfa..ab72b12b 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -828,7 +828,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const line = new QLine([p1.x, p1.y, p2.x, p2.y], { parentId : roof.id, fontSize : roof.fontSize, - stroke : stroke, + stroke : '#3FBAE6', strokeWidth: 4, name : 'eaveHelpLine', lineName : 'eaveHelpLine', @@ -963,8 +963,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } } - - if(isStartEnd.end){ const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() const aStartY = Big(roofLine.y2).plus(moveDist).toNumber()