From 363214654d6985fa330e1db6bf571ce20be93f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=AF=BC=EC=8B=9D?= <43837214+Minsiki@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:23:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=B4.=EB=8B=A8=20=EC=9D=B4=EB=8F=99/?= =?UTF-8?q?=EB=B3=B5=EC=82=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/module/PanelEdit.jsx | 18 +- src/hooks/module/useModule.js | 330 ++++++++++++++++++ 2 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 src/hooks/module/useModule.js diff --git a/src/components/floor-plan/modal/module/PanelEdit.jsx b/src/components/floor-plan/modal/module/PanelEdit.jsx index c63dd0cd..0e7fde7f 100644 --- a/src/components/floor-plan/modal/module/PanelEdit.jsx +++ b/src/components/floor-plan/modal/module/PanelEdit.jsx @@ -9,6 +9,8 @@ import { deepCopyArray } from '@/util/common-utils' import { canvasState } from '@/store/canvasAtom' import * as turf from '@turf/turf' import { POLYGON_TYPE } from '@/common/common' +import { useModal } from '@nextui-org/react' +import { useModule } from '@/hooks/module/useModule' export default function PanelEdit(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -18,6 +20,7 @@ export default function PanelEdit(props) { const [direction, setDirection] = useState('up') const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) + const { moduleMove, moduleCopy, moduleMultiMove, moduleMultiCopy } = useModule() useEffect(() => { if (canvas) { @@ -28,7 +31,20 @@ export default function PanelEdit(props) { //모듈 이동 적용 const handleApply = () => { - contextModuleMove(length, direction) + // const activeModuleIds = canvas.getActiveObjects().map((obj) => obj.id) + if (type === 'move') { + moduleMove(length, direction) + } else if (type === 'copy') { + moduleCopy(length, direction) + } else if (type === 'columnMove') { + moduleMultiMove('column', length, direction) + } else if (type === 'columnCopy') { + moduleMultiCopy('column', length, direction) + } else if (type === 'rowMove') { + moduleMultiMove('row', length, direction) + } else if (type === 'rowCopy') { + moduleMultiCopy('row', length, direction) + } closePopup(id) } diff --git a/src/hooks/module/useModule.js b/src/hooks/module/useModule.js new file mode 100644 index 00000000..cc6ef05e --- /dev/null +++ b/src/hooks/module/useModule.js @@ -0,0 +1,330 @@ +import { BATCH_TYPE, POLYGON_TYPE } from '@/common/common' +import { canvasState } from '@/store/canvasAtom' +import { isOverlap, polygonToTurfPolygon } from '@/util/canvas-util' +import { useRecoilValue } from 'recoil' +import { v4 as uuidv4 } from 'uuid' +import * as turf from '@turf/turf' +import { useSwal } from '../useSwal' +import { QPolygon } from '@/components/fabric/QPolygon' + +export function useModule() { + const canvas = useRecoilValue(canvasState) + const { swalFire } = useSwal() + + const moduleMove = (length, direction) => { + const checkModuleDisjointSurface = (squarePolygon, turfModuleSetupSurface) => { + return turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) + } + + const selectedObj = canvas.getActiveObjects() //선택된 객체들을 가져옴 + const selectedIds = selectedObj.map((obj) => obj.id) // selectedObj의 ID 추출 + + canvas.discardActiveObject() //선택해제 + + const isSetupModules = canvas.getObjects().filter((obj) => obj.name === 'module' && !selectedIds.includes(obj.id)) // selectedObj에 없는 객체만 필터링 + const selectedModules = canvas.getObjects().filter((obj) => selectedIds.includes(obj.id) && obj.name === 'module') //선택했던 객체들만 가져옴 + const setupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === selectedModules[0].surfaceId)[0] + const isOverlapArray = [] + const isInSurfaceArray = [] + + if (selectedModules) { + canvas.remove(...selectedModules) + selectedModules.forEach((module) => { + module.set({ + originCoords: { + left: module.left, + top: module.top, + }, + }) + + if (direction === 'up') { + module.set({ ...module, top: module.top - Number(length) }) + } else if (direction === 'down') { + module.set({ ...module, top: module.top + Number(length) }) + } else if (direction === 'left') { + module.set({ ...module, left: module.left - Number(length) }) + } else if (direction === 'right') { + module.set({ ...module, left: module.left + Number(length) }) + } + module.setCoords() + canvas.renderAll() + + //다른 모듈과 겹치는지 확인하는 로직 + const isOverlap = isSetupModules.some((isSetupModule) => + turf.booleanOverlap(polygonToTurfPolygon(module, true), polygonToTurfPolygon(isSetupModule, true)), + ) + isOverlapArray.push(isOverlap) + + const turfModuleSetupSurface = polygonToTurfPolygon(setupSurface, true) + const turfModule = polygonToTurfPolygon(module, true) + + //나갔는지 확인하는 로직 + const isInSurface = turf.booleanContains(turfModuleSetupSurface, turfModule) || turf.booleanWithin(turfModule, turfModuleSetupSurface) + isInSurfaceArray.push(isInSurface) + }) + + const isNotOverlap = isOverlapArray.some((isOverlap) => isOverlap) // true면 겹침 + const isNotOutSurface = isInSurfaceArray.every((isOutSurface) => isOutSurface) //false면 밖으로 나감 + + //안겹치고 안나갔으면 이동시킨다 아니면 원래 위치로 돌려놓는다 + if (isNotOverlap || !isNotOutSurface) { + selectedModules.forEach((module) => { + module.set({ ...module, left: module.originCoords.left, top: module.originCoords.top }) + module.setCoords() + }) + } + + canvas.add(...selectedModules) + canvas.renderAll() + } + } + + const moduleCopy = (length, direction) => { + if (canvas.getActiveObjects().length === 0) return + const activeModuleIds = canvas.getActiveObjects().map((obj) => obj.id) + const modules = canvas.getObjects().filter((obj) => activeModuleIds.includes(obj.id)) + const otherModules = canvas.getObjects().filter((obj) => obj.surfaceId === modules[0].surfaceId && obj.name === POLYGON_TYPE.MODULE) + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === modules[0].surfaceId)[0] + + canvas.discardActiveObject() //선택해제 + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length) + const moduleOptions = { ...module, left, top, id: uuidv4() } + const rect = new QPolygon(module.points, moduleOptions) + canvas.add(rect) + rect.setCoords() + canvas.renderAll() + + // const isOverlapOtherModules = otherModules.some((otherModule) => rect.intersectsWithObject(otherModule)) + const isOverlapOtherModules = otherModules.some( + (otherModule) => + turf.booleanOverlap(polygonToTurfPolygon(rect, true), polygonToTurfPolygon(otherModule, true)) || + turf.booleanWithin(polygonToTurfPolygon(rect, true), polygonToTurfPolygon(otherModule, true)), + ) + + const isOutsideSurface = + !turf.booleanContains(polygonToTurfPolygon(moduleSetupSurface, true), polygonToTurfPolygon(rect, true)) || + !turf.booleanWithin(polygonToTurfPolygon(rect, true), polygonToTurfPolygon(moduleSetupSurface, true)) + + if (isOverlapOtherModules || isOutsideSurface) { + swalFire({ + title: isOverlapOtherModules ? '겹치는 모듈이 있습니다.' : '영역 밖', + icon: 'error', + type: 'confirm', + confirmFn: () => { + canvas.remove(rect) + canvas.renderAll() + }, + }) + } + }) + } + const moduleMultiMove = (type, length, direction) => { + if (canvas.getActiveObjects().length === 0) return + if (canvas.getActiveObjects().length > 1) { + swalFire({ + title: '여러 개의 모듈을 선택할 수 없습니다.', + icon: 'error', + type: 'alert', + }) + canvas.discardActiveObject() + return + } + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = type === 'row' ? getRowModules(activeModule) : getColumnModules(activeModule) + const otherModules = getOtherModules(modules) + const objects = getObjects() + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === activeModule.surfaceId)[0] + let [isOverlapOtherModules, isOverlapObjects, isOutsideSurface] = [[], [], []] + + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length, false) + module.originPos = { + top: module.top, + left: module.left, + } + + module.set({ top, left }) + module.setCoords() + canvas.renderAll() + + if (otherModules.length > 0) { + isOverlapOtherModules.push( + otherModules.some( + (otherModule) => + turf.booleanOverlap(polygonToTurfPolygon(module, true), polygonToTurfPolygon(otherModule, true)) || + turf.booleanWithin(polygonToTurfPolygon(module, true), polygonToTurfPolygon(otherModule, true)), + ), + ) + } + + if (objects.length > 0) { + isOverlapObjects.push( + objects.some( + (object) => + turf.booleanOverlap(polygonToTurfPolygon(module, true), polygonToTurfPolygon(object, true)) || + turf.booleanWithin(polygonToTurfPolygon(module, true), polygonToTurfPolygon(object, true)), + ), + ) + } + + isOutsideSurface.push( + !turf.booleanContains(polygonToTurfPolygon(moduleSetupSurface, true), polygonToTurfPolygon(module, true)) || + !turf.booleanWithin(polygonToTurfPolygon(module, true), polygonToTurfPolygon(moduleSetupSurface, true)), + ) + }) + + if ( + isOverlapOtherModules.some((isOverlap) => isOverlap) || + isOverlapObjects.some((isOverlap) => isOverlap) || + isOutsideSurface.some((isOutside) => isOutside) + ) { + swalFire({ + title: isOverlapOtherModules.some((isOverlap) => isOverlap) ? '겹치는 모듈이 있습니다.' : '영역 밖', + icon: 'error', + type: 'confirm', + confirmFn: () => { + modules.forEach((module) => { + module.set({ top: module.originPos.top, left: module.originPos.left }) + module.setCoords() + }) + canvas.renderAll() + }, + }) + } + } + + const moduleMultiCopy = (type, length, direction) => { + if (canvas.getActiveObjects().length === 0) return + if (canvas.getActiveObjects().length > 1) { + swalFire({ + title: '여러 개의 모듈을 선택할 수 없습니다.', + icon: 'error', + type: 'alert', + }) + canvas.discardActiveObject() + return + } + const activeModule = canvas.getObjects().filter((obj) => canvas.getActiveObjects()[0].id === obj.id)[0] + const modules = type === 'row' ? getRowModules(activeModule) : getColumnModules(activeModule) + const otherModules = canvas.getObjects().filter((obj) => obj.surfaceId === modules[0].surfaceId && obj.name === POLYGON_TYPE.MODULE) + const objects = getObjects() + const copyRects = [] + const moduleSetupSurface = canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && obj.id === modules[0].surfaceId)[0] + let [isOverlapOtherModules, isOverlapObjects, isOutsideSurface] = [[], [], []] + + modules.forEach((module) => { + const { top, left } = getPosotion(module, direction, length, true) + const moduleOptions = { ...module, left, top, id: uuidv4() } + const rect = new QPolygon(module.points, moduleOptions) + canvas.add(rect) + copyRects.push(rect) + module.setCoords() + + if (otherModules.length > 0) { + isOverlapOtherModules.push( + otherModules.some( + (otherModule) => + turf.booleanOverlap(polygonToTurfPolygon(rect, true), polygonToTurfPolygon(otherModule, true)) || + turf.booleanWithin(polygonToTurfPolygon(rect, true), polygonToTurfPolygon(otherModule, true)), + ), + ) + } + + if (objects.length > 0) { + isOverlapObjects.push( + objects.some( + (object) => + turf.booleanOverlap(polygonToTurfPolygon(module, true), polygonToTurfPolygon(object, true)) || + turf.booleanWithin(polygonToTurfPolygon(module, true), polygonToTurfPolygon(object, true)), + ), + ) + } + + isOutsideSurface.push( + !turf.booleanContains(polygonToTurfPolygon(moduleSetupSurface, true), polygonToTurfPolygon(rect, true)) || + !turf.booleanWithin(polygonToTurfPolygon(rect, true), polygonToTurfPolygon(moduleSetupSurface, true)), + ) + }) + + console.log( + '🚀 ~ moduleMultiCopy ~ sOverlapOtherModules, isOverlapObjects, isOutsideSurface:', + isOverlapOtherModules, + isOverlapObjects, + isOutsideSurface, + ) + if ( + isOverlapOtherModules.some((isOverlap) => isOverlap) || + isOverlapObjects.some((isOverlap) => isOverlap) || + isOutsideSurface.some((isOutside) => isOutside) + ) { + swalFire({ + title: isOverlapOtherModules.some((isOverlap) => isOverlap) ? '겹치는 모듈이 있습니다.' : '영역 밖', + icon: 'error', + type: 'confirm', + confirmFn: () => { + canvas.remove(...copyRects) + canvas.renderAll() + }, + }) + } else { + canvas.renderAll() + } + } + + const getRowModules = (target) => { + return canvas.getObjects().filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && obj.top === target.top) + } + + const getColumnModules = (target) => { + return canvas.getObjects().filter((obj) => target.surfaceId === obj.surfaceId && obj.name === POLYGON_TYPE.MODULE && obj.left === target.left) + } + + const getPosotion = (target, direction, length, hasMargin = false) => { + let top = target.top + let left = target.left + + if (direction === 'up') { + top = Number(target.top) - Number(length) + top = hasMargin ? top - Number(target.height) : top + } else if (direction === 'down') { + top = Number(target.top) + Number(target.height) + Number(length) + top = hasMargin ? top + Number(target.height) : top + } else if (direction === 'left') { + left = Number(target.left) - Number(length) + left = hasMargin ? left - Number(target.width) : left + } else if (direction === 'right') { + left = Number(target.left) + Number(length) + left = hasMargin ? left + Number(target.width) : left + } + return { top, left } + } + + const getOtherModules = (modules) => { + const moduleIds = modules.map((module) => module.id) + return canvas + .getObjects() + .filter((obj) => obj.surfaceId === modules[0].surfaceId && obj.name === POLYGON_TYPE.MODULE && !moduleIds.includes(obj.id)) + } + + const getObjects = () => { + return canvas + ?.getObjects() + .filter((obj) => [BATCH_TYPE.OPENING, BATCH_TYPE.TRIANGLE_DORMER, BATCH_TYPE.PENTAGON_DORMER, BATCH_TYPE.SHADOW].includes(obj.name)) + } + + return { + moduleMove, + moduleMultiMove, + moduleCopy, + moduleMultiCopy, + } +}