v2 #82
|
@ -38,7 +38,7 @@ function RoboticArmMechanics() {
|
||||||
} else {
|
} else {
|
||||||
clearSelectedAction();
|
clearSelectedAction();
|
||||||
}
|
}
|
||||||
}, [selectedAction, selectedEventData, selectedProduct]);
|
}, [selectedEventData, selectedProduct]);
|
||||||
|
|
||||||
const updateBackend = (
|
const updateBackend = (
|
||||||
productName: string,
|
productName: string,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||||
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||||
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
||||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||||
import { useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore";
|
import { useSelectedAction, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore";
|
||||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||||
|
|
||||||
type TriggerProps = {
|
type TriggerProps = {
|
||||||
|
@ -25,6 +25,7 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||||
const [selectedTrigger, setSelectedTrigger] = useState<TriggerSchema | undefined>();
|
const [selectedTrigger, setSelectedTrigger] = useState<TriggerSchema | undefined>();
|
||||||
const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete");
|
const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete");
|
||||||
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { selectedAction } = useSelectedAction();
|
||||||
|
|
||||||
const email = localStorage.getItem('email')
|
const email = localStorage.getItem('email')
|
||||||
const organization = (email!.split("@")[1]).split(".")[0];
|
const organization = (email!.split("@")[1]).split(".")[0];
|
||||||
|
@ -36,12 +37,12 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||||
|
|
||||||
if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') {
|
if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') {
|
||||||
actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid;
|
actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid;
|
||||||
} else if (type === 'RoboticArm') {
|
} else if (type === 'RoboticArm' && selectedAction) {
|
||||||
actionUuid = (selectedPointData as RoboticArmPointSchema).actions[0]?.actionUuid;
|
actionUuid = selectedAction.actionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentAction(actionUuid);
|
setCurrentAction(actionUuid);
|
||||||
}, [selectedPointData, selectedProduct, type]);
|
}, [selectedPointData, selectedProduct, type, selectedAction]);
|
||||||
|
|
||||||
const updateBackend = (
|
const updateBackend = (
|
||||||
productName: string,
|
productName: string,
|
||||||
|
|
|
@ -7,7 +7,6 @@ interface SkeletonUIProps {
|
||||||
|
|
||||||
// Define the SkeletonUI component
|
// Define the SkeletonUI component
|
||||||
const SkeletonUI: React.FC<SkeletonUIProps> = ({ type }) => {
|
const SkeletonUI: React.FC<SkeletonUIProps> = ({ type }) => {
|
||||||
console.log("type: ", type);
|
|
||||||
|
|
||||||
// Function to render skeleton content based on 'type'
|
// Function to render skeleton content based on 'type'
|
||||||
const renderSkeleton = () => {
|
const renderSkeleton = () => {
|
||||||
|
|
|
@ -40,9 +40,9 @@ const SimulationPlayer: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isReset) {
|
if (isReset) {
|
||||||
setTimeout(()=>{
|
setTimeout(() => {
|
||||||
setReset(false);
|
setReset(false);
|
||||||
},0)
|
}, 0)
|
||||||
}
|
}
|
||||||
}, [isReset])
|
}, [isReset])
|
||||||
|
|
||||||
|
@ -282,8 +282,7 @@ const SimulationPlayer: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
{index < intervals.length - 1 && (
|
{index < intervals.length - 1 && (
|
||||||
<div
|
<div
|
||||||
className={`line ${
|
className={`line ${progress >= ((index + 1) / totalSegments) * 100
|
||||||
progress >= ((index + 1) / totalSegments) * 100
|
|
||||||
? "filled"
|
? "filled"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore } from "../../../../../store/usePlayButtonStore";
|
import { usePlayButtonStore, usePauseButtonStore, useResetButtonStore, useAnimationPlaySpeed } from "../../../../../store/usePlayButtonStore";
|
||||||
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
||||||
|
|
||||||
interface DelayInstance {
|
interface DelayInstance {
|
||||||
|
initialDelay: number;
|
||||||
delayEndTime: number;
|
delayEndTime: number;
|
||||||
materialId?: string;
|
materialId?: string;
|
||||||
action: ConveyorAction;
|
action: ConveyorAction;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
remainingTime: number;
|
remainingTime: number;
|
||||||
|
lastUpdateTime: number;
|
||||||
|
elapsedTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDelayHandler() {
|
export function useDelayHandler() {
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
const { isReset } = useResetButtonStore();
|
const { isReset } = useResetButtonStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
const { setIsPaused } = useMaterialStore();
|
const { setIsPaused } = useMaterialStore();
|
||||||
const activeDelays = useRef<Map<string, DelayInstance>>(new Map());
|
const activeDelays = useRef<Map<string, DelayInstance>>(new Map());
|
||||||
|
const lastUpdateTimeRef = useRef<number>(performance.now());
|
||||||
|
|
||||||
const cleanupDelay = useCallback(() => {
|
const cleanupDelay = useCallback(() => {
|
||||||
activeDelays.current.clear();
|
activeDelays.current.clear();
|
||||||
|
@ -30,35 +35,34 @@ export function useDelayHandler() {
|
||||||
|
|
||||||
const delayLogStatus = (materialUuid: string, status: string) => {
|
const delayLogStatus = (materialUuid: string, status: string) => {
|
||||||
// console.log(`${materialUuid}, ${status}`);
|
// console.log(`${materialUuid}, ${status}`);
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useFrame(() => {
|
||||||
const currentTime = performance.now();
|
const currentTime = performance.now();
|
||||||
|
const deltaTime = currentTime - lastUpdateTimeRef.current;
|
||||||
|
lastUpdateTimeRef.current = currentTime;
|
||||||
|
const completedDelays: string[] = [];
|
||||||
|
|
||||||
activeDelays.current.forEach((delay) => {
|
activeDelays.current.forEach((delay, key) => {
|
||||||
if (isPaused && !delay.isPaused) {
|
if (isPaused && !delay.isPaused) {
|
||||||
delay.remainingTime = Math.max(0, delay.delayEndTime - currentTime);
|
delay.remainingTime = Math.max(0, delay.delayEndTime - currentTime);
|
||||||
delay.isPaused = true;
|
delay.isPaused = true;
|
||||||
} else if (!isPaused && delay.isPaused) {
|
} else if (!isPaused && delay.isPaused) {
|
||||||
delay.delayEndTime = currentTime + delay.remainingTime;
|
delay.delayEndTime = currentTime + delay.remainingTime;
|
||||||
delay.isPaused = false;
|
delay.isPaused = false;
|
||||||
delay.remainingTime = 0;
|
delay.lastUpdateTime = currentTime;
|
||||||
}
|
delay.elapsedTime = 0;
|
||||||
});
|
} else if (!delay.isPaused && isPlaying) {
|
||||||
}, [isPaused]);
|
delay.elapsedTime += deltaTime * speed;
|
||||||
|
|
||||||
useFrame(() => {
|
if (delay.elapsedTime >= delay.initialDelay) {
|
||||||
if (!isPlaying || isPaused) return;
|
if (delay.materialId) {
|
||||||
|
delayLogStatus(delay.materialId, `Delay completed`);
|
||||||
const currentTime = performance.now();
|
|
||||||
const completedDelays: string[] = [];
|
|
||||||
|
|
||||||
activeDelays.current.forEach((delay, key) => {
|
|
||||||
if (!delay.isPaused && currentTime >= delay.delayEndTime && delay.materialId) {
|
|
||||||
delayLogStatus(delay.materialId, `Delay completed}`);
|
|
||||||
setIsPaused(delay.materialId, false);
|
setIsPaused(delay.materialId, false);
|
||||||
|
}
|
||||||
completedDelays.push(key);
|
completedDelays.push(key);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
completedDelays.forEach(key => {
|
completedDelays.forEach(key => {
|
||||||
|
@ -67,29 +71,31 @@ export function useDelayHandler() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDelay = useCallback((action: ConveyorAction, materialId?: string) => {
|
const handleDelay = useCallback((action: ConveyorAction, materialId?: string) => {
|
||||||
if (!action || action.actionType !== 'delay' || !isPlaying || !materialId) return;
|
if (!action || action.actionType !== 'delay' || !materialId) return;
|
||||||
|
|
||||||
const delayMs = (action.delay || 0) * 1000;
|
const delayMs = (action.delay || 0) * 1000;
|
||||||
if (delayMs <= 0) return;
|
if (delayMs <= 0) return;
|
||||||
|
|
||||||
const key = materialId ? `${materialId}-${action.actionUuid}` : action.actionUuid;
|
const key = materialId ? `${materialId}-${action.actionUuid}` : action.actionUuid;
|
||||||
|
|
||||||
// If this material already has a delay, cancel it
|
|
||||||
if (activeDelays.current.has(key)) {
|
if (activeDelays.current.has(key)) {
|
||||||
activeDelays.current.delete(key);
|
activeDelays.current.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const now = performance.now();
|
||||||
activeDelays.current.set(key, {
|
activeDelays.current.set(key, {
|
||||||
delayEndTime: performance.now() + delayMs,
|
initialDelay: delayMs,
|
||||||
|
delayEndTime: now + delayMs,
|
||||||
materialId,
|
materialId,
|
||||||
action,
|
action,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
remainingTime: 0
|
remainingTime: 0,
|
||||||
|
lastUpdateTime: now,
|
||||||
|
elapsedTime: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
delayLogStatus(materialId, `Started ${delayMs * 1000}s delay`);
|
delayLogStatus(materialId, `Started ${delayMs / 1000}s delay`);
|
||||||
setIsPaused(materialId, true);
|
setIsPaused(materialId, true);
|
||||||
|
|
||||||
}, [isPlaying]);
|
}, [isPlaying]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
||||||
|
|
||||||
|
export function useDespawnHandler() {
|
||||||
|
const { addMaterial, getMaterialById, removeMaterial } = useMaterialStore();
|
||||||
|
|
||||||
|
const deSpawnLogStatus = (materialUuid: string, status: string) => {
|
||||||
|
// console.log(`${materialUuid}, ${status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDespawn = useCallback((action: ConveyorAction, materialId?: string) => {
|
||||||
|
if (!action || action.actionType !== 'despawn' || !materialId) return;
|
||||||
|
|
||||||
|
const material = getMaterialById(materialId);
|
||||||
|
if (!material) return;
|
||||||
|
|
||||||
|
removeMaterial(material.materialId);
|
||||||
|
|
||||||
|
deSpawnLogStatus(material.materialId, `Despawned`);
|
||||||
|
|
||||||
|
}, [addMaterial, getMaterialById, removeMaterial]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleDespawn,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
||||||
|
@ -32,10 +32,23 @@ export function useSpawnHandler() {
|
||||||
const { isReset } = useResetButtonStore();
|
const { isReset } = useResetButtonStore();
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
|
||||||
const activeSpawns = useRef<Map<string, SpawnInstance>>(new Map());
|
const [activeSpawns, setActiveSpawns] = useState<Map<string, SpawnInstance>>(new Map());
|
||||||
|
|
||||||
|
const getConveyorPausedState = useCallback((action: ConveyorAction) => {
|
||||||
|
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
||||||
|
if (!modelUuid) return false;
|
||||||
|
|
||||||
|
const conveyor = getConveyorById(modelUuid);
|
||||||
|
if (!conveyor) return false;
|
||||||
|
return conveyor.isPaused;
|
||||||
|
}, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productId]);
|
||||||
|
|
||||||
|
const shouldPauseSpawn = useCallback((action: ConveyorAction) => {
|
||||||
|
return isPaused || getConveyorPausedState(action);
|
||||||
|
}, [isPaused, getConveyorPausedState]);
|
||||||
|
|
||||||
const clearAllSpawns = useCallback(() => {
|
const clearAllSpawns = useCallback(() => {
|
||||||
activeSpawns.current.clear();
|
setActiveSpawns(new Map());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -52,6 +65,7 @@ export function useSpawnHandler() {
|
||||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
||||||
const pointUuid = getPointUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
const pointUuid = getPointUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
||||||
if (!modelUuid || !pointUuid) return;
|
if (!modelUuid || !pointUuid) return;
|
||||||
|
const currentTime = performance.now();
|
||||||
|
|
||||||
const newMaterial: MaterialSchema = {
|
const newMaterial: MaterialSchema = {
|
||||||
materialId: THREE.MathUtils.generateUUID(),
|
materialId: THREE.MathUtils.generateUUID(),
|
||||||
|
@ -61,13 +75,12 @@ export function useSpawnHandler() {
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
isPaused: false,
|
isPaused: false,
|
||||||
isRendered: true,
|
isRendered: true,
|
||||||
|
startTime:currentTime,
|
||||||
current: {
|
current: {
|
||||||
modelUuid: modelUuid,
|
modelUuid: modelUuid,
|
||||||
pointUuid: pointUuid,
|
pointUuid: pointUuid,
|
||||||
actionUuid: action.actionUuid
|
actionUuid: action.actionUuid
|
||||||
},
|
},
|
||||||
weight: 1,
|
|
||||||
cost: 1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid &&
|
if (action.triggers[0].triggeredAsset?.triggeredModel.modelUuid &&
|
||||||
|
@ -84,62 +97,56 @@ export function useSpawnHandler() {
|
||||||
return newMaterial;
|
return newMaterial;
|
||||||
}, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productId]);
|
}, [addMaterial, getModelUuidByActionUuid, getPointUuidByActionUuid, selectedProduct.productId]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
const getConveyorPausedState = useCallback((action: ConveyorAction) => {
|
|
||||||
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
|
||||||
if (!modelUuid) return false;
|
|
||||||
|
|
||||||
const conveyor = getConveyorById(modelUuid);
|
|
||||||
return conveyor?.isPaused ?? false;
|
|
||||||
}, [getConveyorById, getModelUuidByActionUuid, selectedProduct.productId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const currentTime = performance.now();
|
const currentTime = performance.now();
|
||||||
|
const completedActions: string[] = [];
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
activeSpawns.current.forEach((spawn) => {
|
activeSpawns.forEach(spawn => {
|
||||||
if (isPaused && !spawn.isPaused) {
|
const isPausedNow = shouldPauseSpawn(spawn.params.action);
|
||||||
|
|
||||||
|
if (isPausedNow && !spawn.isPaused) {
|
||||||
if (spawn.lastSpawnTime === null) {
|
if (spawn.lastSpawnTime === null) {
|
||||||
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.startTime));
|
spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.startTime));
|
||||||
} else {
|
} else {
|
||||||
spawn.remainingTime = Math.max(0, spawn.params.intervalMs - (currentTime - spawn.lastSpawnTime));
|
spawn.remainingTime = Math.max(0, (spawn.params.intervalMs / speed) - (currentTime - spawn.lastSpawnTime));
|
||||||
}
|
}
|
||||||
spawn.pauseStartTime = currentTime;
|
spawn.pauseStartTime = currentTime;
|
||||||
spawn.isPaused = true;
|
spawn.isPaused = true;
|
||||||
} else if (!isPaused && spawn.isPaused) {
|
hasChanges = true;
|
||||||
if (spawn.remainingTime > 0) {
|
} else if (!isPausedNow && spawn.isPaused) {
|
||||||
|
const pauseDuration = currentTime - spawn.pauseStartTime;
|
||||||
if (spawn.lastSpawnTime === null) {
|
if (spawn.lastSpawnTime === null) {
|
||||||
spawn.startTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime);
|
spawn.startTime += pauseDuration;
|
||||||
} else {
|
} else {
|
||||||
spawn.lastSpawnTime = currentTime - (spawn.params.intervalMs - spawn.remainingTime);
|
spawn.lastSpawnTime += pauseDuration;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
spawn.isPaused = false;
|
spawn.isPaused = false;
|
||||||
spawn.pauseStartTime = 0;
|
spawn.pauseStartTime = 0;
|
||||||
spawn.remainingTime = 0;
|
spawn.remainingTime = 0;
|
||||||
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [isPaused]);
|
|
||||||
|
|
||||||
useFrame(() => {
|
if (isPlaying && !isReset && !isPaused) {
|
||||||
if (!isPlaying || isPaused || isReset) return;
|
activeSpawns.forEach((spawn, actionUuid) => {
|
||||||
|
if (spawn.isPaused) return;
|
||||||
|
|
||||||
const currentTime = performance.now();
|
|
||||||
const completedActions: string[] = [];
|
|
||||||
|
|
||||||
activeSpawns.current.forEach((spawn, actionUuid) => {
|
|
||||||
const { material, intervalMs, totalCount, action } = spawn.params;
|
const { material, intervalMs, totalCount, action } = spawn.params;
|
||||||
|
const adjustedInterval = intervalMs / speed;
|
||||||
const isFirstSpawn = spawn.lastSpawnTime === null;
|
const isFirstSpawn = spawn.lastSpawnTime === null;
|
||||||
|
|
||||||
// First spawn
|
// First spawn
|
||||||
if (isFirstSpawn) {
|
if (isFirstSpawn) {
|
||||||
const elapsed = currentTime - spawn.startTime;
|
const elapsed = currentTime - spawn.startTime;
|
||||||
if (elapsed >= intervalMs) {
|
if (elapsed >= adjustedInterval) {
|
||||||
const createdMaterial = createNewMaterial(material, action);
|
const createdMaterial = createNewMaterial(material, action);
|
||||||
if (createdMaterial) {
|
if (createdMaterial) {
|
||||||
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
|
spawnLogStatus(createdMaterial.materialId, `[${elapsed.toFixed(2)}ms] Spawned ${material} (1/${totalCount})`);
|
||||||
}
|
}
|
||||||
spawn.lastSpawnTime = currentTime;
|
spawn.lastSpawnTime = currentTime;
|
||||||
spawn.spawnCount = 1;
|
spawn.spawnCount = 1;
|
||||||
|
hasChanges = true;
|
||||||
|
|
||||||
if (totalCount <= 1) {
|
if (totalCount <= 1) {
|
||||||
completedActions.push(actionUuid);
|
completedActions.push(actionUuid);
|
||||||
|
@ -151,7 +158,7 @@ export function useSpawnHandler() {
|
||||||
// Subsequent spawns
|
// Subsequent spawns
|
||||||
if (spawn.lastSpawnTime !== null) {
|
if (spawn.lastSpawnTime !== null) {
|
||||||
const timeSinceLast = currentTime - spawn.lastSpawnTime;
|
const timeSinceLast = currentTime - spawn.lastSpawnTime;
|
||||||
if (timeSinceLast >= intervalMs) {
|
if (timeSinceLast >= adjustedInterval) {
|
||||||
const count = spawn.spawnCount + 1;
|
const count = spawn.spawnCount + 1;
|
||||||
const createdMaterial = createNewMaterial(material, action);
|
const createdMaterial = createNewMaterial(material, action);
|
||||||
if (createdMaterial) {
|
if (createdMaterial) {
|
||||||
|
@ -159,6 +166,7 @@ export function useSpawnHandler() {
|
||||||
}
|
}
|
||||||
spawn.lastSpawnTime = currentTime;
|
spawn.lastSpawnTime = currentTime;
|
||||||
spawn.spawnCount = count;
|
spawn.spawnCount = count;
|
||||||
|
hasChanges = true;
|
||||||
|
|
||||||
if (count >= totalCount) {
|
if (count >= totalCount) {
|
||||||
completedActions.push(actionUuid);
|
completedActions.push(actionUuid);
|
||||||
|
@ -166,10 +174,19 @@ export function useSpawnHandler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChanges || completedActions.length > 0) {
|
||||||
|
setActiveSpawns(prevSpawns => {
|
||||||
|
const newSpawns = new Map(prevSpawns);
|
||||||
|
|
||||||
completedActions.forEach(actionUuid => {
|
completedActions.forEach(actionUuid => {
|
||||||
activeSpawns.current.delete(actionUuid);
|
newSpawns.delete(actionUuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return newSpawns;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSpawn = useCallback((action: ConveyorAction) => {
|
const handleSpawn = useCallback((action: ConveyorAction) => {
|
||||||
|
@ -178,11 +195,14 @@ export function useSpawnHandler() {
|
||||||
const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action;
|
const { material, spawnInterval = 0, spawnCount = 1, actionUuid } = action;
|
||||||
const intervalMs = spawnInterval * 1000;
|
const intervalMs = spawnInterval * 1000;
|
||||||
|
|
||||||
if (activeSpawns.current.has(actionUuid)) {
|
setActiveSpawns(prevSpawns => {
|
||||||
activeSpawns.current.delete(actionUuid);
|
const newSpawns = new Map(prevSpawns);
|
||||||
|
|
||||||
|
if (newSpawns.has(actionUuid)) {
|
||||||
|
newSpawns.delete(actionUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSpawns.current.set(actionUuid, {
|
newSpawns.set(actionUuid, {
|
||||||
lastSpawnTime: null,
|
lastSpawnTime: null,
|
||||||
startTime: performance.now(),
|
startTime: performance.now(),
|
||||||
spawnCount: 0,
|
spawnCount: 0,
|
||||||
|
@ -196,6 +216,9 @@ export function useSpawnHandler() {
|
||||||
remainingTime: 0,
|
remainingTime: 0,
|
||||||
isPaused: false
|
isPaused: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return newSpawns;
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
import { useMaterialStore } from "../../../../../store/simulation/useMaterialStore";
|
||||||
import { useProductStore } from "../../../../../store/simulation/useProductStore";
|
|
||||||
import { useSelectedProduct } from "../../../../../store/simulation/useSimulationStore";
|
|
||||||
import { usePlayButtonStore } from "../../../../../store/usePlayButtonStore";
|
|
||||||
|
|
||||||
export function useSwapHandler() {
|
export function useSwapHandler() {
|
||||||
const { addMaterial, getMaterialById, setMaterial } = useMaterialStore();
|
const { getMaterialById, setMaterial } = useMaterialStore();
|
||||||
const { getPointUuidByActionUuid } = useProductStore();
|
|
||||||
const { selectedProduct } = useSelectedProduct();
|
|
||||||
const { isPlaying } = usePlayButtonStore();
|
|
||||||
|
|
||||||
const swapLogStatus = (materialUuid: string, status: string) => {
|
const swapLogStatus = (materialUuid: string, status: string) => {
|
||||||
// console.log(`${materialUuid}, ${status}`);
|
// console.log(`${materialUuid}, ${status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSwap = useCallback((action: ConveyorAction, materialId?: string) => {
|
const handleSwap = useCallback((action: ConveyorAction, materialId?: string) => {
|
||||||
if (!action || action.actionType !== 'swap' || !isPlaying || !materialId) return;
|
if (!action || action.actionType !== 'swap' || !materialId) return;
|
||||||
|
|
||||||
const { material: newMaterialType } = action;
|
const { material: newMaterialType } = action;
|
||||||
const material = getMaterialById(materialId);
|
const material = getMaterialById(materialId);
|
||||||
|
@ -24,7 +18,7 @@ export function useSwapHandler() {
|
||||||
setMaterial(material.materialId, newMaterialType);
|
setMaterial(material.materialId, newMaterialType);
|
||||||
swapLogStatus(material.materialId, `Swapped to ${newMaterialType}`);
|
swapLogStatus(material.materialId, `Swapped to ${newMaterialType}`);
|
||||||
|
|
||||||
}, [addMaterial, getMaterialById, getPointUuidByActionUuid, isPlaying, setMaterial, selectedProduct.productId]);
|
}, [getMaterialById, setMaterial]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleSwap,
|
handleSwap,
|
||||||
|
|
|
@ -2,10 +2,12 @@ import { useEffect, useCallback } from "react";
|
||||||
import { useSpawnHandler } from "./actionHandler/useSpawnHandler";
|
import { useSpawnHandler } from "./actionHandler/useSpawnHandler";
|
||||||
import { useSwapHandler } from "./actionHandler/useSwapHandler";
|
import { useSwapHandler } from "./actionHandler/useSwapHandler";
|
||||||
import { useDelayHandler } from "./actionHandler/useDelayHandler";
|
import { useDelayHandler } from "./actionHandler/useDelayHandler";
|
||||||
|
import { useDespawnHandler } from "./actionHandler/useDespawnHandler";
|
||||||
|
|
||||||
export function useConveyorActions() {
|
export function useConveyorActions() {
|
||||||
const { handleSpawn, clearCurrentSpawn } = useSpawnHandler();
|
const { handleSpawn, clearCurrentSpawn } = useSpawnHandler();
|
||||||
const { handleSwap } = useSwapHandler();
|
const { handleSwap } = useSwapHandler();
|
||||||
|
const { handleDespawn } = useDespawnHandler();
|
||||||
const { handleDelay, cleanupDelay } = useDelayHandler();
|
const { handleDelay, cleanupDelay } = useDelayHandler();
|
||||||
|
|
||||||
const handleDefaultAction = useCallback((action: ConveyorAction) => {
|
const handleDefaultAction = useCallback((action: ConveyorAction) => {
|
||||||
|
@ -23,9 +25,9 @@ export function useConveyorActions() {
|
||||||
handleDelay(action, materialId);
|
handleDelay(action, materialId);
|
||||||
}, [handleDelay]);
|
}, [handleDelay]);
|
||||||
|
|
||||||
const handleDespawnAction = useCallback((action: ConveyorAction) => {
|
const handleDespawnAction = useCallback((action: ConveyorAction, materialId?: string) => {
|
||||||
console.log(`Despawning material`);
|
handleDespawn(action, materialId)
|
||||||
}, []);
|
}, [handleDespawn]);
|
||||||
|
|
||||||
const handleConveyorAction = useCallback((action: ConveyorAction, materialId?: string) => {
|
const handleConveyorAction = useCallback((action: ConveyorAction, materialId?: string) => {
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
|
@ -44,7 +46,7 @@ export function useConveyorActions() {
|
||||||
handleDelayAction(action, materialId);
|
handleDelayAction(action, materialId);
|
||||||
break;
|
break;
|
||||||
case 'despawn':
|
case 'despawn':
|
||||||
handleDespawnAction(action);
|
handleDespawnAction(action, materialId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`Unknown conveyor action type: ${action.actionType}`);
|
console.warn(`Unknown conveyor action type: ${action.actionType}`);
|
||||||
|
|
|
@ -46,12 +46,9 @@ function MaterialAnimator({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPlaying || !material.next?.pointUuid) {
|
if (!isPlaying || !material.next?.pointUuid) {
|
||||||
setIsAnimating(false);
|
if (material.current.pointUuid) {
|
||||||
return;
|
const newTarget = getWorldPosition(material.current.pointUuid);
|
||||||
}
|
if (newTarget && matRef.current && !material.isPaused) {
|
||||||
|
|
||||||
const newTarget = getWorldPosition(material.next.pointUuid);
|
|
||||||
if (newTarget && matRef.current) {
|
|
||||||
animationState.current.startPosition.copy(matRef.current.position);
|
animationState.current.startPosition.copy(matRef.current.position);
|
||||||
animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
|
animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
|
||||||
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
|
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
|
||||||
|
@ -60,7 +57,22 @@ function MaterialAnimator({
|
||||||
setTargetPosition(newTarget);
|
setTargetPosition(newTarget);
|
||||||
setIsAnimating(true);
|
setIsAnimating(true);
|
||||||
}
|
}
|
||||||
}, [material.next?.pointUuid, isPlaying]);
|
} else {
|
||||||
|
setIsAnimating(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newTarget = getWorldPosition(material.next.pointUuid);
|
||||||
|
if (newTarget && matRef.current && !material.isPaused) {
|
||||||
|
animationState.current.startPosition.copy(matRef.current.position);
|
||||||
|
animationState.current.totalDistance = animationState.current.startPosition.distanceTo(newTarget);
|
||||||
|
animationState.current.startTime = performance.now() - animationState.current.pausedTime;
|
||||||
|
animationState.current.pausedTime = 0;
|
||||||
|
animationState.current.isPaused = false;
|
||||||
|
setTargetPosition(newTarget);
|
||||||
|
setIsAnimating(true);
|
||||||
|
}
|
||||||
|
}, [material, isPlaying]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldPause) {
|
if (shouldPause) {
|
||||||
|
|
|
@ -83,14 +83,6 @@ function MaterialInstance({ material }: { material: MaterialSchema }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// console.log('material: ', material);
|
|
||||||
if (material.current && material.next) {
|
|
||||||
// console.log('current: ', material.current.pointUuid);
|
|
||||||
// console.log('next: ', material.next.pointUuid);
|
|
||||||
}
|
|
||||||
}, [material])
|
|
||||||
|
|
||||||
const callTrigger = () => {
|
const callTrigger = () => {
|
||||||
if (!material.next) return;
|
if (!material.next) return;
|
||||||
const fromModel = getEventByModelUuid(selectedProduct.productId, material.next.modelUuid);
|
const fromModel = getEventByModelUuid(selectedProduct.productId, material.next.modelUuid);
|
||||||
|
|
|
@ -3,12 +3,16 @@ import MaterialInstance from './instance/materialInstance'
|
||||||
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
|
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
|
||||||
|
|
||||||
function MaterialInstances() {
|
function MaterialInstances() {
|
||||||
const { materials } = useMaterialStore();
|
const { materials, materialHistory } = useMaterialStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('materials: ', materials);
|
// console.log('materials: ', materials);
|
||||||
}, [materials])
|
}, [materials])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// console.log('materialHistory: ', materialHistory);
|
||||||
|
}, [materialHistory])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { getAllProductsApi } from '../../../services/simulation/getallProductsAp
|
||||||
import { useVehicleStore } from '../../../store/simulation/useVehicleStore';
|
import { useVehicleStore } from '../../../store/simulation/useVehicleStore';
|
||||||
import { useArmBotStore } from '../../../store/simulation/useArmBotStore';
|
import { useArmBotStore } from '../../../store/simulation/useArmBotStore';
|
||||||
import { useConveyorStore } from '../../../store/simulation/useConveyorStore';
|
import { useConveyorStore } from '../../../store/simulation/useConveyorStore';
|
||||||
|
import { useResetButtonStore } from '../../../store/usePlayButtonStore';
|
||||||
|
|
||||||
function Products() {
|
function Products() {
|
||||||
const { products, getProductById, addProduct, setProducts } = useProductStore();
|
const { products, getProductById, addProduct, setProducts } = useProductStore();
|
||||||
|
@ -15,6 +16,7 @@ function Products() {
|
||||||
const { addVehicle, clearvehicles } = useVehicleStore();
|
const { addVehicle, clearvehicles } = useVehicleStore();
|
||||||
const { addArmBot, clearArmBots } = useArmBotStore();
|
const { addArmBot, clearArmBots } = useArmBotStore();
|
||||||
const { addConveyor, clearConveyors } = useConveyorStore();
|
const { addConveyor, clearConveyors } = useConveyorStore();
|
||||||
|
const { isReset } = useResetButtonStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const email = localStorage.getItem('email')
|
const email = localStorage.getItem('email')
|
||||||
|
@ -45,7 +47,7 @@ function Products() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedProduct, products]);
|
}, [selectedProduct, products, isReset]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedProduct.productId) {
|
if (selectedProduct.productId) {
|
||||||
|
@ -59,7 +61,7 @@ function Products() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedProduct, products]);
|
}, [selectedProduct, products, isReset]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedProduct.productId) {
|
if (selectedProduct.productId) {
|
||||||
|
@ -73,7 +75,7 @@ function Products() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedProduct, products]);
|
}, [selectedProduct, products, isReset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default function MaterialAnimator({ ikSolver, armBot, currentPhase }: Mat
|
||||||
const [isRendered, setIsRendered] = useState<boolean>(false);
|
const [isRendered, setIsRendered] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentPhase === "start-to-end") {
|
if (currentPhase === "start-to-end" || currentPhase === "dropping") {
|
||||||
setIsRendered(true);
|
setIsRendered(true);
|
||||||
} else {
|
} else {
|
||||||
setIsRendered(false);
|
setIsRendered(false);
|
||||||
|
|
|
@ -266,7 +266,7 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
|
{customCurvePoints && customCurvePoints?.length >= 2 && currentPath && isPlaying && (
|
||||||
<mesh rotation={armBot.rotation} position={armBot.position}>
|
<mesh rotation={armBot.rotation} position={armBot.position} visible={false}>
|
||||||
<Line
|
<Line
|
||||||
points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
|
points={customCurvePoints.map((p) => [p.x, p.y, p.z] as [number, number, number])}
|
||||||
color="green"
|
color="green"
|
||||||
|
@ -278,13 +278,50 @@ function RoboticArmAnimator({ HandleCallback, restPosition, ikSolver, targetBone
|
||||||
<group
|
<group
|
||||||
position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]}
|
position={[armBot.position[0], armBot.position[1] + 1.5, armBot.position[2]]}
|
||||||
rotation={[armBot.rotation[0], armBot.rotation[1], armBot.rotation[2]]}
|
rotation={[armBot.rotation[0], armBot.rotation[1], armBot.rotation[2]]}
|
||||||
|
visible={false}
|
||||||
>
|
>
|
||||||
{/* Green ring */}
|
{/* Green ring */}
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
<mesh rotation={[-Math.PI / 2, 0, 0]} visible={false}>
|
||||||
<ringGeometry args={[CIRCLE_RADIUS, CIRCLE_RADIUS + 0.02, 64]} />
|
<ringGeometry args={[CIRCLE_RADIUS, CIRCLE_RADIUS + 0.02, 64]} />
|
||||||
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
|
<meshBasicMaterial color="green" side={THREE.DoubleSide} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
|
{/* Markers at 90°, 180°, 270°, 360° */}
|
||||||
|
{[90, 180, 270, 360].map((degree, index) => {
|
||||||
|
const rad = ((degree) * Math.PI) / 180;
|
||||||
|
const x = CIRCLE_RADIUS * Math.cos(rad);
|
||||||
|
const z = CIRCLE_RADIUS * Math.sin(rad);
|
||||||
|
const y = 0; // same plane as the ring (Y axis)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh key={index} position={[x, y, z]}>
|
||||||
|
<sphereGeometry args={[0.05, 16, 16]} />
|
||||||
|
<meshStandardMaterial color="red" />
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{[90, 180, 270, 360].map((degree, index) => {
|
||||||
|
const rad = ((degree) * Math.PI) / 180;
|
||||||
|
const x = CIRCLE_RADIUS * Math.cos(rad);
|
||||||
|
const z = CIRCLE_RADIUS * Math.sin(rad);
|
||||||
|
const y = 0.15; // lift the text slightly above the ring
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={`text-${index}`}
|
||||||
|
position={[x, y, z]}
|
||||||
|
fontSize={0.2}
|
||||||
|
color="yellow"
|
||||||
|
anchorX="center"
|
||||||
|
anchorY="middle"
|
||||||
|
>
|
||||||
|
{degree}°
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
import IKInstance from '../ikInstance/ikInstance';
|
import IKInstance from '../ikInstance/ikInstance';
|
||||||
import RoboticArmAnimator from '../animator/roboticArmAnimator';
|
import RoboticArmAnimator from '../animator/roboticArmAnimator';
|
||||||
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
|
||||||
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
|
||||||
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb";
|
|
||||||
import { useThree } from "@react-three/fiber";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import MaterialAnimator from '../animator/materialAnimator';
|
import MaterialAnimator from '../animator/materialAnimator';
|
||||||
|
import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_1.glb";
|
||||||
|
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
|
import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore';
|
||||||
|
import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore';
|
||||||
|
import { useProductStore } from '../../../../../store/simulation/useProductStore';
|
||||||
|
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
|
||||||
|
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
|
||||||
|
|
||||||
function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
|
|
||||||
|
@ -21,8 +24,12 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
const pauseTimeRef = useRef<number | null>(null);
|
const pauseTimeRef = useRef<number | null>(null);
|
||||||
const isPausedRef = useRef<boolean>(false);
|
const isPausedRef = useRef<boolean>(false);
|
||||||
let startTime: number;
|
let startTime: number;
|
||||||
//zustand
|
|
||||||
const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
|
const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
|
||||||
|
const { setIsVisible } = useMaterialStore();
|
||||||
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { getActionByUuid } = useProductStore();
|
||||||
|
const { triggerPointActions } = useTriggerHandler();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { isReset } = useResetButtonStore();
|
const { isReset } = useResetButtonStore();
|
||||||
const { isPaused } = usePauseButtonStore();
|
const { isPaused } = usePauseButtonStore();
|
||||||
|
@ -65,6 +72,10 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
if (curve) {
|
if (curve) {
|
||||||
logStatus(armBot.modelUuid, "picking the object");
|
logStatus(armBot.modelUuid, "picking the object");
|
||||||
setPath(curve.points.map(point => [point.x, point.y, point.z]))
|
setPath(curve.points.map(point => [point.x, point.y, point.z]))
|
||||||
|
|
||||||
|
if (armBot.currentAction) {
|
||||||
|
setIsVisible(armBot.currentAction.materialId || '', false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
|
logStatus(armBot.modelUuid, "Moving armBot from start point to end position.")
|
||||||
|
@ -80,6 +91,18 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
if (curve) {
|
if (curve) {
|
||||||
logStatus(armBot.modelUuid, "dropping the object");
|
logStatus(armBot.modelUuid, "dropping the object");
|
||||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||||
|
|
||||||
|
if (armBot.currentAction) {
|
||||||
|
setIsVisible(armBot.currentAction.materialId || '', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (armBot.currentAction) {
|
||||||
|
const action = getActionByUuid(selectedProduct.productId, armBot.currentAction.actionUuid);
|
||||||
|
if (action && armBot.currentAction.materialId) {
|
||||||
|
triggerPointActions(action, armBot.currentAction.materialId)
|
||||||
|
removeCurrentAction(armBot.modelUuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
|
logStatus(armBot.modelUuid, "Moving armBot from end point to rest position.")
|
||||||
|
@ -137,10 +160,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
//Waiting for trigger.
|
//Waiting for trigger.
|
||||||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) {
|
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && !armBot.currentAction) {
|
||||||
logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction")
|
logStatus(armBot.modelUuid, "Waiting to trigger CurrentAction")
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
addCurrentAction(armBot.modelUuid, armBot.point.actions[0].actionUuid, 'Material 2');
|
|
||||||
}, 3000);
|
|
||||||
return () => clearTimeout(timeoutId);
|
|
||||||
}
|
}
|
||||||
//Moving to pickup point
|
//Moving to pickup point
|
||||||
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) {
|
else if (armBot && !armBot.isActive && armBot.state === "idle" && currentPhase === "rest" && armBot.currentAction) {
|
||||||
|
@ -219,7 +238,6 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||||
setArmBotState(armBot.modelUuid, "idle")
|
setArmBotState(armBot.modelUuid, "idle")
|
||||||
setCurrentPhase("rest");
|
setCurrentPhase("rest");
|
||||||
setPath([])
|
setPath([])
|
||||||
removeCurrentAction(armBot.modelUuid)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const logStatus = (id: string, status: string) => {
|
const logStatus = (id: string, status: string) => {
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
|
||||||
|
|
||||||
|
export function determineExecutionOrder(products: productsSchema): PointsScheme[] {
|
||||||
|
// Create maps for all events and points
|
||||||
|
const eventMap = new Map<string, EventsSchema>();
|
||||||
|
const pointMap = new Map<string, PointsScheme>();
|
||||||
|
const allPoints: PointsScheme[] = [];
|
||||||
|
|
||||||
|
// First pass: collect all points
|
||||||
|
products.forEach(product => {
|
||||||
|
product.eventDatas.forEach(event => {
|
||||||
|
eventMap.set(event.modelUuid, 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') {
|
||||||
|
pointMap.set(event.point.uuid, event.point);
|
||||||
|
allPoints.push(event.point);
|
||||||
|
} else if (event.type === 'roboticArm') {
|
||||||
|
pointMap.set(event.point.uuid, event.point);
|
||||||
|
allPoints.push(event.point);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build dependency graphs
|
||||||
|
const graph = new Map<string, string[]>();
|
||||||
|
const reverseGraph = new Map<string, string[]>();
|
||||||
|
const allTriggeredPoints = 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);
|
||||||
|
allTriggeredPoints.add(targetUuid);
|
||||||
|
|
||||||
|
if (!reverseGraph.has(targetUuid)) {
|
||||||
|
reverseGraph.set(targetUuid, []);
|
||||||
|
}
|
||||||
|
reverseGraph.get(targetUuid)!.push(point.uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.set(point.uuid, dependencies);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Identify root points (points that trigger others but aren't triggered themselves)
|
||||||
|
const rootPoints = allPoints
|
||||||
|
.filter(point => !allTriggeredPoints.has(point.uuid))
|
||||||
|
.filter(point => {
|
||||||
|
// Only include roots that actually have triggers pointing FROM them
|
||||||
|
const triggers = extractTriggersFromPoint(point);
|
||||||
|
return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no root points found but we have triggered points, find the earliest triggers
|
||||||
|
if (rootPoints.length === 0 && allTriggeredPoints.size > 0) {
|
||||||
|
// This handles cases where we have circular dependencies
|
||||||
|
// but still want to include the triggered points
|
||||||
|
const minTriggerCount = Math.min(
|
||||||
|
...Array.from(allTriggeredPoints)
|
||||||
|
.map(uuid => (graph.get(uuid) || []).length)
|
||||||
|
);
|
||||||
|
const potentialRoots = Array.from(allTriggeredPoints)
|
||||||
|
.filter(uuid => (graph.get(uuid) || []).length === minTriggerCount);
|
||||||
|
|
||||||
|
rootPoints.push(...potentialRoots.map(uuid => pointMap.get(uuid)!));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topological sort only for triggered points
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const temp = new Set<string>();
|
||||||
|
const order: string[] = [];
|
||||||
|
let hasCycle = false;
|
||||||
|
|
||||||
|
function visit(node: string) {
|
||||||
|
if (temp.has(node)) {
|
||||||
|
hasCycle = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (visited.has(node)) return;
|
||||||
|
|
||||||
|
temp.add(node);
|
||||||
|
|
||||||
|
const dependencies = reverseGraph.get(node) || [];
|
||||||
|
for (const dep of dependencies) {
|
||||||
|
visit(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.delete(node);
|
||||||
|
visited.add(node);
|
||||||
|
order.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start processing from root points
|
||||||
|
rootPoints.forEach(root => visit(root.uuid));
|
||||||
|
|
||||||
|
// Convert UUIDs back to points and filter out untriggered points
|
||||||
|
const triggeredPoints = order
|
||||||
|
.map(uuid => pointMap.get(uuid)!)
|
||||||
|
.filter(point => allTriggeredPoints.has(point.uuid) ||
|
||||||
|
rootPoints.some(root => root.uuid === point.uuid));
|
||||||
|
|
||||||
|
return triggeredPoints;
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
export function extractTriggersFromPoint(point: PointsScheme): TriggerSchema[] {
|
||||||
|
if ('actions' in point) {
|
||||||
|
return point.actions.flatMap(action => action.triggers);
|
||||||
|
} else if ('action' in point) {
|
||||||
|
return point.action.triggers;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import { useEffect } from 'react';
|
||||||
import { useProductStore } from '../../../store/simulation/useProductStore';
|
import { useProductStore } from '../../../store/simulation/useProductStore';
|
||||||
import { useActionHandler } from '../actions/useActionHandler';
|
import { useActionHandler } from '../actions/useActionHandler';
|
||||||
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
|
import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayButtonStore';
|
||||||
|
import { determineExecutionOrder } from './functions/determineExecutionOrder';
|
||||||
|
|
||||||
function Simulator() {
|
function Simulator() {
|
||||||
const { products } = useProductStore();
|
const { products } = useProductStore();
|
||||||
|
@ -17,231 +18,15 @@ function Simulator() {
|
||||||
const action = 'actions' in point ? point.actions[0] : point.action;
|
const action = 'actions' in point ? point.actions[0] : point.action;
|
||||||
handleAction(action);
|
handleAction(action);
|
||||||
});
|
});
|
||||||
}, [products, handleAction, isPlaying, isReset]);
|
}, [products, isPlaying, isReset]);
|
||||||
|
|
||||||
function determineExecutionOrder(products: productsSchema): PointsScheme[] {
|
return (
|
||||||
// Create maps for all events and points
|
|
||||||
const eventMap = new Map<string, EventsSchema>();
|
|
||||||
const pointMap = new Map<string, PointsScheme>();
|
|
||||||
const allPoints: PointsScheme[] = [];
|
|
||||||
|
|
||||||
// First pass: collect all points
|
<>
|
||||||
products.forEach(product => {
|
|
||||||
product.eventDatas.forEach(event => {
|
|
||||||
eventMap.set(event.modelUuid, 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') {
|
|
||||||
pointMap.set(event.point.uuid, event.point);
|
|
||||||
allPoints.push(event.point);
|
|
||||||
} else if (event.type === 'roboticArm') {
|
|
||||||
pointMap.set(event.point.uuid, event.point);
|
|
||||||
allPoints.push(event.point);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build dependency graphs
|
|
||||||
const graph = new Map<string, string[]>();
|
|
||||||
const reverseGraph = new Map<string, string[]>();
|
|
||||||
const allTriggeredPoints = 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);
|
|
||||||
allTriggeredPoints.add(targetUuid);
|
|
||||||
|
|
||||||
if (!reverseGraph.has(targetUuid)) {
|
|
||||||
reverseGraph.set(targetUuid, []);
|
|
||||||
}
|
|
||||||
reverseGraph.get(targetUuid)!.push(point.uuid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
graph.set(point.uuid, dependencies);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Identify root points (points that trigger others but aren't triggered themselves)
|
|
||||||
const rootPoints = allPoints
|
|
||||||
.filter(point => !allTriggeredPoints.has(point.uuid))
|
|
||||||
.filter(point => {
|
|
||||||
// Only include roots that actually have triggers pointing FROM them
|
|
||||||
const triggers = extractTriggersFromPoint(point);
|
|
||||||
return triggers.some(t => t.triggeredAsset?.triggeredPoint?.pointUuid);
|
|
||||||
});
|
|
||||||
|
|
||||||
// If no root points found but we have triggered points, find the earliest triggers
|
|
||||||
if (rootPoints.length === 0 && allTriggeredPoints.size > 0) {
|
|
||||||
// This handles cases where we have circular dependencies
|
|
||||||
// but still want to include the triggered points
|
|
||||||
const minTriggerCount = Math.min(
|
|
||||||
...Array.from(allTriggeredPoints)
|
|
||||||
.map(uuid => (graph.get(uuid) || []).length)
|
|
||||||
);
|
);
|
||||||
const potentialRoots = Array.from(allTriggeredPoints)
|
|
||||||
.filter(uuid => (graph.get(uuid) || []).length === minTriggerCount);
|
|
||||||
|
|
||||||
rootPoints.push(...potentialRoots.map(uuid => pointMap.get(uuid)!));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Topological sort only for triggered points
|
|
||||||
const visited = new Set<string>();
|
|
||||||
const temp = new Set<string>();
|
|
||||||
const order: string[] = [];
|
|
||||||
let hasCycle = false;
|
|
||||||
|
|
||||||
function visit(node: string) {
|
|
||||||
if (temp.has(node)) {
|
|
||||||
hasCycle = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (visited.has(node)) return;
|
|
||||||
|
|
||||||
temp.add(node);
|
|
||||||
|
|
||||||
const dependencies = reverseGraph.get(node) || [];
|
|
||||||
for (const dep of dependencies) {
|
|
||||||
visit(dep);
|
|
||||||
}
|
|
||||||
|
|
||||||
temp.delete(node);
|
|
||||||
visited.add(node);
|
|
||||||
order.push(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start processing from root points
|
|
||||||
rootPoints.forEach(root => visit(root.uuid));
|
|
||||||
|
|
||||||
// Convert UUIDs back to points and filter out untriggered points
|
|
||||||
const triggeredPoints = order
|
|
||||||
.map(uuid => pointMap.get(uuid)!)
|
|
||||||
.filter(point => allTriggeredPoints.has(point.uuid) ||
|
|
||||||
rootPoints.some(root => root.uuid === point.uuid));
|
|
||||||
|
|
||||||
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);
|
|
||||||
} else if ('action' in point) {
|
|
||||||
return point.action.triggers;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return <></>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Simulator;
|
export default Simulator;
|
|
@ -3,12 +3,14 @@ import { useActionHandler } from '../../actions/useActionHandler';
|
||||||
import { useProductStore } from '../../../../store/simulation/useProductStore';
|
import { useProductStore } from '../../../../store/simulation/useProductStore';
|
||||||
import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
|
import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore';
|
||||||
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
|
import { useMaterialStore } from '../../../../store/simulation/useMaterialStore';
|
||||||
|
import { useArmBotStore } from '../../../../store/simulation/useArmBotStore';
|
||||||
|
|
||||||
export function useTriggerHandler() {
|
export function useTriggerHandler() {
|
||||||
const { getEventByTriggerUuid, getEventByModelUuid } = useProductStore();
|
|
||||||
const { handleAction } = useActionHandler();
|
const { handleAction } = useActionHandler();
|
||||||
const { setCurrentLocation, setNextLocation, getMaterialById } = useMaterialStore();
|
|
||||||
const { selectedProduct } = useSelectedProduct();
|
const { selectedProduct } = useSelectedProduct();
|
||||||
|
const { getEventByTriggerUuid, getEventByModelUuid, getActionByUuid, getModelUuidByActionUuid } = useProductStore();
|
||||||
|
const { addCurrentAction, getArmBotById } = useArmBotStore();
|
||||||
|
const { setCurrentLocation, setNextLocation, getMaterialById, setIsPaused, setEndTime } = useMaterialStore();
|
||||||
|
|
||||||
const handleTrigger = (trigger: TriggerSchema, action: Action, materialId: string) => {
|
const handleTrigger = (trigger: TriggerSchema, action: Action, materialId: string) => {
|
||||||
|
|
||||||
|
@ -46,7 +48,38 @@ export function useTriggerHandler() {
|
||||||
|
|
||||||
} else if (toEvent?.type === 'roboticArm') {
|
} else if (toEvent?.type === 'roboticArm') {
|
||||||
// Transfer to Robotic Arm
|
// Transfer to Robotic Arm
|
||||||
|
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||||
|
const material = getMaterialById(materialId);
|
||||||
|
if (material) {
|
||||||
|
if (material.next) {
|
||||||
|
const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||||
|
if (armBot) {
|
||||||
|
if (armBot.isActive === false && armBot.state === 'idle') {
|
||||||
|
setCurrentLocation(material.materialId, {
|
||||||
|
modelUuid: material.next.modelUuid,
|
||||||
|
pointUuid: material.next.pointUuid,
|
||||||
|
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
setNextLocation(material.materialId, null);
|
||||||
|
|
||||||
|
setIsPaused(material.materialId, true);
|
||||||
|
addCurrentAction(
|
||||||
|
trigger.triggeredAsset?.triggeredModel.modelUuid,
|
||||||
|
trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||||
|
material.materialType,
|
||||||
|
material.materialId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Event Manager Needed
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleAction(action, materialId);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (toEvent?.type === 'storageUnit') {
|
} else if (toEvent?.type === 'storageUnit') {
|
||||||
// Transfer to Storage Unit
|
// Transfer to Storage Unit
|
||||||
|
|
||||||
|
@ -88,6 +121,31 @@ export function useTriggerHandler() {
|
||||||
} else if (fromEvent?.type === 'roboticArm') {
|
} else if (fromEvent?.type === 'roboticArm') {
|
||||||
if (toEvent?.type === 'transfer') {
|
if (toEvent?.type === 'transfer') {
|
||||||
// Robotic Arm to Transfer
|
// Robotic Arm to Transfer
|
||||||
|
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||||
|
const material = getMaterialById(materialId);
|
||||||
|
if (material) {
|
||||||
|
setIsPaused(material.materialId, false);
|
||||||
|
|
||||||
|
setCurrentLocation(material.materialId, {
|
||||||
|
modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid,
|
||||||
|
pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||||
|
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||||
|
|
||||||
|
if (action && action.triggers.length > 0 &&
|
||||||
|
action.triggers[0].triggeredAsset?.triggeredModel.modelUuid &&
|
||||||
|
action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid) {
|
||||||
|
setNextLocation(material.materialId, {
|
||||||
|
modelUuid: action.triggers[0].triggeredAsset?.triggeredModel.modelUuid,
|
||||||
|
pointUuid: action.triggers[0].triggeredAsset?.triggeredPoint?.pointUuid,
|
||||||
|
})
|
||||||
|
handleAction(action, material.materialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (toEvent?.type === 'vehicle') {
|
} else if (toEvent?.type === 'vehicle') {
|
||||||
// Robotic Arm to Vehicle
|
// Robotic Arm to Vehicle
|
||||||
|
@ -122,9 +180,55 @@ export function useTriggerHandler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleFinalAction = (action: Action, materialId: string) => {
|
||||||
|
if (!action) return;
|
||||||
|
|
||||||
|
const modelUuid = getModelUuidByActionUuid(selectedProduct.productId, action.actionUuid);
|
||||||
|
if (!modelUuid) return;
|
||||||
|
const finalModel = getEventByModelUuid(selectedProduct.productId, modelUuid);
|
||||||
|
if (!finalModel) return;
|
||||||
|
const material = getMaterialById(materialId);
|
||||||
|
|
||||||
|
if (finalModel.type === 'transfer') {
|
||||||
|
// Storage Unit to Transfer
|
||||||
|
|
||||||
|
if (material) {
|
||||||
|
const currentTime = performance.now();
|
||||||
|
|
||||||
|
setCurrentLocation(material.materialId, {
|
||||||
|
modelUuid: material.next?.modelUuid || '',
|
||||||
|
pointUuid: material.next?.pointUuid || '',
|
||||||
|
actionUuid: action.actionUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
setNextLocation(material.materialId, null);
|
||||||
|
|
||||||
|
setEndTime(material.materialId, currentTime);
|
||||||
|
|
||||||
|
handleAction(action, material.materialId);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (finalModel.type === 'vehicle') {
|
||||||
|
// Storage Unit to Vehicle
|
||||||
|
|
||||||
|
} else if (finalModel.type === 'machine') {
|
||||||
|
// Storage Unit to Machine
|
||||||
|
|
||||||
|
} else if (finalModel.type === 'roboticArm') {
|
||||||
|
// Storage Unit to Robotic Arm
|
||||||
|
|
||||||
|
} else if (finalModel.type === 'storageUnit') {
|
||||||
|
// Storage Unit to Storage Unit
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const triggerPointActions = useCallback((action: Action, materialId: string) => {
|
const triggerPointActions = useCallback((action: Action, materialId: string) => {
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
|
|
||||||
|
if (action.triggers.length > 0) {
|
||||||
|
|
||||||
action.triggers.forEach(trigger => {
|
action.triggers.forEach(trigger => {
|
||||||
switch (trigger.triggerType) {
|
switch (trigger.triggerType) {
|
||||||
case 'onStart':
|
case 'onStart':
|
||||||
|
@ -142,6 +246,9 @@ export function useTriggerHandler() {
|
||||||
console.warn(`Unknown trigger type: ${trigger.triggerType}`);
|
console.warn(`Unknown trigger type: ${trigger.triggerType}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
handleFinalAction(action, materialId);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface ArmBotStore {
|
||||||
) => void;
|
) => void;
|
||||||
clearArmBots: () => void;
|
clearArmBots: () => void;
|
||||||
|
|
||||||
addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string) => void;
|
addCurrentAction: (modelUuid: string, actionUuid: string, materialType: string, materialId: string) => void;
|
||||||
removeCurrentAction: (modelUuid: string) => void;
|
removeCurrentAction: (modelUuid: string) => void;
|
||||||
|
|
||||||
addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void;
|
addAction: (modelUuid: string, action: RoboticArmPointSchema['actions'][number]) => void;
|
||||||
|
@ -75,7 +75,7 @@ export const useArmBotStore = create<ArmBotStore>()(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addCurrentAction: (modelUuid, actionUuid, materialType) => {
|
addCurrentAction: (modelUuid, actionUuid, materialType, materialId) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
const armBot = state.armBots.find(a => a.modelUuid === modelUuid);
|
||||||
if (armBot) {
|
if (armBot) {
|
||||||
|
@ -84,7 +84,8 @@ export const useArmBotStore = create<ArmBotStore>()(
|
||||||
armBot.currentAction = {
|
armBot.currentAction = {
|
||||||
actionUuid: action.actionUuid,
|
actionUuid: action.actionUuid,
|
||||||
actionName: action.actionName,
|
actionName: action.actionName,
|
||||||
materialType: materialType
|
materialType: materialType,
|
||||||
|
materialId:materialId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { immer } from 'zustand/middleware/immer';
|
||||||
|
|
||||||
type MaterialsStore = {
|
type MaterialsStore = {
|
||||||
materials: MaterialsSchema;
|
materials: MaterialsSchema;
|
||||||
|
materialHistory: MaterialHistorySchema;
|
||||||
|
|
||||||
addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
|
addMaterial: (material: MaterialSchema) => MaterialSchema | undefined;
|
||||||
removeMaterial: (materialId: string) => MaterialSchema | undefined;
|
removeMaterial: (materialId: string) => MaterialSchema | undefined;
|
||||||
|
@ -20,20 +21,20 @@ type MaterialsStore = {
|
||||||
|
|
||||||
setNextLocation: (
|
setNextLocation: (
|
||||||
materialId: string,
|
materialId: string,
|
||||||
location?: {
|
location: {
|
||||||
modelUuid: string;
|
modelUuid: string;
|
||||||
pointUuid: string;
|
pointUuid: string;
|
||||||
} | null
|
} | null
|
||||||
) => MaterialSchema | undefined;
|
) => MaterialSchema | undefined;
|
||||||
|
|
||||||
setMaterial: (materialId: string, materialType: string) => MaterialSchema | undefined;
|
setMaterial: (materialId: string, materialType: string) => MaterialSchema | undefined;
|
||||||
setStartTime: (materialId: string, startTime: string) => MaterialSchema | undefined;
|
setStartTime: (materialId: string, startTime: number) => MaterialSchema | undefined;
|
||||||
setEndTime: (materialId: string, endTime: string) => MaterialSchema | undefined;
|
setEndTime: (materialId: string, endTime: number) => MaterialSchema | undefined;
|
||||||
setCost: (materialId: string, cost: number) => MaterialSchema | undefined;
|
setCost: (materialId: string, cost: number) => MaterialSchema | undefined;
|
||||||
setWeight: (materialId: string, weight: number) => MaterialSchema | undefined;
|
setWeight: (materialId: string, weight: number) => MaterialSchema | undefined;
|
||||||
setIsActive: (materialId: string, isActive: boolean) => MaterialSchema | undefined;
|
setIsActive: (materialId: string, isActive: boolean) => MaterialSchema | undefined;
|
||||||
setIsVisible: (materialId: string, isVisible: boolean) => MaterialSchema | undefined;
|
setIsVisible: (materialId: string, isVisible: boolean) => MaterialSchema | undefined;
|
||||||
setIsPaused: (materialId: string, isPlaying: boolean) => MaterialSchema | undefined;
|
setIsPaused: (materialId: string, isPaused: boolean) => MaterialSchema | undefined;
|
||||||
setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined;
|
setIsRendered: (materialId: string, isRendered: boolean) => MaterialSchema | undefined;
|
||||||
|
|
||||||
getMaterialById: (materialId: string) => MaterialSchema | undefined;
|
getMaterialById: (materialId: string) => MaterialSchema | undefined;
|
||||||
|
@ -41,11 +42,13 @@ type MaterialsStore = {
|
||||||
getMaterialByCurrentPointUuid: (currentPointUuid: string) => MaterialSchema | undefined;
|
getMaterialByCurrentPointUuid: (currentPointUuid: string) => MaterialSchema | undefined;
|
||||||
getMaterialsByPoint: (pointUuid: string) => MaterialSchema[];
|
getMaterialsByPoint: (pointUuid: string) => MaterialSchema[];
|
||||||
getMaterialsByModel: (modelUuid: string) => MaterialSchema[];
|
getMaterialsByModel: (modelUuid: string) => MaterialSchema[];
|
||||||
|
getMaterialHistory: () => MaterialHistorySchema;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useMaterialStore = create<MaterialsStore>()(
|
export const useMaterialStore = create<MaterialsStore>()(
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
materials: [],
|
materials: [],
|
||||||
|
materialHistory: [],
|
||||||
|
|
||||||
addMaterial: (material) => {
|
addMaterial: (material) => {
|
||||||
let updatedMaterial: MaterialSchema | undefined;
|
let updatedMaterial: MaterialSchema | undefined;
|
||||||
|
@ -60,8 +63,13 @@ export const useMaterialStore = create<MaterialsStore>()(
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const material = state.materials.find(m => m.materialId === materialId);
|
const material = state.materials.find(m => m.materialId === materialId);
|
||||||
if (material) {
|
if (material) {
|
||||||
state.materials.filter(m => m.materialId !== material.materialId);
|
state.materials = state.materials.filter(m => m.materialId !== materialId);
|
||||||
updatedMaterial = JSON.parse(JSON.stringify(material));
|
updatedMaterial = JSON.parse(JSON.stringify(material));
|
||||||
|
|
||||||
|
state.materialHistory.push({
|
||||||
|
material,
|
||||||
|
removedAt: new Date().toISOString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return updatedMaterial;
|
return updatedMaterial;
|
||||||
|
@ -102,7 +110,7 @@ export const useMaterialStore = create<MaterialsStore>()(
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const material = state.materials.find(m => m.materialId === materialId);
|
const material = state.materials.find(m => m.materialId === materialId);
|
||||||
if (material) {
|
if (material) {
|
||||||
material.next = location || undefined;
|
material.next = location;
|
||||||
updatedMaterial = JSON.parse(JSON.stringify(material));
|
updatedMaterial = JSON.parse(JSON.stringify(material));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -242,5 +250,9 @@ export const useMaterialStore = create<MaterialsStore>()(
|
||||||
m.next?.modelUuid === modelUuid
|
m.next?.modelUuid === modelUuid
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getMaterialHistory: () => {
|
||||||
|
return get().materialHistory;
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
);
|
);
|
|
@ -171,6 +171,7 @@ interface ArmBotStatus extends RoboticArmEventSchema {
|
||||||
actionUuid: string;
|
actionUuid: string;
|
||||||
actionName: string;
|
actionName: string;
|
||||||
materialType: string | null;
|
materialType: string | null;
|
||||||
|
materialId: string | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +202,8 @@ interface MaterialSchema {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
isRendered: boolean;
|
isRendered: boolean;
|
||||||
startTime?: string;
|
startTime?: number;
|
||||||
endTime?: string;
|
endTime?: number;
|
||||||
cost?: number;
|
cost?: number;
|
||||||
weight?: number;
|
weight?: number;
|
||||||
|
|
||||||
|
@ -215,7 +216,14 @@ interface MaterialSchema {
|
||||||
next?: {
|
next?: {
|
||||||
modelUuid: string;
|
modelUuid: string;
|
||||||
pointUuid: string;
|
pointUuid: string;
|
||||||
};
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type MaterialsSchema = MaterialSchema[];
|
type MaterialsSchema = MaterialSchema[];
|
||||||
|
|
||||||
|
interface MaterialHistoryEntry {
|
||||||
|
material: MaterialSchema;
|
||||||
|
removedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MaterialHistorySchema = MaterialHistoryEntry[];
|
Loading…
Reference in New Issue