diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx index 1732f3f7..d0f87114 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx @@ -49,7 +49,7 @@ export default function AuxiliaryDrawing({ setShowAuxiliaryModal }) { handleFix, buttonAct, setButtonAct, - } = useAuxiliaryDrawing() + } = useAuxiliaryDrawing(setShowAuxiliaryModal) const outerLineProps = { length1, diff --git a/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx b/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx index 1267fe98..bab1e696 100644 --- a/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx +++ b/src/components/floor-plan/modal/eavesGable/EavesGableEdit.jsx @@ -10,7 +10,7 @@ import { useEavesGableEdit } from '@/hooks/roofcover/useEavesGableEdit' export default function EavesGableEdit({ setShowEavesGableEditModal }) { const { getMessage } = useMessage() - const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef } = useEavesGableEdit() + const { type, setType, buttonMenu, TYPES, pitchRef, offsetRef, widthRef, radioTypeRef } = useEavesGableEdit(setShowEavesGableEditModal) const eavesProps = { pitchRef, offsetRef, diff --git a/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx b/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx index 248567e3..d26b2ef3 100644 --- a/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx +++ b/src/components/floor-plan/modal/outerlinesetting/PropertiesSetting.jsx @@ -6,7 +6,7 @@ export default function PropertiesSetting(props) { const { getMessage } = useMessage() const { setShowPropertiesSettingModal } = props - const { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } = usePropertiesSetting() + const { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } = usePropertiesSetting(setShowPropertiesSettingModal) return ( diff --git a/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx b/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx index 54bd6844..975a582a 100644 --- a/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx +++ b/src/components/floor-plan/modal/outerlinesetting/WallLineSetting.jsx @@ -39,7 +39,7 @@ export default function WallLineSetting(props) { outerLineDiagonalLengthRef, handleRollback, handleFix, - } = useOuterLineWall() + } = useOuterLineWall(setShowOutlineModal) const outerLineProps = { length1, @@ -171,7 +171,7 @@ export default function WallLineSetting(props) { + diff --git a/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx b/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx index 828b2ad3..8eaa4a49 100644 --- a/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx +++ b/src/components/floor-plan/modal/roofShape/RoofShapePassivitySetting.jsx @@ -7,7 +7,8 @@ import { useMessage } from '@/hooks/useMessage' import { useRoofShapePassivitySetting } from '@/hooks/roofcover/useRoofShapePassivitySetting' export default function RoofShapePassivitySetting({ setShowRoofShapePassivitySettingModal }) { - const { handleSave, handleConfirm, handleRollback, buttons, type, setType, TYPES, offsetRef, pitchRef } = useRoofShapePassivitySetting() + const { handleSave, handleConfirm, handleRollback, buttons, type, setType, TYPES, offsetRef, pitchRef } = + useRoofShapePassivitySetting(setShowRoofShapePassivitySettingModal) const { getMessage } = useMessage() const eavesProps = { diff --git a/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx b/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx index f906a67e..60355848 100644 --- a/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx +++ b/src/components/floor-plan/modal/roofShape/RoofShapeSetting.jsx @@ -37,7 +37,7 @@ export default function RoofShapeSetting({ setShowRoofShapeSettingModal }) { buttonMenu, handleConfirm, handleRollBack, - } = useRoofShapeSetting() + } = useRoofShapeSetting(setShowRoofShapeSettingModal) const ridgeProps = { pitch, setPitch, eavesOffset, setEavesOffset } const patternProps = { pitch, setPitch, eavesOffset, setEavesOffset, gableOffset, setGableOffset } diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index a2810163..2e2e0a1b 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -28,7 +28,7 @@ import { fabric } from 'fabric' import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' // 보조선 작성 -export function useAuxiliaryDrawing() { +export function useAuxiliaryDrawing(setShowAuxiliaryModal) { const canvas = useRecoilValue(canvasState) const { addCanvasMouseEventListener, addDocumentEventListener, removeMouseLine, initEvent } = useEvent() const { getIntersectMousePoint } = useMouse() @@ -613,11 +613,15 @@ export function useAuxiliaryDrawing() { addCanvasMouseEventListener('mouse:move', mouseMove) } - const handleFix = (fn) => { + const handleFix = () => { if (!confirm('지붕선 완료하시겠습니까?')) { return } - fn(close) + + const roofBases = canvas.getObjects().find((obj) => obj.name === 'roofBase') + roofBases.innerLines = [...lineHistory.current] + + setShowAuxiliaryModal(close) } return { diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index d3b54c1c..269ef24f 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -31,7 +31,7 @@ import { calculateAngle } from '@/util/qpolygon-utils' import { fabric } from 'fabric' //외벽선 그리기 -export function useOuterLineWall() { +export function useOuterLineWall(setShowOutlineModal) { const canvas = useRecoilValue(canvasState) const { addCanvasMouseEventListener, addDocumentEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeMouseEvent } = useEvent() @@ -68,8 +68,6 @@ export function useOuterLineWall() { const isFix = useRef(false) - const closeModalFn = useRef(null) - useEffect(() => { if (adsorptionPointAddMode || tempGridMode) { return @@ -209,7 +207,7 @@ export function useOuterLineWall() { removeAllMouseEventListeners() removeAllDocumentEventListeners() canvas?.renderAll() - closeModalFn.current(false) + setShowOutlineModal(false) } if (points.length < 3) { @@ -749,7 +747,7 @@ export function useOuterLineWall() { setPoints((prev) => prev.slice(0, prev.length - 1)) } - const handleFix = (fn) => { + const handleFix = () => { if (points.length < 3) { return } @@ -779,7 +777,6 @@ export function useOuterLineWall() { }) isFix.current = true - closeModalFn.current = fn } return { diff --git a/src/hooks/roofcover/usePropertiesSetting.js b/src/hooks/roofcover/usePropertiesSetting.js index a2905f43..ad703356 100644 --- a/src/hooks/roofcover/usePropertiesSetting.js +++ b/src/hooks/roofcover/usePropertiesSetting.js @@ -8,7 +8,7 @@ import { useLine } from '@/hooks/useLine' import { outerLinePointsState } from '@/store/outerLineAtom' // 외벽선 속성 설정 -export function usePropertiesSetting() { +export function usePropertiesSetting(setShowPropertiesSettingModal) { const canvas = useRecoilValue(canvasState) const currentObject = useRecoilValue(currentObjectState) @@ -161,7 +161,7 @@ export function usePropertiesSetting() { canvas.renderAll() setPoints([]) - fn(false) + setShowPropertiesSettingModal(false) } return { handleSetEaves, handleSetGable, handleRollback, handleFix, closeModal } diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js new file mode 100644 index 00000000..fe321c1e --- /dev/null +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -0,0 +1,127 @@ +import { useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { useState } from 'react' +import { setSurfaceShapePattern } from '@/util/canvas-util' +import { splitPolygonWithLines } from '@/util/qpolygon-utils' + +// 지붕면 할당 +export function useRoofAllocationSetting(setShowRoofAllocationSettingModal) { + const canvas = useRecoilValue(canvasState) + + const roofMaterials = [ + { + id: 'A', + name: '기와1', + type: 'A', + width: '200', + length: '200', + alignType: 'parallel', + }, + { + id: 'B', + name: '기와2', + type: 'B', + rafter: '200', + alignType: 'parallel', + }, + { + id: 'C', + name: '기와3', + type: 'C', + hajebichi: '200', + alignType: 'stairs', + }, + { + id: 'D', + name: '기와4', + type: 'D', + length: '200', + alignType: 'stairs', + }, + ] + const widths = [ + { name: '200', id: 'q' }, + { name: '250', id: 'q1' }, + { name: '300', id: 'q2' }, + ] + const lengths = [ + { name: '200', id: 'w' }, + { name: '250', id: 'w1' }, + { name: '300', id: 'w2' }, + ] + const rafters = [ + { name: '200', id: 'e' }, + { name: '250', id: 'e1' }, + { name: '300', id: 'e2' }, + ] + + const [values, setValues] = useState([ + { + id: 'A', + type: 'A', + roofMaterial: { name: '기와1' }, + width: { name: '200' }, + length: { name: '250' }, + rafter: { name: '300' }, + alignType: 'stairs', + }, + ]) + + const [radioValue, setRadioValue] = useState('A') + + const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0]) + + const onAddRoofMaterial = () => { + setValues([...values, selectedRoofMaterial]) + } + + const onDeleteRoofMaterial = (id) => { + setValues(values.filter((value) => value.id !== id)) + } + + // 선택한 지붕재로 할당 + const handleSave = () => { + const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + roofBases.forEach((roofBase) => { + splitPolygonWithLines(roofBase) + + roofBase.innerLines.forEach((line) => { + canvas.remove(line) + }) + + canvas.remove(roofBase) + }) + + wallLines.forEach((wallLine) => { + canvas.remove(wallLine) + }) + + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') + + roofs.forEach((roof) => { + setSurfaceShapePattern(roof) + }) + setShowRoofAllocationSettingModal(false) + } + + const handleRadioOnChange = (e) => { + setRadioValue(e.target) + } + + return { + handleSave, + onAddRoofMaterial, + onDeleteRoofMaterial, + handleRadioOnChange, + widths, + lengths, + rafters, + values, + roofMaterials, + selectedRoofMaterial, + setSelectedRoofMaterial, + radioValue, + setRadioValue, + } +} diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index 245ee0d9..8efb53c6 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -9,7 +9,7 @@ import { useMode } from '@/hooks/useMode' import { usePolygon } from '@/hooks/usePolygon' //지붕형상 수동 설정 -export function useRoofShapePassivitySetting() { +export function useRoofShapePassivitySetting(setShowRoofShapePassivitySettingModal) { const TYPES = { EAVES: 'eaves', GABLE: 'gable', @@ -152,7 +152,7 @@ export function useRoofShapePassivitySetting() { canvas.renderAll() } - const handleSave = (fn) => { + const handleSave = () => { const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine') const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') exceptObjs.forEach((obj) => { @@ -169,7 +169,7 @@ export function useRoofShapePassivitySetting() { const roof = drawRoofPolygon(wall) canvas.renderAll() - fn(false) + setShowRoofShapePassivitySettingModal(false) } return { handleSave, handleConfirm, buttons, type, setType, TYPES, offsetRef, pitchRef, handleRollback } } diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index c0d65a21..9988f7a9 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -8,7 +8,7 @@ import { useMode } from '@/hooks/useMode' import { useLine } from '@/hooks/useLine' // 지붕형상 설정 -export function useRoofShapeSetting() { +export function useRoofShapeSetting(setShowRoofShapeSettingModal) { const [shapeNum, setShapeNum] = useState(1) const [buttonAct, setButtonAct] = useState(1) const { getMessage } = useMessage() @@ -99,11 +99,7 @@ export function useRoofShapeSetting() { { id: 6, name: getMessage('shed') }, ] - /** - * - * @param fn 모달 닫기 위한 함수 - */ - const handleSave = (fn) => { + const handleSave = () => { //기존 wallLine 삭제 let outerLines @@ -243,7 +239,7 @@ export function useRoofShapeSetting() { canvas?.renderAll() roof.drawHelpLine() - fn && fn(false) + setShowRoofShapeSettingModal(false) } const initLineSetting = () => { diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 2d981933..4cd63363 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' import { getDirectionByPoint } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' +import { isSamePoint } from '@/util/qpolygon-utils' export const usePolygon = () => { const canvas = useRecoilValue(canvasState) @@ -97,9 +98,214 @@ export const usePolygon = () => { canvas.renderAll() } + //polygon 나누기 + const splitPolygonWithLines = (polygon) => { + const roofs = [] + const allLines = [...polygon.innerLines] + + allLines.forEach((line) => { + line.startPoint = { x: line.x1, y: line.y1 } + line.endPoint = { x: line.x2, y: line.y2 } + }) + + // allLines에 x1,y1,x2,y2를 비교해서 중복되는 값을 제거한다. + allLines.forEach((line, index) => { + const startPoint = line.startPoint + const endPoint = line.endPoint + + allLines.forEach((line2, index2) => { + if (index !== index2) { + if ( + (isSamePoint(startPoint, line2.startPoint) && isSamePoint(endPoint, line2.endPoint)) || + (isSamePoint(endPoint, line2.startPoint) && isSamePoint(startPoint, line2.endPoint)) + ) { + allLines.splice(index2, 1) + } + } + }) + }) + + /** + * 좌표 테스트용 + */ + /*allLines.forEach((line) => { + const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, { + left: line.startPoint.x, + top: line.startPoint.y, + fontSize: 15, + }) + + polygon.canvas.add(text) + polygon.canvas.renderAll() + + const text2 = new fabric.Text(`(${line.endPoint.x},${line.endPoint.y})`, { + left: line.endPoint.x, + top: line.endPoint.y, + fontSize: 15, + }) + + polygon.canvas.add(text2) + polygon.canvas.renderAll() + }) + + polygon.points.forEach((point, index) => { + const text = new fabric.Text(`(${point.x},${point.y})`, { + left: point.x, + top: point.y, + fontSize: 15, + }) + + polygon.canvas.add(text) + polygon.canvas.renderAll() + })*/ + /** + * 좌표 테스트용 끝 + */ + + polygon.points.forEach((point, index) => { + allLines.forEach((line) => { + if (line.endPoint.x === point.x && line.endPoint.y === point.y) { + const temp = line.startPoint + line.startPoint = line.endPoint + line.endPoint = temp + } + }) + }) + + polygon.points.forEach((point, index) => { + const routes = [] + + // 시작점은 시작 hip라인의 출발점 + const startPoint = point + // 도착점은 마지막 hip라인의 끝나는 점 + const endPoint = polygon.points[(index + 1) % polygon.points.length] + + const startLine = allLines.find((line) => line.startPoint.x === startPoint.x && line.startPoint.y === startPoint.y) + const endLine = allLines.find((line) => line.startPoint.x === endPoint.x && line.startPoint.y === endPoint.y) + + const arrivalPoint = endLine.endPoint + routes.push(startLine.startPoint) + routes.push(startLine.endPoint) + + //hip끼리 만나는 경우는 아무것도 안해도됨 + if (!isSamePoint(startLine.endPoint, arrivalPoint)) { + // polygon line까지 추가 + const allLinesCopy = [...allLines, ...polygon.lines] + // hip이 만나지 않는 경우 갈 수 있는 길을 다 돌아야함 + let currentPoint = startLine.endPoint + let currentLine = startLine + let movedLines = [] + let subMovedLines = [] + while (!isSamePoint(currentPoint, arrivalPoint)) { + // startHip에서 만나는 출발선 두개. 두개의 선을 출발하여 arrivalPoint에 도착할 때 까지 count를 세고, 더 낮은 count를 가진 길을 선택한다. + let connectedLines = allLinesCopy.filter((line) => isSamePoint(line.startPoint, currentPoint) || isSamePoint(line.endPoint, currentPoint)) + + connectedLines = connectedLines.filter((line) => line !== currentLine) + + connectedLines = connectedLines.filter((line) => !subMovedLines.includes(line)) + + //마지막 선이 endLine의 startPoint와 같은경우 그 전까지 movedLine을 제거한다. + const endLineMeetLineCnt = connectedLines.filter((line) => { + return isSamePoint(line.endPoint, endLine.startPoint) || isSamePoint(line.startPoint, endLine.startPoint) + }).length + + if (endLineMeetLineCnt !== 0) { + movedLines.push(subMovedLines) + + console.log(movedLines, index) + } + + connectedLines = connectedLines.filter((line) => { + return !isSamePoint(line.endPoint, endLine.startPoint) && !isSamePoint(line.startPoint, endLine.startPoint) + }) + + if (connectedLines.length === 0) { + return + } + + let tempPoints = [] + + for (let i = 0; i < connectedLines.length; i++) { + if (isSamePoint(connectedLines[i].startPoint, currentPoint)) { + tempPoints.push({ point: connectedLines[i].endPoint, index: i, line: connectedLines[i] }) + } else { + tempPoints.push({ point: connectedLines[i].startPoint, index: i, line: connectedLines[i] }) + } + } + + //tempPoints에서 arrivalPoint와 가장 가까운 점을 찾는다. + let minDistance = Number.MAX_SAFE_INTEGER + let minIndex = 0 + tempPoints.forEach((tempPoint, index) => { + const distance = Math.sqrt(Math.pow(tempPoint.point.x - arrivalPoint.x, 2) + Math.pow(tempPoint.point.y - arrivalPoint.y, 2)) + if (distance < minDistance) { + minDistance = distance + minIndex = tempPoint.index + } + }) + + currentPoint = tempPoints[minIndex].point + currentLine = tempPoints[minIndex].line + if (currentLine !== startLine) { + subMovedLines.push(currentLine) + } + routes.push(currentPoint) + } + } + + routes.push(endLine.startPoint) + roofs.push(routes) + }) + + // 중복 제거 + roofs.forEach((roofPoint, index) => { + const samePointLengthRoofPoints = roofs.filter((roof) => roof.length === roofPoint.length && roof !== roofPoint) + + samePointLengthRoofPoints.forEach((samePointRoof) => { + if (arraysHaveSamePoints(samePointRoof, roofPoint)) { + roofs.splice(roofs.indexOf(samePointRoof), 1) + } + }) + }) + + roofs.forEach((roofPoint, index) => { + let defense + const direction = getDirectionByPoint(roofPoint[0], roofPoint[roofPoint.length - 1]) + + switch (direction) { + case 'top': + defense = 'east' + break + case 'right': + defense = 'south' + break + case 'bottom': + defense = 'west' + break + case 'left': + defense = 'north' + break + } + + const roof = new QPolygon(roofPoint, { + fontSize: polygon.fontSize, + stroke: 'black', + fill: 'transparent', + strokeWidth: 3, + name: 'roof', + selectable: false, + defense: defense, + }) + + polygon.canvas.add(roof) + polygon.canvas.renderAll() + }) + } + return { addPolygon, addPolygonByLines, removePolygon, + splitPolygonWithLines, } } diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 6605e3b3..73b9b285 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1150,7 +1150,7 @@ export const splitPolygonWithLines = (polygon) => { fill: 'transparent', strokeWidth: 3, name: 'roof', - selectable: false, + selectable: true, defense: defense, })