From 82a7cd0001b0efd91e7dcc91b73a1f58f0a1f720 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Tue, 15 Apr 2025 09:11:01 +0530 Subject: [PATCH] updated paths --- .../modules/simulation/path/pathConnector.tsx | 2562 ++++++++++------- 1 file changed, 1445 insertions(+), 1117 deletions(-) diff --git a/app/src/modules/simulation/path/pathConnector.tsx b/app/src/modules/simulation/path/pathConnector.tsx index c561cbb..f6ee51f 100644 --- a/app/src/modules/simulation/path/pathConnector.tsx +++ b/app/src/modules/simulation/path/pathConnector.tsx @@ -1,1170 +1,1498 @@ -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 { 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'; +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 { 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 }>({}); +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 [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 || []; + 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; - }) - }; + if ( + !existingTargets.some( + (target) => + target.modelUUID === newTarget.modelUUID && + target.pointUUID === newTarget.pointUUID + ) + ) { + return { + ...point, + connections: { + ...point.connections, + targets: [...existingTargets, newTarget], + }, + }; } - // 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 || []; + } + 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; - }) - }; + if ( + !existingTargets.some( + (target) => + target.modelUUID === reverseTarget.modelUUID && + target.pointUUID === reverseTarget.pointUUID + ) + ) { + return { + ...point, + connections: { + ...point.connections, + targets: [...existingTargets, reverseTarget], + }, + }; } - } - 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 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; - }); + } - setSimulationStates(updatedPaths); + // Check if already has a connection + if (existingTargets.length >= 1) { + console.log("Vehicle can have only one connection"); + return path; + } - const updatedPathDetails = updatedPaths.filter(path => - path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID - ); + 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 || []; - updateBackend(updatedPathDetails); + // 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: ( + | Types.ConveyorEventsSchema + | Types.VehicleEventsSchema + | Types.StaticMachineEventsSchema + | Types.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 updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]) => { - if (updatedPaths.length === 0) return; - const email = localStorage.getItem("email"); - const organization = email ? email.split("@")[1].split(".")[0] : ""; + const onMouseUp = () => { + MouseDown = false; + }; - updatedPaths.forEach(async (updatedPath) => { - if (updatedPath.type === 'Conveyor') { + const onMouseMove = () => { + if (MouseDown) { + drag = true; + } + }; - // await setEventApi( - // organization, - // updatedPath.modeluuid, - // { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } - // ); + const onContextMenu = (evt: MouseEvent) => { + evt.preventDefault(); + if (drag || evt.button === 0) return; - const data = { - organization: organization, - modeluuid: updatedPath.modeluuid, - eventData: { type: "Conveyor", points: updatedPath.points, speed: updatedPath.speed } - } + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects( + pathsGroupRef.current.children, + true + ); - socket.emit('v2:model-asset:updateEventData', data); + if (intersects.length > 0) { + const intersected = intersects[0].object; - } else if (updatedPath.type === 'Vehicle') { + if (intersected.name.includes("events-sphere")) { + const modelUUID = intersected.userData.path.modeluuid; + const sphereUUID = intersected.uuid; + const worldPosition = new THREE.Vector3(); + intersected.getWorldPosition(worldPosition); - // await setEventApi( - // organization, - // updatedPath.modeluuid, - // { type: "Vehicle", points: updatedPath.points } - // ); + let isStartOrEnd = false; - const data = { - organization: organization, - modeluuid: updatedPath.modeluuid, - eventData: { type: "Vehicle", points: updatedPath.points } - } + 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; + } - 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); + 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; + } - const handleAddConnection = (fromModelUUID: string, fromUUID: string, toModelUUID: string, toUUID: string) => { - updatePathConnections(fromModelUUID, fromUUID, toModelUUID, toUUID); + // 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); + } }; - useEffect(() => { - const canvasElement = gl.domElement; - let drag = false; - let MouseDown = 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); + } - const onMouseDown = () => { - MouseDown = true; - drag = false; - }; + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener("contextmenu", onContextMenu); + }; + }, [camera, scene, raycaster, firstSelected, simulationStates, deleteTool]); - 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(() => { + 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: Types.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: Types.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: Types.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: Types.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 + 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") ); - updateBackend(updatedPaths); + let point: THREE.Vector3 | null = null; + let snappedSphere: { + sphereUUID: string; + position: THREE.Vector3; + modelUUID: string; + isCorner: boolean; + } | null = null; + let isInvalidConnection = false; - setSimulationStates(updatedStates); - }; + 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")); - 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; + 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 fromSphere = pathsGroupRef.current?.getObjectByProperty('uuid', point.uuid); - const toSphere = pathsGroupRef.current?.getObjectByProperty('uuid', target.pointUUID); + 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"; - if (fromSphere && toSphere) { - const fromWorldPosition = new THREE.Vector3(); - const toWorldPosition = new THREE.Vector3(); - fromSphere.getWorldPosition(fromWorldPosition); - toSphere.getWorldPosition(toWorldPosition); + // 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 + ); - 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); + // 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; + }); - 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) { + // 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; + }); - const connection1 = { model: path.modeluuid, point: point.uuid } - const connection2 = { model: target.modelUUID, point: target.pointUUID } + // 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"); - removeConnections(connection1, connection2) + // Check if StaticMachine is connecting to non-ArmBot + const isStaticMachineToNonArmBot = + (firstPath?.type === "StaticMachine" && + secondPath?.type !== "ArmBot") || + (secondPath?.type === "StaticMachine" && + firstPath?.type !== "ArmBot"); - } - }} - userData={target} - /> - ); - } - return null; - }) + // 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: Types.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: Types.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: Types.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: Types.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; + }); - 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 && ( - - )} - + 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; \ No newline at end of file +export default PathConnector;