v2 #78
app/src
components
layout/sidebarRight/properties/eventProperties
ui/simulation
modules
collaboration/functions
simulation
actions
materials/instances
roboticArm/instances
simulator
triggers/triggerHandler
store/simulation
types
|
@ -237,7 +237,7 @@ function ConveyorMechanics() {
|
|||
{activeOption === "spawn" && (
|
||||
<SpawnAction
|
||||
onChangeCount={handleSpawnCountChange}
|
||||
options={["Default material", "Material 1", "Material 2"]}
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
defaultOption={currentMaterial}
|
||||
onSelect={handleMaterialSelect}
|
||||
onChangeInterval={handleSpawnIntervalChange}
|
||||
|
@ -253,7 +253,7 @@ function ConveyorMechanics() {
|
|||
)}
|
||||
{activeOption === "swap" && (
|
||||
<SwapAction
|
||||
options={["Default material", "Material 1", "Material 2"]}
|
||||
options={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
defaultOption={currentMaterial}
|
||||
onSelect={handleMaterialSelect}
|
||||
/>
|
||||
|
|
|
@ -129,7 +129,7 @@ function MachineMechanics() {
|
|||
max={60}
|
||||
defaultValue="1"
|
||||
onChange={handleProcessTimeChange}
|
||||
swapOptions={["Default material", "Material 1", "Material 2"]}
|
||||
swapOptions={["Default material", "Material 1", "Material 2", "Material 3"]}
|
||||
swapDefaultOption={currentMaterial}
|
||||
onSwapSelect={handleMaterialSelect}
|
||||
/>
|
||||
|
|
|
@ -359,7 +359,8 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
|||
<LabledDropdown
|
||||
label="Trigger Type"
|
||||
defaultOption={selectedTrigger.triggerType}
|
||||
options={["onComplete", "onStart", "onStop", "delay", "onError"]}
|
||||
// options={["onComplete", "onStart", "onStop", "delay", "onError"]}
|
||||
options={["onComplete"]}
|
||||
onSelect={handleTriggerTypeChange}
|
||||
/>
|
||||
|
||||
|
|
|
@ -38,9 +38,17 @@ const SimulationPlayer: React.FC = () => {
|
|||
const { isReset, setReset } = useResetButtonStore();
|
||||
const { subModule } = useSubModuleStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset) {
|
||||
setTimeout(()=>{
|
||||
setReset(false);
|
||||
},0)
|
||||
}
|
||||
}, [isReset])
|
||||
|
||||
// Button functions
|
||||
const handleReset = () => {
|
||||
setReset(!isReset);
|
||||
setReset(true);
|
||||
setSpeed(1);
|
||||
};
|
||||
const handlePlayStop = () => {
|
||||
|
@ -271,8 +279,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
</div>
|
||||
{index < intervals.length - 1 && (
|
||||
<div
|
||||
className={`line ${
|
||||
progress >= ((index + 1) / totalSegments) * 100
|
||||
className={`line ${progress >= ((index + 1) / totalSegments) * 100
|
||||
? "filled"
|
||||
: ""
|
||||
}`}
|
||||
|
@ -314,8 +321,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
<div className="custom-slider-wrapper">
|
||||
<div className="custom-slider">
|
||||
<button
|
||||
className={`slider-handle ${
|
||||
isDragging ? "dragging" : ""
|
||||
className={`slider-handle ${isDragging ? "dragging" : ""
|
||||
}`}
|
||||
style={{ left: `${calculateHandlePosition()}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
|
|
|
@ -28,6 +28,4 @@ export default async function setCameraView({
|
|||
controls?.setLookAt(...newPosition.toArray(), newPosition.x, 0, newPosition.z, true);
|
||||
}
|
||||
|
||||
// Optionally you can log
|
||||
console.log(`Camera view updated by ${username ?? 'unknown user'}`);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useSelectedProduct } from "../../../../../store/simulation/useSimulatio
|
|||
|
||||
export function useSpawnHandler() {
|
||||
const { addMaterial } = useMaterialStore();
|
||||
const { getModelUuidByActionUuid } = useProductStore();
|
||||
const { getModelUuidByActionUuid, getPointUuidByActionUuid } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const lastSpawnTime = useRef<number | null>(null);
|
||||
const startTime = useRef<number | null>(null);
|
||||
|
@ -16,7 +16,7 @@ export function useSpawnHandler() {
|
|||
material: string;
|
||||
intervalMs: number;
|
||||
totalCount: number;
|
||||
point: ConveyorPointSchema;
|
||||
action: ConveyorAction;
|
||||
} | null>(null);
|
||||
|
||||
const clearCurrentSpawn = useCallback(() => {
|
||||
|
@ -30,9 +30,10 @@ export function useSpawnHandler() {
|
|||
// console.log(`${materialUuid}, ${status}`);
|
||||
}
|
||||
|
||||
const createNewMaterial = useCallback((materialType: string, point: ConveyorPointSchema) => {
|
||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, point.action.actionUuid);
|
||||
if (!modelUuid || !point.uuid) return;
|
||||
const createNewMaterial = useCallback((materialType: string, action: ConveyorAction) => {
|
||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
||||
const pointUuid = getPointUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
||||
if (!modelUuid || !pointUuid) return;
|
||||
|
||||
const newMaterial: MaterialSchema = {
|
||||
materialId: THREE.MathUtils.generateUUID(),
|
||||
|
@ -43,13 +44,24 @@ export function useSpawnHandler() {
|
|||
isRendered: true,
|
||||
current: {
|
||||
modelUuid: modelUuid,
|
||||
pointUuid: point.uuid,
|
||||
actionUuid: point.action?.actionUuid || ''
|
||||
pointUuid: pointUuid,
|
||||
actionUuid: action?.actionUuid || ''
|
||||
},
|
||||
weight: 1,
|
||||
cost: 1
|
||||
};
|
||||
|
||||
if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid &&
|
||||
action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid &&
|
||||
action.triggers[0].triggeredAsset?.triggeredAction?.actionUuid
|
||||
) {
|
||||
newMaterial.next = {
|
||||
modelUuid: action.triggers[0].triggeredAsset?.triggeredModel.modelUuid,
|
||||
pointUuid: action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid,
|
||||
actionUuid: action.triggers[0].triggeredAsset?.triggeredAction?.actionUuid
|
||||
}
|
||||
}
|
||||
|
||||
addMaterial(newMaterial);
|
||||
return newMaterial;
|
||||
}, [addMaterial]);
|
||||
|
@ -58,14 +70,14 @@ export function useSpawnHandler() {
|
|||
if (!spawnParams.current || !startTime.current) return;
|
||||
|
||||
const currentTime = performance.now();
|
||||
const { material, intervalMs, totalCount, point } = spawnParams.current;
|
||||
const { material, intervalMs, totalCount, action } = spawnParams.current;
|
||||
const isFirstSpawn = lastSpawnTime.current === null;
|
||||
const elapsed = currentTime - startTime.current;
|
||||
|
||||
// First spawn
|
||||
if (isFirstSpawn) {
|
||||
if (elapsed >= intervalMs) {
|
||||
const createdMaterial = createNewMaterial(material, point);
|
||||
const createdMaterial = createNewMaterial(material, action);
|
||||
if (createdMaterial) {
|
||||
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
|
||||
}
|
||||
|
@ -84,9 +96,9 @@ export function useSpawnHandler() {
|
|||
const timeSinceLast = currentTime - lastSpawnTime.current;
|
||||
if (timeSinceLast >= intervalMs) {
|
||||
const count = spawnCountRef.current + 1;
|
||||
const createdMaterial = createNewMaterial(material, point);
|
||||
const createdMaterial = createNewMaterial(material, action);
|
||||
if (createdMaterial) {
|
||||
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
|
||||
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (${count}/${totalCount})`);
|
||||
}
|
||||
lastSpawnTime.current = currentTime;
|
||||
spawnCountRef.current = count;
|
||||
|
@ -98,10 +110,10 @@ export function useSpawnHandler() {
|
|||
}
|
||||
});
|
||||
|
||||
const handleSpawn = useCallback((point: ConveyorPointSchema) => {
|
||||
if (!point.action || point.action.actionType !== 'spawn') return;
|
||||
const handleSpawn = useCallback((action: ConveyorAction) => {
|
||||
if (!action || action.actionType !== 'spawn') return;
|
||||
|
||||
const { material, spawnInterval = 0, spawnCount = 1 } = point.action;
|
||||
const { material, spawnInterval = 0, spawnCount = 1 } = action;
|
||||
const intervalMs = spawnInterval * 1000;
|
||||
|
||||
clearCurrentSpawn();
|
||||
|
@ -110,7 +122,7 @@ export function useSpawnHandler() {
|
|||
material,
|
||||
intervalMs,
|
||||
totalCount: spawnCount,
|
||||
point: point
|
||||
action: action
|
||||
};
|
||||
|
||||
startTime.current = performance.now();
|
|
@ -1,40 +1,66 @@
|
|||
import { useEffect } from "react";
|
||||
import { useSpawnHandler } from "./actionHandler/spawnActionHandler";
|
||||
import { useEffect, useCallback, useRef } from "react";
|
||||
import { useSpawnHandler } from "./actionHandler/useSpawnHandler";
|
||||
|
||||
// Conveyor Actions
|
||||
export function useConveyorActions(point: ConveyorPointSchema) {
|
||||
const { handleSpawn } = useSpawnHandler();
|
||||
export function useConveyorActions() {
|
||||
const { handleSpawn, clearCurrentSpawn } = useSpawnHandler();
|
||||
|
||||
useEffect(() => {
|
||||
if (!point.action) return;
|
||||
const handleDefaultAction = useCallback((action: ConveyorAction) => {
|
||||
// console.log(`Default conveyor action ${action.actionUuid}`);
|
||||
}, []);
|
||||
|
||||
const { actionType, material, delay, spawnInterval, spawnCount } = point.action;
|
||||
const handleSpawnAction = useCallback((action: ConveyorAction) => {
|
||||
handleSpawn(action);
|
||||
}, [handleSpawn]);
|
||||
|
||||
const handleAction = () => {
|
||||
switch (actionType) {
|
||||
const handleSwapAction = useCallback((action: ConveyorAction) => {
|
||||
console.log(`Swapping to material ${action.material}`);
|
||||
}, []);
|
||||
|
||||
const handleDelayAction = useCallback((action: ConveyorAction) => {
|
||||
const delayMs = (action.delay || 0) * 1000;
|
||||
console.log(`Delaying for ${delayMs}ms`);
|
||||
}, []);
|
||||
|
||||
const handleDespawnAction = useCallback((action: ConveyorAction) => {
|
||||
console.log(`Despawning material`);
|
||||
}, []);
|
||||
|
||||
const handleConveyorAction = useCallback((action: ConveyorAction) => {
|
||||
if (!action) return;
|
||||
|
||||
switch (action.actionType) {
|
||||
case 'default':
|
||||
console.log(`Default conveyor action at point ${point.uuid}`);
|
||||
handleDefaultAction(action);
|
||||
break;
|
||||
case 'spawn':
|
||||
console.log(`Spawning material ${material} at point ${point.uuid}`);
|
||||
handleSpawn(point);
|
||||
handleSpawnAction(action);
|
||||
break;
|
||||
case 'swap':
|
||||
console.log(`Swapping to material ${material} at point ${point.uuid}`);
|
||||
handleSwapAction(action);
|
||||
break;
|
||||
case 'delay':
|
||||
console.log(`Delaying for ${delay}ms at point ${point.uuid}`);
|
||||
handleDelayAction(action);
|
||||
break;
|
||||
case 'despawn':
|
||||
console.log(`Despawning material at point ${point.uuid}`);
|
||||
handleDespawnAction(action);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown conveyor action type: ${action.actionType}`);
|
||||
}
|
||||
};
|
||||
}, [handleDefaultAction, handleSpawnAction, handleSwapAction, handleDelayAction, handleDespawnAction]);
|
||||
|
||||
handleAction();
|
||||
const cleanup = useCallback(() => {
|
||||
clearCurrentSpawn();
|
||||
}, [clearCurrentSpawn]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Cleanup if needed
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleConveyorAction,
|
||||
cleanup
|
||||
};
|
||||
}, [point]);
|
||||
}
|
|
@ -1,23 +1,35 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useCallback, useRef, useState } from 'react';
|
||||
|
||||
export function useMachineActions() {
|
||||
|
||||
const handleProcessAction = useCallback((action: MachineAction) => {
|
||||
if (!action || action.actionType !== 'process') return;
|
||||
console.log(`Machine processing for ${action.processTime}ms`);
|
||||
}, []);
|
||||
|
||||
const handleMachineAction = useCallback((action: MachineAction) => {
|
||||
if (!action) return;
|
||||
|
||||
switch (action.actionType) {
|
||||
case 'process':
|
||||
handleProcessAction(action);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown machine action type: ${action.actionType}`);
|
||||
}
|
||||
}, [handleProcessAction]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
}, []);
|
||||
|
||||
// Machine Actions
|
||||
export function useMachineActions(point: MachinePointSchema) {
|
||||
useEffect(() => {
|
||||
if (!point.action) return;
|
||||
|
||||
const { actionType, processTime, swapMaterial } = point.action;
|
||||
|
||||
const handleAction = () => {
|
||||
if (actionType === 'process') {
|
||||
console.log(`Machine processing for ${processTime}ms at point ${point.uuid}`);
|
||||
if (swapMaterial) {
|
||||
console.log(`Swapping material to ${swapMaterial}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
// Cleanup if needed
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleMachineAction,
|
||||
cleanup,
|
||||
};
|
||||
}, [point]);
|
||||
}
|
|
@ -1,26 +1,32 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
|
||||
export function useRoboticArmActions() {
|
||||
|
||||
const handlePickAndPlace = useCallback((action: RoboticArmAction) => {
|
||||
console.log(`Robotic arm pick and place`);
|
||||
}, []);
|
||||
|
||||
const handleRoboticArmAction = useCallback((action: RoboticArmAction) => {
|
||||
switch (action.actionType) {
|
||||
case 'pickAndPlace':
|
||||
handlePickAndPlace(action);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown robotic arm action type: ${action.actionType}`);
|
||||
}
|
||||
}, [handlePickAndPlace]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
}, []);
|
||||
|
||||
// Robotic Arm Actions
|
||||
export function useRoboticArmActions(point: RoboticArmPointSchema) {
|
||||
useEffect(() => {
|
||||
point.actions.forEach(action => {
|
||||
const { actionType, process } = action;
|
||||
|
||||
const handleAction = () => {
|
||||
if (actionType === 'pickAndPlace') {
|
||||
console.log(`Robotic arm pick and place at point ${point.uuid}`);
|
||||
if (process.startPoint) {
|
||||
console.log(`Start point: ${process.startPoint}`);
|
||||
}
|
||||
if (process.endPoint) {
|
||||
console.log(`End point: ${process.endPoint}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return () => {
|
||||
// Cleanup if needed
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleRoboticArmAction,
|
||||
cleanup
|
||||
};
|
||||
}, [point]);
|
||||
}
|
|
@ -1,22 +1,33 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useCallback, useRef, useState } from 'react';
|
||||
|
||||
// Storage Actions
|
||||
export function useStorageActions(point: StoragePointSchema) {
|
||||
useEffect(() => {
|
||||
if (!point.action) return;
|
||||
export function useStorageActions() {
|
||||
const handleStoreAction = useCallback((action: StorageAction) => {
|
||||
if (!action || action.actionType !== 'store') return;
|
||||
}, []);
|
||||
|
||||
const { actionType, materials, storageCapacity } = point.action;
|
||||
const handleStorageAction = useCallback((action: StorageAction) => {
|
||||
if (!action) return;
|
||||
|
||||
const handleAction = () => {
|
||||
if (actionType === 'store') {
|
||||
console.log(`Storage action at point ${point.uuid}`);
|
||||
console.log(`Materials: ${materials.map(m => m.materialName).join(', ')}`);
|
||||
console.log(`Capacity: ${storageCapacity}`);
|
||||
switch (action.actionType) {
|
||||
case 'store':
|
||||
handleStoreAction(action);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown storage action type: ${action.actionType}`);
|
||||
}
|
||||
};
|
||||
}, [handleStoreAction]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Cleanup if needed
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleStorageAction,
|
||||
cleanup
|
||||
};
|
||||
}, [point]);
|
||||
}
|
|
@ -3,23 +3,59 @@ import { useMachineActions } from "./machine/useMachineActions";
|
|||
import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
|
||||
import { useStorageActions } from "./storageUnit/useStorageUnitActions";
|
||||
import { useVehicleActions } from "./vehicle/useVehicleActions";
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
// Master hook that selects the appropriate action handler
|
||||
export function useActionHandler(point: PointsScheme) {
|
||||
if ('actions' in point) {
|
||||
// Robotic Arm
|
||||
useRoboticArmActions(point);
|
||||
} else if (point.action.actionType === 'travel') {
|
||||
// Vehicle
|
||||
useVehicleActions(point as VehiclePointSchema);
|
||||
} else if (point.action.actionType === 'process') {
|
||||
// Machine
|
||||
useMachineActions(point as MachinePointSchema);
|
||||
} else if (point.action.actionType === 'store') {
|
||||
// Storage
|
||||
useStorageActions(point as StoragePointSchema);
|
||||
} else {
|
||||
// Conveyor
|
||||
useConveyorActions(point as ConveyorPointSchema);
|
||||
export function useActionHandler() {
|
||||
const { handleConveyorAction, cleanup: cleanupConveyor } = useConveyorActions();
|
||||
const { handleVehicleAction, cleanup: cleanupVehicle } = useVehicleActions();
|
||||
const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions();
|
||||
const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions();
|
||||
const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions();
|
||||
|
||||
const handleAction = useCallback((action: Action) => {
|
||||
if (!action) return;
|
||||
|
||||
try {
|
||||
switch (action.actionType) {
|
||||
case 'default': case 'spawn': case 'swap': case 'delay': case 'despawn':
|
||||
handleConveyorAction(action as ConveyorAction);
|
||||
break;
|
||||
case 'travel':
|
||||
handleVehicleAction(action as VehicleAction);
|
||||
break;
|
||||
case 'pickAndPlace':
|
||||
handleRoboticArmAction(action as RoboticArmAction);
|
||||
break;
|
||||
case 'process':
|
||||
handleMachineAction(action as MachineAction);
|
||||
break;
|
||||
case 'store':
|
||||
handleStorageAction(action as StorageAction);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown action type: ${(action as Action).actionType}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling action:', error);
|
||||
}
|
||||
}, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
cleanupConveyor();
|
||||
cleanupVehicle();
|
||||
cleanupRoboticArm();
|
||||
cleanupMachine();
|
||||
cleanupStorage();
|
||||
}, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleAction,
|
||||
cleanup
|
||||
};
|
||||
}
|
|
@ -1,26 +1,37 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
|
||||
export function useVehicleActions() {
|
||||
|
||||
const handleTravelAction = useCallback((action: VehicleAction) => {
|
||||
if (!action || action.actionType !== 'travel') return;
|
||||
|
||||
console.log(`Vehicle travel action ${action.actionUuid}`);
|
||||
|
||||
}, []);
|
||||
|
||||
const handleVehicleAction = useCallback((action: VehicleAction) => {
|
||||
if (!action) return;
|
||||
|
||||
switch (action.actionType) {
|
||||
case 'travel':
|
||||
handleTravelAction(action);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown vehicle action type: ${action.actionType}`);
|
||||
}
|
||||
}, [handleTravelAction]);
|
||||
|
||||
const cleanup = useCallback(() => {
|
||||
}, []);
|
||||
|
||||
// Vehicle Actions
|
||||
export function useVehicleActions(point: VehiclePointSchema) {
|
||||
useEffect(() => {
|
||||
if (!point.action) return;
|
||||
|
||||
const { actionType, unLoadDuration, loadCapacity, steeringAngle } = point.action;
|
||||
|
||||
const handleAction = () => {
|
||||
if (actionType === 'travel') {
|
||||
console.log(`Vehicle travel action at point ${point.uuid}`);
|
||||
if (point.action.pickUpPoint) {
|
||||
console.log(`Pick up at: ${JSON.stringify(point.action.pickUpPoint)}`);
|
||||
}
|
||||
if (point.action.unLoadPoint) {
|
||||
console.log(`Unload at: ${JSON.stringify(point.action.unLoadPoint)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return () => {
|
||||
// Cleanup if needed
|
||||
cleanup();
|
||||
};
|
||||
}, [cleanup]);
|
||||
|
||||
return {
|
||||
handleVehicleAction,
|
||||
cleanup
|
||||
};
|
||||
}, [point]);
|
||||
}
|
|
@ -1,9 +1,74 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
|
||||
function MaterialAnimator() {
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
interface MaterialAnimatorProps {
|
||||
matRef: React.RefObject<THREE.Mesh>;
|
||||
material: MaterialSchema;
|
||||
speed: number; // units per second
|
||||
onAnimationComplete?: () => void;
|
||||
}
|
||||
|
||||
export default MaterialAnimator
|
||||
function MaterialAnimator({
|
||||
matRef,
|
||||
material,
|
||||
speed,
|
||||
onAnimationComplete
|
||||
}: MaterialAnimatorProps) {
|
||||
const { scene } = useThree();
|
||||
const [targetPosition, setTargetPosition] = useState<THREE.Vector3 | null>(null);
|
||||
const [isAnimating, setIsAnimating] = useState(false);
|
||||
const animationStartTime = useRef<number>(0);
|
||||
const startPosition = useRef<THREE.Vector3>(new THREE.Vector3());
|
||||
const totalDistance = useRef<number>(0);
|
||||
|
||||
const getWorldPosition = (uuid: string): THREE.Vector3 | null => {
|
||||
const obj = scene.getObjectByProperty('uuid', uuid);
|
||||
if (!obj) return null;
|
||||
const position = new THREE.Vector3();
|
||||
obj.getWorldPosition(position);
|
||||
return position;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!material.next?.pointUuid) {
|
||||
setTargetPosition(null);
|
||||
setIsAnimating(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newTarget = getWorldPosition(material.next.pointUuid);
|
||||
if (newTarget && matRef.current) {
|
||||
startPosition.current.copy(matRef.current.position);
|
||||
totalDistance.current = startPosition.current.distanceTo(newTarget);
|
||||
animationStartTime.current = performance.now();
|
||||
setTargetPosition(newTarget);
|
||||
setIsAnimating(true);
|
||||
}
|
||||
}, [material.next?.pointUuid]);
|
||||
|
||||
useFrame(() => {
|
||||
if (!matRef.current || !targetPosition || !isAnimating) return;
|
||||
|
||||
// Calculate exact position based on constant speed
|
||||
const elapsed = (performance.now() - animationStartTime.current) / 1000;
|
||||
const progress = Math.min(1, (speed * elapsed) / totalDistance.current);
|
||||
|
||||
matRef.current.position.lerpVectors(
|
||||
startPosition.current,
|
||||
targetPosition,
|
||||
progress
|
||||
);
|
||||
|
||||
// Check if animation is complete
|
||||
if (progress >= 1) {
|
||||
matRef.current.position.copy(targetPosition);
|
||||
setIsAnimating(false);
|
||||
onAnimationComplete?.();
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default React.memo(MaterialAnimator);
|
|
@ -1,10 +1,82 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import * as THREE from 'three';
|
||||
import MaterialAnimator from '../animator/materialAnimator'
|
||||
import MaterialAnimator from '../animator/materialAnimator';
|
||||
import { useProductStore } from '../../../../../store/simulation/useProductStore';
|
||||
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
|
||||
import { MaterialModel } from '../material/materialModel';
|
||||
import { useThree } from '@react-three/fiber';
|
||||
|
||||
function MaterialInstance({ material }: { material: MaterialSchema }) {
|
||||
const [position, setPosition] = useState<THREE.Vector3>();
|
||||
const [rotation, setRotation] = useState<THREE.Vector3>();
|
||||
const matRef: any = useRef();
|
||||
const { scene } = useThree();
|
||||
const { getModelUuidByPointUuid, getPointByUuid, getEventByModelUuid } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
|
||||
const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => {
|
||||
const pointObj = scene.getObjectByProperty("uuid", pointUuid);
|
||||
if (!pointObj) return null;
|
||||
|
||||
const worldPosition = new THREE.Vector3();
|
||||
pointObj.getWorldPosition(worldPosition);
|
||||
return worldPosition;
|
||||
};
|
||||
|
||||
const { position, rotation, speed } = useMemo(() => {
|
||||
if (!material.current?.pointUuid) {
|
||||
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
|
||||
}
|
||||
|
||||
const modelUuid = getModelUuidByPointUuid(selectedProduct.productId, material.current.pointUuid);
|
||||
if (!modelUuid) {
|
||||
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
|
||||
}
|
||||
|
||||
const speed = getCurrentSpeed(selectedProduct.productId, modelUuid);
|
||||
|
||||
const point = getPointByUuid(selectedProduct.productId, modelUuid, material.current.pointUuid);
|
||||
if (!point) {
|
||||
return { position: new THREE.Vector3(0, 0, 0), rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
|
||||
}
|
||||
|
||||
const position = getWorldPositionFromScene(point.uuid);
|
||||
if (position) {
|
||||
return { position: position, rotation: new THREE.Vector3(0, 0, 0), speed: 1 };
|
||||
}
|
||||
|
||||
return {
|
||||
position: new THREE.Vector3(...point.position),
|
||||
rotation: new THREE.Vector3(...point.rotation),
|
||||
speed: speed || 1
|
||||
};
|
||||
}, [material, getPointByUuid]);
|
||||
|
||||
function getCurrentSpeed(productId: string, modelUuid: string) {
|
||||
const event = getEventByModelUuid(productId, modelUuid)
|
||||
if (event) {
|
||||
if (event.type === 'transfer') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
if (event.type === 'vehicle') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
if (event.type === 'machine') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (event.type === 'roboticArm') {
|
||||
return event.speed;
|
||||
}
|
||||
|
||||
if (event.type === 'storageUnit') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('material: ', material);
|
||||
|
@ -13,8 +85,14 @@ function MaterialInstance({ material }: { material: MaterialSchema }) {
|
|||
return (
|
||||
<>
|
||||
|
||||
<MaterialAnimator />
|
||||
<MaterialModel matRef={matRef} materialType={material.materialType} position={position} />
|
||||
|
||||
<MaterialAnimator
|
||||
matRef={matRef}
|
||||
material={material}
|
||||
speed={speed}
|
||||
onAnimationComplete={() => { console.log('123');}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import { useGLTF } from '@react-three/drei'
|
||||
import { useMemo } from 'react';
|
||||
import * as THREE from 'three';
|
||||
|
||||
import defaultMaterial from '../../../../../assets/gltf-glb/default.glb';
|
||||
import material1 from '../../../../../assets/gltf-glb/material1.glb';
|
||||
import material2 from '../../../../../assets/gltf-glb/material2.glb';
|
||||
import material3 from '../../../../../assets/gltf-glb/material3.glb';
|
||||
|
||||
const modelPaths: Record<string, string> = {
|
||||
'Default material': defaultMaterial,
|
||||
'Material 1': material1,
|
||||
'Material 2': material2,
|
||||
'Material 3': material3,
|
||||
};
|
||||
|
||||
type ModelType = keyof typeof modelPaths;
|
||||
|
||||
interface ModelProps extends React.ComponentProps<'group'> {
|
||||
materialType: ModelType;
|
||||
matRef: React.Ref<THREE.Group<THREE.Object3DEventMap>>
|
||||
}
|
||||
|
||||
export function MaterialModel({ materialType, matRef, ...props }: ModelProps) {
|
||||
const path = modelPaths[materialType] || modelPaths['Default material'];
|
||||
const gltf = useGLTF(path);
|
||||
const cloned = useMemo(() => gltf?.scene.clone(), [gltf]);
|
||||
|
||||
if (!cloned) return null;
|
||||
|
||||
return (
|
||||
<group ref={matRef} {...props} dispose={null}>
|
||||
<primitive
|
||||
object={cloned}
|
||||
scale={[0.25, 0.25, 0.25]}
|
||||
/>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
Object.values(modelPaths).forEach((path) => {
|
||||
useGLTF.preload(path);
|
||||
});
|
|
@ -44,8 +44,9 @@ function RoboticArmAnimator({
|
|||
setCirclePoints(points);
|
||||
}, [armBot.position]);
|
||||
|
||||
//Handle Reset Animation
|
||||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
if (isReset ) {
|
||||
progressRef.current = 0;
|
||||
curveRef.current = null;
|
||||
setCurrentPath([]);
|
||||
|
@ -53,11 +54,15 @@ function RoboticArmAnimator({
|
|||
totalDistanceRef.current = 0;
|
||||
startTimeRef.current = null;
|
||||
segmentDistancesRef.current = [];
|
||||
setReset(false);
|
||||
if (!ikSolver) return
|
||||
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||
if (!bone) return;
|
||||
bone.position.copy(restPosition)
|
||||
ikSolver.update();
|
||||
}
|
||||
}, [isReset, isPlaying])
|
||||
|
||||
|
||||
//Generate Circle Points
|
||||
function generateRingPoints(radius: any, segments: any) {
|
||||
const points: [number, number, number][] = [];
|
||||
for (let i = 0; i < segments; i++) {
|
||||
|
@ -70,7 +75,7 @@ function RoboticArmAnimator({
|
|||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
// Function for find nearest Circlepoints Index
|
||||
const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const [x, y, z] = points[i];
|
||||
|
@ -85,6 +90,15 @@ function RoboticArmAnimator({
|
|||
return -1; // Not found
|
||||
};
|
||||
|
||||
//function to find nearest Circlepoints
|
||||
const findNearest = (target: [number, number, number]) => {
|
||||
return circlePoints.reduce((nearest, point) => {
|
||||
const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
|
||||
const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
|
||||
return distance < nearestDistance ? point : nearest;
|
||||
}, circlePoints[0]);
|
||||
};
|
||||
|
||||
// Handle nearest points and final path (including arc points)
|
||||
useEffect(() => {
|
||||
if (circlePoints.length > 0 && currentPath.length > 0) {
|
||||
|
@ -95,13 +109,13 @@ function RoboticArmAnimator({
|
|||
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
|
||||
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
|
||||
|
||||
const findNearest = (target: [number, number, number]) => {
|
||||
return circlePoints.reduce((nearest, point) => {
|
||||
const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
|
||||
const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
|
||||
return distance < nearestDistance ? point : nearest;
|
||||
}, circlePoints[0]);
|
||||
};
|
||||
// const findNearest = (target: [number, number, number]) => {
|
||||
// return circlePoints.reduce((nearest, point) => {
|
||||
// const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
|
||||
// const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
|
||||
// return distance < nearestDistance ? point : nearest;
|
||||
// }, circlePoints[0]);
|
||||
// };
|
||||
|
||||
const nearestToStart = findNearest(raisedStart);
|
||||
const nearestToEnd = findNearest(raisedEnd);
|
||||
|
@ -176,8 +190,13 @@ function RoboticArmAnimator({
|
|||
if (!bone) return;
|
||||
|
||||
if (isPlaying) {
|
||||
if (isReset) {
|
||||
bone.position.copy(restPosition);
|
||||
setCustomCurvePoints([]);
|
||||
ikSolver.update();
|
||||
}
|
||||
if (!isPaused && customCurvePoints && customCurvePoints.length > 0) {
|
||||
const distances = segmentDistancesRef.current; // distances between each pair of points
|
||||
const distances = segmentDistancesRef.current;
|
||||
const totalDistance = totalDistanceRef.current;
|
||||
|
||||
progressRef.current += delta * (speed * armBot.speed);
|
||||
|
@ -186,11 +205,11 @@ function RoboticArmAnimator({
|
|||
let index = 0;
|
||||
let accumulatedDistance = 0;
|
||||
|
||||
// Find which segment we are currently in
|
||||
while (index < distances.length && coveredDistance > accumulatedDistance + distances[index]) {
|
||||
accumulatedDistance += distances[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < distances.length) {
|
||||
const startPoint = customCurvePoints[index];
|
||||
const endPoint = customCurvePoints[index + 1];
|
||||
|
@ -201,6 +220,7 @@ function RoboticArmAnimator({
|
|||
bone.position.copy(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
HandleCallback();
|
||||
setCurrentPath([]);
|
||||
|
@ -212,13 +232,13 @@ function RoboticArmAnimator({
|
|||
|
||||
ikSolver.update();
|
||||
}
|
||||
} else if ((!isPlaying && currentPath.length === 0) || isReset) {
|
||||
} else if (!isPlaying && currentPath.length === 0) {
|
||||
bone.position.copy(restPosition);
|
||||
ikSolver.update();
|
||||
}
|
||||
|
||||
ikSolver.update();
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
|
||||
|
|
|
@ -97,25 +97,25 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
logStatus(armBot.modelUuid, "Simulation Play Reset Successfully")
|
||||
removeCurrentAction(armBot.modelUuid)
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("init");
|
||||
setPath([])
|
||||
removeCurrentAction(armBot.modelUuid)
|
||||
isPausedRef.current = false
|
||||
pauseTimeRef.current = null
|
||||
startTime = 0
|
||||
const targetBones = ikSolver?.mesh.skeleton.bones.find(
|
||||
(b: any) => b.name === targetBone
|
||||
const targetBones = ikSolver?.mesh.skeleton.bones.find((b: any) => b.name === targetBone
|
||||
);
|
||||
if (targetBones) {
|
||||
if (targetBones && isPlaying) {
|
||||
let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition)
|
||||
if (curve) {
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}
|
||||
}
|
||||
setReset(false);
|
||||
logStatus(armBot.modelUuid, "Moving armBot from initial point to rest position.")
|
||||
}
|
||||
}
|
||||
// setReset(false);
|
||||
}
|
||||
}, [isReset, isPlaying])
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -127,7 +127,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
(b: any) => b.name === targetBone
|
||||
);
|
||||
if (isPlaying) {
|
||||
|
||||
//Moving armBot from initial point to rest position.
|
||||
if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") {
|
||||
|
||||
|
@ -204,16 +203,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
// logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
|
||||
}
|
||||
} else {
|
||||
logStatus(armBot.modelUuid, "Simulation Play Exited")
|
||||
setArmBotActive(armBot.modelUuid, false)
|
||||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("init");
|
||||
setPath([])
|
||||
isPausedRef.current = false
|
||||
pauseTimeRef.current = null
|
||||
isPausedRef.current = false
|
||||
startTime = 0
|
||||
removeCurrentAction(armBot.modelUuid)
|
||||
// logStatus(armBot.modelUuid, "Simulation Play Exited")
|
||||
// setArmBotActive(armBot.modelUuid, false)
|
||||
// setArmBotState(armBot.modelUuid, "idle")
|
||||
// setCurrentPhase("init");
|
||||
// setPath([])
|
||||
// isPausedRef.current = false
|
||||
// pauseTimeRef.current = null
|
||||
// isPausedRef.current = false
|
||||
// startTime = 0
|
||||
// removeCurrentAction(armBot.modelUuid)
|
||||
}
|
||||
|
||||
}, [currentPhase, armBot, isPlaying, ikSolver])
|
||||
|
@ -260,6 +259,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
}
|
||||
const logStatus = (id: string, status: string) => {
|
||||
|
||||
|
||||
//
|
||||
|
||||
}
|
||||
|
|
|
@ -1,114 +1,21 @@
|
|||
import { useEffect } from 'react';
|
||||
import { useProductStore } from '../../../store/simulation/useProductStore';
|
||||
import { useActionHandler } from '../actions/useActionHandler';
|
||||
|
||||
function Simulator() {
|
||||
const { products } = useProductStore();
|
||||
const { handleAction } = useActionHandler();
|
||||
|
||||
useEffect(() => {
|
||||
const executionOrder = determineExecutionOrder(products);
|
||||
|
||||
executionOrder.forEach(point => {
|
||||
useActionHandler(point);
|
||||
});
|
||||
|
||||
function determineExecutionSequences(products: productsSchema): PointsScheme[][] {
|
||||
// Create maps for all points
|
||||
const pointMap = new Map<string, PointsScheme>();
|
||||
const allPoints: PointsScheme[] = [];
|
||||
|
||||
// First pass: collect all points
|
||||
products.forEach(product => {
|
||||
product.eventDatas.forEach(event => {
|
||||
if (event.type === 'transfer') {
|
||||
event.points.forEach(point => {
|
||||
pointMap.set(point.uuid, point);
|
||||
allPoints.push(point);
|
||||
});
|
||||
} else if (event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm') {
|
||||
pointMap.set(event.point.uuid, event.point);
|
||||
allPoints.push(event.point);
|
||||
if ('actions' in point) {
|
||||
handleAction(point.actions[0]);
|
||||
} else {
|
||||
handleAction(point.action);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Build complete dependency graph
|
||||
const dependencyGraph = new Map<string, string[]>();
|
||||
const reverseDependencyGraph = new Map<string, string[]>();
|
||||
const triggeredPoints = new Set<string>();
|
||||
|
||||
allPoints.forEach(point => {
|
||||
const triggers = extractTriggersFromPoint(point);
|
||||
const dependencies: string[] = [];
|
||||
|
||||
triggers.forEach(trigger => {
|
||||
const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
|
||||
if (targetUuid && pointMap.has(targetUuid)) {
|
||||
dependencies.push(targetUuid);
|
||||
triggeredPoints.add(targetUuid);
|
||||
|
||||
if (!reverseDependencyGraph.has(targetUuid)) {
|
||||
reverseDependencyGraph.set(targetUuid, []);
|
||||
}
|
||||
reverseDependencyGraph.get(targetUuid)!.push(point.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
dependencyGraph.set(point.uuid, dependencies);
|
||||
});
|
||||
|
||||
// Identify independent root points (points that trigger others but aren't triggered themselves)
|
||||
const rootPoints = allPoints.filter(point => {
|
||||
const hasOutgoingTriggers = extractTriggersFromPoint(point).some(
|
||||
t => t.triggeredAsset?.triggeredPoint?.pointUuid
|
||||
);
|
||||
return hasOutgoingTriggers && !triggeredPoints.has(point.uuid);
|
||||
});
|
||||
|
||||
// For each root point, build its complete trigger chain
|
||||
const executionSequences: PointsScheme[][] = [];
|
||||
|
||||
function buildSequence(startUuid: string): PointsScheme[] {
|
||||
const sequence: PointsScheme[] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
function traverse(uuid: string) {
|
||||
if (visited.has(uuid)) return;
|
||||
visited.add(uuid);
|
||||
|
||||
const point = pointMap.get(uuid);
|
||||
if (point) {
|
||||
sequence.push(point);
|
||||
}
|
||||
|
||||
// Follow forward dependencies
|
||||
const nextPoints = dependencyGraph.get(uuid) || [];
|
||||
nextPoints.forEach(nextUuid => traverse(nextUuid));
|
||||
}
|
||||
|
||||
traverse(startUuid);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Build sequences for all root points
|
||||
rootPoints.forEach(root => {
|
||||
executionSequences.push(buildSequence(root.uuid));
|
||||
});
|
||||
|
||||
// Handle any triggered points not reachable from roots (isolated chains)
|
||||
const processedPoints = new Set(
|
||||
executionSequences.flat().map(p => p.uuid)
|
||||
);
|
||||
|
||||
allPoints.forEach(point => {
|
||||
if (triggeredPoints.has(point.uuid) && !processedPoints.has(point.uuid)) {
|
||||
executionSequences.push(buildSequence(point.uuid));
|
||||
}
|
||||
});
|
||||
|
||||
return executionSequences;
|
||||
}
|
||||
}, [products, handleAction]);
|
||||
|
||||
function determineExecutionOrder(products: productsSchema): PointsScheme[] {
|
||||
// Create maps for all events and points
|
||||
|
@ -223,6 +130,106 @@ function Simulator() {
|
|||
return triggeredPoints;
|
||||
}
|
||||
|
||||
function determineExecutionSequences(products: productsSchema): PointsScheme[][] {
|
||||
// Create maps for all points
|
||||
const pointMap = new Map<string, PointsScheme>();
|
||||
const allPoints: PointsScheme[] = [];
|
||||
|
||||
// First pass: collect all points
|
||||
products.forEach(product => {
|
||||
product.eventDatas.forEach(event => {
|
||||
if (event.type === 'transfer') {
|
||||
event.points.forEach(point => {
|
||||
pointMap.set(point.uuid, point);
|
||||
allPoints.push(point);
|
||||
});
|
||||
} else if (event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm') {
|
||||
pointMap.set(event.point.uuid, event.point);
|
||||
allPoints.push(event.point);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Build complete dependency graph
|
||||
const dependencyGraph = new Map<string, string[]>();
|
||||
const reverseDependencyGraph = new Map<string, string[]>();
|
||||
const triggeredPoints = new Set<string>();
|
||||
|
||||
allPoints.forEach(point => {
|
||||
const triggers = extractTriggersFromPoint(point);
|
||||
const dependencies: string[] = [];
|
||||
|
||||
triggers.forEach(trigger => {
|
||||
const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
|
||||
if (targetUuid && pointMap.has(targetUuid)) {
|
||||
dependencies.push(targetUuid);
|
||||
triggeredPoints.add(targetUuid);
|
||||
|
||||
if (!reverseDependencyGraph.has(targetUuid)) {
|
||||
reverseDependencyGraph.set(targetUuid, []);
|
||||
}
|
||||
reverseDependencyGraph.get(targetUuid)!.push(point.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
dependencyGraph.set(point.uuid, dependencies);
|
||||
});
|
||||
|
||||
// Identify independent root points (points that trigger others but aren't triggered themselves)
|
||||
const rootPoints = allPoints.filter(point => {
|
||||
const hasOutgoingTriggers = extractTriggersFromPoint(point).some(
|
||||
t => t.triggeredAsset?.triggeredPoint?.pointUuid
|
||||
);
|
||||
return hasOutgoingTriggers && !triggeredPoints.has(point.uuid);
|
||||
});
|
||||
|
||||
// For each root point, build its complete trigger chain
|
||||
const executionSequences: PointsScheme[][] = [];
|
||||
|
||||
function buildSequence(startUuid: string): PointsScheme[] {
|
||||
const sequence: PointsScheme[] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
function traverse(uuid: string) {
|
||||
if (visited.has(uuid)) return;
|
||||
visited.add(uuid);
|
||||
|
||||
const point = pointMap.get(uuid);
|
||||
if (point) {
|
||||
sequence.push(point);
|
||||
}
|
||||
|
||||
// Follow forward dependencies
|
||||
const nextPoints = dependencyGraph.get(uuid) || [];
|
||||
nextPoints.forEach(nextUuid => traverse(nextUuid));
|
||||
}
|
||||
|
||||
traverse(startUuid);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Build sequences for all root points
|
||||
rootPoints.forEach(root => {
|
||||
executionSequences.push(buildSequence(root.uuid));
|
||||
});
|
||||
|
||||
// Handle any triggered points not reachable from roots (isolated chains)
|
||||
const processedPoints = new Set(
|
||||
executionSequences.flat().map(p => p.uuid)
|
||||
);
|
||||
|
||||
allPoints.forEach(point => {
|
||||
if (triggeredPoints.has(point.uuid) && !processedPoints.has(point.uuid)) {
|
||||
executionSequences.push(buildSequence(point.uuid));
|
||||
}
|
||||
});
|
||||
|
||||
return executionSequences;
|
||||
}
|
||||
|
||||
function extractTriggersFromPoint(point: PointsScheme): TriggerSchema[] {
|
||||
if ('actions' in point) {
|
||||
return point.actions.flatMap(action => action.triggers);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useActionHandler } from '../../actions/useActionHandler';
|
||||
import { useProductStore } from '../../../../store/simulation/useProductStore';
|
||||
|
||||
export function useTriggerHandler() {
|
||||
const { getActionByUuid } = useProductStore();
|
||||
const { handleAction } = useActionHandler();
|
||||
|
||||
const handleTrigger = (trigger: TriggerSchema) => {
|
||||
|
||||
}
|
||||
|
||||
const triggerPointActions = useCallback((action: Action) => {
|
||||
if (!action) return;
|
||||
|
||||
action.triggers.forEach(trigger => {
|
||||
switch (trigger.triggerType) {
|
||||
case 'onStart':
|
||||
break;
|
||||
case 'onComplete':
|
||||
handleTrigger(trigger);
|
||||
break;
|
||||
case 'onStop':
|
||||
break;
|
||||
case 'onError':
|
||||
break;
|
||||
case 'delay':
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown trigger type: ${trigger.triggerType}`);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
triggerPointActions
|
||||
};
|
||||
}
|
|
@ -84,6 +84,7 @@ export const useArmBotStore = create<ArmBotStore>()(
|
|||
armBot.currentAction = {
|
||||
actionUuid: action.actionUuid,
|
||||
actionName: action.actionName,
|
||||
materialType: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ export const useMachineStore = create<MachineStore>()(
|
|||
armBot.currentAction = {
|
||||
actionUuid: action.actionUuid,
|
||||
actionName: action.actionName,
|
||||
materialType: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,9 @@ type ProductsStore = {
|
|||
getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined;
|
||||
getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined;
|
||||
getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined;
|
||||
getModelUuidByPointUuid: (productId: string, actionUuid: string) => (string) | undefined;
|
||||
getModelUuidByActionUuid: (productId: string, actionUuid: string) => (string) | undefined;
|
||||
getPointUuidByActionUuid: (productId: string, actionUuid: string) => (string) | undefined;
|
||||
getTriggerByUuid: (productId: string, triggerUuid: string) => TriggerSchema | undefined;
|
||||
getIsEventInProduct: (productId: string, modelUuid: string) => boolean;
|
||||
};
|
||||
|
@ -574,6 +576,27 @@ export const useProductStore = create<ProductsStore>()(
|
|||
return undefined;
|
||||
},
|
||||
|
||||
getModelUuidByPointUuid: (productId, pointUuid) => {
|
||||
const product = get().products.find(p => p.productId === productId);
|
||||
if (!product) return undefined;
|
||||
|
||||
for (const event of product.eventDatas) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
if (point.uuid === pointUuid) {
|
||||
return event.modelUuid;
|
||||
}
|
||||
}
|
||||
} else if ('point' in event) {
|
||||
const point = (event as any).point;
|
||||
if (point.uuid === pointUuid) {
|
||||
return event.modelUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
getModelUuidByActionUuid: (productId, actionUuid) => {
|
||||
const product = get().products.find(p => p.productId === productId);
|
||||
if (!product) return undefined;
|
||||
|
@ -598,6 +621,30 @@ export const useProductStore = create<ProductsStore>()(
|
|||
return undefined;
|
||||
},
|
||||
|
||||
getPointUuidByActionUuid: (productId, actionUuid) => {
|
||||
const product = get().products.find(p => p.productId === productId);
|
||||
if (!product) return undefined;
|
||||
|
||||
for (const event of product.eventDatas) {
|
||||
if ('points' in event) {
|
||||
for (const point of (event as ConveyorEventSchema).points) {
|
||||
if (point.action?.actionUuid === actionUuid) {
|
||||
return point.uuid;
|
||||
}
|
||||
}
|
||||
} else if ('point' in event) {
|
||||
const point = (event as any).point;
|
||||
if ('action' in point && point.action?.actionUuid === actionUuid) {
|
||||
return point.uuid;
|
||||
} else if ('actions' in point) {
|
||||
const action = point.actions.find((a: any) => a.actionUuid === actionUuid);
|
||||
if (action) return point.uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
getTriggerByUuid: (productId, triggerUuid) => {
|
||||
const product = get().products.find(p => p.productId === productId);
|
||||
if (!product) return undefined;
|
||||
|
|
|
@ -2,15 +2,6 @@
|
|||
import { create } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
interface VehicleStatus extends VehicleEventSchema {
|
||||
productId: string;
|
||||
isActive: boolean;
|
||||
idleTime: number;
|
||||
activeTime: number;
|
||||
currentLoad: number;
|
||||
distanceTraveled: number;
|
||||
}
|
||||
|
||||
interface VehiclesStore {
|
||||
vehicles: VehicleStatus[];
|
||||
|
||||
|
@ -27,6 +18,7 @@ interface VehiclesStore {
|
|||
incrementVehicleLoad: (modelUuid: string, incrementBy: number) => void;
|
||||
decrementVehicleLoad: (modelUuid: string, decrementBy: number) => void;
|
||||
setVehicleState: (modelUuid: string, newState: VehicleStatus['state']) => void;
|
||||
setMaterialType: (modelUuid: string, materialType: string | null) => void;
|
||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
|
||||
|
||||
|
@ -51,6 +43,7 @@ export const useVehicleStore = create<VehiclesStore>()(
|
|||
idleTime: 0,
|
||||
activeTime: 0,
|
||||
currentLoad: 0,
|
||||
materialType: null,
|
||||
distanceTraveled: 0,
|
||||
});
|
||||
}
|
||||
|
@ -123,6 +116,15 @@ export const useVehicleStore = create<VehiclesStore>()(
|
|||
});
|
||||
},
|
||||
|
||||
setMaterialType: (modelUuid, materialType) => {
|
||||
set((state) => {
|
||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
||||
if (vehicle) {
|
||||
vehicle.materialType = materialType;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
incrementActiveTime: (modelUuid, incrementBy) => {
|
||||
set((state) => {
|
||||
const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid);
|
||||
|
|
|
@ -22,74 +22,35 @@ interface ConveyorPointSchema {
|
|||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
action: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "default" | "spawn" | "swap" | "delay" | "despawn";
|
||||
material: string;
|
||||
delay: number;
|
||||
spawnInterval: number;
|
||||
spawnCount: number;
|
||||
triggers: TriggerSchema[];
|
||||
};
|
||||
action: ConveyorAction;
|
||||
}
|
||||
|
||||
interface VehiclePointSchema {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
action: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "travel";
|
||||
unLoadDuration: number;
|
||||
loadCapacity: number;
|
||||
steeringAngle: number;
|
||||
pickUpPoint: { position: { x: number; y: number, z: number }, rotation: { x: number; y: number, z: number } } | null;
|
||||
unLoadPoint: { position: { x: number; y: number, z: number }, rotation: { x: number; y: number, z: number } } | null;
|
||||
triggers: TriggerSchema[];
|
||||
};
|
||||
action: VehicleAction;
|
||||
}
|
||||
|
||||
interface RoboticArmPointSchema {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
actions: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "pickAndPlace";
|
||||
process: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; };
|
||||
triggers: TriggerSchema[];
|
||||
}[];
|
||||
actions: RoboticArmAction[];
|
||||
}
|
||||
|
||||
interface MachinePointSchema {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
action: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "process";
|
||||
processTime: number;
|
||||
swapMaterial: string;
|
||||
triggers: TriggerSchema[];
|
||||
};
|
||||
action: MachineAction;
|
||||
}
|
||||
|
||||
interface StoragePointSchema {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
action: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "store";
|
||||
materials: { materialName: string; materialId: string; }[];
|
||||
storageCapacity: number;
|
||||
triggers: TriggerSchema[];
|
||||
};
|
||||
action: StorageAction;
|
||||
}
|
||||
|
||||
interface ConveyorEventSchema extends AssetEventSchema {
|
||||
|
@ -120,6 +81,57 @@ interface StorageEventSchema extends AssetEventSchema {
|
|||
point: StoragePointSchema;
|
||||
}
|
||||
|
||||
interface ConveyorAction {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "default" | "spawn" | "swap" | "delay" | "despawn";
|
||||
material: string;
|
||||
delay: number;
|
||||
spawnInterval: number;
|
||||
spawnCount: number;
|
||||
triggers: TriggerSchema[];
|
||||
}
|
||||
|
||||
interface VehicleAction {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "travel";
|
||||
unLoadDuration: number;
|
||||
loadCapacity: number;
|
||||
steeringAngle: number;
|
||||
pickUpPoint: { position: { x: number; y: number, z: number }, rotation: { x: number; y: number, z: number } } | null;
|
||||
unLoadPoint: { position: { x: number; y: number, z: number }, rotation: { x: number; y: number, z: number } } | null;
|
||||
triggers: TriggerSchema[];
|
||||
}
|
||||
|
||||
interface RoboticArmAction {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "pickAndPlace";
|
||||
process: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; };
|
||||
triggers: TriggerSchema[];
|
||||
}
|
||||
|
||||
interface MachineAction {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "process";
|
||||
processTime: number;
|
||||
swapMaterial: string;
|
||||
triggers: TriggerSchema[];
|
||||
}
|
||||
|
||||
interface StorageAction {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
actionType: "store";
|
||||
materials: { materialName: string; materialId: string; }[];
|
||||
storageCapacity: number;
|
||||
triggers: TriggerSchema[];
|
||||
}
|
||||
|
||||
type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction;
|
||||
|
||||
type PointsScheme = ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema;
|
||||
|
||||
type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema;
|
||||
|
@ -136,7 +148,6 @@ interface ConveyorStatus extends ConveyorEventSchema {
|
|||
isActive: boolean;
|
||||
idleTime: number;
|
||||
activeTime: number;
|
||||
|
||||
}
|
||||
|
||||
interface MachineStatus extends MachineEventSchema {
|
||||
|
@ -147,6 +158,7 @@ interface MachineStatus extends MachineEventSchema {
|
|||
currentAction?: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
materialType: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -158,6 +170,7 @@ interface ArmBotStatus extends RoboticArmEventSchema {
|
|||
currentAction?: {
|
||||
actionUuid: string;
|
||||
actionName: string;
|
||||
materialType: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -167,6 +180,7 @@ interface VehicleStatus extends VehicleEventSchema {
|
|||
idleTime: number;
|
||||
activeTime: number;
|
||||
currentLoad: number;
|
||||
materialType: string | null;
|
||||
distanceTraveled: number;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue