import * as THREE from "three"; import { useEffect, useMemo, useRef, useState } from "react"; import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; import { SelectionHelper } from "./selectionHelper"; import { useFrame, useThree } from "@react-three/fiber"; import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore, useToggleView, } from "../../../../store/store"; import BoundingBox from "./boundingBoxHelper"; import { toast } from "react-toastify"; // import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi'; import * as Types from "../../../../types/world/worldTypes"; import * as SimulationTypes from "../../../../types/simulationTypes"; import DuplicationControls from "./duplicationControls"; import CopyPasteControls from "./copyPasteControls"; import MoveControls from "./moveControls"; import RotateControls from "./rotateControls"; import useModuleStore from "../../../../store/useModuleStore"; const SelectionControls: React.FC = () => { const { camera, controls, gl, scene, pointer } = useThree(); const itemsGroupRef = useRef(undefined); const selectionGroup = useRef() as Types.RefGroup; const { toggleView } = useToggleView(); const { simulationStates, setSimulationStates } = useSimulationStates(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); const [movedObjects, setMovedObjects] = useState([]); const [rotatedObjects, setRotatedObjects] = useState([]); const [copiedObjects, setCopiedObjects] = useState([]); const [pastedObjects, setpastedObjects] = useState([]); const [duplicatedObjects, setDuplicatedObjects] = useState([]); const boundingBoxRef = useRef(); const { floorItems, setFloorItems } = useFloorItems(); const { activeModule } = useModuleStore(); const { socket } = useSocketStore(); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); useEffect(() => { if (!camera || !scene || toggleView) return; const canvasElement = gl.domElement; canvasElement.tabIndex = 0; const itemsGroup: any = scene.getObjectByName("itemsGroup"); itemsGroupRef.current = itemsGroup; let isSelecting = false; let isRightClick = false; let rightClickMoved = false; let isCtrlSelecting = false; const helper = new SelectionHelper(gl); if (!itemsGroup) { toast.warn("itemsGroup not found in the scene."); return; } const onPointerDown = (event: PointerEvent) => { if (event.button === 2) { isRightClick = true; rightClickMoved = false; } else if (event.button === 0) { isSelecting = false; isCtrlSelecting = event.ctrlKey; if (event.ctrlKey && duplicatedObjects.length === 0) { if (controls) (controls as any).enabled = false; selectionBox.startPoint.set(pointer.x, pointer.y, 0); } } }; const onPointerMove = (event: PointerEvent) => { if (isRightClick) { rightClickMoved = true; } isSelecting = true; if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting) { selectionBox.endPoint.set(pointer.x, pointer.y, 0); } }; const onPointerUp = (event: PointerEvent) => { if (event.button === 2) { isRightClick = false; if (!rightClickMoved) { clearSelection(); } return; } if (isSelecting && isCtrlSelecting) { isCtrlSelecting = false; isSelecting = false; if (event.ctrlKey && duplicatedObjects.length === 0) { selectAssets(); } } else if (!isSelecting && selectedAssets.length > 0 && ((pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) { clearSelection(); helper.enabled = true; isCtrlSelecting = false; } }; const onKeyDown = (event: KeyboardEvent) => { if (movedObjects.length > 0 || rotatedObjects.length > 0) return; if (event.key.toLowerCase() === "escape") { event.preventDefault(); clearSelection(); } if (event.key.toLowerCase() === "delete") { event.preventDefault(); deleteSelection(); } }; const onContextMenu = (event: MouseEvent) => { event.preventDefault(); if (!rightClickMoved) { clearSelection(); } }; if (!toggleView && activeModule === "builder") { helper.enabled = true; if (duplicatedObjects.length === 0 && pastedObjects.length === 0) { canvasElement.addEventListener("pointerdown", onPointerDown); canvasElement.addEventListener("pointermove", onPointerMove); canvasElement.addEventListener("pointerup", onPointerUp); } else { helper.enabled = false; helper.dispose(); } canvasElement.addEventListener("contextmenu", onContextMenu); canvasElement.addEventListener("keydown", onKeyDown); } else { helper.enabled = false; helper.dispose(); } return () => { canvasElement.removeEventListener("pointerdown", onPointerDown); canvasElement.removeEventListener("pointermove", onPointerMove); canvasElement.removeEventListener("contextmenu", onContextMenu); canvasElement.removeEventListener("pointerup", onPointerUp); canvasElement.removeEventListener("keydown", onKeyDown); helper.enabled = false; helper.dispose(); }; }, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects, activeModule,]); useEffect(() => { if (activeModule !== "builder") { clearSelection(); } }, [activeModule]); useFrame(() => { if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) { selectionGroup.current.position.set(0, 0, 0); } }); const selectAssets = () => { selectionBox.endPoint.set(pointer.x, pointer.y, 0); if (controls) (controls as any).enabled = true; let selectedObjects = selectionBox.select(); let Objects = new Set(); selectedObjects.map((object) => { let currentObject: THREE.Object3D | null = object; while (currentObject) { if (currentObject.userData.modelId) { Objects.add(currentObject); break; } currentObject = currentObject.parent || null; } }); if (Objects.size === 0) { clearSelection(); return; } const updatedSelections = new Set(selectedAssets); Objects.forEach((obj) => { updatedSelections.has(obj) ? updatedSelections.delete(obj) : updatedSelections.add(obj); }); const selected = Array.from(updatedSelections); setSelectedAssets(selected); }; const clearSelection = () => { selectionGroup.current.children = []; selectionGroup.current.position.set(0, 0, 0); selectionGroup.current.rotation.set(0, 0, 0); setpastedObjects([]); setDuplicatedObjects([]); setSelectedAssets([]); }; const updateBackend = async (updatedPaths: (SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => { if (updatedPaths.length === 0) return; const email = localStorage.getItem("email"); const organization = email ? email.split("@")[1].split(".")[0] : ""; updatedPaths.forEach(async (updatedPath) => { if (updatedPath.type === "Conveyor") { // await setEventApi( // organization, // updatedPath.modeluuid, // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } // ); const data = { organization: organization, modeluuid: updatedPath.modeluuid, eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed, }, }; socket.emit("v2:model-asset:updateEventData", data); } else if (updatedPath.type === "Vehicle") { // await setEventApi( // organization, // updatedPath.modeluuid, // { type: "Vehicle", points: updatedPath.points } // ); const data = { organization: organization, modeluuid: updatedPath.modeluuid, eventData: { type: "Vehicle", points: updatedPath.points }, }; socket.emit("v2:model-asset:updateEventData", data); } else if (updatedPath.type === "StaticMachine") { // await setEventApi( // organization, // updatedPath.modeluuid, // { type: "StaticMachine", points: updatedPath.points } // ); const data = { organization: organization, modeluuid: updatedPath.modeluuid, eventData: { type: "StaticMachine", points: updatedPath.points }, }; socket.emit("v2:model-asset:updateEventData", data); } else if (updatedPath.type === "ArmBot") { // await setEventApi( // organization, // updatedPath.modeluuid, // { type: "ArmBot", points: updatedPath.points } // ); const data = { organization: organization, modeluuid: updatedPath.modeluuid, eventData: { type: "ArmBot", points: updatedPath.points }, }; socket.emit("v2:model-asset:updateEventData", data); } }); }; const removeConnections = (deletedModelUUIDs: string[]) => { const updatedStates = simulationStates.map((state) => { // Handle Conveyor if (state.type === "Conveyor") { const updatedConveyor: SimulationTypes.ConveyorEventsSchema = { ...state, points: state.points.map((point) => { return { ...point, connections: { ...point.connections, targets: point.connections.targets.filter( (target) => !deletedModelUUIDs.includes(target.modelUUID) ), }, }; }), }; return updatedConveyor; } // Handle Vehicle else if (state.type === "Vehicle") { const updatedVehicle: SimulationTypes.VehicleEventsSchema = { ...state, points: { ...state.points, connections: { ...state.points.connections, targets: state.points.connections.targets.filter( (target) => !deletedModelUUIDs.includes(target.modelUUID) ), }, }, }; return updatedVehicle; } // Handle StaticMachine else if (state.type === "StaticMachine") { const updatedStaticMachine: SimulationTypes.StaticMachineEventsSchema = { ...state, points: { ...state.points, connections: { ...state.points.connections, targets: state.points.connections.targets.filter( (target) => !deletedModelUUIDs.includes(target.modelUUID) ), }, }, }; return updatedStaticMachine; } // Handle ArmBot else if (state.type === "ArmBot") { const updatedArmBot: SimulationTypes.ArmBotEventsSchema = { ...state, points: { ...state.points, connections: { ...state.points.connections, targets: state.points.connections.targets.filter( (target: any) => !deletedModelUUIDs.includes(target.modelUUID) ), }, actions: { ...state.points.actions, processes: (state.points.actions.processes = state.points.actions.processes?.filter((process) => { const matchedStates = simulationStates.filter((s) => deletedModelUUIDs.includes(s.modeluuid)); if (matchedStates.length > 0) { if (matchedStates[0]?.type === "StaticMachine") { const trigPoints = matchedStates[0]?.points; return !( process.triggerId === trigPoints?.triggers?.uuid ); } else if (matchedStates[0]?.type === "Conveyor") { const trigPoints = matchedStates[0]?.points; if (Array.isArray(trigPoints)) { const nonEmptyTriggers = trigPoints.filter((point) => point && point.triggers && point.triggers.length > 0); const allTriggerUUIDs = nonEmptyTriggers.flatMap((point) => point.triggers).map((trigger) => trigger.uuid); return !allTriggerUUIDs.includes(process.triggerId); } } } return true; })), }, }, }; return updatedArmBot; } return state; }); const filteredStates = updatedStates.filter((state) => !deletedModelUUIDs.includes(state.modeluuid)); updateBackend(filteredStates); setSimulationStates(filteredStates); }; const deleteSelection = () => { if (selectedAssets.length > 0 && duplicatedObjects.length === 0) { const email = localStorage.getItem("email"); const organization = email!.split("@")[1].split(".")[0]; const storedItems = JSON.parse(localStorage.getItem("FloorItems") || "[]"); const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid); const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid)); localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems)); selectedAssets.forEach((selectedMesh: THREE.Object3D) => { //REST // const response = await deleteFloorItem(organization, selectedMesh.uuid, selectedMesh.userData.name); //SOCKET const data = { organization: organization, modeluuid: selectedMesh.uuid, modelname: selectedMesh.userData.name, socketId: socket.id, }; socket.emit("v2:model-asset:delete", data); selectedMesh.traverse((child: THREE.Object3D) => { if (child instanceof THREE.Mesh) { if (child.geometry) child.geometry.dispose(); if (Array.isArray(child.material)) { child.material.forEach((material) => { if (material.map) material.map.dispose(); material.dispose(); }); } else if (child.material) { if (child.material.map) child.material.map.dispose(); child.material.dispose(); } } }); setSimulationStates((prevEvents: (| SimulationTypes.ConveyorEventsSchema | SimulationTypes.VehicleEventsSchema | SimulationTypes.StaticMachineEventsSchema | SimulationTypes.ArmBotEventsSchema)[]) => { const updatedEvents = (prevEvents || []).filter((event) => event.modeluuid !== selectedMesh.uuid); return updatedEvents; }); itemsGroupRef.current?.remove(selectedMesh); }); const allUUIDs = selectedAssets.map((val: any) => val.uuid); removeConnections(allUUIDs); const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid)); setFloorItems(updatedItems); } toast.success("Selected models removed!"); clearSelection(); }; return ( <> ); }; export default SelectionControls;