import { useFrame, useThree } from "@react-three/fiber"; import React, { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import * as Types from "../../../types/world/worldTypes"; import * as SimulationTypes from "../../../types/simulation"; import { QuadraticBezierLine } from "@react-three/drei"; import { useDeleteTool, useIsConnecting, useRenderDistance, useSimulationStates, useSocketStore, } from "../../../store/store"; import useModuleStore from "../../../store/useModuleStore"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt"; function PathConnector({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject; }) { const { activeModule } = useModuleStore(); const { gl, raycaster, scene, pointer, camera } = useThree(); const { deleteTool } = useDeleteTool(); const { renderDistance } = useRenderDistance(); const { setIsConnecting } = useIsConnecting(); const { simulationStates, setSimulationStates } = useSimulationStates(); const { isPlaying } = usePlayButtonStore(); const { socket } = useSocketStore(); const groupRefs = useRef<{ [key: string]: any }>({}); const [firstSelected, setFirstSelected] = useState<{ modelUUID: string; sphereUUID: string; position: THREE.Vector3; isCorner: boolean; } | null>(null); const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null); const [helperlineColor, setHelperLineColor] = useState("red"); const [hoveredLineKey, setHoveredLineKey] = useState(null); const updatePathConnections = ( fromModelUUID: string, fromPointUUID: string, toModelUUID: string, toPointUUID: string ) => { const updatedPaths = simulationStates.map((path) => { if (path.type === "Conveyor") { // Handle outgoing connections from Conveyor if (path.modeluuid === fromModelUUID) { return { ...path, points: path.points.map((point) => { if (point.uuid === fromPointUUID) { const newTarget = { modelUUID: toModelUUID, pointUUID: toPointUUID, }; const existingTargets = point.connections.targets || []; if ( !existingTargets.some( (target) => target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID ) ) { return { ...point, connections: { ...point.connections, targets: [...existingTargets, newTarget], }, }; } } return point; }), }; } // Handle incoming connections to Conveyor else if (path.modeluuid === toModelUUID) { return { ...path, points: path.points.map((point) => { if (point.uuid === toPointUUID) { const reverseTarget = { modelUUID: fromModelUUID, pointUUID: fromPointUUID, }; const existingTargets = point.connections.targets || []; if ( !existingTargets.some( (target) => target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID ) ) { return { ...point, connections: { ...point.connections, targets: [...existingTargets, reverseTarget], }, }; } } return point; }), }; } } else if (path.type === "Vehicle") { // Handle outgoing connections from Vehicle if ( path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID ) { const newTarget = { modelUUID: toModelUUID, pointUUID: toPointUUID, }; const existingTargets = path.points.connections.targets || []; // Check if target is a Conveyor const toPath = simulationStates.find( (p) => p.modeluuid === toModelUUID ); if (toPath?.type !== "Conveyor") { console.log("Vehicle can only connect to Conveyors"); return path; } // Check if already has a connection if (existingTargets.length >= 1) { console.log("Vehicle can have only one connection"); return path; } if ( !existingTargets.some( (target) => target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID ) ) { return { ...path, points: { ...path.points, connections: { ...path.points.connections, targets: [...existingTargets, newTarget], }, }, }; } } // Handle incoming connections to Vehicle else if ( path.modeluuid === toModelUUID && path.points.uuid === toPointUUID ) { const reverseTarget = { modelUUID: fromModelUUID, pointUUID: fromPointUUID, }; const existingTargets = path.points.connections.targets || []; // Check if source is a Conveyor const fromPath = simulationStates.find( (p) => p.modeluuid === fromModelUUID ); if (fromPath?.type !== "Conveyor") { console.log("Vehicle can only connect to Conveyors"); return path; } // Check if already has a connection if (existingTargets.length >= 1) { console.log("Vehicle can have only one connection"); return path; } if ( !existingTargets.some( (target) => target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID ) ) { return { ...path, points: { ...path.points, connections: { ...path.points.connections, targets: [...existingTargets, reverseTarget], }, }, }; } } return path; } else if (path.type === "StaticMachine") { // Handle outgoing connections from StaticMachine if ( path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID ) { const newTarget = { modelUUID: toModelUUID, pointUUID: toPointUUID, }; // Ensure target is an ArmBot const toPath = simulationStates.find( (p) => p.modeluuid === toModelUUID ); if (toPath?.type !== "ArmBot") { console.log("StaticMachine can only connect to ArmBot"); return path; } const existingTargets = path.points.connections.targets || []; // Allow only one connection if (existingTargets.length >= 1) { console.log("StaticMachine can only have one connection"); return path; } if ( !existingTargets.some( (target) => target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID ) ) { return { ...path, points: { ...path.points, connections: { ...path.points.connections, targets: [...existingTargets, newTarget], }, }, }; } } // Handle incoming connections to StaticMachine else if ( path.modeluuid === toModelUUID && path.points.uuid === toPointUUID ) { const reverseTarget = { modelUUID: fromModelUUID, pointUUID: fromPointUUID, }; const fromPath = simulationStates.find( (p) => p.modeluuid === fromModelUUID ); if (fromPath?.type !== "ArmBot") { console.log("StaticMachine can only be connected from ArmBot"); return path; } const existingTargets = path.points.connections.targets || []; if (existingTargets.length >= 1) { console.log("StaticMachine can only have one connection"); return path; } if ( !existingTargets.some( (target) => target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID ) ) { return { ...path, points: { ...path.points, connections: { ...path.points.connections, targets: [...existingTargets, reverseTarget], }, }, }; } } return path; } else if (path.type === "ArmBot") { // Handle outgoing connections from ArmBot if ( path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID ) { const newTarget = { modelUUID: toModelUUID, pointUUID: toPointUUID, }; const toPath = simulationStates.find( (p) => p.modeluuid === toModelUUID ); if (!toPath) return path; const existingTargets = path.points.connections.targets || []; // Check if connecting to a StaticMachine and already connected to one const alreadyConnectedToStatic = existingTargets.some((target) => { const targetPath = simulationStates.find( (p) => p.modeluuid === target.modelUUID ); return targetPath?.type === "StaticMachine"; }); if (toPath.type === "StaticMachine") { if (alreadyConnectedToStatic) { console.log("ArmBot can only connect to one StaticMachine"); return path; } } if ( !existingTargets.some( (target) => target.modelUUID === newTarget.modelUUID && target.pointUUID === newTarget.pointUUID ) ) { return { ...path, points: { ...path.points, connections: { ...path.points.connections, targets: [...existingTargets, newTarget], }, }, }; } } // Handle incoming connections to ArmBot else if ( path.modeluuid === toModelUUID && path.points.uuid === toPointUUID ) { const reverseTarget = { modelUUID: fromModelUUID, pointUUID: fromPointUUID, }; const fromPath = simulationStates.find( (p) => p.modeluuid === fromModelUUID ); if (!fromPath) return path; const existingTargets = path.points.connections.targets || []; const alreadyConnectedFromStatic = existingTargets.some((target) => { const targetPath = simulationStates.find( (p) => p.modeluuid === target.modelUUID ); return targetPath?.type === "StaticMachine"; }); if (fromPath.type === "StaticMachine") { if (alreadyConnectedFromStatic) { console.log( "ArmBot can only be connected from one StaticMachine" ); return path; } } if ( !existingTargets.some( (target) => target.modelUUID === reverseTarget.modelUUID && target.pointUUID === reverseTarget.pointUUID ) ) { return { ...path, points: { ...path.points, connections: { ...path.points.connections, targets: [...existingTargets, reverseTarget], }, }, }; } } return path; } return path; }); setSimulationStates(updatedPaths); const updatedPathDetails = updatedPaths.filter( (path) => path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID ); updateBackend(updatedPathDetails); }; 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 handleAddConnection = ( fromModelUUID: string, fromUUID: string, toModelUUID: string, toUUID: string ) => { updatePathConnections(fromModelUUID, fromUUID, toModelUUID, toUUID); setFirstSelected(null); setCurrentLine(null); setIsConnecting(false); }; useEffect(() => { const canvasElement = gl.domElement; let drag = false; let MouseDown = false; const onMouseDown = () => { MouseDown = true; drag = false; }; const onMouseUp = () => { MouseDown = false; }; const onMouseMove = () => { if (MouseDown) { drag = true; } }; const onContextMenu = (evt: MouseEvent) => { evt.preventDefault(); if (drag || evt.button === 0) return; raycaster.setFromCamera(pointer, camera); const intersects = raycaster.intersectObjects( pathsGroupRef.current.children, true ); if (intersects.length > 0) { const intersected = intersects[0].object; if (intersected.name.includes("events-sphere")) { const modelUUID = intersected.userData.path.modeluuid; const sphereUUID = intersected.uuid; const worldPosition = new THREE.Vector3(); intersected.getWorldPosition(worldPosition); let isStartOrEnd = false; if ( intersected.userData.path.points && intersected.userData.path.points.length > 1 ) { isStartOrEnd = intersected.userData.path.points.length > 0 && (sphereUUID === intersected.userData.path.points[0].uuid || sphereUUID === intersected.userData.path.points[ intersected.userData.path.points.length - 1 ].uuid); } else if (intersected.userData.path.points) { isStartOrEnd = sphereUUID === intersected.userData.path.points.uuid; } if (modelUUID) { const firstPath = simulationStates.find( (p) => p.modeluuid === firstSelected?.modelUUID ); const secondPath = simulationStates.find( (p) => p.modeluuid === modelUUID ); // Prevent vehicle-to-vehicle connections if ( firstPath && secondPath && firstPath.type === "Vehicle" && secondPath.type === "Vehicle" ) { console.log("Cannot connect two vehicle paths together"); return; } // Prevent conveyor middle point to conveyor connections if ( firstPath && secondPath && firstPath.type === "Conveyor" && secondPath.type === "Conveyor" && (!firstSelected?.isCorner || !isStartOrEnd) ) { console.log( "Conveyor connections must be between start/end points" ); return; } // Check if this specific connection already exists const isDuplicateConnection = firstSelected ? simulationStates.some((path) => { if (path.modeluuid === firstSelected.modelUUID) { if (path.type === "Conveyor") { const point = path.points.find( (p) => p.uuid === firstSelected.sphereUUID ); return point?.connections.targets.some( (t) => t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } else if (path.type === "Vehicle") { return path.points.connections.targets.some( (t) => t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } } return false; }) : false; if (isDuplicateConnection) { console.log("These points are already connected. Ignoring."); return; } // For Vehicles, check if they're already connected to anything if (intersected.userData.path.type === "Vehicle") { const vehicleConnections = intersected.userData.path.points.connections.targets.length; if (vehicleConnections >= 1) { console.log("Vehicle can only have one connection"); return; } } // For non-Vehicle paths, check if already connected if (intersected.userData.path.type !== "Vehicle") { const isAlreadyConnected = simulationStates.some((path) => { if (path.type === "Conveyor") { return path.points.some( (point) => point.uuid === sphereUUID && point.connections.targets.length > 0 ); } return false; }); if (isAlreadyConnected) { console.log("Conveyor point is already connected. Ignoring."); return; } } if (firstSelected) { // Check if trying to connect Vehicle to non-Conveyor if ( (firstPath?.type === "Vehicle" && secondPath?.type !== "Conveyor") || (secondPath?.type === "Vehicle" && firstPath?.type !== "Conveyor") ) { console.log("Vehicle can only connect to Conveyors"); return; } // Prevent same-path connections if (firstSelected.modelUUID === modelUUID) { console.log("Cannot connect spheres on the same path."); return; } // Check if StaticMachine is involved in the connection if ( (firstPath?.type === "StaticMachine" && secondPath?.type !== "ArmBot") || (secondPath?.type === "StaticMachine" && firstPath?.type !== "ArmBot") ) { console.log("StaticMachine can only connect to ArmBot"); return; } // Check if StaticMachine already has a connection if (firstPath?.type === "StaticMachine") { const staticConnections = firstPath.points.connections.targets.length; if (staticConnections >= 1) { console.log("StaticMachine can only have one connection"); return; } } if (secondPath?.type === "StaticMachine") { const staticConnections = secondPath.points.connections.targets.length; if (staticConnections >= 1) { console.log("StaticMachine can only have one connection"); return; } } // Check if ArmBot is involved if ( (firstPath?.type === "ArmBot" && secondPath?.type === "StaticMachine") || (secondPath?.type === "ArmBot" && firstPath?.type === "StaticMachine") ) { const armBotPath = firstPath?.type === "ArmBot" ? firstPath : secondPath; const staticPath = firstPath?.type === "StaticMachine" ? firstPath : secondPath; const armBotConnections = armBotPath.points.connections.targets || []; const alreadyConnectedToStatic = armBotConnections.some( (target) => { const targetPath = simulationStates.find( (p) => p.modeluuid === target.modelUUID ); return targetPath?.type === "StaticMachine"; } ); if (alreadyConnectedToStatic) { console.log("ArmBot can only connect to one StaticMachine"); return; } const staticConnections = staticPath.points.connections.targets.length; if (staticConnections >= 1) { console.log("StaticMachine can only have one connection"); return; } } // Prevent ArmBot ↔ ArmBot if ( firstPath?.type === "ArmBot" && secondPath?.type === "ArmBot" ) { console.log("Cannot connect two ArmBots together"); return; } // If one is ArmBot, ensure the other is StaticMachine or Conveyor if ( firstPath?.type === "ArmBot" || secondPath?.type === "ArmBot" ) { const otherType = firstPath?.type === "ArmBot" ? secondPath?.type : firstPath?.type; if (otherType !== "StaticMachine" && otherType !== "Conveyor") { console.log( "ArmBot can only connect to Conveyors or one StaticMachine" ); return; } } // At least one must be start/end point if (!firstSelected.isCorner && !isStartOrEnd) { console.log( "At least one of the selected spheres must be a start or end point." ); return; } // All checks passed - make the connection handleAddConnection( firstSelected.modelUUID, firstSelected.sphereUUID, modelUUID, sphereUUID ); } else { // First selection - just store it setFirstSelected({ modelUUID, sphereUUID, position: worldPosition, isCorner: isStartOrEnd, }); setIsConnecting(true); } } } } else { // Clicked outside - cancel connection setFirstSelected(null); setCurrentLine(null); setIsConnecting(false); } }; if (activeModule === "simulation" && !deleteTool) { canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mousemove", onMouseMove); canvasElement.addEventListener("contextmenu", onContextMenu); } else { setFirstSelected(null); setCurrentLine(null); setIsConnecting(false); } return () => { canvasElement.removeEventListener("mousedown", onMouseDown); canvasElement.removeEventListener("mouseup", onMouseUp); canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("contextmenu", onContextMenu); }; }, [camera, scene, raycaster, firstSelected, simulationStates, deleteTool]); useFrame(() => { Object.values(groupRefs.current).forEach((group) => { if (group) { const distance = new THREE.Vector3( ...group.position.toArray() ).distanceTo(camera.position); group.visible = distance <= renderDistance && !isPlaying; } }); }); useFrame(() => { if (firstSelected) { raycaster.setFromCamera(pointer, camera); const intersects = raycaster .intersectObjects(scene.children, true) .filter( (intersect) => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("agv-collider") && !intersect.object.name.includes("MeasurementReference") && !intersect.object.userData.isPathObject && !(intersect.object.type === "GridHelper") ); let point: THREE.Vector3 | null = null; let snappedSphere: { sphereUUID: string; position: THREE.Vector3; modelUUID: string; isCorner: boolean; } | null = null; let isInvalidConnection = false; if (intersects.length > 0) { point = intersects[0].point; if (point.y < 0.05) { point = new THREE.Vector3(point.x, 0.05, point.z); } } const sphereIntersects = raycaster .intersectObjects(pathsGroupRef.current.children, true) .filter((obj) => obj.object.name.includes("events-sphere")); if (sphereIntersects.length > 0) { const sphere = sphereIntersects[0].object; const sphereUUID = sphere.uuid; const spherePosition = new THREE.Vector3(); sphere.getWorldPosition(spherePosition); const pathData = sphere.userData.path; const modelUUID = pathData.modeluuid; const firstPath = simulationStates.find( (p) => p.modeluuid === firstSelected.modelUUID ); const secondPath = simulationStates.find( (p) => p.modeluuid === modelUUID ); const isVehicleToVehicle = firstPath?.type === "Vehicle" && secondPath?.type === "Vehicle"; // Inside the useFrame hook, where we check for snapped spheres: const isConnectable = (pathData.type === "Vehicle" || pathData.type === "ArmBot" || (pathData.points.length > 0 && (sphereUUID === pathData.points[0].uuid || sphereUUID === pathData.points[pathData.points.length - 1].uuid || (pathData.type === "Conveyor" && firstPath?.type === "ArmBot")))) && // Allow ArmBot to connect to middle points !isVehicleToVehicle && !( firstPath?.type === "Conveyor" && pathData.type === "Conveyor" && !firstSelected.isCorner ); // Check for duplicate connection (regardless of path type) const isDuplicateConnection = simulationStates.some((path) => { if (path.modeluuid === firstSelected.modelUUID) { if (path.type === "Conveyor") { const point = path.points.find( (p) => p.uuid === firstSelected.sphereUUID ); return point?.connections.targets.some( (t) => t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } else if (path.type === "Vehicle") { return path.points.connections.targets.some( (t) => t.modelUUID === modelUUID && t.pointUUID === sphereUUID ); } } return false; }); // For non-Vehicle paths, check if already connected const isNonVehicleAlreadyConnected = pathData.type !== "Vehicle" && simulationStates.some((path) => { if (path.type === "Conveyor") { return path.points.some( (point) => point.uuid === sphereUUID && point.connections.targets.length > 0 ); } return false; }); // Check vehicle connection rules const isVehicleAtMaxConnections = pathData.type === "Vehicle" && pathData.points.connections.targets.length >= 1; const isVehicleConnectingToNonConveyor = (firstPath?.type === "Vehicle" && secondPath?.type !== "Conveyor") || (secondPath?.type === "Vehicle" && firstPath?.type !== "Conveyor"); // Check if StaticMachine is connecting to non-ArmBot const isStaticMachineToNonArmBot = (firstPath?.type === "StaticMachine" && secondPath?.type !== "ArmBot") || (secondPath?.type === "StaticMachine" && firstPath?.type !== "ArmBot"); // Check if StaticMachine already has a connection const isStaticMachineAtMaxConnections = (firstPath?.type === "StaticMachine" && firstPath.points.connections.targets.length >= 1) || (secondPath?.type === "StaticMachine" && secondPath.points.connections.targets.length >= 1); // Check if ArmBot is connecting to StaticMachine const isArmBotToStaticMachine = (firstPath?.type === "ArmBot" && secondPath?.type === "StaticMachine") || (secondPath?.type === "ArmBot" && firstPath?.type === "StaticMachine"); // Prevent multiple StaticMachine connections to ArmBot let isArmBotAlreadyConnectedToStatic = false; if (isArmBotToStaticMachine) { const armBotPath = firstPath?.type === "ArmBot" ? firstPath : secondPath; isArmBotAlreadyConnectedToStatic = armBotPath.points.connections.targets.some((target) => { const targetPath = simulationStates.find( (p) => p.modeluuid === target.modelUUID ); return targetPath?.type === "StaticMachine"; }); } // Prevent ArmBot to ArmBot const isArmBotToArmBot = firstPath?.type === "ArmBot" && secondPath?.type === "ArmBot"; // If ArmBot is involved, other must be Conveyor or StaticMachine const isArmBotToInvalidType = (firstPath?.type === "ArmBot" || secondPath?.type === "ArmBot") && !( firstPath?.type === "Conveyor" || firstPath?.type === "StaticMachine" || secondPath?.type === "Conveyor" || secondPath?.type === "StaticMachine" ); if ( !isDuplicateConnection && !isVehicleToVehicle && !isNonVehicleAlreadyConnected && !isVehicleAtMaxConnections && !isVehicleConnectingToNonConveyor && !isStaticMachineToNonArmBot && !isStaticMachineAtMaxConnections && !isArmBotToArmBot && !isArmBotToInvalidType && !isArmBotAlreadyConnectedToStatic && firstSelected.sphereUUID !== sphereUUID && firstSelected.modelUUID !== modelUUID && (firstSelected.isCorner || isConnectable) && !( firstPath?.type === "Conveyor" && pathData.type === "Conveyor" && !(firstSelected.isCorner && isConnectable) ) ) { snappedSphere = { sphereUUID, position: spherePosition, modelUUID, isCorner: isConnectable, }; } else { isInvalidConnection = true; } } if (snappedSphere) { point = snappedSphere.position; } if (point) { const distance = firstSelected.position.distanceTo(point); const heightFactor = Math.max(0.5, distance * 0.2); const midPoint = new THREE.Vector3( (firstSelected.position.x + point.x) / 2, Math.max(firstSelected.position.y, point.y) + heightFactor, (firstSelected.position.z + point.z) / 2 ); setCurrentLine({ start: firstSelected.position, end: point, mid: midPoint, }); if (sphereIntersects.length > 0) { setHelperLineColor(isInvalidConnection ? "red" : "#6cf542"); } else { setHelperLineColor("yellow"); } } else { setCurrentLine(null); setIsConnecting(false); } } else { setCurrentLine(null); setIsConnecting(false); } }); const removeConnections = ( connection1: { model: string; point: string }, connection2: { model: string; point: string } ) => { const updatedStates = simulationStates.map((state) => { // Handle Conveyor (which has multiple points) if (state.type === "Conveyor") { const updatedConveyor: SimulationTypes.ConveyorEventsSchema = { ...state, points: state.points.map((point) => { // Check if this point is either connection1 or connection2 if ( (state.modeluuid === connection1.model && point.uuid === connection1.point) || (state.modeluuid === connection2.model && point.uuid === connection2.point) ) { return { ...point, connections: { ...point.connections, targets: point.connections.targets.filter((target) => { // Remove the target that matches the other connection return !( (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || (target.modelUUID === connection2.model && target.pointUUID === connection2.point) ); }), }, }; } return point; }), }; return updatedConveyor; } // Handle Vehicle else if (state.type === "Vehicle") { if ( (state.modeluuid === connection1.model && state.points.uuid === connection1.point) || (state.modeluuid === connection2.model && state.points.uuid === connection2.point) ) { const updatedVehicle: SimulationTypes.VehicleEventsSchema = { ...state, points: { ...state.points, connections: { ...state.points.connections, targets: state.points.connections.targets.filter((target) => { return !( (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || (target.modelUUID === connection2.model && target.pointUUID === connection2.point) ); }), }, // Ensure all required Vehicle point properties are included speed: state.points.speed, actions: state.points.actions, }, }; return updatedVehicle; } } // Handle StaticMachine else if (state.type === "StaticMachine") { if ( (state.modeluuid === connection1.model && state.points.uuid === connection1.point) || (state.modeluuid === connection2.model && state.points.uuid === connection2.point) ) { const updatedStaticMachine: SimulationTypes.StaticMachineEventsSchema = { ...state, points: { ...state.points, connections: { ...state.points.connections, targets: state.points.connections.targets.filter((target) => { return !( (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || (target.modelUUID === connection2.model && target.pointUUID === connection2.point) ); }), }, // Ensure all required StaticMachine point properties are included actions: state.points.actions, triggers: state.points.triggers, }, }; return updatedStaticMachine; } } // Handle ArmBot else if (state.type === "ArmBot") { if ( (state.modeluuid === connection1.model && state.points.uuid === connection1.point) || (state.modeluuid === connection2.model && state.points.uuid === connection2.point) ) { const updatedArmBot: SimulationTypes.ArmBotEventsSchema = { ...state, points: { ...state.points, connections: { ...state.points.connections, targets: state.points.connections.targets.filter((target) => { return !( (target.modelUUID === connection1.model && target.pointUUID === connection1.point) || (target.modelUUID === connection2.model && target.pointUUID === connection2.point) ); }), }, actions: { ...state.points.actions, processes: state.points.actions.processes?.filter((process) => { return !( process.startPoint === connection1.point || process.endPoint === connection1.point || process.startPoint === connection2.point || process.endPoint === connection2.point ); }) || [], }, triggers: state.points.triggers, }, }; return updatedArmBot; } } return state; }); const updatedPaths = updatedStates.filter( (state) => state.modeluuid === connection1.model || state.modeluuid === connection2.model ); console.log("updatedPaths: ", updatedPaths); updateBackend(updatedPaths); setSimulationStates(updatedStates); }; return ( {simulationStates.flatMap((path) => { if (path.type === "Conveyor") { return path.points.flatMap((point) => point.connections.targets.map((target, index) => { const targetPath = simulationStates.find( (p) => p.modeluuid === target.modelUUID ); if ( targetPath?.type !== "Conveyor" && targetPath?.type !== "ArmBot" ) return null; const fromSphere = pathsGroupRef.current?.getObjectByProperty( "uuid", point.uuid ); const toSphere = pathsGroupRef.current?.getObjectByProperty( "uuid", target.pointUUID ); if (fromSphere && toSphere) { const fromWorldPosition = new THREE.Vector3(); const toWorldPosition = new THREE.Vector3(); fromSphere.getWorldPosition(fromWorldPosition); toSphere.getWorldPosition(toWorldPosition); const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); const midPoint = new THREE.Vector3( (fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, (fromWorldPosition.z + toWorldPosition.z) / 2 ); return ( (groupRefs.current[ `${point.uuid}-${target.pointUUID}-${index}` ] = el!) } start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} color={ deleteTool && hoveredLineKey === `${point.uuid}-${target.pointUUID}-${index}` ? "red" : targetPath?.type === "ArmBot" ? "#42a5f5" : "white" } lineWidth={4} dashed={ deleteTool && hoveredLineKey === `${point.uuid}-${target.pointUUID}-${index}` ? false : true } dashSize={0.75} dashScale={20} onPointerOver={() => setHoveredLineKey( `${point.uuid}-${target.pointUUID}-${index}` ) } onPointerOut={() => setHoveredLineKey(null)} onClick={() => { if (deleteTool) { const connection1 = { model: path.modeluuid, point: point.uuid, }; const connection2 = { model: target.modelUUID, point: target.pointUUID, }; removeConnections(connection1, connection2); } }} userData={target} /> ); } return null; }) ); } if (path.type === "Vehicle") { return path.points.connections.targets.map((target, index) => { const fromSphere = pathsGroupRef.current?.getObjectByProperty( "uuid", path.points.uuid ); const toSphere = pathsGroupRef.current?.getObjectByProperty( "uuid", target.pointUUID ); if (fromSphere && toSphere) { const fromWorldPosition = new THREE.Vector3(); const toWorldPosition = new THREE.Vector3(); fromSphere.getWorldPosition(fromWorldPosition); toSphere.getWorldPosition(toWorldPosition); const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); const midPoint = new THREE.Vector3( (fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, (fromWorldPosition.z + toWorldPosition.z) / 2 ); return ( (groupRefs.current[ `${path.points.uuid}-${target.pointUUID}-${index}` ] = el!) } start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} color={ deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}` ? "red" : "orange" } lineWidth={4} dashed={ deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}` ? false : true } dashSize={0.75} dashScale={20} onPointerOver={() => setHoveredLineKey( `${path.points.uuid}-${target.pointUUID}-${index}` ) } onPointerOut={() => setHoveredLineKey(null)} onClick={() => { if (deleteTool) { const connection1 = { model: path.modeluuid, point: path.points.uuid, }; const connection2 = { model: target.modelUUID, point: target.pointUUID, }; removeConnections(connection1, connection2); } }} userData={target} /> ); } return null; }); } if (path.type === "StaticMachine") { return path.points.connections.targets.map((target, index) => { const targetPath = simulationStates.find( (p) => p.modeluuid === target.modelUUID ); if (targetPath?.type !== "ArmBot") return null; const fromSphere = pathsGroupRef.current?.getObjectByProperty( "uuid", path.points.uuid ); const toSphere = pathsGroupRef.current?.getObjectByProperty( "uuid", target.pointUUID ); if (fromSphere && toSphere) { const fromWorldPosition = new THREE.Vector3(); const toWorldPosition = new THREE.Vector3(); fromSphere.getWorldPosition(fromWorldPosition); toSphere.getWorldPosition(toWorldPosition); const distance = fromWorldPosition.distanceTo(toWorldPosition); const heightFactor = Math.max(0.5, distance * 0.2); const midPoint = new THREE.Vector3( (fromWorldPosition.x + toWorldPosition.x) / 2, Math.max(fromWorldPosition.y, toWorldPosition.y) + heightFactor, (fromWorldPosition.z + toWorldPosition.z) / 2 ); return ( (groupRefs.current[ `${path.points.uuid}-${target.pointUUID}-${index}` ] = el!) } start={fromWorldPosition.toArray()} end={toWorldPosition.toArray()} mid={midPoint.toArray()} color={ deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}` ? "red" : "#42a5f5" } lineWidth={4} dashed={ deleteTool && hoveredLineKey === `${path.points.uuid}-${target.pointUUID}-${index}` ? false : true } dashSize={0.75} dashScale={20} onPointerOver={() => setHoveredLineKey( `${path.points.uuid}-${target.pointUUID}-${index}` ) } onPointerOut={() => setHoveredLineKey(null)} onClick={() => { if (deleteTool) { const connection1 = { model: path.modeluuid, point: path.points.uuid, }; const connection2 = { model: target.modelUUID, point: target.pointUUID, }; removeConnections(connection1, connection2); } }} userData={target} /> ); } return null; }); } return []; })} {currentLine && ( )} ); } export default PathConnector;