diff --git a/app/src/components/layout/sidebarRight/SideBarRight.tsx b/app/src/components/layout/sidebarRight/SideBarRight.tsx index f5668bf..38f0b18 100644 --- a/app/src/components/layout/sidebarRight/SideBarRight.tsx +++ b/app/src/components/layout/sidebarRight/SideBarRight.tsx @@ -37,7 +37,7 @@ const SideBarRight: React.FC = () => { useEffect(() => { if (activeModule !== "mechanics" && selectedEventData && selectedEventSphere) { setSubModule("mechanics"); - } else { + } else if (!selectedEventData && !selectedEventSphere) { if (activeModule === 'simulation') { setSubModule("simulations"); } @@ -123,7 +123,7 @@ const SideBarRight: React.FC = () => { )} - {subModule === "mechanics" && selectedEventData && selectedEventSphere && ( + {subModule === "mechanics" && (
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index 6c44947..ca32594 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -1,52 +1,34 @@ import React, { useEffect, useRef, useState } from "react"; -import InputWithDropDown from "../../../../ui/inputs/InputWithDropDown"; -import LabledDropdown from "../../../../ui/inputs/LabledDropdown"; -import { - AddIcon, - RemoveIcon, - ResizeHeightIcon, -} from "../../../../icons/ExportCommonIcons"; -import RenameInput from "../../../../ui/inputs/RenameInput"; -import { handleResize } from "../../../../../functions/handleResizePannel"; -import { handleActionToggle } from "./functions/handleActionToggle"; -import { handleDeleteAction } from "./functions/handleDeleteAction"; -import DefaultAction from "./actions/DefaultAction"; -import SpawnAction from "./actions/SpawnAction"; -import SwapAction from "./actions/SwapAction"; -import DespawnAction from "./actions/DespawnAction"; -import TravelAction from "./actions/TravelAction"; -import PickAndPlaceAction from "./actions/PickAndPlaceAction"; -import ProcessAction from "./actions/ProcessAction"; -import StorageAction from "./actions/StorageAction"; -import Trigger from "./trigger/Trigger"; - import { useSelectedEventData, useSelectedProduct } from "../../../../../store/simulation/useSimulationStore"; import { useProductStore } from "../../../../../store/simulation/useProductStore"; +import ConveyorMechanics from "./mechanics/conveyorMechanics"; +import VehicleMechanics from "./mechanics/vehicleMechanics"; +import RoboticArmMechanics from "./mechanics/roboticArmMechanics"; +import MachineMechanics from "./mechanics/machineMechanics"; +import StorageMechanics from "./mechanics/storageMechanics"; const EventProperties: React.FC = () => { - const actionsContainerRef = useRef(null); - - const [activeOption, setActiveOption] = useState("default"); - const [selectedItem, setSelectedItem] = useState<{ item: { uuid: string; name: string } | null; }>({ item: null }); const { selectedEventData } = useSelectedEventData(); const { getEventByModelUuid } = useProductStore(); const { selectedProduct } = useSelectedProduct(); + const [currentEventData, setCurrentEventData] = useState(null); + const [assetType, setAssetType] = useState(null); useEffect(() => { - const actions = getActions(); - if (actions.length > 0 && !selectedItem.item) { - setSelectedItem({ item: actions[0] }); - } - }, [selectedEventData]); + const event = getCurrentEventData(); + setCurrentEventData(event); + + const type = determineAssetType(event); + setAssetType(type); + + }, [selectedEventData, selectedProduct]); const getCurrentEventData = () => { if (!selectedEventData?.data || !selectedProduct) return null; - const event = getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid); - return event || null; + return getEventByModelUuid(selectedProduct.productId, selectedEventData.data.modelUuid) || null; }; - const getAssetType = () => { - const event = getCurrentEventData(); + const determineAssetType = (event: EventsSchema | null) => { if (!event) return null; switch (event.type) { @@ -59,221 +41,24 @@ const EventProperties: React.FC = () => { } }; - const getAvailableActions = () => { - switch (getAssetType()) { - case "conveyor": - return { - defaultOption: "default", - options: ["default", "spawn", "swap", "despawn"], - }; - case "vehicle": - return { - defaultOption: "travel", - options: ["travel"], - }; - case "roboticArm": - return { - defaultOption: "pickAndPlace", - options: ["pickAndPlace"], - }; - case "machine": - return { - defaultOption: "process", - options: ["process"], - }; - case "storageUnit": - return { - defaultOption: "store", - options: ["store", "spawn"], - }; - default: - return { - defaultOption: "default", - options: ["default"], - }; - } - }; - - // Get actions based on asset type - const getActions = () => { - if (!selectedEventData?.data) return []; - - const event = selectedEventData.data; - switch (getAssetType()) { - case "conveyor": - return (event as ConveyorEventSchema).points - .find((point) => point.uuid === selectedEventData?.selectedPoint) - ?.action?.triggers.map((trigger) => ({ - uuid: trigger.triggerUuid, - name: trigger.triggerName, - })) || []; - case "vehicle": - return (event as VehicleEventSchema).point.action.triggers.map( - (trigger) => ({ - uuid: trigger.triggerUuid, - name: trigger.triggerName, - }) - ) || []; - case "roboticArm": - return (event as RoboticArmEventSchema).point.actions.flatMap( - (action) => - action.triggers.map((trigger) => ({ - uuid: trigger.triggerUuid, - name: trigger.triggerName, - })) - ) || []; - case "machine": - return (event as MachineEventSchema).point.action.triggers.map( - (trigger) => ({ - uuid: trigger.triggerUuid, - name: trigger.triggerName, - }) - ) || []; - case "storageUnit": - return [ - { - uuid: (event as StorageEventSchema).point.action.actionUuid, - name: (event as StorageEventSchema).point.action.actionName, - }, - ]; - default: - return []; - } - }; - - const handleActionToggle = (actionUuid: string) => { - const actions = getActions(); - const selected = actions.find(action => action.uuid === actionUuid); - if (selected) { - setSelectedItem({ item: selected }); - } - }; - - const handleDeleteAction = (actionUuid: string) => { - - }; - - const actions = getActions(); - - const getSpeed = () => { - if (!selectedEventData) return "0.5"; - if ("speed" in selectedEventData.data) return selectedEventData.data.speed.toString(); - return "0.5"; - }; - return ( <> - {getCurrentEventData() && -
-
-
{selectedEventData?.data.modelName}
-
-
-
-
- { }} - onChange={(value) => console.log(value)} - /> -
- -
- { }} - onChange={(value) => console.log(value)} - /> -
+
+ {currentEventData && + <> +
+
{selectedEventData?.data.modelName}
-
- {getAssetType() === 'roboticArm' && -
-
-
-
Actions
-
{ }}> - Add -
-
-
-
- {actions.map((action) => ( -
-
handleActionToggle(action.uuid)} - > - -
- {actions.length > 1 && ( -
handleDeleteAction(action.uuid)} - > - -
- )} -
- ))} -
-
handleResize(e, actionsContainerRef)} - > - -
-
-
-
- } -
-
- -
-
- setActiveOption(option)} - /> - {activeOption === "default" && } - {activeOption === "spawn" && } - {activeOption === "swap" && } - {activeOption === "despawn" && } - {activeOption === "travel" && } - {activeOption === "pickAndPlace" && } - {activeOption === "process" && } - {activeOption === "store" && } -
-
-
- -
-
- } + {assetType === 'conveyor' && } + {assetType === 'vehicle' && } + {assetType === 'roboticArm' && } + {assetType === 'machine' && } + {assetType === 'storageUnit' && } + + } +
); }; -export default EventProperties; +export default EventProperties; \ No newline at end of file diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx new file mode 100644 index 0000000..2bb63d4 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DelayAction.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface DelayActionProps { + value: string; + defaultValue: string; + min: number; + max: number; + onChange: (value: string) => void; +} + +const DelayAction: React.FC = ({ value, defaultValue, min, max, onChange }) => { + return ( + <> + { }} + onChange={onChange} + /> + + ); +}; + +export default DelayAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx index a0f081f..0d54476 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/DespawnAction.tsx @@ -4,17 +4,6 @@ import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; const DespawnAction: React.FC = () => { return ( <> - {}} - onChange={(value) => console.log(value)} - /> ); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx index 9574669..175a824 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/PickAndPlaceAction.tsx @@ -1,13 +1,25 @@ import React from "react"; import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; -const PickAndPlaceAction: React.FC = () => { - return ( - <> - {}} /> - {}} /> - - ); +interface PickAndPlaceActionProps { + pickPointValue: string; + pickPointOnChange: (value: string) => void; + placePointValue: string; + placePointOnChange: (value: string) => void; +} + +const PickAndPlaceAction: React.FC = ({ + pickPointValue, + pickPointOnChange, + placePointValue, + placePointOnChange, +}) => { + return ( + <> + + + + ); }; export default PickAndPlaceAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx index a27894e..331cf1b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/ProcessAction.tsx @@ -2,23 +2,47 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import SwapAction from "./SwapAction"; -const ProcessAction: React.FC = () => { - return ( - <> - {}} - onChange={(value) => console.log(value)} - /> - - - ); +interface ProcessActionProps { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + swapOptions: string[]; + swapDefaultOption: string; + onSwapSelect: (value: string) => void; +} + +const ProcessAction: React.FC = ({ + value, + min, + max, + defaultValue, + onChange, + swapOptions, + swapDefaultOption, + onSwapSelect, +}) => { + return ( + <> + { }} + onChange={onChange} + /> + + + ); }; export default ProcessAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx index 0c37cdb..7d8002e 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SpawnAction.tsx @@ -1,35 +1,72 @@ import React from "react"; import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; -const SpawnAction: React.FC = () => { - return ( - <> - {}} - onChange={(value) => console.log(value)} - /> - {}} - onChange={(value) => console.log(value)} - /> - - - ); +interface SpawnActionProps { + onChangeInterval: (value: string) => void; + onChangeCount: (value: string) => void; + defaultOption: string; + options: string[]; + onSelect: (option: string) => void; + intervalValue: string; + countValue: string; + intervalMin: number; + intervalMax: number; + intervalDefaultValue: string; + countMin: number; + countMax: number; + countDefaultValue: string; +} + +const SpawnAction: React.FC = ({ + onChangeInterval, + onChangeCount, + defaultOption, + options, + onSelect, + intervalValue, + countValue, + intervalMin, + intervalMax, + intervalDefaultValue, + countMin, + countMax, + countDefaultValue, +}) => { + return ( + <> + { }} + onChange={onChangeInterval} + /> + { }} + onChange={onChangeCount} + /> + {/* */} + + + ); }; export default SpawnAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx index ab2109b..5c9bf86 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/StorageAction.tsx @@ -1,20 +1,28 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -const StorageAction: React.FC = () => { - return ( - {}} - onChange={(value) => console.log(value)} - /> - ); +interface StorageActionProps { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; +} + +const StorageAction: React.FC = ({ value, min, max, defaultValue, onChange }) => { + return ( + { }} + onChange={onChange} + /> + ); }; export default StorageAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx index 2e18d80..23b203f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/SwapAction.tsx @@ -1,12 +1,27 @@ import React from "react"; import PreviewSelectionWithUpload from "../../../../../ui/inputs/PreviewSelectionWithUpload"; +import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; -const SwapAction: React.FC = () => { - return ( - <> - - - ); +interface SwapActionProps { + onSelect: (option: string) => void; + defaultOption: string; + options: string[]; +} + +const SwapAction: React.FC = ({ onSelect, defaultOption, options }) => { + + return ( + <> + {/* */} + + + + ); }; export default SwapAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx index ee4bda0..49eb683 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx @@ -2,35 +2,77 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; -const TravelAction: React.FC = () => { - return ( - <> - {}} - onChange={(value) => console.log(value)} - /> - {}} - onChange={(value) => console.log(value)} - /> - {}} /> - {}} /> - - ); +interface TravelActionProps { + loadCapacity: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + unloadDuration: { + value: string; + min: number; + max: number; + defaultValue: string; + onChange: (value: string) => void; + }; + pickPoint?: { + value: string; + onChange: (value: string) => void; + }; + unloadPoint?: { + value: string; + onChange: (value: string) => void; + }; +} + +const TravelAction: React.FC = ({ + loadCapacity, + unloadDuration, + pickPoint, + unloadPoint, +}) => { + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> + { }} + onChange={unloadDuration.onChange} + /> + {pickPoint && ( + + )} + {unloadPoint && ( + + )} + + ); }; export default TravelAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx index 000a9d7..c13bd75 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -1,8 +1,210 @@ -import React from 'react' +import { useEffect, useState } from 'react' +import InputWithDropDown from '../../../../../ui/inputs/InputWithDropDown' +import DelayAction from '../actions/DelayAction' +import RenameInput from '../../../../../ui/inputs/RenameInput' +import LabledDropdown from '../../../../../ui/inputs/LabledDropdown' +import DespawnAction from '../actions/DespawnAction' +import SwapAction from '../actions/SwapAction' +import SpawnAction from '../actions/SpawnAction' +import DefaultAction from '../actions/DefaultAction' +import Trigger from '../trigger/Trigger' +import { useSelectedEventData, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; function ConveyorMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "spawn" | "swap" | "delay" | "despawn">("default"); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as ConveyorPointSchema | undefined; + if (point && 'action' in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "default" | "spawn" | "swap" | "delay" | "despawn"); + } + } + }, [selectedProduct, selectedEventData]) + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent( + selectedProduct.productId, + selectedEventData.data.modelUuid, + { speed: parseFloat(value) } + ); + }; + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "default" | "spawn" | "swap" | "delay" | "despawn"; + setActiveOption(validOption); + + updateAction( + selectedPointData.action.actionUuid, + { actionType: validOption } + ); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { actionName: newName } + ); + }; + + const handleSpawnCountChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { spawnCount: value === "inherit" ? "inherit" : parseFloat(value) } + ); + }; + + const handleSpawnIntervalChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { spawnInterval: value === "inherit" ? "inherit" : parseFloat(value) } + ); + }; + + const handleMaterialSelect = (material: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { material } + ); + }; + + const handleDelayChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { delay: value === "inherit" ? "inherit" : parseFloat(value) } + ); + }; + + const availableActions = { + defaultOption: "default", + options: ["default", "spawn", "swap", "delay", "despawn"], + }; + + // Get current values from store + const currentSpeed = selectedEventData?.data.type === "transfer" + ? selectedEventData.data.speed.toString() + : "0.5"; + + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentMaterial = selectedPointData + ? selectedPointData.action.material + : "Default material"; + + const currentSpawnCount = selectedPointData + ? selectedPointData.action.spawnCount?.toString() || "1" + : "1"; + + const currentSpawnInterval = selectedPointData + ? selectedPointData.action.spawnInterval?.toString() || "1" + : "1"; + + const currentDelay = selectedPointData + ? selectedPointData.action.delay?.toString() || "0" + : "0"; + return ( <> + {selectedEventData && + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+ +
+
+ +
+
+ + {activeOption === "default" && + + } + {activeOption === "spawn" && + + } + {activeOption === "swap" && + + } + {activeOption === "despawn" && + + } + {activeOption === "delay" && + + } +
+
+
+ +
+ + } ) } diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx index b6e4fb9..e131864 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -1,8 +1,121 @@ -import React from 'react' +import { useEffect, useState } from 'react' +import RenameInput from '../../../../../ui/inputs/RenameInput' +import LabledDropdown from '../../../../../ui/inputs/LabledDropdown' +import Trigger from '../trigger/Trigger' +import { useSelectedEventData, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import ProcessAction from '../actions/ProcessAction' function MachineMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "process">("default"); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as MachinePointSchema | undefined; + if (point && 'action' in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "process"); + } + } + }, [selectedProduct, selectedEventData]) + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "process"; + setActiveOption(validOption); + + updateAction( + selectedPointData.action.actionUuid, + { actionType: validOption } + ); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { actionName: newName } + ); + }; + + const handleProcessTimeChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { processTime: parseFloat(value) } + ); + }; + + const handleMaterialSelect = (material: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { swapMaterial: material } + ); + }; + + // Get current values from store + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentProcessTime = selectedPointData + ? selectedPointData.action.processTime.toString() + : "1"; + + const currentMaterial = selectedPointData + ? selectedPointData.action.swapMaterial + : "Default material"; + + const availableActions = { + defaultOption: "process", + options: ["process"], + }; + return ( <> + {selectedEventData && + <> +
+
+ +
+
+ + {activeOption === "process" && + + } +
+
+
+ +
+ + } ) } diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx index 56e4c27..78a32f5 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -1,8 +1,274 @@ -import React from 'react' +import { useEffect, useRef, useState } from 'react' +import * as THREE from 'three'; +import InputWithDropDown from '../../../../../ui/inputs/InputWithDropDown' +import RenameInput from '../../../../../ui/inputs/RenameInput' +import LabledDropdown from '../../../../../ui/inputs/LabledDropdown' +import Trigger from '../trigger/Trigger' +import { useSelectedEventData, useSelectedProduct, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import { AddIcon, RemoveIcon, ResizeHeightIcon } from '../../../../../icons/ExportCommonIcons' +import { handleResize } from '../../../../../../functions/handleResizePannel' +import PickAndPlaceAction from '../actions/PickAndPlaceAction' function RoboticArmMechanics() { + const actionsContainerRef = useRef(null); + const [activeOption, setActiveOption] = useState<"default" | "pickAndPlace">("default"); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction, addAction, removeAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as RoboticArmPointSchema | undefined; + if (point) { + setSelectedPointData(point); + setActiveOption(point.actions[0].actionType as "default" | "pickAndPlace"); + if (point.actions.length > 0 && !selectedAction.actionId) { + setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); + } + } + } else { + clearSelectedAction(); + } + }, [selectedEventData, selectedProduct]); + + const handleActionSelect = (actionUuid: string, actionName: string) => { + setSelectedAction(actionUuid, actionName); + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction = { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "pickAndPlace" as "pickAndPlace", + process: { + startPoint: null, + endPoint: null + }, + triggers: [] as TriggerSchema[] + }; + + addAction( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + const updatedPoint = { + ...selectedPointData, + actions: [...selectedPointData.actions, newAction] + }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + removeAction(actionUuid); + const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid); + + const updatedPoint = { + ...selectedPointData, + actions: newActions + }; + setSelectedPointData(updatedPoint); + + if (selectedAction.actionId === actionUuid) { + if (newActions.length > 0) { + setSelectedAction(newActions[0].actionUuid, newActions[0].actionName); + } else { + clearSelectedAction(); + } + } + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + updateAction( + selectedAction.actionId, + { actionName: newName } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map(action => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ + ...selectedPointData, + actions: updatedActions + }); + } + }; + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent( + selectedProduct.productId, + selectedEventData.data.modelUuid, + { speed: parseFloat(value) } + ); + }; + + const handlePickPointChange = (value: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + const [x, y, z] = value.split(',').map(Number); + + updateAction( + selectedAction.actionId, + { + process: { + startPoint: [x, y, z] as [number, number, number], + endPoint: selectedPointData.actions.find(a => a.actionUuid === selectedAction.actionId)?.process.endPoint || null + } + } + ); + }; + + const handlePlacePointChange = (value: string) => { + if (!selectedAction.actionId || !selectedPointData) return; + const [x, y, z] = value.split(',').map(Number); + + updateAction( + selectedAction.actionId, + { + process: { + startPoint: selectedPointData.actions.find(a => a.actionUuid === selectedAction.actionId)?.process.startPoint || null, + endPoint: [x, y, z] as [number, number, number] + } + } + ); + }; + + const availableActions = { + defaultOption: "pickAndPlace", + options: ["pickAndPlace"], + }; + + const currentSpeed = selectedEventData?.data.type === "roboticArm" + ? selectedEventData.data.speed.toString() + : "0.5"; + + const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId); + const currentPickPoint = currentAction?.process.startPoint + ? `${currentAction.process.startPoint[0]},${currentAction.process.startPoint[1]},${currentAction.process.startPoint[2]}` + : ""; + const currentPlacePoint = currentAction?.process.endPoint + ? `${currentAction.process.endPoint[0]},${currentAction.process.endPoint[1]},${currentAction.process.endPoint[2]}` + : ""; + return ( <> + {selectedEventData && selectedPointData && ( + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+ +
+
+
+
Actions
+
+ Add +
+
+
+
+ {selectedPointData.actions.map((action) => ( +
+
handleActionSelect(action.actionUuid, action.actionName)} + > + +
+ {selectedPointData.actions.length > 1 && ( +
handleDeleteAction(action.actionUuid)} + > + +
+ )} +
+ ))} +
+
handleResize(e, actionsContainerRef)} + > + +
+
+
+
+ + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ { }} + disabled={true} + /> + +
+
+ +
+
+ )} + + )} ) } diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx index 0cda40e..593dcc3 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -1,8 +1,111 @@ -import React from 'react' +import { useEffect, useState } from 'react' +import RenameInput from '../../../../../ui/inputs/RenameInput' +import LabledDropdown from '../../../../../ui/inputs/LabledDropdown' +import Trigger from '../trigger/Trigger' +import { useSelectedEventData, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import StorageAction from '../actions/StorageAction'; function StorageMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default"); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData?.data.modelUuid, + selectedEventData?.selectedPoint + ) as StoragePointSchema | undefined; + if (point && 'action' in point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "store" | "spawn"); + } + } + }, [selectedProduct, selectedEventData]) + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "store" | "spawn"; + setActiveOption(validOption); + + updateAction( + selectedPointData.action.actionUuid, + { actionType: validOption } + ); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { actionName: newName } + ); + }; + + const handleCapacityChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { storageCapacity: parseInt(value) } + ); + }; + + // Get current values from store + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentCapacity = selectedPointData + ? selectedPointData.action.storageCapacity.toString() + : "0"; + + const availableActions = { + defaultOption: "store", + options: ["store", "spawn"], + }; + return ( <> + {selectedEventData && + <> +
+
+ +
+
+ + {activeOption === "store" && + + } + {activeOption === "spawn" && ( +
+

Spawn configuration options would go here

+
+ )} +
+
+
+ +
+ + } ) } diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index cdb32c4..cc2bfa0 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -1,8 +1,195 @@ -import React from 'react' +import { useEffect, useState } from 'react' +import InputWithDropDown from '../../../../../ui/inputs/InputWithDropDown' +import RenameInput from '../../../../../ui/inputs/RenameInput' +import LabledDropdown from '../../../../../ui/inputs/LabledDropdown' +import Trigger from '../trigger/Trigger' +import { useSelectedEventData, useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import TravelAction from '../actions/TravelAction' function VehicleMechanics() { + const [activeOption, setActiveOption] = useState<"default" | "travel">("default"); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { getPointByUuid, updateEvent, updateAction } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productId, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as VehiclePointSchema | undefined; + + if (point) { + setSelectedPointData(point); + setActiveOption(point.action.actionType as "travel"); + } + } + }, [selectedProduct, selectedEventData]) + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + updateEvent( + selectedProduct.productId, + selectedEventData.data.modelUuid, + { speed: parseFloat(value) } + ); + }; + + const handleActionTypeChange = (option: string) => { + if (!selectedEventData || !selectedPointData) return; + const validOption = option as "travel"; + setActiveOption(validOption); + + updateAction( + selectedPointData.action.actionUuid, + { actionType: validOption } + ); + }; + + const handleRenameAction = (newName: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { actionName: newName } + ); + }; + + const handleLoadCapacityChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { loadCapacity: parseFloat(value) } + ); + }; + + const handleUnloadDurationChange = (value: string) => { + if (!selectedPointData) return; + updateAction( + selectedPointData.action.actionUuid, + { unLoadDuration: parseFloat(value) } + ); + }; + + const handlePickPointChange = (value: string) => { + if (!selectedPointData) return; + const [x, y, z] = value.split(',').map(Number); + updateAction( + selectedPointData.action.actionUuid, + { pickUpPoint: { x, y, z } } + ); + }; + + const handleUnloadPointChange = (value: string) => { + if (!selectedPointData) return; + const [x, y, z] = value.split(',').map(Number); + updateAction( + selectedPointData.action.actionUuid, + { unLoadPoint: { x, y, z } } + ); + }; + + // Get current values from store + const currentSpeed = selectedEventData?.data.type === "vehicle" + ? selectedEventData.data.speed.toString() + : "0.5"; + + const currentActionName = selectedPointData + ? selectedPointData.action.actionName + : "Action Name"; + + const currentLoadCapacity = selectedPointData + ? selectedPointData.action.loadCapacity.toString() + : "1"; + + const currentUnloadDuration = selectedPointData + ? selectedPointData.action.unLoadDuration.toString() + : "1"; + + const currentPickPoint = selectedPointData?.action.pickUpPoint + ? `${selectedPointData.action.pickUpPoint.x},${selectedPointData.action.pickUpPoint.y},${selectedPointData.action.pickUpPoint.z}` + : ""; + + const currentUnloadPoint = selectedPointData?.action.unLoadPoint + ? `${selectedPointData.action.unLoadPoint.x},${selectedPointData.action.unLoadPoint.y},${selectedPointData.action.unLoadPoint.z}` + : ""; + + const availableActions = { + defaultOption: "travel", + options: ["travel"], + }; + return ( <> + {selectedEventData && + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+ +
+
+ +
+
+ + + {activeOption === 'travel' && + + } +
+
+
+ +
+ + } ) } diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 12a424c..926ce44 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -12,6 +12,7 @@ import { useProductStore } from "../../../../store/simulation/useProductStore"; import { generateUUID } from "three/src/math/MathUtils"; import RenderOverlay from "../../../templates/Overlay"; import EditWidgetOption from "../../../ui/menu/EditWidgetOption"; +import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; interface Event { pathName: string; @@ -75,6 +76,11 @@ const Simulations: React.FC = () => { const handleAddEventToProduct = () => { if (selectedAsset) { addEvent(selectedProduct.productId, selectedAsset); + // upsertProductOrEventApi({ + // productName: selectedProduct.productName, + // productId: selectedProduct.productId, + // eventDatas: selectedAsset + // }); clearSelectedAsset(); } }; @@ -90,7 +96,7 @@ const Simulations: React.FC = () => { (product) => product.productId === selectedProduct.productId ); - const events: Event[] = selectedProductData?.eventsData.map((event) => ({ + const events: Event[] = selectedProductData?.eventDatas.map((event) => ({ pathName: event.modelName, })) || []; diff --git a/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx b/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx index e2c2936..3e14517 100644 --- a/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx +++ b/app/src/components/ui/inputs/PreviewSelectionWithUpload.tsx @@ -35,12 +35,6 @@ const PreviewSelectionWithUpload: React.FC = () => { Upload here
- console.log(option)} - />
); diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index cac0b43..ac21c5a 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -142,9 +142,6 @@ const List: React.FC = ({ items = [], remove }) => { ) ); } - - console.log('newName: ', newName); - } const checkZoneNameDuplicate = (name: string) => { return zones.some( diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts index 2378bf3..c46c0e7 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts @@ -14,6 +14,7 @@ async function loadInitialFloorItems( itemsGroup: Types.RefGroup, setFloorItems: Types.setFloorItemSetState, addEvent: (event: EventsSchema) => void, + renderDistance: number ): Promise { if (!itemsGroup.current) return; let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; @@ -65,7 +66,7 @@ async function loadInitialFloorItems( const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z); - if (cameraPosition.distanceTo(itemPosition) < 50) { + if (cameraPosition.distanceTo(itemPosition) < renderDistance) { await new Promise(async (resolve) => { // Check Three.js Cache @@ -210,7 +211,6 @@ function processLoadedModel( actionUuid: THREE.MathUtils.generateUUID(), actionName: "Vehicle Action", actionType: "travel", - material: null, unLoadDuration: 5, loadCapacity: 10, pickUpPoint: null, @@ -243,9 +243,9 @@ function processLoadedModel( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: `Action ${index}`, + actionName: `Action ${index + 1}`, actionType: 'default', - material: 'inherit', + material: 'Default material', delay: 0, spawnInterval: 5, spawnCount: 1, diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index 670c7b5..d7c278c 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -202,7 +202,7 @@ async function handleModelLoad( actionUuid: THREE.MathUtils.generateUUID(), actionName: `Action ${index}`, actionType: 'default', - material: 'inherit', + material: 'Default Material', delay: 0, spawnInterval: 5, spawnCount: 1, @@ -226,9 +226,8 @@ async function handleModelLoad( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Vehicle Action", + actionName: "Action 1", actionType: "travel", - material: null, unLoadDuration: 5, loadCapacity: 10, pickUpPoint: null, @@ -254,11 +253,11 @@ async function handleModelLoad( actions: [ { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Pick and Place", + actionName: "Action 1", actionType: "pickAndPlace", process: { - startPoint: [0, 0, 0], - endPoint: [0, 0, 0] + startPoint: null, + endPoint: null }, triggers: [] } @@ -280,10 +279,10 @@ async function handleModelLoad( rotation: [0, 0, 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Process Action", + actionName: "Action 1", actionType: "process", processTime: 10, - swapMaterial: "material-id", + swapMaterial: "Default Material", triggers: [] } } diff --git a/app/src/modules/builder/groups/floorItemsGroup.tsx b/app/src/modules/builder/groups/floorItemsGroup.tsx index 241f628..f3f5050 100644 --- a/app/src/modules/builder/groups/floorItemsGroup.tsx +++ b/app/src/modules/builder/groups/floorItemsGroup.tsx @@ -75,7 +75,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject gltfLoaderWorker.postMessage({ floorItems: data }); } else { gltfLoaderWorker.postMessage({ floorItems: [] }); - loadInitialFloorItems(itemsGroup, setFloorItems, addEvent); + loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance); updateLoadingProgress(100); } }); @@ -94,7 +94,7 @@ const FloorItemsGroup = ({ itemsGroup, hoveredDeletableFloorItem, AttachedObject updateLoadingProgress(progress); if (loadedAssets === totalAssets) { - loadInitialFloorItems(itemsGroup, setFloorItems, addEvent); + loadInitialFloorItems(itemsGroup, setFloorItems, addEvent, renderDistance); updateLoadingProgress(100); } }); diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index f5e61d3..24a8ac6 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -243,8 +243,73 @@ function PointsCreator() { {activeModule === "simulation" && ( <> - {armBotStatusSample.map((event, i) => { - if (event.type === "roboticArm") { + {events.map((event, i) => { + if (event.type === "transfer") { + return ( + + {event.points.map((point, j) => ( + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + key={`${i}-${j}`} + position={new THREE.Vector3(...point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: point.uuid, + }} + > + + + + ))} + + ); + } else if (event.type === "vehicle") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); + } else if (event.type === "roboticArm") { const defaultPositions = getDefaultPositions(event.modelUuid); const isSelected = selectedPoint?.userData?.modelUuid === event.modelUuid; @@ -320,6 +385,37 @@ function PointsCreator() { })} ); + } else if (event.type === "machine") { + return ( + + (sphereRefs.current[event.point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + setSelectedEventSphere( + sphereRefs.current[event.point.uuid] + ); + }} + onPointerMissed={() => { + clearSelectedEventSphere(); + setTransformMode(null); + }} + position={new THREE.Vector3(...event.point.position)} + userData={{ + modelUuid: event.modelUuid, + pointUuid: event.point.uuid, + }} + > + + + + + ); } else { return null; } @@ -332,6 +428,3 @@ function PointsCreator() { } export default PointsCreator; - - - diff --git a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx new file mode 100644 index 0000000..9eececc --- /dev/null +++ b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx @@ -0,0 +1,124 @@ +import { useThree } from '@react-three/fiber' +import { useEffect } from 'react' +import { Object3D } from 'three'; +import { useSubModuleStore } from '../../../../store/useModuleStore'; +import { useLeftData, useTopData } from '../../../../store/visualization/useZone3DWidgetStore'; +import { useEventsStore } from '../../../../store/simulation/useEventsStore'; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; + +function AddOrRemoveEventsInProducts() { + const { gl, raycaster, scene } = useThree(); + const { subModule } = useSubModuleStore(); + const { setTop } = useTopData(); + const { setLeft } = useLeftData(); + const { getIsEventInProduct } = useProductStore(); + const { getEventByModelUuid } = useEventsStore(); + const { selectedProduct } = useSelectedProduct(); + const { selectedAsset, setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); + + useEffect(() => { + + const canvasElement = gl.domElement; + + let drag = false; + let isRightMouseDown = false; + + const onMouseDown = (evt: MouseEvent) => { + if (selectedAsset) { + clearSelectedAsset(); + } + if (evt.button === 2) { + isRightMouseDown = true; + drag = false; + } + }; + + const onMouseUp = (evt: MouseEvent) => { + if (evt.button === 2) { + isRightMouseDown = false; + } + } + + const onMouseMove = () => { + if (isRightMouseDown) { + drag = true; + } + }; + + const handleRightClick = (evt: MouseEvent) => { + if (drag) return; + evt.preventDefault(); + const canvasElement = gl.domElement; + if (!canvasElement) return; + + let intersects = raycaster.intersectObjects(scene.children, true); + if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { + let currentObject = intersects[0].object; + + while (currentObject) { + if (currentObject.name === "Scene") { + break; + } + currentObject = currentObject.parent as Object3D; + } + if (currentObject) { + const isInProduct = getIsEventInProduct(selectedProduct.productId, currentObject.uuid); + + if (isInProduct) { + const event = getEventByModelUuid(currentObject.uuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } else { + const event = getEventByModelUuid(currentObject.uuid); + if (event) { + setSelectedAsset(event) + const canvasRect = canvasElement.getBoundingClientRect(); + const relativeX = evt.clientX - canvasRect.left; + const relativeY = evt.clientY - canvasRect.top; + + setTop(relativeY); + setLeft(relativeX); + } else { + clearSelectedAsset() + } + } + + } + } else { + clearSelectedAsset() + } + + }; + + if (subModule === 'simulations') { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + canvasElement.addEventListener('contextmenu', handleRightClick); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + canvasElement.removeEventListener('contextmenu', handleRightClick); + }; + + }, [gl, subModule, selectedProduct, selectedAsset]); + return ( + <> + ) +} + +export default AddOrRemoveEventsInProducts \ 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 2ddd7d1..38175e2 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -1,7 +1,10 @@ -import React, { useEffect } from 'react' -import { useProductStore } from '../../../store/simulation/useProductStore' import * as THREE from 'three'; +import { useEffect } from 'react'; +import { useProductStore } from '../../../store/simulation/useProductStore'; import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; +import AddOrRemoveEventsInProducts from './events/addOrRemoveEventsInProducts'; +import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProductOrEventApi'; +import { getAllProductsApi } from '../../../services/simulation/getallProductsApi'; function Products() { const { products, addProduct } = useProductStore(); @@ -12,12 +15,27 @@ function Products() { const id = THREE.MathUtils.generateUUID(); const name = 'Product 1'; addProduct(name, id); + // upsertProductOrEventApi({ productName: name, productId: id }).then((data) => { + // console.log('data: ', data); + // }); setSelectedProduct(id, name); } }, [products]) + useEffect(() => { + // const email = localStorage.getItem('email') + // const organization = (email!.split("@")[1]).split(".")[0]; + // console.log(organization); + // getAllProductsApi(organization).then((data) => { + // console.log('data: ', data); + // }) + }, []) + return ( <> + + + ) } diff --git a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx index 6b35a43..7136a79 100644 --- a/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx +++ b/app/src/modules/simulation/roboticArm/instances/animator/roboticArmAnimator.tsx @@ -1,6 +1,64 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from "three" +import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; + +function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase, ikSolver, targetBone, robot, logStatus, groupRef, processes, armBotCurveRef, path }: any) { + const { armBots } = useArmBotStore(); + const { scene } = useThree(); + const restSpeed = 0.1; + const restPosition = new THREE.Vector3(0, 1, -1.6); + const initialCurveRef = useRef(null); + const initialStartPositionRef = useRef(null); + const [initialProgress, setInitialProgress] = useState(0); + const [progress, setProgress] = useState(0); + const [needsInitialMovement, setNeedsInitialMovement] = useState(true); + const [isInitializing, setIsInitializing] = useState(true); + const { isPlaying } = usePlayButtonStore(); + const statusRef = useRef("idle"); + // Create a ref for initialProgress + const initialProgressRef = useRef(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + + useEffect(() => { + setCurrentPath(path) + }, [path]) + + useEffect(() => { + + }, [currentPath]) + + useFrame((_, delta) => { + if (!ikSolver || !currentPath || currentPath.length === 0) return; + + const bone = ikSolver.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); + if (!bone) return; + + // Ensure currentPath is a valid array of 3D points, create a CatmullRomCurve3 from it + const curve = new THREE.CatmullRomCurve3( + currentPath.map(point => new THREE.Vector3(point[0], point[1], point[2])) + ); + + + const next = initialProgressRef.current + delta * 0.5; + if (next >= 1) { + // bone.position.copy(restPosition); + HandleCallback(); // Call the callback when the path is completed + initialProgressRef.current = 0; // Set ref to 1 when done + } else { + const point = curve.getPoint(next); // Get the interpolated point from the curve + bone.position.copy(point); // Update the bone position along the curve + initialProgressRef.current = next; // Update progress + } + + ikSolver.update(); + }); + + -function RoboticArmAnimator({ armUuid, HandleCallback, currentPhase }: any) { return ( <> ) diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 42b775a..7972895 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -1,58 +1,180 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import IKInstance from '../ikInstance/ikInstance'; import RoboticArmAnimator from '../animator/roboticArmAnimator'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useArmBotStore } from '../../../../../store/simulation/useArmBotStore'; +import armModel from "../../../../../assets/gltf-glb/rigged/ik_arm_4.glb"; +import { useThree } from "@react-three/fiber"; +import { useFloorItems } from '../../../../../store/store'; +import useModuleStore from '../../../../../store/useModuleStore'; +import { Vector3 } from "three"; +import * as THREE from "three"; + +interface Process { + triggerId: string; + startPoint?: Vector3; + endPoint?: Vector3; + speed: number; +} + +function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { -function RoboticArmInstance({ armBot }: any) { const { isPlaying } = usePlayButtonStore(); const [currentPhase, setCurrentPhase] = useState<(string)>("init"); - console.log('currentPhase: ', currentPhase); - const { armBots, addArmBot, addCurrentAction } = useArmBotStore(); + const { scene } = useThree(); + const targetBone = "Target"; + const { activeModule } = useModuleStore(); + const [ikSolver, setIkSolver] = useState(null); + const { addCurrentAction, setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore(); + const { floorItems } = useFloorItems(); + const groupRef = useRef(null); + const [processes, setProcesses] = useState([]); + const [armBotCurvePoints, setArmBotCurvePoints] = useState({ start: [], end: [] }) + const restPosition = new THREE.Vector3(0, 1, -1.6); + let armBotCurveRef = useRef(null) + const [path, setPath] = useState<[number, number, number][]>([]); useEffect(() => { + let armItems = floorItems?.filter((val: any) => + val.modeluuid === "3abf5d46-b59e-4e6b-9c02-a4634b64b82d" + ); + // Get the first matching item + let armItem = armItems?.[0]; + if (armItem) { + const targetMesh = scene?.getObjectByProperty("uuid", armItem.modeluuid); + if (targetMesh) { + targetMesh.visible = activeModule !== "simulation" + } + } + const targetBones = ikSolver?.mesh.skeleton.bones.find( + (b: any) => b.name === targetBone + ); - console.log('isPlaying: ', isPlaying); if (isPlaying) { //Moving armBot from initial point to rest position. - - if (armBot?.isActive && armBot?.state == "idle" && currentPhase == "init") { - addCurrentAction(armBot.modelUuid, 'action-001'); - setCurrentPhase("moving-to-rest"); - + if (!robot?.isActive && robot?.state == "idle" && currentPhase == "init") { + + setArmBotActive(robot.modelUuid, true) + setArmBotState(robot.modelUuid, "running") + setCurrentPhase("init-to-rest"); + if (targetBones) { + let curve = createCurveBetweenTwoPoints(targetBones.position, restPosition) + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from init to rest") } //Waiting for trigger. - if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "moving-to-rest") { - setCurrentPhase("rest"); + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && !robot.currentAction) { + console.log("trigger"); + setTimeout(() => { + addCurrentAction(robot.modelUuid, 'action-003'); + }, 3000); } - // Moving armBot from rest position to pick up point. - if (!armBot?.isActive && armBot?.state == "idle" && currentPhase == "rest") { - + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "rest" && robot.currentAction) { + if (robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("rest-to-start"); + const startPoint = robot.point.actions[0].process.startPoint; + if (startPoint) { + let curve = createCurveBetweenTwoPoints(restPosition, new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2])); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + } + logStatus(robot.modelUuid, "Starting from rest to start") } - //Moving arm from start point to end point. - if (armBot?.isActive && armBot?.state == "running " && currentPhase == "rest-to-start ") { - + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "picking" && robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("start-to-end"); + const startPoint = robot.point.actions[0].process.startPoint; + const endPoint = robot.point.actions[0].process.endPoint; + if (startPoint && endPoint) { + let curve = createCurveBetweenTwoPoints( + new THREE.Vector3(startPoint[0], startPoint[1], startPoint[2]), + new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]) + ); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from start to end") } - //Moving arm from end point to idle. - if (armBot?.isActive && armBot?.state == "running" && currentPhase == "end-to-start") { - + else if (robot && !robot.isActive && robot.state === "idle" && currentPhase === "dropping" && robot.currentAction) { + setArmBotActive(robot.modelUuid, true); + setArmBotState(robot.modelUuid, "running"); + setCurrentPhase("end-to-rest"); + const endPoint = robot.point.actions[0].process.endPoint; + if (endPoint) { + let curve = createCurveBetweenTwoPoints(new THREE.Vector3(endPoint[0], endPoint[1], endPoint[2]), restPosition + ); + if (curve) { + setPath(curve.points.map(point => [point.x, point.y, point.z])); + } + } + logStatus(robot.modelUuid, "Starting from end to rest") } - } - }, [currentPhase, armBot, isPlaying]) + }, [currentPhase, robot, isPlaying, ikSolver]) + + + function createCurveBetweenTwoPoints(p1: any, p2: any) { + const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5); + mid.y += 0.5; + + const points = [p1, mid, p2]; + return new THREE.CatmullRomCurve3(points); + } + const HandleCallback = () => { - if (armBot.isActive && armBot.state == "idle" && currentPhase == "init") { - addCurrentAction('armbot-xyz-001', 'action-001'); + if (robot.isActive && robot.state == "running" && currentPhase == "init-to-rest") { + console.log("Callback triggered: rest"); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("rest"); + setPath([]) } + else if (robot.isActive && robot.state == "running" && currentPhase == "rest-to-start") { + console.log("Callback triggered: pick."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("picking"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "start-to-end") { + console.log("Callback triggered: drop."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("dropping"); + setPath([]) + } + else if (robot.isActive && robot.state == "running" && currentPhase == "end-to-rest") { + console.log("Callback triggered: rest, cycle completed."); + setArmBotActive(robot.modelUuid, false) + setArmBotState(robot.modelUuid, "idle") + setCurrentPhase("rest"); + setPath([]) + removeCurrentAction(robot.modelUuid) + } + } + const logStatus = (id: string, status: string) => { + console.log(id +","+ status); } return ( <> - - + + ) diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx index 52a8610..7ae16e3 100644 --- a/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/ikInstance/ikInstance.tsx @@ -1,8 +1,97 @@ -import React from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' +import * as THREE from "three"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; +import { clone } from "three/examples/jsm/utils/SkeletonUtils"; +import { useFrame, useLoader, useThree } from "@react-three/fiber"; +import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver"; +type IKInstanceProps = { + modelUrl: string; + ikSolver: any; + setIkSolver: any + robot: any; + groupRef: React.RefObject; + processes: any; + setArmBotCurvePoints: any +}; +function IKInstance({ modelUrl, setIkSolver, ikSolver, robot, groupRef, processes, setArmBotCurvePoints }: IKInstanceProps) { + console.log('robot: ', robot.position, robot.rotation); + const { scene } = useThree(); + const gltf = useLoader(GLTFLoader, modelUrl, (loader) => { + const draco = new DRACOLoader(); + draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/"); + loader.setDRACOLoader(draco); + }); + const cloned = useMemo(() => clone(gltf?.scene), [gltf]); + const targetBoneName = "Target"; + const skinnedMeshName = "link_0"; + useEffect(() => { + if (!gltf) return; + const OOI: any = {}; + cloned.traverse((n: any) => { + if (n.name === targetBoneName) OOI.Target_Bone = n; + if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n; + }); + if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return; + const iks = [ + { + target: 7, + effector: 6, + links: [ + { + index: 5, + enabled: true, + rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0), + rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0), + }, + { + index: 4, + enabled: true, + rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0), + rotationMax: new THREE.Vector3(0, 0, 0), + }, + { + index: 3, + enabled: true, + rotationMin: new THREE.Vector3(0, 0, 0), + rotationMax: new THREE.Vector3(2, 0, 0), + }, + { index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) }, + { index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) }, + ], + }, + ]; + + const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks); + setIkSolver(solver); + + const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05); + if (solver) { + const bone = solver.mesh.skeleton.bones.find( + (b: any) => b.name === targetBoneName + ) ?? ""; + if (bone) { + const position = new THREE.Vector3(); + bone.getWorldPosition(position); + console.log("world position", position.x, position.y, position.z); // this is the bone's world position + } + } + scene.add(helper) + + + }, [gltf]); -function IKInstance() { return ( - <> + <> + + + + ) } diff --git a/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx b/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx deleted file mode 100644 index d44ddd2..0000000 --- a/app/src/modules/simulation/roboticArm/instances/ikInstances.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import IKInstance from './ikInstance/ikInstance'; - -function IkInstances() { - return ( - <> - - - - - ) -} - -export default IkInstances; \ No newline at end of file diff --git a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx index 1f963c8..1089fa5 100644 --- a/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx +++ b/app/src/modules/simulation/roboticArm/instances/roboticArmInstances.tsx @@ -5,17 +5,11 @@ import { useArmBotStore } from '../../../../store/simulation/useArmBotStore'; function RoboticArmInstances() { const { armBots } = useArmBotStore(); - return ( <> - { - armBots?.map((robot: any) => ( - - - ) - ) - - } + {armBots?.map((robot: ArmBotStatus) => ( + + ))} ) diff --git a/app/src/modules/simulation/roboticArm/roboticArm.tsx b/app/src/modules/simulation/roboticArm/roboticArm.tsx index 4295ac7..270c5e3 100644 --- a/app/src/modules/simulation/roboticArm/roboticArm.tsx +++ b/app/src/modules/simulation/roboticArm/roboticArm.tsx @@ -1,21 +1,86 @@ import { useEffect } from "react"; import RoboticArmInstances from "./instances/roboticArmInstances"; import { useArmBotStore } from "../../../store/simulation/useArmBotStore"; +import { useFloorItems } from "../../../store/store"; function RoboticArm() { - const { armBots, addArmBot, addCurrentAction } = useArmBotStore(); + const { armBots, addArmBot, removeArmBot } = useArmBotStore(); + const { floorItems } = useFloorItems(); const armBotStatusSample: RoboticArmEventSchema[] = [ { state: "idle", - // currentAction: { - // actionUuid: "action-001", - // actionName: "Pick Component", - // }, modelUuid: "armbot-xyz-001", modelName: "ArmBot-X200", position: [0, 0, 0], - rotation: [91.94347308985614, 0, 6.742905194869091], + rotation: [-3.141592653589793, 0.3861702258311774, -3.141592653589793], + type: "roboticArm", + speed: 1.5, + point: { + uuid: "point-123", + position: [0, 1.5, 0], + rotation: [0, 0, 0], + actions: [ + { + actionUuid: "action-003", + actionName: "Pick Component", + actionType: "pickAndPlace", + process: { + startPoint: [-1, 2, 1], + endPoint: [-2, 1, -1], + }, + triggers: [ + { + triggerUuid: "trigger-001", + triggerName: "Start Trigger", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "Conveyor A1", + modelUuid: "conveyor-01", + }, + triggeredPoint: { + pointName: "Start Point", + pointUuid: "conveyor-01-point-001", + }, + triggeredAction: { + actionName: "Move Forward", + actionUuid: "conveyor-action-01", + }, + }, + }, + { + triggerUuid: "trigger-002", + triggerName: "Complete Trigger", + triggerType: "onComplete", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: "StaticMachine B2", + modelUuid: "machine-02", + }, + triggeredPoint: { + pointName: "Receive Point", + pointUuid: "machine-02-point-001", + }, + triggeredAction: { + actionName: "Process Part", + actionUuid: "machine-action-01", + }, + }, + }, + ], + }, + ], + }, + }, + { + state: "idle", + modelUuid: "armbot-xyz-002", + modelName: "ArmBot-X200", + position: [95.94347308985614, 0, 6.742905194869091], + rotation: [0, 0, 0], type: "roboticArm", speed: 1.5, point: { @@ -28,8 +93,8 @@ function RoboticArm() { actionName: "Pick Component", actionType: "pickAndPlace", process: { - startPoint: [1.2, 0.3, 0.5], - endPoint: [-0.8, 1.1, 0.7], + startPoint: [2.52543010919071, 0, 8.433681161200905], + endPoint: [95.3438373267953, 0, 9.0279187421610025], }, triggers: [ { @@ -80,20 +145,20 @@ function RoboticArm() { ]; useEffect(() => { + + removeArmBot(armBotStatusSample[0].modelUuid); addArmBot('123', armBotStatusSample[0]); + // addArmBot('123', armBotStatusSample[1]); // addCurrentAction('armbot-xyz-001', 'action-001'); }, []); - useEffect(() => { - console.log('armBots: ', armBots); + // }, [armBots]); return ( <> - - ); } diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 757a9ef..5ca0ec5 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -23,7 +23,7 @@ function Simulation() { }, [events]) useEffect(() => { - console.log('products: ', products); + // console.log('products: ', products); }, [products]) return ( diff --git a/app/src/modules/simulation/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index 8cc22d6..c4f1c40 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -1,9 +1,16 @@ -import React from 'react' +import { useEffect } from 'react' +import { useProductStore } from '../../../store/simulation/useProductStore' function Simulator() { + const { products } = useProductStore(); + + useEffect(() => { + // console.log('products: ', products); + }, [products]) + return ( <> - + ) } diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx index b38e6dd..f836ea4 100644 --- a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx +++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx @@ -1,22 +1,16 @@ -import { useThree } from '@react-three/fiber' -import React, { useEffect } from 'react' -import { Object3D } from 'three'; -import { useSubModuleStore } from '../../../../store/useModuleStore'; -import { useLeftData, useTopData } from '../../../../store/visualization/useZone3DWidgetStore'; -import { useEventsStore } from '../../../../store/simulation/useEventsStore'; -import { useProductStore } from '../../../../store/simulation/useProductStore'; -import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; -import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore'; +import { useEffect } from "react"; +import { useThree } from "@react-three/fiber"; +import { useSubModuleStore } from "../../../../store/useModuleStore"; +import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; +import { useProductStore } from "../../../../store/simulation/useProductStore"; +import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; function TriggerConnector() { const { gl, raycaster, scene } = useThree(); const { subModule } = useSubModuleStore(); - const { setTop } = useTopData(); - const { setLeft } = useLeftData(); - const { getIsEventInProduct } = useProductStore(); - const { getEventByModelUuid } = useEventsStore(); + const { getPointByUuid, getIsEventInProduct } = useProductStore(); + const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); const { selectedProduct } = useSelectedProduct(); - const { selectedAsset, setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); useEffect(() => { @@ -57,48 +51,31 @@ function TriggerConnector() { if (intersects.length > 0 && intersects[0]?.object?.parent?.parent?.position && intersects[0]?.object?.parent?.parent?.scale && intersects[0]?.object?.parent?.parent?.rotation) { let currentObject = intersects[0].object; - while (currentObject) { - if (currentObject.name === "Scene") { - break; - } - currentObject = currentObject.parent as Object3D; - } - if (currentObject) { - const isInProduct = getIsEventInProduct(selectedProduct.productId, currentObject.uuid); + if (currentObject && currentObject.name === 'Event-Sphere') { + + const isInProduct = getIsEventInProduct( + selectedProduct.productId, + currentObject.userData.modelUuid + ); + + // You left Here if (isInProduct) { - const event = getEventByModelUuid(currentObject.uuid); - if (event) { - setSelectedAsset(event) - const canvasRect = canvasElement.getBoundingClientRect(); - const relativeX = evt.clientX - canvasRect.left; - const relativeY = evt.clientY - canvasRect.top; - setTop(relativeY); - setLeft(relativeX); - } else { - clearSelectedAsset() - } + const event = getPointByUuid( + selectedProduct.productId, + currentObject.userData.modelUuid, + currentObject.userData.pointUuid + ); + console.log('event: ', event); } else { - const event = getEventByModelUuid(currentObject.uuid); - if (event) { - setSelectedAsset(event) - const canvasRect = canvasElement.getBoundingClientRect(); - const relativeX = evt.clientX - canvasRect.left; - const relativeY = evt.clientY - canvasRect.top; - setTop(relativeY); - setLeft(relativeX); - } else { - clearSelectedAsset() - } } - } - } else { - clearSelectedAsset() - } + } else { + + } }; if (subModule === 'simulations') { @@ -115,7 +92,7 @@ function TriggerConnector() { canvasElement.removeEventListener('contextmenu', handleRightClick); }; - }, [gl, subModule, selectedProduct, selectedAsset]); + }, [gl, subModule]); return ( <> diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index cf5fd81..fc5303e 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -1,62 +1,170 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useFrame, useThree } from '@react-three/fiber'; +import { useFloorItems } from '../../../../../store/store'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; interface VehicleAnimatorProps { - path: [number, number, number][]; - handleCallBack: () => void; - currentPhase: string; - agvUuid: number + path: [number, number, number][]; + handleCallBack: () => void; + currentPhase: string; + agvUuid: number; + agvDetail: any; } +function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail }: VehicleAnimatorProps) { + const { decrementVehicleLoad, vehicles } = useVehicleStore(); + const { isPaused } = usePauseButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset } = useResetButtonStore(); + const [restRotation, setRestingRotation] = useState(true); + const [progress, setProgress] = useState(0); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + const progressRef = useRef(0); + const movingForward = useRef(true); + const completedRef = useRef(false); + let startTime: number; + let pausedTime: number; + let fixedInterval: number; -function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid }: VehicleAnimatorProps) { - const [progress, setProgress] = useState(0) - const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); - const { scene } = useThree(); + useEffect(() => { + if (currentPhase === 'stationed-pickup' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'pickup-drop' && path.length > 0) { + setCurrentPath(path); + } else if (currentPhase === 'drop-pickup' && path.length > 0) { + setCurrentPath(path); + } + }, [currentPhase, path]); - useEffect(() => { + useEffect(() => { + setProgress(0); + completedRef.current = false; + }, [currentPath]); - if (currentPhase === 'stationed-pickup' && path.length > 0) { - setCurrentPath(path); + useFrame((_, delta) => { + const object = scene.getObjectByProperty('uuid', agvUuid); + if (!object || currentPath.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + + for (let i = 0; i < currentPath.length - 1; i++) { + const start = new THREE.Vector3(...currentPath[i]); + const end = new THREE.Vector3(...currentPath[i + 1]); + const segmentDistance = start.distanceTo(end); + distances.push(segmentDistance); + totalDistance += segmentDistance; + } + + let coveredDistance = progressRef.current; + let accumulatedDistance = 0; + let index = 0; + + while ( + index < distances.length && + coveredDistance > accumulatedDistance + distances[index] + ) { + accumulatedDistance += distances[index]; + index++; + } + + if (index < distances.length) { + const start = new THREE.Vector3(...currentPath[index]); + const end = new THREE.Vector3(...currentPath[index + 1]); + const segmentDistance = distances[index]; + + const currentDirection = new THREE.Vector3().subVectors(end, start).normalize(); + const targetAngle = Math.atan2(currentDirection.x, currentDirection.z); + const rotationSpeed = 2.0; + const currentAngle = object.rotation.y; + + let angleDifference = targetAngle - currentAngle; + if (angleDifference > Math.PI) angleDifference -= 2 * Math.PI; + if (angleDifference < -Math.PI) angleDifference += 2 * Math.PI; + + const maxRotationStep = rotationSpeed * delta; + object.rotation.y += Math.sign(angleDifference) * Math.min(Math.abs(angleDifference), maxRotationStep); + + const isAligned = Math.abs(angleDifference) < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * agvDetail.speed); + coveredDistance = progressRef.current; + + const t = (coveredDistance - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation) { + const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); + object.quaternion.slerp(targetQuaternion, delta * 2); + const angleDiff = object.quaternion.angleTo(targetQuaternion); + if (angleDiff < 0.01) { + let objectRotation = agvDetail.point.rotation + object.rotation.set(objectRotation[0], objectRotation[1], objectRotation[2]); + setRestingRotation(false); } + return; + } + } - }, [currentPhase, path]) + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(firstFrame); + } + } + }); - useFrame((_, delta) => { - if (!path || path.length < 2) return; + function firstFrame() { + const unLoadDuration = agvDetail.point.action.unLoadDuration; + const droppedMaterial = agvDetail.currentLoad; + fixedInterval = (unLoadDuration / droppedMaterial) * 1000; + startTime = performance.now(); + step(droppedMaterial); + } - const object = scene.getObjectByProperty("uuid", agvUuid) - if (!object) return; + function step(droppedMaterial: number) { + const elapsedTime = performance.now() - startTime; - setProgress(prev => { - const next = prev + delta * 0.1; // speed - return next >= 1 ? 1 : next; - }); + if (elapsedTime >= fixedInterval) { + let droppedMat = droppedMaterial - 1; + decrementVehicleLoad(agvDetail.modelUuid, 1); + if (droppedMat === 0) return; + startTime = performance.now(); + requestAnimationFrame(() => step(droppedMat)); + } else { + requestAnimationFrame(() => step(droppedMaterial)); + } + } - const totalSegments = path.length - 1; - const segmentIndex = Math.floor(progress * totalSegments); - const t = progress * totalSegments - segmentIndex; - - const start = path[segmentIndex]; - const end = path[segmentIndex + 1] || start; - - // Directly set position without creating a new Vector3 - object.position.x = start[0] + (end[0] - start[0]) * t; - object.position.y = start[1] + (end[1] - start[1]) * t; - object.position.z = start[2] + (end[2] - start[2]) * t; - }); - // useFrame(() => { - // if (currentPath.length === 0) return; - // const object = scene.getObjectByProperty("uuid", agvUuid); - // if (!object) return; - - - - // }) - return ( + return ( + <> + {currentPath.length > 0 && ( <> + + {currentPath.map((point, index) => ( + + + + + ))} - ) + )} + + ); } -export default VehicleAnimator \ No newline at end of file +export default VehicleAnimator; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index bf767ec..d0691f9 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,66 +1,123 @@ -import React, { useCallback, useEffect, useState } from 'react' -import VehicleAnimator from '../animator/vehicleAnimator' -import * as THREE from "three"; +import React, { useCallback, useEffect, useState } from 'react'; +import VehicleAnimator from '../animator/vehicleAnimator'; +import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/store'; import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore'; -function VehicleInstance({ agvDetails }: any) { - const { navMesh } = useNavMesh(); - const { isPlaying } = usePlayButtonStore(); - const { setVehicleActive, setVehicleState } = useVehicleStore(); - const [currentPhase, setCurrentPhase] = useState<(string)>("stationed"); - const [path, setPath] = useState<[number, number, number][]>([]); +function VehicleInstance({ agvDetail }: any) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { vehicles, setVehicleActive, setVehicleState, incrementVehicleLoad } = useVehicleStore(); + const [currentPhase, setCurrentPhase] = useState('stationed'); + const [path, setPath] = useState<[number, number, number][]>([]); - const computePath = useCallback((start: any, end: any) => { + const computePath = useCallback( + (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + return ( + segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || [] + ); + } catch { + return []; + } + }, + [navMesh] + ); + function vehicleStatus(modelid: string, status: string) { + // console.log(`AGV ${modelid}: ${status}`); + } - try { - const navMeshQuery = new NavMeshQuery(navMesh); - const { path: segmentPath } = navMeshQuery.computePath(start, end); - return ( - segmentPath?.map( - ({ x, y, z }) => [x, y + 0.1, z] as [number, number, number] - ) || [] - ); - } catch { - return []; - } - }, [navMesh]); - - useEffect(() => { - - - if (isPlaying) { - if (!agvDetails.isActive && agvDetails.state == "idle" && currentPhase == "stationed") { - const toPickupPath = computePath(new THREE.Vector3(agvDetails.position[0], agvDetails.position[1], agvDetails.position[2]), agvDetails.point.action.pickUpPoint); - setPath(toPickupPath) - setVehicleActive(agvDetails.modelUuid, true) - setVehicleState(agvDetails.modelUuid, "running") - setCurrentPhase("stationed-pickup") - // - } - } - }, [agvDetails, currentPhase, path, isPlaying]) - - function handleCallBack() { - if (currentPhase === "stationed-pickup") { - setVehicleActive(agvDetails.modelUuid, false) - setVehicleState(agvDetails.modelUuid, "idle") - setCurrentPhase("picking") - setPath([]) + useEffect(() => { + if (isPlaying) { + if (!agvDetail.isActive && agvDetail.state === 'idle' && currentPhase === 'stationed') { + const toPickupPath = computePath( + new THREE.Vector3(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]), + agvDetail.point.action.pickUpPoint + ); + setPath(toPickupPath); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('stationed-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from station, heading to pickup'); + return; + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'picking' + ) { + + setTimeout(() => { + incrementVehicleLoad(agvDetail.modelUuid, 2); + }, 5000); + + if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { + const toDrop = computePath( + agvDetail.point.action.pickUpPoint, + agvDetail.point.action.unLoadPoint + ); + setPath(toDrop); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('pickup-drop'); + vehicleStatus(agvDetail.modelUuid, 'Started from pickup point, heading to drop point'); } + } else if ( + !agvDetail.isActive && + agvDetail.state === 'idle' && + currentPhase === 'dropping' && + agvDetail.currentLoad === 0 + ) { + const dropToPickup = computePath( + agvDetail.point.action.unLoadPoint, + agvDetail.point.action.pickUpPoint + ); + setPath(dropToPickup); + setVehicleActive(agvDetail.modelUuid, true); + setVehicleState(agvDetail.modelUuid, 'running'); + setCurrentPhase('drop-pickup'); + vehicleStatus(agvDetail.modelUuid, 'Started from dropping point, heading to pickup point'); + } } + }, [vehicles, currentPhase, path, isPlaying]); + function handleCallBack() { + if (currentPhase === 'stationed-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); + } else if (currentPhase === 'pickup-drop') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('dropping'); + vehicleStatus(agvDetail.modelUuid, 'Reached drop point'); + setPath([]); + } else if (currentPhase === 'drop-pickup') { + setVehicleActive(agvDetail.modelUuid, false); + setVehicleState(agvDetail.modelUuid, 'idle'); + setCurrentPhase('picking'); + setPath([]); + vehicleStatus(agvDetail.modelUuid, 'Reached pickup point again, cycle complete'); + } + } - return ( - <> - - - - - ) + return ( + <> + + + ); } -export default VehicleInstance \ No newline at end of file +export default VehicleInstance; \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 0848883..2a0070b 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -1,15 +1,14 @@ import React from 'react' import VehicleInstance from './instance/vehicleInstance' -import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'; +import { useVehicleStore } from '../../../../store/simulation/useVehicleStore' function VehicleInstances() { const { vehicles } = useVehicleStore(); - return ( <> {vehicles.map((val: any, i: any) => - + )} diff --git a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx index fa0d9dd..370304e 100644 --- a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx @@ -17,7 +17,7 @@ export default function PolygonGenerator({ useEffect(() => { let allLines = arrayLinesToObject(lines.current); const wallLines = allLines?.filter((line) => line?.type === "WallLine"); - const aisleLines = allLines?.filter((line) => line?.type === "AisleLine"); + const aisleLines = allLines?.filter((line) => line?.type === "AisleLine") const wallPoints = wallLines .map((pair) => pair?.line.map((vals) => vals.position)) @@ -39,9 +39,10 @@ export default function PolygonGenerator({ ); const polygons = turf.polygonize(turf.featureCollection(lineFeatures)); + renderWallGeometry(wallPoints); - if (polygons.features.length > 1) { + if (polygons.features.length > 0) { polygons.features.forEach((feature) => { if (feature.geometry.type === "Polygon") { diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 3364717..9aa89f4 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -1,17 +1,19 @@ import React, { useEffect } from 'react' import VehicleInstances from './instances/vehicleInstances'; - import { useVehicleStore } from '../../../store/simulation/useVehicleStore'; +import { useFloorItems } from '../../../store/store'; function Vehicles() { const { vehicles, addVehicle } = useVehicleStore(); + const { floorItems } = useFloorItems(); + const vehicleStatusSample: VehicleEventSchema[] = [ { - modelUuid: "veh-123", - modelName: "Autonomous Truck A1", - position: [10, 0, 5], + modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74", + modelName: "AGV", + position: [97.9252965204558, 0, 37.96138815638661], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -24,11 +26,10 @@ function Vehicles() { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", - material: "crate", - unLoadDuration: 15, - loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, - unLoadPoint: { x: 20, y: 0, z: 10 }, + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, + unLoadPoint: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, triggers: [ { triggerUuid: "trig-001", @@ -53,9 +54,51 @@ function Vehicles() { } }, { - modelUuid: "veh-123", - modelName: "Autonomous Truck A1", - position: [10, 0, 5], + modelUuid: "b06960bb-3d2e-41f7-a646-335f389c68b4", + modelName: "AGV", + position: [89.61609306554463, 0, 33.634136622267356], + rotation: [0, 0, 0], + state: "idle", + type: "vehicle", + speed: 2.5, + point: { + uuid: "point-789", + position: [0, 1, 0], + rotation: [0, 0, 0], + action: { + actionUuid: "action-456", + actionName: "Deliver to Zone A", + actionType: "travel", + unLoadDuration: 10, + loadCapacity: 2, + pickUpPoint: { x: 90, y: 0, z: 28 }, + unLoadPoint: { x: 20, y: 0, z: 10 }, + triggers: [ + { + triggerUuid: "trig-001", + triggerName: "Start Travel", + triggerType: "onStart", + delay: 0, + triggeredAsset: { + triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" }, + triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" }, + triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" } + } + }, + { + triggerUuid: "trig-002", + triggerName: "Complete Travel", + triggerType: "onComplete", + delay: 2, + triggeredAsset: null + } + ] + } + } + }, { + modelUuid: "e729a4f1-11d2-4778-8d6a-468f1b4f6b79", + modelName: "forklift", + position: [98.85729337188162, 0, 38.36616546567653], rotation: [0, 0, 0], state: "idle", type: "vehicle", @@ -68,10 +111,9 @@ function Vehicles() { actionUuid: "action-456", actionName: "Deliver to Zone A", actionType: "travel", - material: "crate", unLoadDuration: 15, loadCapacity: 5, - pickUpPoint: { x: 5, y: 0, z: 3 }, + pickUpPoint: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, unLoadPoint: { x: 20, y: 0, z: 10 }, triggers: [ { @@ -101,7 +143,8 @@ function Vehicles() { useEffect(() => { addVehicle('123', vehicleStatusSample[0]); - addVehicle('123', vehicleStatusSample[1]); + // addVehicle('123', vehicleStatusSample[1]); + // addVehicle('123', vehicleStatusSample[2]); }, []) useEffect(() => { @@ -111,9 +154,7 @@ function Vehicles() { return ( <> - - ) } diff --git a/app/src/services/simulation/UpsertProductOrEventApi.ts b/app/src/services/simulation/UpsertProductOrEventApi.ts new file mode 100644 index 0000000..e2f45d1 --- /dev/null +++ b/app/src/services/simulation/UpsertProductOrEventApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const upsertProductOrEventApi = async (body: any) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/UpsertProductOrEvent`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to add product or event"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/deleteEventDataApi.ts b/app/src/services/simulation/deleteEventDataApi.ts new file mode 100644 index 0000000..f263065 --- /dev/null +++ b/app/src/services/simulation/deleteEventDataApi.ts @@ -0,0 +1,26 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteEventDataApi = async (body: any) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/EventDataDelete`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error("Failed to delete event data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/deleteProductDataApi.ts b/app/src/services/simulation/deleteProductDataApi.ts new file mode 100644 index 0000000..06718f8 --- /dev/null +++ b/app/src/services/simulation/deleteProductDataApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const deleteProductDataApi = async (productId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productDataDelete?productId=${productId}&organization=${organization}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to delete product data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/simulation/getProductApi.ts b/app/src/services/simulation/getProductApi.ts new file mode 100644 index 0000000..cf80013 --- /dev/null +++ b/app/src/services/simulation/getProductApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getProductApi = async (productId: string, organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/productDatas?productId=${productId}&organization=${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch product data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/simulation/getallProductsApi.ts b/app/src/services/simulation/getallProductsApi.ts new file mode 100644 index 0000000..46627f9 --- /dev/null +++ b/app/src/services/simulation/getallProductsApi.ts @@ -0,0 +1,25 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getAllProductsApi = async ( organization: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/v2/AllProducts/${organization}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch all products data"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + throw new Error(error.message); + } else { + throw new Error("An unknown error occurred"); + } + } +}; \ No newline at end of file diff --git a/app/src/services/simulation/temp.md b/app/src/services/simulation/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/store/simulation/useArmBotStore.ts b/app/src/store/simulation/useArmBotStore.ts index d907f21..642762f 100644 --- a/app/src/store/simulation/useArmBotStore.ts +++ b/app/src/store/simulation/useArmBotStore.ts @@ -21,7 +21,7 @@ interface ArmBotStore { updateEndPoint: (modelUuid: string, actionUuid: string, endPoint: [number, number, number] | null) => void; setArmBotActive: (modelUuid: string, isActive: boolean) => void; - + setArmBotState: (modelUuid: string, newState: ArmBotStatus['state']) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; @@ -75,7 +75,6 @@ export const useArmBotStore = create()( actionUuid: action.actionUuid, actionName: action.actionName, }; - armBot.isActive = true; } } }); @@ -86,7 +85,6 @@ export const useArmBotStore = create()( const armBot = state.armBots.find(a => a.modelUuid === modelUuid); if (armBot) { armBot.currentAction = undefined; - armBot.isActive = false; } }); }, @@ -142,6 +140,15 @@ export const useArmBotStore = create()( }); }, + setArmBotState: (modelUuid, newState) => { + set((state) => { + const armBot = state.armBots.find(a => a.modelUuid === modelUuid); + if (armBot) { + armBot.state = newState; + } + }); + }, + incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 91576c7..8ec74cf 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -7,7 +7,7 @@ type ProductsStore = { // Product-level actions addProduct: (productName: string, productId: string) => void; removeProduct: (productId: string) => void; - updateProduct: (productId: string, updates: Partial<{ productName: string; eventsData: EventsSchema[] }>) => void; + updateProduct: (productId: string, updates: Partial<{ productName: string; eventDatas: EventsSchema[] }>) => void; // Event-level actions addEvent: (productId: string, event: EventsSchema) => void; @@ -54,7 +54,7 @@ type ProductsStore = { renameTrigger: (triggerUuid: string, newName: string) => void; // Helper functions - getProductById: (productId: string) => { productName: string; productId: string; eventsData: EventsSchema[] } | undefined; + getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined; getEventByModelUuid: (productId: string, modelUuid: string) => EventsSchema | undefined; getPointByUuid: (productId: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined; getActionByUuid: (productId: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined; @@ -72,7 +72,7 @@ export const useProductStore = create()( const newProduct = { productName, productId: productId, - eventsData: [] + eventDatas: [] }; state.products.push(newProduct); }); @@ -98,7 +98,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventsData.push(event); + product.eventDatas.push(event); } }); }, @@ -107,7 +107,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventsData = product.eventsData.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); + product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); } }); }, @@ -116,7 +116,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event) { Object.assign(event, updates); } @@ -129,7 +129,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); } else if (event && 'point' in event) { @@ -143,7 +143,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { (event as ConveyorEventSchema).points = (event as ConveyorEventSchema).points.filter(p => p.uuid !== pointUuid); } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { @@ -157,7 +157,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); if (point) { @@ -175,7 +175,7 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - const event = product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + const event = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { const point = (event as ConveyorEventSchema).points.find(p => p.uuid === pointUuid); if (point) { @@ -195,7 +195,7 @@ export const useProductStore = create()( removeAction: (actionUuid: string) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { // Handle ConveyorEventSchema for (const point of (event as ConveyorEventSchema).points) { @@ -219,7 +219,7 @@ export const useProductStore = create()( updateAction: (actionUuid, updates) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -249,7 +249,7 @@ export const useProductStore = create()( addTrigger: (actionUuid, trigger) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -278,7 +278,7 @@ export const useProductStore = create()( removeTrigger: (triggerUuid) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -305,7 +305,7 @@ export const useProductStore = create()( updateTrigger: (triggerUuid, updates) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -354,7 +354,7 @@ export const useProductStore = create()( renameAction: (actionUuid, newName) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { @@ -383,7 +383,7 @@ export const useProductStore = create()( renameTrigger: (triggerUuid, newName) => { set((state) => { for (const product of state.products) { - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && 'triggers' in point.action) { @@ -427,7 +427,7 @@ export const useProductStore = create()( getEventByModelUuid: (productId, modelUuid) => { const product = get().getProductById(productId); if (!product) return undefined; - return product.eventsData.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); + return product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); }, getPointByUuid: (productId, modelUuid, pointUuid) => { @@ -446,7 +446,7 @@ export const useProductStore = create()( const product = get().products.find(p => p.productId === productId); if (!product) return undefined; - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action?.actionUuid === actionUuid) { @@ -470,7 +470,7 @@ export const useProductStore = create()( const product = get().products.find(p => p.productId === productId); if (!product) return undefined; - for (const event of product.eventsData) { + for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { for (const trigger of point.action?.triggers || []) { @@ -504,7 +504,7 @@ export const useProductStore = create()( getIsEventInProduct: (productId, modelUuid) => { const product = get().getProductById(productId); if (!product) return false; - return product.eventsData.some(e => 'modelUuid' in e && e.modelUuid === modelUuid); + return product.eventDatas.some(e => 'modelUuid' in e && e.modelUuid === modelUuid); } })) ); diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index ae6ac81..5085688 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -90,4 +90,28 @@ export const useSelectedProduct = create()( }); }, })) +); + +interface SelectedActionState { + selectedAction: { actionId: string; actionName: string }; + setSelectedAction: (actionId: string, actionName: string) => void; + clearSelectedAction: () => void; +} + +export const useSelectedAction = create()( + immer((set) => ({ + selectedAction: { actionId: '', actionName: '' }, + setSelectedAction: (actionId, actionName) => { + set((state) => { + state.selectedAction.actionId = actionId; + state.selectedAction.actionName = actionName; + }); + }, + clearSelectedAction: () => { + set((state) => { + state.selectedAction.actionId = ''; + state.selectedAction.actionName = ''; + }); + }, + })) ); \ No newline at end of file diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index ce28916..449ceb7 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -1,3 +1,4 @@ + import { create } from 'zustand'; import { immer } from 'zustand/middleware/immer'; @@ -88,7 +89,7 @@ export const useVehicleStore = create()( set((state) => { const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); if (vehicle) { - vehicle.currentLoad = decrementBy; + vehicle.currentLoad -= decrementBy; } }); }, diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 12c0dc2..3293699 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -42,7 +42,6 @@ interface VehiclePointSchema { actionUuid: string; actionName: string; actionType: "travel"; - material: string | null; unLoadDuration: number; loadCapacity: number; pickUpPoint: { x: number; y: number, z: number } | null; @@ -126,7 +125,7 @@ type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSc type productsSchema = { productName: string; productId: string; - eventsData: EventsSchema[]; + eventDatas: EventsSchema[]; }[] @@ -135,6 +134,7 @@ interface ConveyorStatus extends ConveyorEventSchema { isActive: boolean; idleTime: number; activeTime: number; + } interface MachineStatus extends MachineEventSchema {