diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx new file mode 100644 index 0000000..790a330 --- /dev/null +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -0,0 +1,341 @@ +import * as THREE from "three"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useFrame, useThree } from "@react-three/fiber"; +import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; +import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; +import { useParams } from "react-router-dom"; +import { getUserData } from "../../../../../functions/getUserData"; +import { useSceneContext } from "../../../sceneContext"; +import { useVersionContext } from "../../../../builder/version/versionContext"; +import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore"; +import useModuleStore from "../../../../../store/useModuleStore"; + +// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi"; +// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi"; +// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi"; +// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; + +function MoveControls2D({ + movedObjects, + setMovedObjects, + pastedObjects, + setpastedObjects, + duplicatedObjects, + setDuplicatedObjects, + rotatedObjects, + setRotatedObjects, +}: any) { + const { camera, controls, gl, scene, pointer, raycaster } = useThree(); + const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []); + const { toolMode } = useToolMode(); + const { toggleView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { selectedPoints, clearSelectedPoints } = useSelectedPoints(); + const { socket } = useSocketStore(); + const { userId, organization } = getUserData(); + const { projectId } = useParams(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { setPosition: setAislePosition, getAislesByPointId } = aisleStore(); + const { setPosition: setWallPosition, getWallsByPointId } = wallStore(); + const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore(); + const { setPosition: setZonePosition, getZonesByPointId } = zoneStore(); + const [dragOffset, setDragOffset] = useState(null); + const [initialPositions, setInitialPositions] = useState>({}); + const [initialStates, setInitialStates] = useState>({}); + const [isMoving, setIsMoving] = useState(false); + + useEffect(() => { + if (!camera || !scene || !toggleView) return; + + const canvasElement = gl.domElement; + canvasElement.tabIndex = 0; + + let isMoving = false; + + const onPointerDown = () => { + isMoving = false; + }; + + const onPointerMove = () => { + isMoving = true; + }; + + const onPointerUp = (event: PointerEvent) => { + if (!isMoving && movedObjects.length > 0 && event.button === 0) { + event.preventDefault(); + placeMovedAssets(); + } + if (!isMoving && movedObjects.length > 0 && event.button === 2) { + event.preventDefault(); + + clearSelection(); + setMovedObjects([]); + resetToInitialPositions(); + } + }; + + const onKeyDown = (event: KeyboardEvent) => { + const keyCombination = detectModifierKeys(event); + + if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return; + + if (keyCombination === "G") { + if (selectedPoints.length > 0) { + moveAssets(); + } + } + + if (keyCombination === "ESCAPE") { + event.preventDefault(); + + clearSelection(); + setMovedObjects([]); + resetToInitialPositions(); + } + }; + + if (toggleView && selectedPoints.length > 0) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + canvasElement.addEventListener("keydown", onKeyDown); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + canvasElement.removeEventListener("keydown", onKeyDown); + }; + }, [camera, controls, scene, toggleView, selectedPoints, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]); + + useEffect(() => { + if (toolMode !== 'move' || !toggleView) { + if (movedObjects.length > 0) { + resetToInitialPositions(); + } + clearSelection(); + } + }, [activeModule, toolMode, toggleView, movedObjects]); + + useFrame(() => { + if (!isMoving || movedObjects.length === 0 || !dragOffset) return; + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit) { + const baseNewPosition = new THREE.Vector3().addVectors(hit, dragOffset); + + movedObjects.forEach((movedPoint: THREE.Object3D) => { + if (movedPoint.userData.pointUuid) { + const point: Point = movedPoint.userData as Point; + const initialPosition = initialPositions[movedPoint.uuid]; + + if (initialPosition) { + const relativeOffset = new THREE.Vector3().subVectors( + initialPosition, + initialPositions[movedObjects[0].uuid] + ); + + const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset); + const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z]; + + if (point.pointType === 'Aisle') { + setAislePosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Wall') { + setWallPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Floor') { + setFloorPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Zone') { + setZonePosition(point.pointUuid, positionArray); + } + } + } + }); + } + }); + + const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { + const pointPosition = new THREE.Vector3().copy(point.position); + return new THREE.Vector3().subVectors(pointPosition, hitPoint); + }, []); + + const moveAssets = useCallback(() => { + if (selectedPoints.length === 0) return; + + const states: Record = {}; + + selectedPoints.forEach((point: THREE.Object3D) => { + states[point.uuid] = { + position: new THREE.Vector3().copy(point.position), + rotation: point.rotation ? new THREE.Euler().copy(point.rotation) : undefined + }; + }); + setInitialStates(states); + + const positions: Record = {}; + selectedPoints.forEach((point: THREE.Object3D) => { positions[point.uuid] = new THREE.Vector3().copy(point.position); }); + setInitialPositions(positions); + + raycaster.setFromCamera(pointer, camera); + const intersectionPoint = new THREE.Vector3(); + const hit = raycaster.ray.intersectPlane(plane, intersectionPoint); + + if (hit && selectedPoints[0]) { + const offset = calculateDragOffset(selectedPoints[0], hit); + setDragOffset(offset); + } + + setMovedObjects(selectedPoints); + setIsMoving(true); + }, [selectedPoints, camera, pointer, plane, raycaster, calculateDragOffset]); + + const resetToInitialPositions = useCallback(() => { + setTimeout(() => { + movedObjects.forEach((movedPoint: THREE.Object3D) => { + if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) { + const point: Point = movedPoint.userData as Point; + const initialState = initialStates[movedPoint.uuid]; + const positionArray: [number, number, number] = [ + initialState.position.x, + initialState.position.y, + initialState.position.z + ]; + + if (point.pointType === 'Aisle') { + setAislePosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Wall') { + setWallPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Floor') { + setFloorPosition(point.pointUuid, positionArray); + } else if (point.pointType === 'Zone') { + setZonePosition(point.pointUuid, positionArray); + } + } + }); + }, 0) + }, [movedObjects, initialStates, setAislePosition, setWallPosition, setFloorPosition, setZonePosition]); + + const placeMovedAssets = () => { + if (movedObjects.length === 0) return; + + movedObjects.forEach((movedObject: THREE.Object3D) => { + if (movedObject.userData.pointUuid) { + const point: Point = movedObject.userData as Point; + + if (point.pointType === 'Aisle') { + const updatedAisles = getAislesByPointId(point.pointUuid); + if (updatedAisles.length > 0 && projectId) { + updatedAisles.forEach((updatedAisle) => { + + // API + + // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: updatedAisle.aisleUuid, + points: updatedAisle.points, + type: updatedAisle.type + }) + }) + } + } else if (point.pointType === 'Wall') { + const updatedWalls = getWallsByPointId(point.pointUuid); + if (updatedWalls && updatedWalls.length > 0 && projectId) { + updatedWalls.forEach((updatedWall) => { + + // API + + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); + + // SOCKET + + const data = { + wallData: updatedWall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Wall:add', data); + }); + } + } else if (point.pointType === 'Floor') { + const updatedFloors = getFloorsByPointId(point.pointUuid); + if (updatedFloors && updatedFloors.length > 0 && projectId) { + updatedFloors.forEach((updatedFloor) => { + + // API + + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); + + // SOCKET + + const data = { + floorData: updatedFloor, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:model-Floor:add', data); + }); + } + } else if (point.pointType === 'Zone') { + const updatedZones = getZonesByPointId(point.pointUuid); + if (updatedZones && updatedZones.length > 0 && projectId) { + updatedZones.forEach((updatedZone) => { + + // API + + // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); + + // SOCKET + + const data = { + zoneData: updatedZone, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:zone:add', data); + }); + } + } + } + }) + + echo.success("Object moved!"); + + clearSelection(); + }; + + const clearSelection = () => { + setpastedObjects([]); + setDuplicatedObjects([]); + setMovedObjects([]); + setRotatedObjects([]); + clearSelectedPoints(); + }; + + return ( + <> + + ); +} + +export default MoveControls2D; diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx index 9081b4f..e4b5da3 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx @@ -14,6 +14,7 @@ import { useProductContext } from "../../../../simulation/products/productContex import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store"; import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore"; import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; +import MoveControls2D from "./moveControls2D"; // import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi"; // import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi"; @@ -24,7 +25,6 @@ import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; const SelectionControls2D: React.FC = () => { const { camera, controls, gl, scene, raycaster, pointer } = useThree(); - const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints(); const [movedObjects, setMovedObjects] = useState([]); @@ -32,22 +32,19 @@ const SelectionControls2D: React.FC = () => { const [copiedObjects, setCopiedObjects] = useState([]); const [pastedObjects, setpastedObjects] = useState([]); const [duplicatedObjects, setDuplicatedObjects] = useState([]); - const boundingBoxRef = useRef(); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); const { toolMode } = useToolMode(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); const { projectId } = useParams(); const { hoveredLine, hoveredPoint } = useBuilderStore(); const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); - const { setPosition: setAislePosition, removePoint: removeAislePoint, getAislesByPointId } = aisleStore(); - const { setPosition: setWallPosition, removePoint: removeWallPoint, getWallsByPointId } = wallStore(); - const { setPosition: setFloorPosition, removePoint: removeFloorPoint, getFloorsByPointId } = floorStore(); - const { setPosition: setZonePosition, removePoint: removeZonePoint, getZonesByPointId } = zoneStore(); + const { removePoint: removeAislePoint } = aisleStore(); + const { removePoint: removeWallPoint } = wallStore(); + const { removePoint: removeFloorPoint } = floorStore(); + const { removePoint: removeZonePoint } = zoneStore(); const isDragging = useRef(false); const isLeftMouseDown = useRef(false); @@ -178,12 +175,6 @@ const SelectionControls2D: React.FC = () => { } }, [activeModule, toolMode, toggleView]); - useFrame(() => { - if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { - selectionGroup.current.position.set(0, 0, 0); - } - }); - const selectAssets = useCallback(() => { selectionBox.endPoint.set(pointer.x, pointer.y, 0); if (controls) (controls as any).enabled = true; @@ -226,9 +217,6 @@ const SelectionControls2D: React.FC = () => { }, [selectionBox, pointer, controls, selectedPoints, setSelectedPoints]); const clearSelection = () => { - selectionGroup.current.children = []; - selectionGroup.current.position.set(0, 0, 0); - selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); clearSelectedPoints(); @@ -237,7 +225,7 @@ const SelectionControls2D: React.FC = () => { const deleteSelection = () => { if (selectedPoints.length > 0 && duplicatedObjects.length === 0) { - selectedPoints.map((selectedPoint) => { + selectedPoints.forEach((selectedPoint) => { if (selectedPoint.userData.pointUuid) { const point: Point = selectedPoint.userData as Point; if (point.pointType === 'Aisle') { @@ -394,9 +382,7 @@ const SelectionControls2D: React.FC = () => { return ( <> - - - + ); diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index a872a60..080a5d8 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -254,7 +254,7 @@ const SelectionControls3D: React.FC = () => { }); if (Objects.size === 0) { - clearSelection(); + // clearSelection(); return; } diff --git a/app/src/modules/simulation/events/arrows/arrows.tsx b/app/src/modules/simulation/events/arrows/arrows.tsx index 2053fe5..0b8e821 100644 --- a/app/src/modules/simulation/events/arrows/arrows.tsx +++ b/app/src/modules/simulation/events/arrows/arrows.tsx @@ -103,9 +103,21 @@ export function Arrows({ connections }: { readonly connections: ConnectionLine[] return ( setHoveredArrowTrigger(trigger.triggerUuid)} - onPointerOut={() => setHoveredArrowTrigger(null)} - onClick={() => { removeConnection(trigger) }} + onPointerOver={() => { + if (toolMode === '3D-Delete') { + setHoveredArrowTrigger(trigger.triggerUuid) + } + }} + onPointerOut={() => { + if (toolMode === '3D-Delete') { + setHoveredArrowTrigger(null) + } + }} + onClick={() => { + if (toolMode === '3D-Delete') { + removeConnection(trigger) + } + }} >