-
- Current Version ({selectedVersion.versionName})
+ {selectedVersion && (
+
+
+
-
- {versions.length} Saved History
-
-
-
-
- {/* Versions List */}
-
- {versions.map((version, index) => (
-
);
diff --git a/app/src/components/layout/sidebarRight/versionHisory/VersionSaved.tsx b/app/src/components/layout/sidebarRight/versionHisory/VersionSaved.tsx
new file mode 100644
index 0000000..fb998fa
--- /dev/null
+++ b/app/src/components/layout/sidebarRight/versionHisory/VersionSaved.tsx
@@ -0,0 +1,186 @@
+import React, { useState, useEffect, useRef } from "react";
+import { useVersionStore } from "../../../../store/builder/store";
+import {
+ CloseIcon,
+ SaveIcon,
+ SaveVersionIcon,
+} from "../../../icons/ExportCommonIcons";
+import { RenameIcon } from "../../../icons/ContextMenuIcons";
+import RenderOverlay from "../../../templates/Overlay";
+
+const VersionSaved = () => {
+ const { versions, updateVersion } = useVersionStore();
+ const [isEditing, setIsEditing] = useState(false);
+ const [shouldDismiss, setShouldDismiss] = useState(false);
+ const [showNotification, setShowNotification] = useState(false);
+ const [newName, setNewName] = useState("");
+ const [description, setDescription] = useState("");
+ const prevVersionCount = useRef(versions.length);
+ const dismissTimerRef = useRef
(null);
+
+ const latestVersion = versions?.[0];
+
+ // Clear dismiss timer when component unmounts
+ useEffect(() => {
+ return () => {
+ if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
+ };
+ }, []);
+
+ // Handle new version notification and setup dismiss timer
+ useEffect(() => {
+ if (versions.length > prevVersionCount.current) {
+ setShowNotification(true);
+ setShouldDismiss(false);
+ setIsEditing(false);
+ setNewName(versions[0].versionName ?? "");
+ setDescription(versions[0]?.description ?? "");
+
+ // Only start dismiss timer if not in edit mode
+ if (!isEditing) {
+ startDismissTimer();
+ }
+
+ prevVersionCount.current = versions.length;
+ } else if (versions.length < prevVersionCount.current) {
+ prevVersionCount.current = versions.length;
+ }
+ }, [versions, isEditing]);
+
+ // Start or restart the dismiss timer
+ const startDismissTimer = (delay = 5000) => {
+ if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
+ dismissTimerRef.current = setTimeout(() => {
+ console.log("isEditing: ", isEditing);
+ setShouldDismiss(true);
+ }, delay);
+ };
+
+ // Hide notification after dismiss animation delay
+ useEffect(() => {
+ if (shouldDismiss) {
+ const timer = setTimeout(() => setShowNotification(false), 200);
+ return () => clearTimeout(timer);
+ }
+ }, [shouldDismiss]);
+
+ const handleEditName = () => {
+ setIsEditing(true);
+ setNewName(latestVersion?.versionName ?? "");
+ setDescription(latestVersion?.description ?? "");
+
+ // Clear any existing dismiss timer when editing starts
+ if (dismissTimerRef.current) {
+ clearTimeout(dismissTimerRef.current);
+ dismissTimerRef.current = null;
+ }
+ };
+
+ const handleFinishEdit = () => {
+ if (latestVersion) {
+ updateVersion(latestVersion.id, {
+ versionName: newName,
+ description,
+ });
+ console.log("saved");
+ startDismissTimer(); // Restart 5s timer after save
+ }
+ startDismissTimer(); // Restart 5s timer after save
+ setIsEditing(false);
+ };
+
+ const handleCancel = () => {
+ setIsEditing(false);
+ startDismissTimer(); // Restart 5s timer after cancel
+ };
+
+ const handleClose = () => {
+ setShouldDismiss(true);
+ if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
+ };
+
+ if (!showNotification || !latestVersion) return null;
+
+ return (
+
+
+
+
+
+
+
Saved New Version
+
+
+
+
+
+
+
+
+
+
+ New Version Created {latestVersion.versionLabel}{" "}
+ {latestVersion.timestamp.toUpperCase()}
+
+
Edit name
+
+
+
+ {isEditing && (
+
+
+
+
+
+
+
setNewName(e.target.value)}
+ placeholder="Enter new version name"
+ />
+
+ by @{latestVersion.savedBy}{" "}
+ {new Date(latestVersion.timestamp).toLocaleString("en-US", {
+ month: "short",
+ day: "numeric",
+ year: "2-digit",
+ hour: "numeric",
+ minute: "2-digit",
+ })}
+
+
+
+ {/* setDescription(e.target.value)}
+ placeholder="Add description"
+ /> */}
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default VersionSaved;
diff --git a/app/src/components/ui/compare/compare.tsx b/app/src/components/ui/compareVersion/Compare.tsx
similarity index 95%
rename from app/src/components/ui/compare/compare.tsx
rename to app/src/components/ui/compareVersion/Compare.tsx
index 6306520..f3d0b0b 100644
--- a/app/src/components/ui/compare/compare.tsx
+++ b/app/src/components/ui/compareVersion/Compare.tsx
@@ -1,9 +1,8 @@
-import React, { useState } from "react";
+import React from "react";
import { InfoIcon } from "../../icons/ShortcutIcons";
import { SaveDiskIcon } from "../../icons/ExportCommonIcons";
import { useCompareStore } from "../../../store/builder/store";
import OuterClick from "../../../utils/outerClick";
-import useToggleStore from "../../../store/useUIToggleStore";
interface ComparePopUpProps {
onClose: () => void;
diff --git a/app/src/components/ui/compare/CompareLayOut.tsx b/app/src/components/ui/compareVersion/CompareLayOut.tsx
similarity index 100%
rename from app/src/components/ui/compare/CompareLayOut.tsx
rename to app/src/components/ui/compareVersion/CompareLayOut.tsx
diff --git a/app/src/components/ui/menu/menu.tsx b/app/src/components/ui/menu/menu.tsx
index 745176b..d4f7989 100644
--- a/app/src/components/ui/menu/menu.tsx
+++ b/app/src/components/ui/menu/menu.tsx
@@ -4,6 +4,7 @@ import { ArrowIcon } from "../../icons/ExportCommonIcons";
import { toggleTheme } from "../../../utils/theme";
import useVersionHistoryStore, {
useShortcutStore,
+ useVersionStore,
} from "../../../store/builder/store";
import { useSubModuleStore } from "../../../store/useModuleStore";
@@ -20,6 +21,8 @@ interface MenuItem {
}
const MenuBar: React.FC = ({ setOpenMenu }) => {
+ const userName = localStorage.getItem("userName") ?? "Anonymous";
+
const navigate = useNavigate();
const [activeMenu, setActiveMenu] = useState(null);
const [activeSubMenu, setActiveSubMenu] = useState(null);
@@ -59,7 +62,32 @@ const MenuBar: React.FC = ({ setOpenMenu }) => {
File: [
{ label: "New File", shortcut: "Ctrl + N" },
{ label: "Open Local File", shortcut: "Ctrl + O" },
- { label: "Save Version" },
+ {
+ label: "Save Version",
+ action: () => {
+ const versionStore = useVersionStore.getState();
+ const versionCount = versionStore.versions.length;
+
+ const newVersion = {
+ id: crypto.randomUUID(),
+ versionLabel: `v${versionCount + 1}.0`,
+ timestamp: `${new Date().toLocaleTimeString("en-US", {
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true,
+ })} ${new Date().toLocaleDateString("en-US", {
+ year: "numeric",
+ month: "long",
+ day: "2-digit",
+ })}`,
+
+ savedBy: userName,
+ };
+
+ console.log("newVersion: ", newVersion);
+ versionStore.addVersion(newVersion);
+ },
+ },
{ label: "Make a Copy" },
{ label: "Share" },
{ label: "Rename" },
diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx
index 57ead56..18d407b 100644
--- a/app/src/modules/builder/builder.tsx
+++ b/app/src/modules/builder/builder.tsx
@@ -48,6 +48,7 @@ import NavMesh from "../simulation/vehicle/navMesh/navMesh";
import CalculateAreaGroup from "./groups/calculateAreaGroup";
import LayoutImage from "./layout/layoutImage";
import AssetsGroup from "./assetGroup/assetsGroup";
+import { Bvh } from "@react-three/drei";
export default function Builder() {
const state = useThree(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
@@ -203,29 +204,33 @@ export default function Builder() {
<>
-
+
+
+
-
+
+
+
-
+
+
-
+
-
+
-
+
-
+
- {/* */}
+ {/* */}
-
+
-
+
-
+
-
+
+
>
);
}
diff --git a/app/src/modules/scene/clouds/clouds.tsx b/app/src/modules/scene/clouds/clouds.tsx
new file mode 100644
index 0000000..f876530
--- /dev/null
+++ b/app/src/modules/scene/clouds/clouds.tsx
@@ -0,0 +1,90 @@
+import * as THREE from 'three';
+import { useRef, useState } from 'react';
+import { useFrame } from '@react-three/fiber';
+import { Clouds, Cloud } from '@react-three/drei';
+
+interface CloudGroupProps {
+ initialX: number;
+ initialZ: number;
+ speed: number;
+ height: number;
+}
+
+function CloudGroup({ initialX, initialZ, speed, height }: CloudGroupProps) {
+ const group = useRef(null);
+
+ useFrame((_, delta) => {
+ if (group.current) {
+
+ group.current.position.x += delta * speed;
+ group.current.position.z += delta * speed * 0.5;
+
+ if (group.current.position.x > 500) group.current.position.x = -500;
+ if (group.current.position.z > 500) group.current.position.z = -500;
+ }
+ });
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export function MovingClouds() {
+
+ const savedTheme: string | null = localStorage.getItem("theme");
+ const [theme, setTheme] = useState(savedTheme || "light");
+ const cloudGroups = [
+ { initialX: 0, initialZ: 0, speed: 8, height: 300 },
+ { initialX: -300, initialZ: 100, speed: 10, height: 300 },
+ { initialX: 200, initialZ: -150, speed: 4, height: 300 },
+ { initialX: -400, initialZ: -200, speed: 7, height: 300 },
+ { initialX: 400, initialZ: 300, speed: 5, height: 300 },
+ { initialX: -200, initialZ: -300, speed: 7, height: 300 },
+ { initialX: 300, initialZ: 200, speed: 10, height: 300 },
+ ];
+
+ return (
+ <>
+ {theme === 'light' &&
+ <>
+ {cloudGroups.map((group, index) => (
+
+ ))}
+ >
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/app/src/modules/scene/environment/ground.tsx b/app/src/modules/scene/environment/ground.tsx
index e8d36d1..ef51791 100644
--- a/app/src/modules/scene/environment/ground.tsx
+++ b/app/src/modules/scene/environment/ground.tsx
@@ -2,50 +2,37 @@ import { useTileDistance, useToggleView } from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants";
const Ground = ({ grid, plane }: any) => {
- const { toggleView } = useToggleView();
- const { planeValue, gridValue } = useTileDistance();
+ const { toggleView } = useToggleView();
+ const { planeValue, gridValue } = useTileDistance();
- return (
-
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+
+
+
+
+ );
};
export default Ground;
diff --git a/app/src/modules/scene/environment/shadow.tsx b/app/src/modules/scene/environment/shadow.tsx
index e44b9cd..59b348b 100644
--- a/app/src/modules/scene/environment/shadow.tsx
+++ b/app/src/modules/scene/environment/shadow.tsx
@@ -2,111 +2,105 @@ import { useRef, useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import {
- useAzimuth,
- useElevation,
- useShadows,
- useSunPosition,
- useFloorItems,
- useWallItems,
- useTileDistance,
+ useAzimuth,
+ useElevation,
+ useShadows,
+ useSunPosition,
+ useFloorItems,
+ useWallItems,
+ useTileDistance,
} from "../../../store/builder/store";
import * as CONSTANTS from "../../../types/world/worldConstants";
const shadowWorker = new Worker(
- new URL(
- "../../../services/factoryBuilder/webWorkers/shadowWorker",
- import.meta.url
- )
+ new URL(
+ "../../../services/factoryBuilder/webWorkers/shadowWorker",
+ import.meta.url
+ )
);
export default function Shadows() {
- const { shadows, setShadows } = useShadows();
- const { sunPosition, setSunPosition } = useSunPosition();
- const lightRef = useRef(null);
- const targetRef = useRef(null);
- const { controls, gl } = useThree();
- const { elevation, setElevation } = useElevation();
- const { azimuth, setAzimuth } = useAzimuth();
- const { floorItems } = useFloorItems();
- const { wallItems } = useWallItems();
- const { planeValue } = useTileDistance();
+ const { shadows, setShadows } = useShadows();
+ const { sunPosition, setSunPosition } = useSunPosition();
+ const lightRef = useRef(null);
+ const targetRef = useRef(null);
+ const { controls, gl } = useThree();
+ const { elevation, setElevation } = useElevation();
+ const { azimuth, setAzimuth } = useAzimuth();
+ const { floorItems } = useFloorItems();
+ const { wallItems } = useWallItems();
+ const { planeValue } = useTileDistance();
- useEffect(() => {
- gl.shadowMap.enabled = true;
- gl.shadowMap.type = THREE.PCFShadowMap;
- }, [gl, floorItems, wallItems]);
+ useEffect(() => {
+ gl.shadowMap.enabled = true;
+ gl.shadowMap.type = THREE.PCFShadowMap;
+ }, [gl, floorItems, wallItems]);
- useEffect(() => {
- if (lightRef.current && targetRef.current) {
- lightRef.current.target = targetRef.current;
- }
- }, []);
+ useEffect(() => {
+ if (lightRef.current && targetRef.current) {
+ lightRef.current.target = targetRef.current;
+ }
+ }, []);
- useEffect(() => {
- shadowWorker.onmessage = (event) => {
- const { lightPosition, controlsTarget } = event.data;
- if (lightRef.current && targetRef.current && controls) {
- lightRef.current.position.copy(lightPosition);
- targetRef.current.position.copy(controlsTarget);
- }
+ useEffect(() => {
+ shadowWorker.onmessage = (event) => {
+ const { lightPosition, controlsTarget } = event.data;
+ if (lightRef.current && targetRef.current && controls) {
+ lightRef.current.position.copy(lightPosition);
+ targetRef.current.position.copy(controlsTarget);
+ gl.shadowMap.needsUpdate = true;
+ }
+ };
+ }, [shadowWorker, controls]);
+
+ const updateShadows = () => {
+ if (controls && shadowWorker) {
+ const offsetDistance = CONSTANTS.shadowConfig.shadowOffset;
+ const controlsTarget = (controls as any).getTarget();
+ shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance });
+ }
};
- }, [shadowWorker, controls]);
- const updateShadows = () => {
- if (controls && shadowWorker) {
- const offsetDistance = CONSTANTS.shadowConfig.shadowOffset;
- const controlsTarget = (controls as any).getTarget();
- shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance });
- }
- };
+ useEffect(() => {
+ if (controls && shadows) {
+ updateShadows();
+ (controls as any).addEventListener("update", updateShadows);
+ return () => {
+ (controls as any).removeEventListener("update", updateShadows);
+ };
+ }
+ }, [controls, elevation, azimuth, shadows]);
- useEffect(() => {
- if (controls && shadows) {
- updateShadows();
- (controls as any).addEventListener("update", updateShadows);
- return () => {
- (controls as any).removeEventListener("update", updateShadows);
- };
- }
- }, [controls, elevation, azimuth, shadows]);
-
- return (
- <>
- {/* {(lightRef.current?.shadow) &&
-
- } */}
-
-
-
- {/*
- */}
-
-
-
- >
- );
+ return (
+ <>
+ {/* {(lightRef.current?.shadow) &&
+
+ } */}
+
+
+
+
+
+
+ >
+ );
}
diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx
index 0e81cf6..d8d3447 100644
--- a/app/src/modules/scene/scene.tsx
+++ b/app/src/modules/scene/scene.tsx
@@ -9,35 +9,32 @@ import Simulation from "../simulation/simulation";
import Collaboration from "../collaboration/collaboration";
export default function Scene() {
- const map = useMemo(
- () => [
- { name: "forward", keys: ["ArrowUp", "w", "W"] },
- { name: "backward", keys: ["ArrowDown", "s", "S"] },
- { name: "left", keys: ["ArrowLeft", "a", "A"] },
- { name: "right", keys: ["ArrowRight", "d", "D"] },
- ],
- []
- );
+ const map = useMemo(() => [
+ { name: "forward", keys: ["ArrowUp", "w", "W"] },
+ { name: "backward", keys: ["ArrowDown", "s", "S"] },
+ { name: "left", keys: ["ArrowLeft", "a", "A"] },
+ { name: "right", keys: ["ArrowRight", "d", "D"] },
+ ], []);
- return (
-
-
+
+ );
}
diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx
index 660264b..f3f8d15 100644
--- a/app/src/modules/scene/setup/setup.tsx
+++ b/app/src/modules/scene/setup/setup.tsx
@@ -5,6 +5,7 @@ import Controls from '../controls/controls';
import { Environment } from '@react-three/drei'
import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
+import { MovingClouds } from '../clouds/clouds';
function Setup() {
return (
@@ -17,6 +18,8 @@ function Setup() {
+
+
>
)
diff --git a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx
index f1fc34d..fa1b33b 100644
--- a/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx
+++ b/app/src/modules/simulation/conveyor/instances/conveyorInstance/conveyorInstance.tsx
@@ -2,36 +2,43 @@ import React, { useEffect } from 'react'
import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore';
import { useConveyorStore } from '../../../../../store/simulation/useConveyorStore';
import { useResetButtonStore } from '../../../../../store/usePlayButtonStore';
+import { findConveyorInSequences } from '../../../simulator/functions/findConveyorInSequences';
+import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
+import { useProductStore } from '../../../../../store/simulation/useProductStore';
function ConveyorInstance({ conveyor }: { conveyor: ConveyorStatus }) {
+ const { getProductById } = useProductStore();
+ const { selectedProduct } = useSelectedProduct();
const { materials, getMaterialsByCurrentModelUuid } = useMaterialStore();
const { isReset } = useResetButtonStore();
-
const { setConveyorPaused } = useConveyorStore();
useEffect(() => {
+ const product = getProductById(selectedProduct.productId);
+ if (!product) return;
+
+ const sequenceInfo = findConveyorInSequences(product, conveyor.modelUuid);
+ if (!sequenceInfo) return;
+
+ const { currentSubSequence } = sequenceInfo;
const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid);
- if (conveyorMaterials && conveyorMaterials?.length > 0) {
- const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused);
+ if (conveyorMaterials && conveyorMaterials.length > 0) {
+ const shouldPauseSubsequence = currentSubSequence.some(subConveyor => {
+ if (subConveyor.type !== 'transfer') return false;
+ const subMaterials = getMaterialsByCurrentModelUuid(subConveyor.modelUuid);
+ return subMaterials?.some(m => m.isPaused) ?? false;
+ });
- if (hasPausedMaterials) {
- setConveyorPaused(conveyor.modelUuid, true);
- } else {
- setConveyorPaused(conveyor.modelUuid, false);
- }
+ currentSubSequence.forEach(subConveyor => {
+ if (subConveyor.type === 'transfer') {
+ setConveyorPaused(subConveyor.modelUuid, shouldPauseSubsequence);
+ }
+ });
}
+ }, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset, selectedProduct.productId, getProductById]);
- }, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset]);
-
- useEffect(() => {
- // console.log('conveyor: ', conveyor);
- }, [conveyor])
-
- return (
- <>
- >
- )
+ return null;
}
-export default ConveyorInstance
\ No newline at end of file
+export default React.memo(ConveyorInstance);
\ No newline at end of file
diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx
index d56b05e..4bd0bcb 100644
--- a/app/src/modules/simulation/products/products.tsx
+++ b/app/src/modules/simulation/products/products.tsx
@@ -52,7 +52,7 @@ function Products() {
});
}
}
- }, [selectedProduct, products, isReset]);
+ }, [selectedProduct, products, isReset, isPlaying]);
useEffect(() => {
if (selectedProduct.productId) {
@@ -66,7 +66,7 @@ function Products() {
});
}
}
- }, [selectedProduct, products, isReset]);
+ }, [selectedProduct, products, isReset, isPlaying]);
useEffect(() => {
if (selectedProduct.productId) {
@@ -80,7 +80,7 @@ function Products() {
});
}
}
- }, [selectedProduct, products, isReset]);
+ }, [selectedProduct, products, isReset, isPlaying]);
useEffect(() => {
if (selectedProduct.productId) {
@@ -94,7 +94,7 @@ function Products() {
});
}
}
- }, [selectedProduct, products, isReset]);
+ }, [selectedProduct, products, isReset, isPlaying]);
useEffect(() => {
if (selectedProduct.productId) {
diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
index 0925b04..3427733 100644
--- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
+++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx
@@ -13,6 +13,7 @@ import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore
import { useStorageUnitStore } from '../../../../../store/simulation/useStorageUnitStore';
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
+import { useCheckActiveRoboticArmsInSubsequence } from '../../../simulator/functions/checkActiveRoboticArmsInSubsequence';
function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
@@ -30,13 +31,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
const { decrementVehicleLoad, removeLastMaterial } = useVehicleStore();
const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = useStorageUnitStore();
- const { setIsVisible, getMaterialById } = useMaterialStore();
+ const { setIsVisible, setIsPaused, getMaterialById } = useMaterialStore();
const { selectedProduct } = useSelectedProduct();
- const { getActionByUuid, getEventByActionUuid, getEventByModelUuid } = useProductStore();
+ const { getActionByUuid, getEventByActionUuid, getEventByModelUuid, getProductById } = useProductStore();
const { triggerPointActions } = useTriggerHandler();
const { isPlaying } = usePlayButtonStore();
const { isReset } = useResetButtonStore();
const { isPaused } = usePauseButtonStore();
+ const checkActiveRoboticArms = useCheckActiveRoboticArmsInSubsequence();
+
+ const lastRemoved = useRef<{ type: string, materialId: string } | null>(null);
function firstFrame() {
startTime = performance.now();
@@ -63,6 +67,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
removeLastStorageMaterial(previousModel.modelUuid);
updateCurrentLoad(previousModel.modelUuid, -1)
}
+ lastRemoved.current = { type: previousModel.type, materialId: armBot.currentAction.materialId };
} else {
setIsVisible(armBot.currentAction.materialId, false);
}
@@ -76,11 +81,20 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
if (armBot.currentAction) {
const action = getActionByUuid(selectedProduct.productId, armBot.currentAction.actionUuid);
+ const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid || '');
if (action && action.triggers[0].triggeredAsset?.triggeredModel.modelUuid) {
- const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid);
if (!model) return;
if (model.type === 'transfer') {
setIsVisible(armBot.currentAction.materialId || '', true);
+
+ const product = getProductById(selectedProduct.productId);
+ if (product) {
+ const result = checkActiveRoboticArms(product, armBot.modelUuid);
+ // console.log('result: ', result);
+ // if (result?.hasActiveRoboticArm) {
+ // lastRemoved.current = null;
+ // }
+ }
} else if (model.type === 'machine') {
//
} else if (model.type === 'vehicle') {
@@ -287,6 +301,14 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
setArmBotState(armBot.modelUuid, "idle")
setCurrentPhase("rest");
setPath([])
+
+ if (lastRemoved.current) {
+ if (lastRemoved.current.type === 'transfer') {
+ setIsPaused(lastRemoved.current.materialId, true)
+ } else {
+ setIsPaused(lastRemoved.current.materialId, false)
+ }
+ }
}
}
const logStatus = (id: string, status: string) => {
diff --git a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts
new file mode 100644
index 0000000..797f836
--- /dev/null
+++ b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts
@@ -0,0 +1,188 @@
+import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
+import { useArmBotStore } from "../../../../store/simulation/useArmBotStore";
+
+export function getRoboticArmSequencesInProduct(
+ product: {
+ productName: string;
+ productId: string;
+ eventDatas: EventsSchema[];
+ }
+): EventsSchema[][][] {
+ // Get all machine sequences for this product
+ const machineSequences = determineExecutionMachineSequences([product]);
+
+ const allRoboticArmSequences: EventsSchema[][][] = [];
+
+ // Process each machine sequence separately
+ for (const machineSequence of machineSequences) {
+ const roboticArmSequencesForThisMachineSequence: EventsSchema[][] = [];
+ let currentRoboticArmSequence: EventsSchema[] = [];
+
+ for (const event of machineSequence) {
+ if (event.type === 'roboticArm') {
+ // Add robotic arm to current sequence
+ currentRoboticArmSequence.push(event);
+ } else if (event.type === 'vehicle') {
+ // Vehicle encountered - split the sequence
+ if (currentRoboticArmSequence.length > 0) {
+ roboticArmSequencesForThisMachineSequence.push([...currentRoboticArmSequence]);
+ currentRoboticArmSequence = [];
+ }
+ }
+ // Other machine types continue the current sequence
+ }
+
+ // Add any remaining robotic arms in the current sequence
+ if (currentRoboticArmSequence.length > 0) {
+ roboticArmSequencesForThisMachineSequence.push([...currentRoboticArmSequence]);
+ }
+
+ if (roboticArmSequencesForThisMachineSequence.length > 0) {
+ allRoboticArmSequences.push(roboticArmSequencesForThisMachineSequence);
+ }
+ }
+
+ return allRoboticArmSequences;
+}
+
+export function findRoboticArmSubsequence(
+ product: {
+ productName: string;
+ productId: string;
+ eventDatas: EventsSchema[];
+ },
+ roboticArmModelUuid: string
+): {
+ allSequences: EventsSchema[][][];
+ parentSequence: EventsSchema[][];
+ currentSubSequence: EventsSchema[];
+} | null {
+ const allSequences = getRoboticArmSequencesInProduct(product);
+
+ for (const parentSequence of allSequences) {
+ for (const currentSubSequence of parentSequence) {
+ const hasTargetRoboticArm = currentSubSequence.some(
+ event => event.type === 'roboticArm' && event.modelUuid === roboticArmModelUuid
+ );
+
+ if (hasTargetRoboticArm) {
+ return {
+ allSequences,
+ parentSequence,
+ currentSubSequence
+ };
+ }
+ }
+ }
+
+ return null;
+}
+
+// React component/hook that uses the pure functions
+export function useCheckActiveRoboticArmsInSubsequence() {
+ const { getArmBotById } = useArmBotStore();
+
+ return function (product: {
+ productName: string;
+ productId: string;
+ eventDatas: EventsSchema[];
+ }, roboticArmModelUuid: string) {
+ const result = findRoboticArmSubsequence(product, roboticArmModelUuid);
+
+ if (!result) return null;
+
+ const hasActiveRoboticArm = result.currentSubSequence.some(event => {
+ if (event.type === 'roboticArm' && event.modelUuid !== roboticArmModelUuid) {
+ const armBot = getArmBotById(event.modelUuid);
+ return armBot?.isActive;
+ }
+ return false;
+ });
+
+ return {
+ ...result,
+ hasActiveRoboticArm
+ };
+ };
+}
+
+// Helper function to get machine sequences (simplified from your example)
+function determineExecutionMachineSequences(products: productsSchema): EventsSchema[][] {
+ const pointToEventMap = new Map();
+ const allPoints: PointsScheme[] = [];
+
+ // First pass: map points to their corresponding events
+ products.forEach(product => {
+ product.eventDatas.forEach(event => {
+ if (event.type === 'transfer') {
+ event.points.forEach(point => {
+ pointToEventMap.set(point.uuid, event);
+ allPoints.push(point);
+ });
+ } else if (
+ event.type === 'vehicle' ||
+ event.type === 'machine' ||
+ event.type === 'storageUnit' ||
+ event.type === 'roboticArm'
+ ) {
+ pointToEventMap.set(event.point.uuid, event);
+ allPoints.push(event.point);
+ }
+ });
+ });
+
+ // Build dependency graph
+ const dependencyGraph = new Map();
+ const triggeredPoints = new Set();
+
+ allPoints.forEach(point => {
+ const triggers = extractTriggersFromPoint(point);
+ const dependencies: string[] = [];
+
+ triggers.forEach(trigger => {
+ const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
+ if (targetUuid && pointToEventMap.has(targetUuid)) {
+ dependencies.push(targetUuid);
+ triggeredPoints.add(targetUuid);
+ }
+ });
+
+ dependencyGraph.set(point.uuid, dependencies);
+ });
+
+ // Find root points (points that aren't triggered by others)
+ const rootPoints = allPoints.filter(point =>
+ !triggeredPoints.has(point.uuid) &&
+ dependencyGraph.get(point.uuid)?.length
+ );
+
+ const executionSequences: EventsSchema[][] = [];
+
+ function buildSequence(startUuid: string): EventsSchema[] {
+ const sequence: EventsSchema[] = [];
+ const visited = new Set();
+
+ function traverse(uuid: string) {
+ if (visited.has(uuid)) return;
+ visited.add(uuid);
+
+ const event = pointToEventMap.get(uuid);
+ if (event && !sequence.includes(event)) {
+ sequence.push(event);
+ }
+
+ const nextPoints = dependencyGraph.get(uuid) || [];
+ nextPoints.forEach(nextUuid => traverse(nextUuid));
+ }
+
+ traverse(startUuid);
+ return sequence;
+ }
+
+ // Build sequences from root points
+ rootPoints.forEach(root => {
+ executionSequences.push(buildSequence(root.uuid));
+ });
+
+ return executionSequences;
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts b/app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts
new file mode 100644
index 0000000..3c9ee44
--- /dev/null
+++ b/app/src/modules/simulation/simulator/functions/findConveyorInSequences.ts
@@ -0,0 +1,49 @@
+import { getConveyorSequencesInProduct } from "./getConveyorSequencesForProduct";
+
+export function findConveyorInSequences(
+ product: {
+ productName: string;
+ productId: string;
+ eventDatas: EventsSchema[];
+ },
+ conveyorUuid: string
+): {
+ allSequences: EventsSchema[][][];
+ parentSequence: EventsSchema[][];
+ currentSubSequence: EventsSchema[];
+} | null {
+ // Get all conveyor sequences
+ const allSequences = getConveyorSequencesInProduct(product);
+
+ // Search through all sequences
+ for (const parentSequence of allSequences) {
+ for (const currentSubSequence of parentSequence) {
+ for (const conveyor of currentSubSequence) {
+ // Check if this is the conveyor we're looking for
+ if (conveyor.modelUuid === conveyorUuid) {
+ return {
+ allSequences,
+ parentSequence,
+ currentSubSequence
+ };
+ }
+
+ // Also check points in case the UUID matches a point's conveyor
+ if (conveyor.type === 'transfer') {
+ for (const point of conveyor.points) {
+ if (point.uuid === conveyorUuid) {
+ return {
+ allSequences,
+ parentSequence,
+ currentSubSequence
+ };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Conveyor not found
+ return null;
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts b/app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts
new file mode 100644
index 0000000..3aaaa9b
--- /dev/null
+++ b/app/src/modules/simulation/simulator/functions/getConveyorSequencesForProduct.ts
@@ -0,0 +1,126 @@
+import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
+
+export function getConveyorSequencesInProduct(
+ product: {
+ productName: string;
+ productId: string;
+ eventDatas: EventsSchema[];
+ }
+): EventsSchema[][][] { // Now returns array of array of arrays
+ // Get all machine sequences for this product
+ const machineSequences = determineExecutionMachineSequences([product]);
+
+ const allConveyorSequences: EventsSchema[][][] = [];
+
+ // Process each machine sequence separately
+ for (const machineSequence of machineSequences) {
+ const conveyorSequencesForThisMachineSequence: EventsSchema[][] = [];
+ let currentConveyorSequence: EventsSchema[] = [];
+
+ for (const event of machineSequence) {
+ if (event.type === 'transfer') {
+ // Add conveyor to current sequence
+ currentConveyorSequence.push(event);
+ } else if (event.type === 'vehicle') {
+ // Vehicle encountered - split the sequence
+ if (currentConveyorSequence.length > 0) {
+ conveyorSequencesForThisMachineSequence.push([...currentConveyorSequence]);
+ currentConveyorSequence = [];
+ }
+ }
+ // Other machine types don't affect the conveyor sequence
+ }
+
+ // Add any remaining conveyors in the current sequence
+ if (currentConveyorSequence.length > 0) {
+ conveyorSequencesForThisMachineSequence.push([...currentConveyorSequence]);
+ }
+
+ if (conveyorSequencesForThisMachineSequence.length > 0) {
+ allConveyorSequences.push(conveyorSequencesForThisMachineSequence);
+ }
+ }
+
+ return allConveyorSequences;
+}
+
+// Helper function to get machine sequences (simplified from your example)
+function determineExecutionMachineSequences(products: productsSchema): EventsSchema[][] {
+ const pointToEventMap = new Map();
+ const allPoints: PointsScheme[] = [];
+
+ // First pass: map points to their corresponding events
+ products.forEach(product => {
+ product.eventDatas.forEach(event => {
+ if (event.type === 'transfer') {
+ event.points.forEach(point => {
+ pointToEventMap.set(point.uuid, event);
+ allPoints.push(point);
+ });
+ } else if (
+ event.type === 'vehicle' ||
+ event.type === 'machine' ||
+ event.type === 'storageUnit' ||
+ event.type === 'roboticArm'
+ ) {
+ pointToEventMap.set(event.point.uuid, event);
+ allPoints.push(event.point);
+ }
+ });
+ });
+
+ // Build dependency graph
+ const dependencyGraph = new Map();
+ const triggeredPoints = new Set();
+
+ allPoints.forEach(point => {
+ const triggers = extractTriggersFromPoint(point);
+ const dependencies: string[] = [];
+
+ triggers.forEach(trigger => {
+ const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
+ if (targetUuid && pointToEventMap.has(targetUuid)) {
+ dependencies.push(targetUuid);
+ triggeredPoints.add(targetUuid);
+ }
+ });
+
+ dependencyGraph.set(point.uuid, dependencies);
+ });
+
+ // Find root points (points that aren't triggered by others)
+ const rootPoints = allPoints.filter(point =>
+ !triggeredPoints.has(point.uuid) &&
+ dependencyGraph.get(point.uuid)?.length
+ );
+
+ const executionSequences: EventsSchema[][] = [];
+
+ function buildSequence(startUuid: string): EventsSchema[] {
+ const sequence: EventsSchema[] = [];
+ const visited = new Set();
+
+ function traverse(uuid: string) {
+ if (visited.has(uuid)) return;
+ visited.add(uuid);
+
+ const event = pointToEventMap.get(uuid);
+ if (event && !sequence.includes(event)) {
+ sequence.push(event);
+ }
+
+ const nextPoints = dependencyGraph.get(uuid) || [];
+ nextPoints.forEach(nextUuid => traverse(nextUuid));
+ }
+
+ traverse(startUuid);
+ return sequence;
+ }
+
+ // Build sequences from root points
+ rootPoints.forEach(root => {
+ executionSequences.push(buildSequence(root.uuid));
+ });
+
+ return executionSequences;
+}
\ No newline at end of file
diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts
index 9936535..7de5d29 100644
--- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts
+++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts
@@ -138,6 +138,7 @@ export function useTriggerHandler() {
if (armBot.isActive === false && armBot.state === 'idle') {
// Handle current action from arm bot
+ setIsPaused(materialId, true);
handleAction(action, materialId);
} else {
@@ -291,6 +292,9 @@ export function useTriggerHandler() {
// Machine to Robotic Arm
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
const material = getMaterialById(materialId);
+
+ setIsPaused(materialId, true);
+
if (material) {
const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid);
const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid);
@@ -302,7 +306,7 @@ export function useTriggerHandler() {
})
setCurrentLocation(material.materialId, {
- modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid,
+ modelUuid: material.current.modelUuid,
pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
});
@@ -338,7 +342,7 @@ export function useTriggerHandler() {
const material = getMaterialById(materialId);
if (material) {
- setIsPaused(material.materialId, false);
+ // setIsPaused(material.materialId, false);
setPreviousLocation(material.materialId, {
modelUuid: material.current.modelUuid,
@@ -477,7 +481,7 @@ export function useTriggerHandler() {
const material = getMaterialById(materialId);
if (material) {
- setIsPaused(material.materialId, false);
+ // setIsPaused(material.materialId, false);
const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid);
const machine = getMachineById(trigger.triggeredAsset?.triggeredModel.modelUuid);
@@ -498,7 +502,7 @@ export function useTriggerHandler() {
})
setCurrentLocation(material.materialId, {
- modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid,
+ modelUuid: material.current.modelUuid,
pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
});
diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx
index df64dbe..3a45409 100644
--- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx
+++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx
@@ -280,26 +280,34 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
unLoadDuration: number,
action: VehicleAction
) {
- startTime = performance.now();
- const fixedInterval = unLoadDuration * (1000 / speed);
+ let lastIncrementTime = performance.now();
+ let pauseStartTime: number | null = null;
+ let totalPausedDuration = 0;
+ const fixedInterval = (unLoadDuration * 1000) / speed;
- const dropLoop = () => {
- if (isPausedRef.current) {
- pauseTimeRef.current ??= performance.now();
+ const dropLoop = (currentTime: number) => {
+ const conveyor = getConveyorById(conveyorId);
+
+ if (isPausedRef.current || (conveyor && conveyor.isPaused)) {
+ if (pauseStartTime === null) {
+ pauseStartTime = currentTime;
+ }
requestAnimationFrame(dropLoop);
return;
}
- if (pauseTimeRef.current) {
- const pauseDuration = performance.now() - pauseTimeRef.current;
- startTime += pauseDuration;
- pauseTimeRef.current = null;
+ // If we were paused but now resumed
+ if (pauseStartTime !== null) {
+ totalPausedDuration += currentTime - pauseStartTime;
+ pauseStartTime = null;
}
- const elapsedTime = performance.now() - startTime;
- const conveyor = getConveyorById(conveyorId);
- if (elapsedTime >= fixedInterval) {
- if (conveyor && !conveyor.isPaused && vehicleCurrentLoad > 0) {
+ // Adjust for paused time
+ const adjustedCurrentTime = currentTime - totalPausedDuration;
+ const elapsedSinceLastIncrement = adjustedCurrentTime - lastIncrementTime;
+
+ if (elapsedSinceLastIncrement >= fixedInterval) {
+ if (conveyor && vehicleCurrentLoad > 0) {
decrementVehicleLoad(vehicleId, 1);
vehicleCurrentLoad -= 1;
@@ -308,18 +316,18 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
triggerPointActions(action, material.materialId);
}
- if (vehicleCurrentLoad > 0) {
- startTime = performance.now();
- requestAnimationFrame(dropLoop);
- }
- } else if (!conveyor?.isActive) {
- requestAnimationFrame(dropLoop);
+ // Update the last increment time (using adjusted time)
+ lastIncrementTime = adjustedCurrentTime;
}
- } else {
+ }
+
+ // Continue the loop if there's more load to drop
+ if (vehicleCurrentLoad > 0) {
requestAnimationFrame(dropLoop);
}
};
- dropLoop();
+
+ requestAnimationFrame(dropLoop);
}
function handleMaterialDropToArmBot(model: RoboticArmEventSchema) {
diff --git a/app/src/modules/visualization/widgets/panel/AddButtons.tsx b/app/src/modules/visualization/widgets/panel/AddButtons.tsx
index 973154f..1055131 100644
--- a/app/src/modules/visualization/widgets/panel/AddButtons.tsx
+++ b/app/src/modules/visualization/widgets/panel/AddButtons.tsx
@@ -244,7 +244,7 @@ const AddButtons: React.FC = ({
{/* "+" Button */}
= ({
? "active"
: ""
}`}
- id="hide-panel-visulization"
+ id={`${side}-hide-panel-visulization`}
title={
hiddenPanels[selectedZone.zoneId]?.includes(side)
? "Show Panel"
@@ -301,7 +301,7 @@ const AddButtons: React.FC = ({
cleanPanel(side)}
style={{
cursor:
@@ -319,7 +319,7 @@ const AddButtons: React.FC = ({
className={`icon ${
selectedZone.lockedPanels.includes(side) ? "active" : ""
}`}
- id="lock-panel-visulization"
+ id={`${side}-lock-panel-visulization`}
title={
selectedZone.lockedPanels.includes(side)
? "Unlock Panel"
diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx
index 6a129ad..8419e13 100644
--- a/app/src/pages/Project.tsx
+++ b/app/src/pages/Project.tsx
@@ -20,7 +20,6 @@ import { useNavigate } from "react-router-dom";
import { usePlayButtonStore } from "../store/usePlayButtonStore";
import MarketPlace from "../modules/market/MarketPlace";
import LoadingPage from "../components/templates/LoadingPage";
-import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
import { useSelectedUserStore } from "../store/useCollabStore";
import FollowPerson from "../components/templates/FollowPerson";
@@ -34,9 +33,11 @@ import LogList from "../components/ui/log/LogList";
import Footer from "../components/footer/Footer";
import SelectFloorPlan from "../components/temporary/SelectFloorPlan";
import ControlsPlayer from "../components/layout/controls/ControlsPlayer";
-import CompareLayOut from "../components/ui/compare/CompareLayOut";
+import CompareLayOut from "../components/ui/compareVersion/CompareLayOut";
import useToggleStore from "../store/useUIToggleStore";
import RegularDropDown from "../components/ui/inputs/RegularDropDown";
+import VersionSaved from "../components/layout/sidebarRight/versionHisory/VersionSaved";
+import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
const Project: React.FC = () => {
let navigate = useNavigate();
@@ -56,7 +57,9 @@ const Project: React.FC = () => {
if (!isVersionSaved) {
setToggleUI(true, true);
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isVersionSaved]);
+
useEffect(() => {
setFloorItems([]);
setWallItems([]);
@@ -176,6 +179,7 @@ const Project: React.FC = () => {
>
)}
+
);
};
diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts
index 34108dc..e93078a 100644
--- a/app/src/store/builder/store.ts
+++ b/app/src/store/builder/store.ts
@@ -477,15 +477,52 @@ export const useCompareStore = create