This commit is contained in:
yscha 2025-11-30 02:52:13 +09:00
parent b42142888e
commit 2ac1e80964
4 changed files with 427 additions and 32 deletions

View File

@ -407,28 +407,20 @@ export function useRoofAllocationSetting(id) {
roofBases.forEach((roofBase) => {
try {
const roofEaveHelpLines = canvas.getObjects().filter(obj =>
obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id
);
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];
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];
roofBase.lines = [...roofEaveHelpLines]
}
if (!roofBase.innerLines) {
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)

View File

@ -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) {
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를 변경 (교차점 계산 전에 적용)
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
})
}
}
})

387
src/hooks/useSkeleton.js Normal file
View File

@ -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 = []
}
}
}

View File

@ -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;