From d222c60b89d4527e8dae7099b77c1c5183639135 Mon Sep 17 00:00:00 2001 From: yjnoh Date: Wed, 13 Nov 2024 15:42:00 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EC=84=B8=ED=8C=85=20=EC=88=98=EB=8F=99=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dictionary.txt | 1 + src/common/common.js | 1 + .../floor-plan/modal/basic/BasicSetting.jsx | 25 +- src/hooks/module/useModuleBasicSetting.js | 595 ++++++++++++++++++ src/hooks/useCanvasEvent.js | 4 +- src/hooks/useEvent.js | 7 + src/store/canvasAtom.js | 21 + src/util/canvas-util.js | 16 +- 8 files changed, 659 insertions(+), 11 deletions(-) create mode 100644 src/hooks/module/useModuleBasicSetting.js diff --git a/docs/dictionary.txt b/docs/dictionary.txt index 268cde7a..94bfdb49 100644 --- a/docs/dictionary.txt +++ b/docs/dictionary.txt @@ -28,3 +28,4 @@ Allpainted : allPainted 치수선: dimensionLine 복도치수: planeSize 실제치수: actualSize +모듈설치면: moduleSetupSurface \ No newline at end of file diff --git a/src/common/common.js b/src/common/common.js index 588b836c..02a953fe 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -111,6 +111,7 @@ export const POLYGON_TYPE = { ROOF: 'roof', WALL: 'wall', TRESTLE: 'trestle', + MODULE_SETUP_SURFACE: 'moduleSetupSurface', } export const SAVE_KEY = [ diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index 51cf7280..322e068f 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -5,18 +5,21 @@ import Module from '@/components/floor-plan/modal/basic/step/Module' import PitchModule from '@/components/floor-plan/modal/basic/step/pitch/PitchModule' import PitchPlacement from '@/components/floor-plan/modal/basic/step/pitch/PitchPlacement' import Placement from '@/components/floor-plan/modal/basic/step/Placement' -import { useRecoilState } from 'recoil' +import { useRecoilValue } from 'recoil' import { canvasSettingState } from '@/store/canvasAtom' import { usePopup } from '@/hooks/usePopup' import { Orientation } from '@/components/floor-plan/modal/basic/step/Orientation' +import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' +import { useEvent } from '@/hooks/useEvent' export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { const { getMessage } = useMessage() const { closePopup } = usePopup() const [tabNum, setTabNum] = useState(1) - const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) + const canvasSetting = useRecoilValue(canvasSettingState) const orientationRef = useRef(null) - + const { initEvent } = useEvent() + const { makeModuleInstArea, manualModuleSetup, autoModuleSetup } = useModuleBasicSetting() const handleBtnNextStep = () => { if (tabNum === 1) { orientationRef.current.handleNextStep() @@ -24,6 +27,14 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { setTabNum(tabNum + 1) } + useEffect(() => { + makeModuleInstArea() + + return () => { + initEvent() + } + }, []) + return (
@@ -64,8 +75,12 @@ export default function BasicSetting({ id, pos = { x: 50, y: 230 } }) { )} {tabNum === 3 && ( <> - - + + )}
diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js new file mode 100644 index 00000000..ed2e91b1 --- /dev/null +++ b/src/hooks/module/useModuleBasicSetting.js @@ -0,0 +1,595 @@ +import { useEffect, useState } from 'react' +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { setSurfaceShapePattern } from '@/util/canvas-util' +import { roofDisplaySelector } from '@/store/settingAtom' +import offsetPolygon from '@/util/qpolygon-utils' +import { QPolygon } from '@/components/fabric/QPolygon' +import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom' +import { useEvent } from '@/hooks/useEvent' +import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common' +import * as turf from '@turf/turf' + +export function useModuleBasicSetting() { + const canvas = useRecoilValue(canvasState) + const roofDisplay = useRecoilValue(roofDisplaySelector) + const setModuleInstSurface = useSetRecoilState(moduleSetupSurfaceState) + const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState) + const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useEvent() + let selectedModuleInstSurfaceArray = [] + + const makeModuleInstArea = () => { + //지붕 객체 반환 + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') + + if (!roofs) { + return + } + + roofs.forEach((roof) => { + setSurfaceShapePattern(roof, roofDisplay.column, true) //패턴 변경 + const offsetPoints = offsetPolygon(roof.points, -20) //안쪽 offset + //모듈설치영역?? 생성 + const trestle = new QPolygon(offsetPoints, { + stroke: 'red', + fill: 'transparent', + strokeDashArray: [10, 4], + strokeWidth: 1, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + selectable: true, + parentId: roof.id, //가대 폴리곤의 임시 인덱스를 넣어줌 + name: POLYGON_TYPE.MODULE_SETUP_SURFACE, + }) + + canvas.add(trestle) + //지붕면 선택 금지 + roof.set({ + selectable: false, + }) + + //모듈설치면 클릭이벤트 + addTargetMouseEventListener('mousedown', trestle, function () { + toggleSelection(trestle) + }) + }) + } + + //설치 범위 지정 클릭 이벤트 + const toggleSelection = (polygon) => { + const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === polygon.parentId) + //최초 선택일때 + if (!isExist) { + //기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄 + polygon.set({ + ...polygon, + strokeWidth: 3, + strokeDashArray: [0], + fill: 'transparent', + }) + canvas.discardActiveObject() // 객체의 활성 상태 해제 + //중복으로 들어가는걸 방지하기 위한 코드 + + canvas?.renderAll() + selectedModuleInstSurfaceArray.push(polygon) + } else { + //선택후 재선택하면 선택안됨으로 변경 + polygon.set({ + ...polygon, + fill: 'transparent', + strokeDashArray: [10, 4], + strokeWidth: 1, + }) + canvas.discardActiveObject() // 객체의 활성 상태 해제 + + //폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함 + const removeIndex = polygon.parentId + const removeArrayIndex = selectedModuleInstSurfaceArray.findIndex((obj) => obj.parentId === removeIndex) + selectedModuleInstSurfaceArray.splice(removeArrayIndex, 1) + } + + canvas?.renderAll() + setModuleInstSurface([...selectedModuleInstSurfaceArray]) + } + + /** + * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인 + * 확인 후 셀을 이동시킴 + */ + const manualModuleSetup = () => { + const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //가대를 가져옴 + const batchObjects = canvas + ?.getObjects() + .filter( + (obj) => + obj.name === BATCH_TYPE.OPENING || + obj.name === BATCH_TYPE.TRIANGLE_DORMER || + obj.name === BATCH_TYPE.PENTAGON_DORMER || + obj.name === BATCH_TYPE.SHADOW, + ) //도머s 객체 + + if (trestlePolygons.length !== 0) { + let fabricPolygon = null + let inside = false + let turfPolygon + let manualDrawCells = moduleIsSetup // 앞에서 자동으로 했을때 추가됨 + let direction + let trestlePolygon + addCanvasMouseEventListener('mouse:move', (e) => { + //마우스 이벤트 삭제 후 재추가 + const mousePoint = canvas.getPointer(e.e) + + for (let i = 0; i < trestlePolygons.length; i++) { + turfPolygon = polygonToTurfPolygon(trestlePolygons[i]) + trestlePolygon = trestlePolygons[i] + direction = trestlePolygons[i].direction //도형의 방향 + let width = direction === 'south' || direction === 'north' ? 172 : 113 + let height = direction === 'south' || direction === 'north' ? 113 : 172 + + const points = [ + { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, + { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, + ] + + const turfPoints = coordToTurfPolygon(points) + + if (turf.booleanWithin(turfPoints, turfPolygon)) { + let isDrawing = false + + if (isDrawing) return + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) //움직일때 일단 지워가면서 움직임 + + fabricPolygon = new fabric.Rect({ + fill: 'white', + stroke: 'black', + strokeWidth: 1, + width: width, + height: height, + left: mousePoint.x - width / 2, + top: mousePoint.y - height / 2, + selectable: false, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + opacity: 0.8, + name: 'tempModule', + parentId: trestlePolygons[i].parentId, + }) + + canvas?.add(fabricPolygon) //움직여가면서 추가됨 + + /** + * 스냅기능 + */ + let snapDistance = 10 + let cellSnapDistance = 20 + + const trestleLeft = trestlePolygons[i].left + const trestleTop = trestlePolygons[i].top + const trestleRight = trestleLeft + trestlePolygons[i].width * trestlePolygons[i].scaleX + const trestleBottom = trestleTop + trestlePolygons[i].height * trestlePolygons[i].scaleY + const bigCenterY = (trestleTop + trestleTop + trestlePolygons[i].height) / 2 + + // 작은 폴리곤의 경계 좌표 계산 + const smallLeft = fabricPolygon.left + const smallTop = fabricPolygon.top + const smallRight = smallLeft + fabricPolygon.width * fabricPolygon.scaleX + const smallBottom = smallTop + fabricPolygon.height * fabricPolygon.scaleY + const smallCenterX = smallLeft + (fabricPolygon.width * fabricPolygon.scaleX) / 2 + const smallCenterY = smallTop + (fabricPolygon.height * fabricPolygon.scaleX) / 2 + + /** + * 미리 깔아놓은 셀이 있을때 셀에 흡착됨 + */ + if (manualDrawCells) { + manualDrawCells.forEach((cell) => { + const holdCellLeft = cell.left + const holdCellTop = cell.top + const holdCellRight = holdCellLeft + cell.width * cell.scaleX + const holdCellBottom = holdCellTop + cell.height * cell.scaleY + const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2 + const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2 + + //설치된 셀에 좌측에 스냅 + if (Math.abs(smallRight - holdCellLeft) < snapDistance) { + fabricPolygon.left = holdCellLeft - width - 0.5 + } + + //설치된 셀에 우측에 스냅 + if (Math.abs(smallLeft - holdCellRight) < snapDistance) { + fabricPolygon.left = holdCellRight + 0.5 + } + + //설치된 셀에 위쪽에 스냅 + if (Math.abs(smallBottom - holdCellTop) < snapDistance) { + fabricPolygon.top = holdCellTop - height - 0.5 + } + + //설치된 셀에 밑쪽에 스냅 + if (Math.abs(smallTop - holdCellBottom) < snapDistance) { + fabricPolygon.top = holdCellBottom + 0.5 + } + //가운데 -> 가운데 + if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) { + fabricPolygon.left = holdCellCenterX - width / 2 + } + //왼쪽 -> 가운데 + if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) { + fabricPolygon.left = holdCellCenterX + } + // 오른쪽 -> 가운데 + if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) { + fabricPolygon.left = holdCellCenterX - width + } + //세로 가운데 -> 가운데 + if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) { + fabricPolygon.top = holdCellCenterY - height / 2 + } + //위쪽 -> 가운데 + if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { + fabricPolygon.top = holdCellCenterY + } + //아랫쪽 -> 가운데 + if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { + fabricPolygon.top = holdCellCenterY - height + } + }) + } + + // 위쪽 변에 스냅 + if (Math.abs(smallTop - trestleTop) < snapDistance) { + fabricPolygon.top = trestleTop + } + + // 아래쪽 변에 스냅 + if (Math.abs(smallTop + fabricPolygon.height * fabricPolygon.scaleY - (trestleTop + trestlePolygons[i].height)) < snapDistance) { + fabricPolygon.top = trestleTop + trestlePolygons[i].height - fabricPolygon.height * fabricPolygon.scaleY + } + + // 왼쪽변에 스냅 + if (Math.abs(smallLeft - trestleLeft) < snapDistance) { + fabricPolygon.left = trestleLeft + } + //오른쪽 변에 스냅 + if (Math.abs(smallRight - trestleRight) < snapDistance) { + fabricPolygon.left = trestleRight - fabricPolygon.width * fabricPolygon.scaleX + } + + if (direction === 'south' || direction === 'north') { + // 모듈왼쪽이 세로중앙선에 붙게 스냅 + if (Math.abs(smallLeft - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) { + fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 + } + + // 모듈이 가운데가 세로중앙선에 붙게 스냅 + if (Math.abs(smallCenterX - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) { + fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - (fabricPolygon.width * fabricPolygon.scaleX) / 2 + } + + // 모듈오른쪽이 세로중앙선에 붙게 스냅 + if (Math.abs(smallRight - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) { + fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - fabricPolygon.width * fabricPolygon.scaleX + } + } else { + // 모듈이 가로중앙선에 스냅 + if (Math.abs(smallTop + fabricPolygon.height / 2 - bigCenterY) < snapDistance) { + fabricPolygon.top = bigCenterY - fabricPolygon.height / 2 + } + + if (Math.abs(smallTop - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) { + fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2 + } + // 모듈 밑면이 가로중앙선에 스냅 + if (Math.abs(smallBottom - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) { + fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2 - fabricPolygon.height * fabricPolygon.scaleY + } + } + + fabricPolygon.setCoords() + canvas?.renderAll() + inside = true + break + } else { + inside = false + } + } + + if (!inside) { + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) + canvas?.renderAll() + } + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + let isIntersection = true + if (!inside) return + if (fabricPolygon) { + const rectPoints = [ + { x: fabricPolygon.left + 0.5, y: fabricPolygon.top + 0.5 }, + { x: fabricPolygon.left + 0.5 + fabricPolygon.width * fabricPolygon.scaleX, y: fabricPolygon.top + 0.5 }, + { + x: fabricPolygon.left + fabricPolygon.width * fabricPolygon.scaleX + 0.5, + y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5, + }, + { x: fabricPolygon.left + 0.5, y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5 }, + ] + + fabricPolygon.set({ points: rectPoints }) + const tempTurfModule = polygonToTurfPolygon(fabricPolygon) + + //도머 객체를 가져옴 + if (batchObjects) { + batchObjects.forEach((object) => { + const dormerTurfPolygon = polygonToTurfPolygon(object) //turf객체로 변환 + const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 + //겹치면 안됨 + if (intersection) { + alert('도머위에 모듈을 올릴 수 없습니다.') + isIntersection = false + } + }) + } + + if (!isIntersection) return + + fabricPolygon.setCoords() //좌표 재정렬 + + if (turf.booleanWithin(tempTurfModule, turfPolygon)) { + //마우스 클릭시 set으로 해당 위치에 셀을 넣음 + const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(cell))) //겹치는지 확인 + if (!isOverlap) { + //안겹치면 넣는다 + fabricPolygon.setCoords() + fabricPolygon.set({ name: 'cell', fill: '#BFFD9F' }) + manualDrawCells.push(fabricPolygon) //모듈배열에 추가 + //해당 모듈에 프로퍼티로 넣는다 + trestlePolygon.set({ + modules: manualDrawCells, + }) + } else { + alert('셀끼리 겹치면 안되죠?') + } + } else { + alert('나갔죠?!!') + } + } + }) + } + } + + const coordToTurfPolygon = (points) => { + const coordinates = points.map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + return turf.polygon([coordinates]) + } + + const polygonToTurfPolygon = (polygon) => { + const coordinates = polygon.points.map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + return turf.polygon( + [coordinates], + {}, + { + parentId: polygon.parentId, + }, + ) + } + + const autoModuleSetup = () => { + const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && obj.selected) + const notSelectedTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && !obj.selected) + + const dormerTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'dormerTrestle') //도머 객체 + + if (trestlePolygons.length === 0) { + alert('가대가 없습니다.') + return + } + + if (drewRoofCells.length > 0) { + alert('기존 셀은 제거됩니다.') + } + + notSelectedTrestlePolygons.forEach((trestle) => { + trestle.cells.forEach((cell) => { + canvas?.remove(cell) + }) + trestle.cells = [] + }) + + const drawCellsArray = [] + trestlePolygons.forEach((trestle, index) => { + trestle.fire('mousedown') + let maxLengthLine = trestle.lines.reduce((acc, cur) => { + return acc.length > cur.length ? acc : cur + }) + + const turfTrestlePolygon = polygonToTurfPolygon(trestle) //폴리곤을 turf 객체로 변환 + + const containsDormerTrestlePolygons = dormerTrestlePolygons.filter((dormerTrestle) => { + // 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직 + return ( + turf.booleanContains(turfTrestlePolygon, polygonToTurfPolygon(dormerTrestle)) || + turf.booleanWithin(polygonToTurfPolygon(dormerTrestle), turfTrestlePolygon) + ) + }) + + let difference = turfTrestlePolygon //기본 객체(면형상) + + if (containsDormerTrestlePolygons.length > 0) { + //turf로 도머를 제외시키는 로직 + for (let i = 0; i < containsDormerTrestlePolygons.length; i++) { + if (i === 0) { + difference = turf.difference(turf.featureCollection([turfTrestlePolygon, polygonToTurfPolygon(containsDormerTrestlePolygons[i])])) //한 면에 도머가 1개일때 + } else { + if (difference) { + difference = turf.difference(turf.featureCollection([difference, polygonToTurfPolygon(containsDormerTrestlePolygons[i])])) //한면에 도머가 여러개일때 계속 제외시킴 + } + } + } + } + + const bbox = turf.bbox(difference) + let width = maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left' ? 172.2 : 113.4 + let height = maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left' ? 113.4 : 172.2 + + //배치면때는 방향쪽으로 패널이 넓게 누워져야함 + if (trestle.direction !== undefined) { + width = trestle.direction === 'south' || trestle.direction === 'north' ? 172.2 : 113.4 + height = trestle.direction === 'south' || trestle.direction === 'north' ? 113.4 : 172.2 + } + const cols = Math.floor((bbox[2] - bbox[0]) / width) + const rows = Math.floor((bbox[3] - bbox[1]) / height) + + for (let col = 0; col <= cols; col++) { + for (let row = 0; row <= rows; row++) { + let x = 0, + y = 0, + square = [], + margin = 0 + + if (trestle.direction !== undefined) { + //배치면 처림 방향이 정해져있는 경우 + + if (trestle.direction === 'south' || trestle.direction === 'north') { + //남,북 + margin = (bbox[2] - bbox[0] - cols * width) / 2 //박스 끝에서 박스 시작값을 빼고 width와 계산된 cols를 곱한값을 뺀뒤 나누기 2 하면 가운데 배치됨 + if (trestle.direction === 'south') { + //남쪽 + x = col === 0 ? trestle.left + margin : bbox[0] + col * width + margin //상하 위치 기준이면 좌우 가운데 정렬한다 + y = bbox[3] - row * height + } else { + //북쪽 + x = col === 0 ? trestle.left + margin : bbox[0] + col * width + margin + y = bbox[1] + row * height + } + } else if (trestle.direction === 'east' || trestle.direction === 'west') { + //동쪽 + margin = (bbox[3] - bbox[1] - rows * height) / 2 + if (trestle.direction === 'east') { + x = bbox[2] - col * width + y = rows === 0 ? trestle.top + margin : bbox[1] + row * height + margin //좌우 위치 기준이면 상하 가운데 정렬한다 + } else { + x = bbox[0] + col * width + y = rows === 0 ? trestle.top + margin : bbox[1] + row * height + margin + } + } + } else { + //방향이 없는 경우 ex) 템플릿 + x = bbox[0] + col * width + y = bbox[1] + row * height + } + + square = [ + [x, y], + [x + width, y], + [x + width, y + height], + [x, y + height], + [x, y], + ] + + const squarePolygon = turf.polygon([square]) + + const disjointFromTrestle = turf.booleanContains(turfTrestlePolygon, squarePolygon) || turf.booleanWithin(squarePolygon, turfTrestlePolygon) + + if (disjointFromTrestle) { + let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) + const points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) + + if (containsDormerTrestlePolygons.length > 0) { + //도머가 있으면 적용되는 로직 + const isDisjoint = containsDormerTrestlePolygons.some((dormerTrestle) => { + return turf.booleanDisjoint(squarePolygon, polygonToTurfPolygon(dormerTrestle)) //도머가 여러개일수있으므로 겹치는게 있다면... + }) + if (isDisjoint) { + const fabricPolygon = new QPolygon(points, { + fill: '#BFFD9F', + stroke: 'black', + selectable: true, // 선택 가능하게 설정 + lockMovementX: false, // X 축 이동 잠금 + lockMovementY: false, // Y 축 이동 잠금 + lockRotation: false, // 회전 잠금 + lockScalingX: false, // X 축 크기 조정 잠금 + lockScalingY: false, // Y 축 크기 조정 잠금 + opacity: 0.8, + parentId: trestle.parentId, + }) + canvas?.add(fabricPolygon) + drawCellsArray.push(fabricPolygon) + } + } else { + //도머가 없을땐 그냥 그림 + const fabricPolygon = new QPolygon(points, { + fill: '#BFFD9F', + stroke: 'black', + selectable: true, // 선택 가능하게 설정 + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + lockScalingX: true, // X 축 크기 조정 잠금 + lockScalingY: true, // Y 축 크기 조정 잠금 + opacity: 0.8, + parentId: trestle.parentId, + lineCol: col, + lineRow: row, + }) + canvas?.add(fabricPolygon) + drawCellsArray.push(fabricPolygon) + } + } + } + } + + // let drawRoofCells + // if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') { + // drawRoofCells = trestle.fillCell({ width: 113.4, height: 172.2, padding: 0 }) + // trestle.direction = 'south' + // } else { + // drawRoofCells = trestle.fillCell({ width: 172.2, height: 113.4, padding: 0 }) + // trestle.direction = 'east' + // } + + // drawRoofCells.forEach((cell) => { + // drawCellsArray.push(cell) + // }) + + /** + * 추후에 가대까지 완료하면 그룹시켜버림 + */ + // const groupCellObj = canvas + // ?.getObjects() + // .filter( + // (obj) => + // obj?.parentId === trestle.parentId || + // obj?.id === trestle.parentId || + // (obj?.name === 'arrow' && obj?.parent.id === trestle.parentId) || + // (obj?.name === 'directionText' && obj?.parent.parent.id === trestle.parentId), + // ) + + // console.log('groupCellObj', groupCellObj) + + // canvas?.add( + // new fabric.Group(groupCellObj, { + // name: 'cellGroup', + // originX: 'center', + // originY: 'center', + // }), + // ) + }) + + setDrewRoofCells(drawCellsArray) + } + + return { + makeModuleInstArea, + manualModuleSetup, + autoModuleSetup, + } +} diff --git a/src/hooks/useCanvasEvent.js b/src/hooks/useCanvasEvent.js index 42266c8a..b4cc4223 100644 --- a/src/hooks/useCanvasEvent.js +++ b/src/hooks/useCanvasEvent.js @@ -225,7 +225,9 @@ export function useCanvasEvent() { if (deselected?.length > 0) { deselected.forEach((obj) => { if (obj.type === 'QPolygon') { - obj.set({ stroke: 'black' }) + if (obj.name !== 'moduleInstSurface') { + obj.set({ stroke: 'black' }) + } } }) } diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index 891ad101..ba441b75 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -219,6 +219,12 @@ export function useEvent() { mouseEventListeners.current.length = 0 // 배열 초기화 } + const addTargetMouseEventListener = (eventType, target, handler) => { + target.off(eventType) + target.on(eventType, handler) + mouseEventListeners.current.push({ eventType, handler }) + } + /** * document 이벤트의 경우 이 함수를 통해서만 등록 * @param eventType @@ -264,6 +270,7 @@ export function useEvent() { return { addDocumentEventListener, addCanvasMouseEventListener, + addTargetMouseEventListener, removeAllMouseEventListeners, removeAllDocumentEventListeners, removeDocumentEvent, diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index 0242a7b2..588808b1 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -363,3 +363,24 @@ export const showAngleUnitSelector = selector({ return roofAngleSet === 'slope' ? '寸' : '°' }, }) + +export const moduleSetupSurfaceState = atom({ + key: 'moduleSetupSurfaceState', + default: [], + dangerouslyAllowMutability: true, +}) + +export const moduleInstSurfaceSelector = selector({ + key: 'moduleInstSurfaceSelector', + get: ({ get, parentId }) => { + const moduleSetupSurfaceStateValue = get(moduleSetupSurfaceState) + return moduleSetupSurfaceStateValue.filter((obj) => obj.parentId === parentId) + }, +}) + +//셀 그린 이후에 생성하는 state +export const moduleIsSetupState = atom({ + key: 'moduleIsSetupState', + default: [], + dangerouslyAllowMutability: true, +}) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 5f55092c..4ef2a2ec 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -792,7 +792,7 @@ export const rectToPolygon = (rect) => { } //면형상 선택 클릭시 지붕 패턴 입히기 -export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { +export function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false) { const ratio = window.devicePixelRatio || 1 let width = 265 / 10 @@ -820,6 +820,12 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.lineWidth = mode === 'allPainted' ? 1 : 0.4 ctx.fillStyle = mode === 'allPainted' ? 'rgba(0, 159, 64, 0.7)' : 'white' + if (trestleMode) { + ctx.strokeStyle = 'black' + ctx.lineWidth = 0.2 + ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' + } + if (polygon.direction === 'east' || polygon.direction === 'west') { offset = roofStyle === 1 ? 0 : patternSize.height / 2 for (let col = 0; col <= cols; col++) { @@ -830,7 +836,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } @@ -842,7 +848,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(xStart, y) // 선 시작점 ctx.lineTo(xEnd, y) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(xStart, y, xEnd - xStart, patternSize.height) } } @@ -855,7 +861,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(0, y) // 선 시작점 ctx.lineTo(patternSourceCanvas.width, y) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(0, y, patternSourceCanvas.width, patternSize.height) } @@ -868,7 +874,7 @@ export function setSurfaceShapePattern(polygon, mode = 'onlyBorder') { ctx.moveTo(x, yStart) // 선 시작점 ctx.lineTo(x, yEnd) // 선 끝점 ctx.stroke() - if (mode === 'allPainted') { + if (mode === 'allPainted' || trestleMode) { ctx.fillRect(x, yStart, patternSize.width, yEnd - yStart) } }