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/simulationTypes'; 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 removeConnection = (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 ); 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 } removeConnection(connection1, connection2) } }} depthWrite={false} 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 } removeConnection(connection1, connection2) } }} depthWrite={false} 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 } removeConnection(connection1, connection2) } }} depthWrite={false} userData={target} /> ); } return null; }); } return []; })} {currentLine && ( )} ); } export default PathConnector;