Dwinzo_dev/app/src/modules/simulation/path/pathConnector.tsx

1500 lines
52 KiB
TypeScript

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