diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index ee89e443..39b8b53f 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -407,29 +407,21 @@ export function useRoofAllocationSetting(id) { roofBases.forEach((roofBase) => { try { - const roofEaveHelpLines = canvas.getObjects().filter(obj => - obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id - ); - - - 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]; + 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.innerLines) { - roofBase.innerLines = []; - } - - // Add only eaveHelpLines that belong to this roofBase - // const baseEaveHelpLines = roofEaveHelpLines.filter(line => line.roofId === roofBase.id); - // roofBase.innerLines = [...new Set([...roofBase.innerLines, ...baseEaveHelpLines])]; - if (roofBase.separatePolygon.length > 0) { splitPolygonWithSeparate(roofBase.separatePolygon) } else { diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 2ae37440..3fa2d741 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 + }) + } } }) diff --git a/src/hooks/useSkeleton.js b/src/hooks/useSkeleton.js new file mode 100644 index 00000000..ea61b7e3 --- /dev/null +++ b/src/hooks/useSkeleton.js @@ -0,0 +1,387 @@ +import { useEffect } from 'react' +import { useRecoilState } from 'recoil' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' +import Big from 'big.js' +import { isSamePoint, toGeoJSON } from '@/util/qpolygon-utils' +import { SkeletonBuilder } from '@/lib/skeletons' +import { + preprocessPolygonCoordinates, + findOppositeLine, + createOrderedBasePoints, + createInnerLinesFromSkeleton +} from '@/util/skeleton-utils' + + + +export default function useSkeleton(canvas, roofId, textMode = false) { + // 2. 스켈레톤 생성 및 그리기 + //처마 + if (!canvas) { + console.warn('Canvas is not available'); + return; + } + + let roof = canvas?.getObjects().find((object) => object.id === roofId) + + if (!roof) { + console.warn(`Roof with id ${roofId} not found`); + return; + } + + if (!roof.points || roof.points.length < 3) { + console.warn('Roof points are invalid'); + return; + } + + const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE] + const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD] + + /** 외벽선 */ + const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) + //const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) + + const baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || []; + const baseLinePoints = baseLines.map((line) => ({x:line.left, y:line.top})); + + const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || []; + const outerLinePoints = outerLines.map((line) => ({x:line.left, y:line.top})) + + const hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && object.parentId === roofId) || []; + const ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || []; + + //const skeletonLines = []; + // 1. 지붕 폴리곤 좌표 전처리 + const coordinates = preprocessPolygonCoordinates(roof.points); + if (coordinates.length < 3) { + console.warn("Polygon has less than 3 unique points. Cannot generate skeleton."); + return; + } + + const moveFlowLine = roof.moveFlowLine || 0; // Provide a default value + const moveUpDown = roof.moveUpDown || 0; // Provide a default value + + + + let points = roof.points; + + + + //마루이동 + if (moveFlowLine !== 0 || moveUpDown !== 0) { + const movingLineFromSkeleton = (roofId, canvas) => { + if (!canvas) { + console.warn('Canvas is not available in movingLineFromSkeleton'); + return null; + } + + let roof = canvas?.getObjects().find((object) => object.id === roofId) + + if (!roof) { + console.warn(`Roof with id ${roofId} not found in movingLineFromSkeleton`); + return null; + } + + let moveDirection = roof.moveDirect; + let moveFlowLine = roof.moveFlowLine??0; + let moveUpDown = roof.moveUpDown??0; + const getSelectLine = () => roof.moveSelectLine; + const selectLine = getSelectLine(); + + if (!selectLine || !selectLine.startPoint || !selectLine.endPoint) { + console.warn('SelectLine is not available'); + return null; + } + + let movePosition = roof.movePosition; + + const startPoint = selectLine.startPoint + const endPoint = selectLine.endPoint + const orgRoofPoints = roof.points; // orgPoint를 orgPoints로 변경 + + if (!canvas.skeleton || !canvas.skeleton.Edges) { + console.warn('Skeleton edges are not available'); + return null; + } + + const oldPoints = canvas?.skeleton.lastPoints ?? orgRoofPoints // 여기도 변경 + const oppositeLine = findOppositeLine(canvas.skeleton.Edges, startPoint, endPoint, oldPoints); + + const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) + + if (!wall || !wall.baseLines) { + console.warn('Wall or baseLines are not available'); + return null; + } + + const baseLines = wall.baseLines + roof.basePoints = createOrderedBasePoints(roof.points, baseLines) + + const skeletonPolygon = canvas.getObjects().filter((object) => object.skeletonType === 'polygon' && object.parentId === roofId) + const skeletonLines = canvas.getObjects().filter((object) => object.skeletonType === 'line' && object.parentId === roofId) + + if (oppositeLine) { + console.log('Opposite line found:', oppositeLine); + } else { + console.log('No opposite line found'); + } + + if(moveFlowLine !== 0) { + return oldPoints.map((point, index) => { + console.log('Point:', point); + const newPoint = { ...point }; + const absMove = Big(moveFlowLine).times(2).div(10); + + console.log('skeletonBuilder moveDirection:', moveDirection); + + switch (moveDirection) { + case 'left': + // Move left: decrease X + if (moveFlowLine !== 0) { + for (const line of oppositeLine) { + if (line.position === 'left') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).plus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).plus(absMove).toNumber(); + } + + break; + + } + + } + } else if (moveUpDown !== 0) { + + } + + break; + case 'right': + for (const line of oppositeLine) { + if (line.position === 'right') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).minus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).minus(absMove).toNumber(); + } + break + + } + + } + + break; + case 'up': + // Move up: decrease Y (toward top of screen) + + for (const line of oppositeLine) { + if (line.position === 'top') { + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).minus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + } + break; + + } + } + + break; + case 'down': + // Move down: increase Y (toward bottom of screen) + for (const line of oppositeLine) { + + if (line.position === 'bottom') { + + console.log('oldPoint:', point); + + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).minus(absMove).toNumber(); + + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + } + + + break; + } + + } + break; + default : + + } + + console.log('newPoint:', newPoint); + //baseline 변경 + return newPoint; + }) + } else if(moveUpDown !== 0) { + + const position = movePosition //result.position; + const absMove = Big(moveUpDown).times(1).div(10); + const modifiedStartPoints = []; + // oldPoints를 복사해서 새로운 points 배열 생성 + let newPoints = oldPoints.map(point => ({...point})); + + // selectLine과 일치하는 baseLines 찾기 + const matchingLines = baseLines + .map((line, index) => ({ ...line, findIndex: index })) + .filter(line => + (isSamePoint(line.startPoint, selectLine.startPoint) && + isSamePoint(line.endPoint, selectLine.endPoint)) || + (isSamePoint(line.startPoint, selectLine.endPoint) && + isSamePoint(line.endPoint, selectLine.startPoint)) + ); + + + matchingLines.forEach(line => { + const originalStartPoint = line.startPoint; + const originalEndPoint = line.endPoint; + const offset = line.attributes.offset + // 새로운 좌표 계산 + let newStartPoint = {...originalStartPoint}; + let newEndPoint = {...originalEndPoint}; + + +// 원본 라인 업데이트 + // newPoints 배열에서 일치하는 포인트들을 찾아서 업데이트 + console.log('absMove::', absMove); + newPoints.forEach((point, index) => { + if(position === 'bottom'){ + if (moveDirection === 'in') { + if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + // point.y = Big(point.y).minus(absMove).toNumber(); + // } + }else if (moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + // point.y = Big(point.y).plus(absMove).toNumber(); + // } + } + + }else if (position === 'top'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + } + + }else if(position === 'left'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + // point.x = Big(point.x).plus(absMove).toNumber(); + // } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + // point.x = Big(point.x).minus(absMove).toNumber(); + // } + } + + }else if(position === 'right'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + } + + } + + }); + + // 원본 baseLine도 업데이트 + line.startPoint = newStartPoint; + line.endPoint = newEndPoint; + }); + return newPoints; + } + } + const result = movingLineFromSkeleton(roofId, canvas); + if (result) { + points = result; + } + } + + + console.log('points:', points); + const geoJSONPolygon = toGeoJSON(points) + + try { + // SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거 + geoJSONPolygon.pop() + const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]]) + + // 스켈레톤 데이터를 기반으로 내부선 생성 + roof.innerLines = roof.innerLines || []; + roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode) + + // 캔버스에 스켈레톤 상태 저장 + if (!canvas.skeletonStates) { + canvas.skeletonStates = {} + canvas.skeletonLines = [] + } + canvas.skeletonStates[roofId] = true + canvas.skeletonLines = []; + canvas.skeletonLines.push(...roof.innerLines) + roof.skeletonLines = canvas.skeletonLines; + + const cleanSkeleton = { + Edges: skeleton.Edges.map(edge => ({ + X1: edge.Edge.Begin.X, + Y1: edge.Edge.Begin.Y, + X2: edge.Edge.End.X, + Y2: edge.Edge.End.Y, + Polygon: edge.Polygon, + + // Add other necessary properties, but skip circular references + })), + roofId: roofId, + // Add other necessary top-level properties + }; + canvas.skeleton = []; + canvas.skeleton = cleanSkeleton + canvas.skeleton.lastPoints = points + canvas.set("skeleton", cleanSkeleton); + canvas.renderAll() + + + console.log('skeleton rendered.', canvas); + } catch (e) { + console.error('스켈레톤 생성 중 오류 발생:', e) + if (canvas.skeletonStates) { + canvas.skeletonStates[roofId] = false + canvas.skeletonStates = {} + canvas.skeletonLines = [] + } + } +} diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 956f2343..82ea4ea0 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -1138,10 +1138,10 @@ if(roof.moveUpDown??0 > 0) { // canvas.renderAll if (findPoints.length > 0) { // 모든 점에 대해 라인 업데이트를 누적 - // return findPoints.reduce((lines, point) => { - // return updateAndAddLine(lines, point); - // }, [...innerLines]); - return updateAndAddLine(innerLines, findPoints[0]); + return findPoints.reduce((lines, point) => { + return updateAndAddLine(lines, point); + }, [...innerLines]); + } return innerLines;