feat: Extend simulation state types to include ArmBot events and update related components

- Updated RotateControls and SelectionControls to handle ArmBot events in simulation states.
- Enhanced PathConnector to manage connections involving ArmBot and StaticMachine types.
- Added ArmBotMechanics and StaticMachineMechanics components for managing properties of ArmBot and StaticMachine.
- Modified types in worldTypes to include rotation for ArmBot and StaticMachine events.
- Updated store to accommodate new ArmBot event types in simulation states.
This commit is contained in:
2025-04-07 18:16:34 +05:30
parent c44629f07d
commit 272317991a
20 changed files with 2095 additions and 1363 deletions

View File

@@ -1,9 +1,9 @@
import { useFrame, useThree } from '@react-three/fiber';
import React, { useEffect, useState } from 'react';
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 { useIsConnecting, useSimulationStates, useSocketStore } from '../../../store/store';
import { 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';
@@ -11,28 +11,21 @@ import { setEventApi } from '../../../services/factoryBuilder/assest/floorAsset/
function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObject<THREE.Group> }) {
const { activeModule } = useModuleStore();
const { gl, raycaster, scene, pointer, camera } = useThree();
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 [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 updatePathConnections = (
fromModelUUID: string,
fromPointUUID: string,
toModelUUID: string,
toPointUUID: string
) => {
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,
@@ -61,6 +54,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
})
};
}
// Handle incoming connections to Conveyor
else if (path.modeluuid === toModelUUID) {
return {
...path,
@@ -167,82 +161,170 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
}
return path;
}
// else if (path.type === 'StaticMachine') {
// if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) {
// const newTarget = {
// modelUUID: toModelUUID,
// pointUUID: toPointUUID
// };
// const existingTargets = path.points.connections.targets || [];
else if (path.type === 'StaticMachine') {
// Handle outgoing connections from StaticMachine
if (path.modeluuid === fromModelUUID && path.points.uuid === fromPointUUID) {
const newTarget = {
modelUUID: toModelUUID,
pointUUID: toPointUUID
};
// // Check if 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;
// }
// 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;
}
// // Check if already has a connection
// if (existingTargets.length >= 1) {
// console.log("StaticMachine can have only one connection");
// return path;
// }
const existingTargets = path.points.connections.targets || [];
// 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 existingTargets = path.points.connections.targets || [];
// Allow only one connection
if (existingTargets.length >= 1) {
console.log("StaticMachine can only have one connection");
return path;
}
// // Check if source is an ArmBot
// const fromPath = simulationStates.find(p => p.modeluuid === fromModelUUID);
// if (fromPath?.type !== 'ArmBot') {
// console.log("StaticMachine can only connect to ArmBot");
// 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]
}
}
};
}
}
// // Check if already has a connection
// if (existingTargets.length >= 1) {
// console.log("StaticMachine can have only one connection");
// return path;
// }
// 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;
}
// 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;
});
@@ -252,10 +334,10 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
path.modeluuid === fromModelUUID || path.modeluuid === toModelUUID
);
updateBackend(updatedPathDetails);
// updateBackend(updatedPathDetails);
};
const updateBackend = async (updatedPaths: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema)[]) => {
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] : "";
@@ -437,6 +519,69 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
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.");
@@ -489,6 +634,15 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
};
}, [camera, scene, raycaster, firstSelected, simulationStates]);
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);
@@ -574,12 +728,50 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
(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) &&
@@ -596,6 +788,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
} else {
isInvalidConnection = true;
}
}
if (snappedSphere) {
@@ -633,7 +826,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
});
return (
<group name='simulationConnectionGroup' visible={!isPlaying} >
<group name='simulationConnectionGroup' visible={!isPlaying}>
{simulationStates.flatMap(path => {
if (path.type === 'Conveyor') {
return path.points.flatMap(point =>
@@ -652,7 +845,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
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,
@@ -662,6 +854,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
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()}
@@ -676,7 +869,9 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
return null;
})
);
} else if (path.type === 'Vehicle') {
}
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);
@@ -689,7 +884,6 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
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,
@@ -699,6 +893,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
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()}
@@ -713,6 +908,48 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
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="#42a5f5"
lineWidth={4}
dashed
dashSize={0.75}
dashScale={20}
/>
);
}
return null;
});
}
return [];
})}
@@ -730,6 +967,7 @@ function PathConnector({ pathsGroupRef }: { pathsGroupRef: React.MutableRefObjec
)}
</group>
);
}
export default PathConnector;

View File

@@ -206,6 +206,7 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
@@ -271,10 +272,11 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
})}
</group>
);
} else if (path.type === "Vehicle" || path.type === "StaticMachine") {
} else if (path.type === "Vehicle") {
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
@@ -323,6 +325,114 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
</Sphere>
</group>
);
} else if (path.type === "StaticMachine") {
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({
path,
group: groupRefs.current[path.modeluuid],
});
setSelectedActionSphere(null);
setTransformMode(null);
setSubModule("mechanics");
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule("properties");
}}
>
<Sphere
key={path.points.uuid}
uuid={path.points.uuid}
position={path.points.position}
args={[0.15, 32, 32]}
name="events-sphere"
ref={(el) => (sphereRefs.current[path.points.uuid] = el!)}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
points: sphereRefs.current[path.points.uuid],
});
setSubModule("mechanics");
setSelectedPath(null);
}}
userData={{ points: path.points, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule("properties");
setSelectedActionSphere(null);
}}
>
<meshStandardMaterial color="yellow" />
</Sphere>
</group>
);
} else if (path.type === "ArmBot") {
return (
<group
name={`${path.modeluuid}-${path.type}-path`}
uuid={path.modeluuid}
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedPath({
path,
group: groupRefs.current[path.modeluuid],
});
setSelectedActionSphere(null);
setTransformMode(null);
setSubModule("mechanics");
}}
onPointerMissed={() => {
if (eyeDropMode) return;
setSelectedPath(null);
setSubModule("properties");
}}
>
<Sphere
key={path.points.uuid}
uuid={path.points.uuid}
position={path.points.position}
args={[0.15, 32, 32]}
name="events-sphere"
ref={(el) => (sphereRefs.current[path.points.uuid] = el!)}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
setSelectedActionSphere({
path,
points: sphereRefs.current[path.points.uuid],
});
setSubModule("mechanics");
setSelectedPath(null);
}}
userData={{ points: path.points, path }}
onPointerMissed={() => {
if (eyeDropMode) return;
setSubModule("properties");
setSelectedActionSphere(null);
}}
>
<meshStandardMaterial color="pink" />
</Sphere>
</group>
);
}
return null;
})}