diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 818bd8a..6607937 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -6,145 +6,128 @@ import PositionInput from "../customInput/PositionInputs"; import RotationInput from "../customInput/RotationInput"; import { useSelectedFloorItem, useObjectPosition, useObjectRotation } from "../../../../store/builder/store"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; interface UserData { - id: number; // Unique identifier for the user data - label: string; // Label of the user data field - value: string; // Value of the user data field + id: number; + label: string; + value: string; } const AssetProperties: React.FC = () => { - const [userData, setUserData] = useState([]); // State to track user data - const [nextId, setNextId] = useState(1); // Unique ID for new entries - const { selectedFloorItem } = useSelectedFloorItem(); - const { objectPosition } = useObjectPosition(); - const { objectRotation } = useObjectRotation(); - const { assetStore } = useSceneContext(); - const { assets, setCurrentAnimation } = assetStore() - const [hoveredIndex, setHoveredIndex] = useState(null); - const [isPlaying, setIsplaying] = useState(false); - // Function to handle adding new user data - const handleAddUserData = () => { - const newUserData: UserData = { - id: nextId, - label: `Property ${nextId}`, - value: "", + const [userData, setUserData] = useState([]); + const { selectedFloorItem } = useSelectedFloorItem(); + const { objectPosition } = useObjectPosition(); + const { objectRotation } = useObjectRotation(); + const { assetStore } = useSceneContext(); + const { assets, setCurrentAnimation } = assetStore(); + const { loopAnimation } = useBuilderStore(); + const [hoveredIndex, setHoveredIndex] = useState(null); + + const handleAddUserData = () => { }; - setUserData([...userData, newUserData]); - setNextId(nextId + 1); // Increment the ID for the next entry - }; - // Function to update the value of a user data entry - const handleUserDataChange = (id: number, newValue: string) => { - setUserData((prevUserData) => - prevUserData.map((data) => - data.id === id ? { ...data, value: newValue } : data - ) - ); - }; + const handleUserDataChange = (id: number, newValue: string) => { + }; - // Remove user data - const handleRemoveUserData = (id: number) => { - setUserData((prevUserData) => - prevUserData.filter((data) => data.id !== id) - ); - }; + const handleRemoveUserData = (id: number) => { + }; - const handleAnimationClick = (animation: string) => { - if (selectedFloorItem) { - setCurrentAnimation(selectedFloorItem.uuid, animation, true); + const handleAnimationClick = (animation: string) => { + if (selectedFloorItem) { + setCurrentAnimation(selectedFloorItem.uuid, animation, true, loopAnimation, true); + } } - } - if (!selectedFloorItem) return null; + if (!selectedFloorItem) return null; - return ( -
- {/* Name */} -
{selectedFloorItem.userData.modelName}
-
- {objectPosition.x && objectPosition.z && - { }} - value1={parseFloat(objectPosition.x.toFixed(5))} - value2={parseFloat(objectPosition.z.toFixed(5))} - /> - } - {objectRotation.y && - { }} - value={parseFloat(objectRotation.y.toFixed(5))} - /> - } -
+ return ( +
+ {/* Name */} +
{selectedFloorItem.userData.modelName}
+
+ {objectPosition && + { }} + value1={parseFloat(objectPosition.x.toFixed(5))} + value2={parseFloat(objectPosition.z.toFixed(5))} + /> + } + {objectRotation && + { }} + value={parseFloat(objectRotation.y.toFixed(5))} + /> + } +
-
-
Render settings
- - -
+
+
Render settings
+ + +
-
-
User Data
- {userData.map((data) => ( -
- handleUserDataChange(data.id, newValue)} // Pass the change handler - /> -
handleRemoveUserData(data.id)} - > - -
-
- ))} +
+
User Data
+ {userData.map((data) => ( +
+ handleUserDataChange(data.id, newValue)} + /> +
handleRemoveUserData(data.id)} + > + +
+
+ ))} - {/* Add new user data */} -
- + Add -
-
-
- {selectedFloorItem.uuid &&
Animations
} - {assets.map((asset) => ( -
- {asset.modelUuid === selectedFloorItem.uuid && - asset.animations && - asset.animations.length > 0 && - asset.animations.map((animation, index) => ( -
-
handleAnimationClick(animation)} - onMouseEnter={() => setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(null)} - style={{ - height: "20px", - width: "100%", - borderRadius: "5px", - background: - hoveredIndex === index - ? "#7b4cd3" - : "transparent", - }} - > - {animation.charAt(0).toUpperCase() + - animation.slice(1).toLowerCase()} -
+ {/* Add new user data */} +
+ + Add
- ))} -
- ))} -
-
- ); +
+
+ {selectedFloorItem.uuid &&
Animations
} + {assets.map((asset) => ( +
+ {asset.modelUuid === selectedFloorItem.uuid && + asset.animations && + asset.animations.length > 0 && + asset.animations.map((animation, index) => ( +
+
handleAnimationClick(animation)} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + style={{ + height: "20px", + width: "100%", + borderRadius: "5px", + background: + hoveredIndex === index + ? "#7b4cd3" + : "transparent", + }} + > + {animation.charAt(0).toUpperCase() + + animation.slice(1).toLowerCase()} +
+
+ ))} +
+ ))} +
+
+ ); }; export default AssetProperties; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index ed799e5..ed9ef9f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -8,6 +8,7 @@ import VehicleMechanics from "./mechanics/vehicleMechanics"; import RoboticArmMechanics from "./mechanics/roboticArmMechanics"; import MachineMechanics from "./mechanics/machineMechanics"; import StorageMechanics from "./mechanics/storageMechanics"; +import HumanMechanics from "./mechanics/humanMechanics"; import { AddIcon } from "../../../../icons/ExportCommonIcons"; import { handleAddEventToProduct } from "../../../../../modules/simulation/events/points/functions/handleAddEventToProduct"; import { useProductContext } from "../../../../../modules/simulation/products/productContext"; @@ -61,6 +62,8 @@ const EventProperties: React.FC = () => { return "machine"; case "storageUnit": return "storageUnit"; + case "human": + return "human"; default: return null; } @@ -80,6 +83,7 @@ const EventProperties: React.FC = () => { {assetType === "roboticArm" && } {assetType === "machine" && } {assetType === "storageUnit" && } + {assetType === "human" && } )} {!currentEventData && selectedEventSphere && ( 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 219fce3..db11181 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/TravelAction.tsx @@ -1,95 +1,70 @@ import React from "react"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; -import EyeDropInput from "../../../../../ui/inputs/EyeDropInput"; 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; - }; - clearPoints: () => void; + 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; + }; + clearPoints: () => void; } const TravelAction: React.FC = ({ - loadCapacity, - unloadDuration, - pickPoint, - unloadPoint, - clearPoints, + loadCapacity, + unloadDuration, + clearPoints, }) => { - return ( - <> - {}} - onChange={loadCapacity.onChange} - /> - {}} - onChange={unloadDuration.onChange} - /> -
-
-
Reset
- -
-
- {pickPoint && ( - - )} - {unloadPoint && ( - - )} - - ); + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> + { }} + onChange={unloadDuration.onChange} + /> +
+
+
Reset
+ +
+
+ + ); }; export default TravelAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx new file mode 100644 index 0000000..6cda9a0 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/actions/workerAction.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; + +interface WorkerActionProps { + loadCapacity: { + value: string; + min: number; + max: number; + step: number; + defaultValue: string; + disabled?: boolean, + onChange: (value: string) => void; + }; + clearPoints: () => void; +} + +const WorkerAction: React.FC = ({ + loadCapacity, + clearPoints, +}) => { + return ( + <> + { }} + onChange={loadCapacity.onChange} + /> +
+
+
Reset
+ +
+
+ + ); +}; + +export default WorkerAction; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx new file mode 100644 index 0000000..9f2fd2d --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -0,0 +1,292 @@ +import { useEffect, useState } from "react"; +import { MathUtils } 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 ActionsList from "../components/ActionsList"; +import { useSelectedEventData, useSelectedAction, useSelectedAnimation } from "../../../../../../store/simulation/useSimulationStore"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; +import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; +import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; +import { useParams } from "react-router-dom"; +import WorkerAction from "../actions/workerAction"; + +function HumanMechanics() { + const [activeOption, setActiveOption] = useState<"worker">("worker"); + const [speed, setSpeed] = useState("0.5"); + const [loadCapacity, setLoadCapacity] = useState("1"); + const [currentAction, setCurrentAction] = useState(); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { productStore } = useSceneContext(); + const { getPointByUuid, updateEvent, updateAction, addAction, removeAction, getEventByModelUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + useEffect(() => { + if (selectedEventData && selectedEventData.data.type === "human") { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as HumanPointSchema | undefined; + + if (point?.action) { + setSelectedPointData(point); + setCurrentAction(point.action); + setSelectedAction(point.action.actionUuid, point.action.actionName); + setSpeed(( + getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData?.data.modelUuid || "" + ) as HumanEventSchema | undefined + )?.speed?.toString() || "1"); + setLoadCapacity(point.action.loadCapacity.toString()); + } + } else { + clearSelectedAction(); + } + }, [selectedEventData, selectedProduct]); + + useEffect(() => { + if (selectedEventData && selectedProduct.productUuid) { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as HumanPointSchema | undefined; + + if (point?.action) { + setSelectedPointData(point); + setCurrentAction(point.action); + setActiveOption(point.action.actionType); + setSelectedAction(point.action.actionUuid, point.action.actionName); + } + } else { + clearSelectedAction(); + setCurrentAction(undefined); + setSpeed("0.5"); + setLoadCapacity("1"); + } + }, [selectedEventData, selectedProduct, selectedAction]); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName, + productUuid, + projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || "", + }); + }; + + const handleSelectActionType = (actionType: string) => { + if (!selectedAction.actionId || !currentAction || !selectedPointData) return; + + const updatedAction = { ...currentAction, actionType: actionType as "worker" }; + const updatedPoint = { ...selectedPointData, action: updatedAction }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + }; + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + + const numericValue = parseFloat(value); + if (isNaN(numericValue)) return; + + const updatedEvent = { + ...selectedEventData.data, + speed: numericValue + } as HumanEventSchema; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + updatedEvent + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setSpeed(value); + }; + + const handleLoadCapacityChange = (value: string) => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { ...currentAction }; + updatedAction.loadCapacity = parseInt(value) + + const updatedPoint = { ...selectedPointData, action: updatedAction }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + setLoadCapacity(value); + }; + + const handleClearPoints = () => { + if (!currentAction || !selectedPointData || !selectedAction.actionId) return; + + const updatedAction = { ...currentAction }; + delete updatedAction.pickUpPoint; + delete updatedAction.dropPoint; + + const updatedPoint = { ...selectedPointData, action: updatedAction }; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + updatedAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + setCurrentAction(updatedAction); + setSelectedPointData(updatedPoint); + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction: HumanAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action`, + actionType: "worker", + loadCapacity: 1, + triggers: [], + }; + + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const updatedPoint = { ...selectedPointData, action: newAction }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = () => { + if (!selectedPointData) return; + + const event = removeAction( + selectedProduct.productUuid, + selectedPointData.action.actionUuid + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const updatedPoint = { ...selectedPointData, action: undefined as any }; + setSelectedPointData(updatedPoint); + clearSelectedAction(); + setCurrentAction(undefined); + }; + + return ( + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+
+ + + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ +
+ +
+ +
+
+ )} +
+ + ); +} + +export default HumanMechanics; \ No newline at end of file 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 78583d8..eb9e41c 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -272,14 +272,6 @@ function VehicleMechanics() { onChange: handleUnloadDurationChange, }} clearPoints={handleClearPoints} - // pickPoint={{ - // value: currentPickPoint, - // onChange: handlePickPointChange, - // }} - // unloadPoint={{ - // value: currentUnloadPoint, - // onChange: handleUnloadPointChange, - // }} /> )}
diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx index 4ecb1e9..eb4b0ac 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -13,7 +13,7 @@ import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; type TriggerProps = { selectedPointData?: PointsScheme | undefined; - type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit"; + type?: "Conveyor" | "Vehicle" | "RoboticArm" | "Machine" | "StorageUnit" | "Human"; }; const Trigger = ({ selectedPointData, type }: TriggerProps) => { @@ -38,7 +38,7 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { if (type === "Conveyor" || type === "Vehicle" || type === "Machine" || type === "StorageUnit") { actionUuid = (selectedPointData as | ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid; - } else if (type === "RoboticArm" && selectedAction.actionId) { + } else if ((type === "RoboticArm" || type === "Human") && selectedAction.actionId) { actionUuid = selectedAction.actionId; } @@ -365,18 +365,16 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => { } /> - {triggers.length > 1 && ( - - )} + ))} diff --git a/app/src/components/ui/inputs/InputWithDropDown.tsx b/app/src/components/ui/inputs/InputWithDropDown.tsx index f749af7..61fbaf4 100644 --- a/app/src/components/ui/inputs/InputWithDropDown.tsx +++ b/app/src/components/ui/inputs/InputWithDropDown.tsx @@ -8,6 +8,7 @@ type InputWithDropDownProps = { max?: number; step?: number; defaultValue?: string; + disabled?: boolean; options?: string[]; // Array of dropdown options activeOption?: string; // The currently active dropdown option onClick?: () => void; @@ -23,6 +24,7 @@ const InputWithDropDown: React.FC = ({ max, step, defaultValue, + disabled = false, options, activeOption, onClick, @@ -54,6 +56,7 @@ const InputWithDropDown: React.FC = ({ type="number" defaultValue={value} // value={value} + disabled={disabled} onChange={(e) => { onChange(e.target.value); }} diff --git a/app/src/components/ui/inputs/RenameInput.tsx b/app/src/components/ui/inputs/RenameInput.tsx index 593e1f1..637dec3 100644 --- a/app/src/components/ui/inputs/RenameInput.tsx +++ b/app/src/components/ui/inputs/RenameInput.tsx @@ -9,9 +9,10 @@ interface RenameInputProps { value: string; onRename?: (newText: string) => void; checkDuplicate?: (name: string) => boolean; + canEdit?: boolean; } -const RenameInput: React.FC = ({ value, onRename, checkDuplicate }) => { +const RenameInput: React.FC = ({ value, onRename, checkDuplicate, canEdit = true }) => { const [isEditing, setIsEditing] = useState(false); const [text, setText] = useState(value); const [isDuplicate, setIsDuplicate] = useState(false); @@ -28,13 +29,15 @@ const RenameInput: React.FC = ({ value, onRename, checkDuplica }, [text, checkDuplicate]); const handleDoubleClick = () => { - setIsEditing(true); - setTimeout(() => inputRef.current?.focus(), 0); + if (canEdit) { + setIsEditing(true); + setTimeout(() => inputRef.current?.focus(), 0); + } }; const handleBlur = () => { - - if(isDuplicate) return + + if (isDuplicate) return setIsEditing(false); if (onRename && !isDuplicate) { onRename(text); diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index cb54f87..547445e 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -1,4 +1,4 @@ - import * as THREE from "three" +import * as THREE from "three" import { useEffect } from 'react' import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi'; import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store'; @@ -226,7 +226,8 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { modelUuid: item.modelUuid, modelName: item.modelName, position: item.position, - rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", type: "storageUnit", point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -242,6 +243,30 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { } }; addEvent(storageEvent); + } else if (item.eventData.type === 'Human') { + const humanEvent: HumanEventSchema = { + modelUuid: item.modelUuid, + modelName: item.modelName, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "human", + speed: 1, + point: { + uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), + position: [item.eventData.point?.position[0] || 0, item.eventData.point?.position[1] || 0, item.eventData.point?.position[2] || 0], + rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + + } + } + addEvent(humanEvent); } } else { assets.push({ diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 978b45c..8a09bb7 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -170,7 +170,7 @@ async function handleModelLoad( if (!data || !data.points) return; - const eventData: any = { type: selectedItem.type, }; + const eventData: any = { type: selectedItem.type }; if (selectedItem.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { @@ -368,24 +368,19 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "human", + speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], rotation: [0, 0, 0], - actions: [ - { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Action 1", - actionType: "animation", - animation: null, - loadCapacity: 1, - travelPoints: { - startPoint: null, - endPoint: null, - }, - triggers: [] - } - ] + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + } } addEvent(humanEvent); diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index b47fcd7..ff5e0bf 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -6,7 +6,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { ThreeEvent, useFrame, useThree } from '@react-three/fiber'; import { useActiveTool, useDeletableFloorItem, useLimitDistance, useRenderDistance, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; -import { CameraControls, Html } from '@react-three/drei'; +import { CameraControls } from '@react-three/drei'; import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; @@ -15,6 +15,8 @@ import { useParams } from 'react-router-dom'; import { getUserData } from '../../../../../functions/getUserData'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useVersionContext } from '../../../version/versionContext'; +import { SkeletonUtils } from 'three-stdlib'; +import { useAnimationPlaySpeed } from '../../../../../store/usePlayButtonStore'; function Model({ asset }: { readonly asset: Asset }) { const { camera, controls, gl } = useThree(); @@ -22,8 +24,9 @@ function Model({ asset }: { readonly asset: Asset }) { const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); + const { speed } = useAnimationPlaySpeed(); const { assetStore, eventStore, productStore } = useSceneContext(); - const { assets, removeAsset, setAnimations } = assetStore(); + const { removeAsset, setAnimations, resetAnimation, setAnimationComplete, setCurrentAnimation: setAnmationAnimation } = assetStore(); const { setTop } = useTopData(); const { setLeft } = useLeftData(); const { getIsEventInProduct } = productStore(); @@ -33,7 +36,7 @@ function Model({ asset }: { readonly asset: Asset }) { const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); const { socket } = useSocketStore(); const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); - const { setSelectedFloorItem } = useSelectedFloorItem(); + const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { limitDistance } = useLimitDistance(); const { renderDistance } = useRenderDistance(); const [isRendered, setIsRendered] = useState(false); @@ -46,13 +49,18 @@ function Model({ asset }: { readonly asset: Asset }) { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { userId, organization } = getUserData(); - const [animationNames, setAnimationNames] = useState([]); const mixerRef = useRef(); const actions = useRef<{ [name: string]: THREE.AnimationAction }>({}); + const [previousAnimation, setPreviousAnimation] = useState(null); + const blendFactor = useRef(0); + const blendDuration = 0.5; useEffect(() => { setDeletableFloorItem(null); - }, [activeModule, toolMode]) + if (selectedFloorItem === null) { + resetAnimation(asset.modelUuid); + } + }, [activeModule, toolMode, selectedFloorItem]) useEffect(() => { const loader = new GLTFLoader(); @@ -62,40 +70,21 @@ function Model({ asset }: { readonly asset: Asset }) { loader.setDRACOLoader(dracoLoader); const loadModel = async () => { try { - // Check Cache - // const assetId = asset.assetId; - // const cachedModel = THREE.Cache.get(assetId); - // if (cachedModel) { - // setGltfScene(cachedModel.scene.clone()); - // calculateBoundingBox(cachedModel.scene); - // return; - // } // Check Cache - // const assetId = asset.assetId; - // console.log('assetId: ', assetId); - // const cachedModel = THREE.Cache.get(assetId); - // console.log('cachedModel: ', cachedModel); - // if (cachedModel) { - // setGltfScene(cachedModel.scene.clone()); - // calculateBoundingBox(cachedModel.scene); - // return; - // } - const assetId = asset.assetId; const cachedModel = THREE.Cache.get(assetId); if (cachedModel) { - const clonedScene = cachedModel.scene.clone(); - clonedScene.animations = cachedModel.animations || []; - setGltfScene(clonedScene); - calculateBoundingBox(clonedScene); - if (cachedModel.animations && clonedScene.animations.length > 0) { - const animationName = clonedScene.animations.map((clip: any) => clip.name); - setAnimationNames(animationName) + const clone: any = SkeletonUtils.clone(cachedModel.scene); + clone.animations = cachedModel.animations || []; + setGltfScene(clone); + calculateBoundingBox(clone); + if (cachedModel.animations && clone.animations.length > 0) { + const animationName = clone.animations.map((clip: any) => clip.name); setAnimations(asset.modelUuid, animationName) - mixerRef.current = new THREE.AnimationMixer(clonedScene); + mixerRef.current = new THREE.AnimationMixer(clone); - clonedScene.animations.forEach((animation: any) => { + clone.animations.forEach((animation: any) => { const action = mixerRef.current!.clipAction(animation); actions.current[animation.name] = action; }); @@ -293,29 +282,50 @@ function Model({ asset }: { readonly asset: Asset }) { clearSelectedAsset() } } + + const handleAnimationComplete = useCallback(() => { + if (asset.animationState) { + setAnimationComplete(asset.modelUuid, true); + } + }, [asset.animationState]); + useFrame((_, delta) => { if (mixerRef.current) { - mixerRef.current.update(delta); + mixerRef.current.update(delta * speed); } }); - useEffect(() => { + if (!asset.animationState || !mixerRef.current) return; - if (asset.animationState && asset.animationState.playing) { - if (!mixerRef.current) return; + const { current, loopAnimation, isPlaying } = asset.animationState; + const currentAction = actions.current[current]; + const previousAction = previousAnimation ? actions.current[previousAnimation] : null; - Object.values(actions.current).forEach((action) => action.stop()); + if (isPlaying && currentAction) { + blendFactor.current = 0; - const action = actions.current[asset.animationState.current]; - if (action && asset.animationState?.playing) { - action.reset().setLoop(THREE.LoopOnce, 1).play(); + currentAction.reset(); + currentAction.setLoop(loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce, loopAnimation ? Infinity : 1); + currentAction.clampWhenFinished = true; + + if (previousAction && previousAction !== currentAction) { + previousAction.crossFadeTo(currentAction, blendDuration, false); } + + currentAction.play(); + mixerRef.current.addEventListener('finished', handleAnimationComplete); + setPreviousAnimation(current); } else { Object.values(actions.current).forEach((action) => action.stop()); } - }, [asset.animationState]) + return () => { + if (mixerRef.current) { + mixerRef.current.removeEventListener('finished', handleAnimationComplete); + } + }; + }, [asset.animationState?.current, asset.animationState?.isPlaying]); return ( { + echo.info(`${materialUuid}, ${status}`); + } + + const handleWorker = useCallback((action: HumanAction, materialId?: string) => { + if (!action || action.actionType !== 'worker' || !materialId) return; + + const material = getMaterialById(materialId); + if (!material) return; + + const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid); + if (!modelUuid) return; + + incrementHumanLoad(modelUuid, 1); + addCurrentMaterial(modelUuid, material.materialType, material.materialId); + + workerLogStatus(material.materialName, `performing worker action`); + + }, [getMaterialById]); + + return { + handleWorker, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/human/useHumanActions.ts b/app/src/modules/simulation/actions/human/useHumanActions.ts new file mode 100644 index 0000000..9713f11 --- /dev/null +++ b/app/src/modules/simulation/actions/human/useHumanActions.ts @@ -0,0 +1,36 @@ +import { useEffect, useCallback } from 'react'; +import { useWorkerHandler } from './actionHandler/useWorkerHandler'; + +export function useHumanActions() { + const { handleWorker } = useWorkerHandler(); + + const handleWorkerAction = useCallback((action: HumanAction, materialId: string) => { + handleWorker(action, materialId); + }, [handleWorker]); + + const handleHumanAction = useCallback((action: HumanAction, materialId: string) => { + if (!action) return; + + switch (action.actionType) { + case 'worker': + handleWorkerAction(action, materialId); + break; + default: + console.warn(`Unknown Human action type: ${action.actionType}`); + } + }, [handleWorkerAction]); + + const cleanup = useCallback(() => { + }, []); + + useEffect(() => { + return () => { + cleanup(); + }; + }, [cleanup]); + + return { + handleHumanAction, + cleanup + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts b/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts index 6f5fb19..151ca20 100644 --- a/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts +++ b/app/src/modules/simulation/actions/storageUnit/useStorageUnitActions.ts @@ -27,7 +27,7 @@ export function useStorageActions() { default: console.warn(`Unknown storage action type: ${action.actionType}`); } - }, [handleStoreAction]); + }, [handleStoreAction, handleRetrieveAction]); const cleanup = useCallback(() => { }, []); diff --git a/app/src/modules/simulation/actions/useActionHandler.ts b/app/src/modules/simulation/actions/useActionHandler.ts index 23f2712..3bb95bf 100644 --- a/app/src/modules/simulation/actions/useActionHandler.ts +++ b/app/src/modules/simulation/actions/useActionHandler.ts @@ -7,6 +7,7 @@ import { useMachineActions } from "./machine/useMachineActions"; import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions"; import { useStorageActions } from "./storageUnit/useStorageUnitActions"; import { useVehicleActions } from "./vehicle/useVehicleActions"; +import { useHumanActions } from "./human/useHumanActions"; import { useCallback, useEffect } from "react"; export function useActionHandler() { @@ -17,6 +18,7 @@ export function useActionHandler() { const { handleRoboticArmAction, cleanup: cleanupRoboticArm } = useRoboticArmActions(); const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions(); const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions(); + const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions(); const handleAction = useCallback((action: Action, materialId?: string) => { if (!action) return; @@ -37,6 +39,9 @@ export function useActionHandler() { case 'store': case 'retrieve': handleStorageAction(action as StorageAction, materialId as string); break; + case 'worker': + handleHumanAction(action as HumanAction, materialId as string); + break; default: console.warn(`Unknown action type: ${(action as Action).actionType}`); } @@ -44,7 +49,7 @@ export function useActionHandler() { echo.error("Failed to handle action"); console.error("Error handling action:", error); } - }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction,]); + }, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction]); const cleanup = useCallback(() => { cleanupConveyor(); @@ -52,7 +57,8 @@ export function useActionHandler() { cleanupRoboticArm(); cleanupMachine(); cleanupStorage(); - }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage,]); + cleanupHuman(); + }, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman]); useEffect(() => { return () => { diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 918007a..ec7406c 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -155,8 +155,8 @@ function TriggerConnector() { // Handle Human point else if (event.type === "human" && 'point' in event) { const point = event.point; - point.actions?.forEach(action => { - action.triggers?.forEach(trigger => { + if (point.action?.triggers) { + point.action.triggers.forEach(trigger => { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { newConnections.push({ id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, @@ -166,7 +166,7 @@ function TriggerConnector() { }); } }); - }); + } } }); diff --git a/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts new file mode 100644 index 0000000..94ce8b3 --- /dev/null +++ b/app/src/modules/simulation/human/eventManager/useHumanEventManager.ts @@ -0,0 +1,76 @@ +import { useEffect, useRef } from 'react'; +import { useFrame } from '@react-three/fiber'; +import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../scene/sceneContext'; + +type HumanCallback = { + humanId: string; + callback: () => void; +}; + +export function useHumanEventManager() { + const { humanStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const callbacksRef = useRef([]); + const isMonitoringRef = useRef(false); + const { isPlaying } = usePlayButtonStore(); + const { isPaused } = usePauseButtonStore(); + const { isReset } = useResetButtonStore(); + + useEffect(() => { + if (isReset) { + callbacksRef.current = []; + } + }, [isReset]) + + // Add a new human to monitor + const addHumanToMonitor = (humanId: string, callback: () => void) => { + // Avoid duplicates + if (!callbacksRef.current.some((entry) => entry.humanId === humanId)) { + callbacksRef.current.push({ humanId, callback }); + } + + // Start monitoring if not already running + if (!isMonitoringRef.current) { + isMonitoringRef.current = true; + } + }; + + // Remove a human from monitoring + const removeHumanFromMonitor = (humanId: string) => { + callbacksRef.current = callbacksRef.current.filter( + (entry) => entry.humanId !== humanId + ); + + // Stop monitoring if no more humans to track + if (callbacksRef.current.length === 0) { + isMonitoringRef.current = false; + } + }; + + // Check human states every frame + useFrame(() => { + if (!isMonitoringRef.current || callbacksRef.current.length === 0 || !isPlaying || isPaused) return; + + callbacksRef.current.forEach(({ humanId, callback }) => { + const human = getHumanById(humanId); + if (human && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { + callback(); + removeHumanFromMonitor(humanId); // Remove after triggering + } + }); + }); + + // Cleanup on unmount + useEffect(() => { + return () => { + callbacksRef.current = []; + isMonitoringRef.current = false; + }; + }, []); + + return { + addHumanToMonitor, + removeHumanFromMonitor, + }; +} \ No newline at end of file diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx index 30608d4..767269c 100644 --- a/app/src/modules/simulation/human/human.tsx +++ b/app/src/modules/simulation/human/human.tsx @@ -1,11 +1,38 @@ +import { useEffect, useState } from 'react' +import { useSelectedAnimation, useSelectedEventSphere } from '../../../store/simulation/useSimulationStore'; +import { usePlayButtonStore } from '../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../scene/sceneContext'; import HumanInstances from './instances/humanInstances' +import HumanUi from './instances/instance/humanUi'; function Human() { + const { humanStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const { selectedAnimation } = useSelectedAnimation(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { isPlaying } = usePlayButtonStore(); + const [isVehicleSelected, setIsHumanSelected] = useState(false); + + useEffect(() => { + if (selectedEventSphere) { + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + if (selectedHuman) { + setIsHumanSelected(true); + } else { + setIsHumanSelected(false); + } + } + }, [getHumanById, selectedEventSphere, selectedAnimation]) + return ( <> + {isVehicleSelected && selectedEventSphere && !isPlaying && + + } + ) } diff --git a/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx new file mode 100644 index 0000000..89d7a82 --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/humanAnimator.tsx @@ -0,0 +1,191 @@ +import { useEffect, useRef, useState } from 'react' +import { useFrame, useThree } from '@react-three/fiber'; +import * as THREE from 'three'; +import { Line } from '@react-three/drei'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useSceneContext } from '../../../../scene/sceneContext'; + +interface HumanAnimatorProps { + path: [number, number, number][]; + handleCallBack: () => void; + reset: () => void; + startUnloadingProcess: () => void; + currentPhase: string; + human: HumanStatus; +} + +function HumanAnimator({ path, handleCallBack, currentPhase, human, reset, startUnloadingProcess }: Readonly) { + const { humanStore, assetStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const { setCurrentAnimation } = assetStore(); + const { isPaused } = usePauseButtonStore(); + const { isPlaying } = usePlayButtonStore(); + const { speed } = useAnimationPlaySpeed(); + const { isReset, setReset } = useResetButtonStore(); + const progressRef = useRef(0); + const movingForward = useRef(true); + const completedRef = useRef(false); + const [objectRotation, setObjectRotation] = useState<[number, number, number] | null>(human.point?.action?.pickUpPoint?.rotation || [0, 0, 0]) + const [restRotation, setRestingRotation] = useState(true); + const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]); + const { scene } = useThree(); + + useEffect(() => { + if (currentPhase === 'init-pickup' && path.length > 0) { + setCurrentPath(path); + setObjectRotation(human.point.action.pickUpPoint?.rotation ?? null) + } else if (currentPhase === 'pickup-drop' && path.length > 0) { + setObjectRotation(human.point.action?.dropPoint?.rotation ?? null) + setCurrentPath(path); + } else if (currentPhase === 'drop-pickup' && path.length > 0) { + setObjectRotation(human.point.action?.pickUpPoint?.rotation ?? null) + setCurrentPath(path); + } + }, [currentPhase, path, objectRotation]); + + useEffect(() => { + completedRef.current = false; + }, [currentPath]); + + useEffect(() => { + if (isReset || !isPlaying) { + reset(); + setCurrentPath([]); + completedRef.current = false; + movingForward.current = true; + progressRef.current = 0; + setReset(false); + setRestingRotation(true); + const object = scene.getObjectByProperty('uuid', human.modelUuid); + const humanData = getHumanById(human.modelUuid); + if (object && humanData) { + object.position.set(humanData.position[0], humanData.position[1], humanData.position[2]); + object.rotation.set(humanData.rotation[0], humanData.rotation[1], humanData.rotation[2]); + } + } + }, [isReset, isPlaying]) + + const lastTimeRef = useRef(performance.now()); + + useFrame(() => { + const now = performance.now(); + const delta = (now - lastTimeRef.current) / 1000; + lastTimeRef.current = now; + + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (!object || currentPath.length < 2) return; + if (isPaused) return; + + let totalDistance = 0; + const distances = []; + let accumulatedDistance = 0; + let index = 0; + const rotationSpeed = 1; + + 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; + } + + while (index < distances.length && progressRef.current > 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 targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + targetQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + } else { + object.quaternion.slerp(targetQuaternion, delta * rotationSpeed * speed * human.speed * 5); + } + + const isAligned = angle < 0.01; + + if (isAligned) { + progressRef.current += delta * (speed * human.speed); + const t = (progressRef.current - accumulatedDistance) / segmentDistance; + const position = start.clone().lerp(end, t); + object.position.copy(position); + if (human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + } + } else { + if (human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } + } + } + + if (progressRef.current >= totalDistance) { + if (restRotation && objectRotation) { + const targetEuler = new THREE.Euler(0, objectRotation[1], 0); + + const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); + const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI); + const targetQuaternion = baseQuaternion.multiply(y180); + + const angle = object.quaternion.angleTo(targetQuaternion); + if (angle < 0.01) { + object.quaternion.copy(targetQuaternion); + setRestingRotation(false); + } else { + object.quaternion.slerp(targetQuaternion, delta * rotationSpeed * speed * human.speed * 4); + } + + if (human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'idle_with_box', true, true, true); + } else { + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + } + + return; + } + } + + if (progressRef.current >= totalDistance) { + setRestingRotation(true); + progressRef.current = 0; + movingForward.current = !movingForward.current; + setCurrentPath([]); + handleCallBack(); + if (currentPhase === 'pickup-drop') { + requestAnimationFrame(startUnloadingProcess); + } + } + }); + + return ( + <> + {currentPath.length > 0 && ( + // helper + + + {currentPath.map((point, index) => ( + + + + + ))} + + )} + + ) +} + +export default HumanAnimator \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx new file mode 100644 index 0000000..ffea67a --- /dev/null +++ b/app/src/modules/simulation/human/instances/animator/materialAnimator.tsx @@ -0,0 +1,44 @@ +import { useEffect, useRef, useState } from 'react'; +import { useThree } from '@react-three/fiber'; +import * as THREE from 'three'; +import { MaterialModel } from '../../../materials/instances/material/materialModel'; + +const MaterialAnimator = ({ human }: { human: HumanStatus }) => { + const meshRef = useRef(null!); + const [hasLoad, setHasLoad] = useState(false); + const { scene } = useThree(); + + useEffect(() => { + setHasLoad(human.currentLoad > 0); + }, [human.currentLoad]); + + useEffect(() => { + if (!hasLoad || !meshRef.current) return; + + const humanModel = scene.getObjectByProperty("uuid", human.modelUuid) as THREE.Object3D; + if (!humanModel) return; + + const bone = humanModel.getObjectByName('PlaceObjectRefBone') as THREE.Bone; + if (bone) { + bone.add(meshRef.current); + + meshRef.current.position.set(0, 0, 0); + meshRef.current.rotation.set(0, 0, 0); + meshRef.current.scale.set(1, 1, 1); + } + }, [hasLoad, human.modelUuid]); + + return ( + <> + {hasLoad && human.currentMaterials.length > 0 && ( + + )} + + ); +}; + +export default MaterialAnimator; diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index d8dc023..12d2d61 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,13 +1,686 @@ -import { useEffect } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as THREE from 'three'; +import { useThree } from '@react-three/fiber'; +import { NavMeshQuery } from '@recast-navigation/core'; +import { useNavMesh } from '../../../../../store/builder/store'; +import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; +import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { useProductContext } from '../../../products/productContext'; + +import HumanAnimator from '../animator/humanAnimator'; +import MaterialAnimator from '../animator/materialAnimator'; function HumanInstance({ human }: { human: HumanStatus }) { + const { navMesh } = useNavMesh(); + const { isPlaying } = usePlayButtonStore(); + const { scene } = useThree(); + const { assetStore, materialStore, armBotStore, conveyorStore, machineStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); + const { removeMaterial, setEndTime } = materialStore(); + const { getStorageUnitById } = storageUnitStore(); + const { getArmBotById } = armBotStore(); + const { getConveyorById } = conveyorStore(); + const { getVehicleById } = vehicleStore(); + const { getMachineById } = machineStore(); + const { triggerPointActions } = useTriggerHandler(); + const { setCurrentAnimation, resetAnimation, getAssetById } = assetStore(); + const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore(); + + const [currentPhase, setCurrentPhase] = useState('init'); + const [path, setPath] = useState<[number, number, number][]>([]); + const pauseTimeRef = useRef(null); + const idleTimeRef = useRef(0); + const activeTimeRef = useRef(0); + const isPausedRef = useRef(false); + const isSpeedRef = useRef(0); + const { speed } = useAnimationPlaySpeed(); + const { isPaused } = usePauseButtonStore(); + const previousTimeRef = useRef(null); + const animationFrameIdRef = useRef(null); + const humanAsset = getAssetById(human.modelUuid); useEffect(() => { - console.log('human: ', human); - }, [human]) + isPausedRef.current = isPaused; + }, [isPaused]); + + useEffect(() => { + isSpeedRef.current = speed; + }, [speed]); + + const computePath = useCallback( + (start: any, end: any) => { + try { + const navMeshQuery = new NavMeshQuery(navMesh); + const { path: segmentPath } = navMeshQuery.computePath(start, end); + if ( + segmentPath.length > 0 && + Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(end.x) && + Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(end.z) + ) { + return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } else { + console.log("There is no path here...Choose valid path") + const { path: segmentPaths } = navMeshQuery.computePath(start, start); + return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || []; + } + } catch { + console.error("Failed to compute path"); + return []; + } + }, [navMesh]); + + function humanStatus(modelId: string, status: string) { + // console.log(`${modelId} , ${status}`); + } + + function reset() { + setCurrentPhase('init'); + setHumanActive(human.modelUuid, false); + setHumanPicking(human.modelUuid, false); + setHumanState(human.modelUuid, 'idle'); + setHumanLoad(human.modelUuid, 0); + resetAnimation(human.modelUuid); + setPath([]); + isPausedRef.current = false; + pauseTimeRef.current = 0; + resetTime(human.modelUuid) + activeTimeRef.current = 0 + idleTimeRef.current = 0 + previousTimeRef.current = null + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current) + animationFrameIdRef.current = null + } + const object = scene.getObjectByProperty('uuid', human.modelUuid); + if (object && human) { + object.position.set(human.position[0], human.position[1], human.position[2]); + object.rotation.set(human.rotation[0], human.rotation[1], human.rotation[2]); + } + } + + useEffect(() => { + if (isPlaying) { + if (!human.point.action.pickUpPoint || !human.point.action.dropPoint) return; + + if (!human.isActive && human.state === 'idle' && currentPhase === 'init') { + const toPickupPath = computePath( + new THREE.Vector3(human?.position[0], human?.position[1], human?.position[2]), + new THREE.Vector3( + human?.point?.action?.pickUpPoint?.position?.[0] ?? 0, + human?.point?.action?.pickUpPoint?.position?.[1] ?? 0, + human?.point?.action?.pickUpPoint?.position?.[2] ?? 0 + ) + ); + setPath(toPickupPath); + setCurrentPhase('init-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanPicking(human.modelUuid, false); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from init, heading to pickup'); + return; + } else if (!human.isActive && human.state === 'idle' && currentPhase === 'picking') { + if (humanAsset && human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0 && humanAsset.animationState?.current === 'pickup' && humanAsset.animationState?.isCompleted) { + if (human.point.action.pickUpPoint && human.point.action.dropPoint) { + const toDrop = computePath( + new THREE.Vector3( + human.point.action.pickUpPoint.position?.[0] ?? 0, + human.point.action.pickUpPoint.position?.[1] ?? 0, + human.point.action.pickUpPoint.position?.[2] ?? 0 + ), + new THREE.Vector3( + human.point.action.dropPoint.position?.[0] ?? 0, + human.point.action.dropPoint.position?.[1] ?? 0, + human.point.action.dropPoint.position?.[2] ?? 0 + ) + ); + setPath(toDrop); + setCurrentPhase('pickup-drop'); + setHumanState(human.modelUuid, 'running'); + setHumanPicking(human.modelUuid, false); + setHumanPicking(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walk_with_box', true, true, true); + humanStatus(human.modelUuid, 'Started from pickup point, heading to drop point'); + } + } else if (human.currentLoad === human.point.action.loadCapacity && human.currentMaterials.length > 0) { + setCurrentAnimation(human.modelUuid, 'pickup', true, false, false); + } + } else if (!human.isActive && human.state === 'idle' && currentPhase === 'dropping' && human.currentLoad === 0) { + if (human.point.action.pickUpPoint && human.point.action.dropPoint) { + const dropToPickup = computePath( + new THREE.Vector3( + human.point.action.dropPoint.position?.[0] ?? 0, + human.point.action.dropPoint.position?.[1] ?? 0, + human.point.action.dropPoint.position?.[2] ?? 0 + ), + new THREE.Vector3( + human.point.action.pickUpPoint.position?.[0] ?? 0, + human.point.action.pickUpPoint.position?.[1] ?? 0, + human.point.action.pickUpPoint.position?.[2] ?? 0 + ) + ); + setPath(dropToPickup); + setCurrentPhase('drop-pickup'); + setHumanState(human.modelUuid, 'running'); + setHumanPicking(human.modelUuid, false); + setHumanActive(human.modelUuid, true); + setCurrentAnimation(human.modelUuid, 'walking', true, true, true); + humanStatus(human.modelUuid, 'Started from dropping point, heading to pickup point'); + } + } + + } else { + reset() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [human, currentPhase, path, isPlaying, humanAsset?.animationState?.isCompleted]); + + function handleCallBack() { + if (currentPhase === 'init-pickup') { + setCurrentPhase('picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanPicking(human.modelUuid, true); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached pickup point, waiting for material'); + setPath([]); + } else if (currentPhase === 'pickup-drop') { + setCurrentPhase('dropping'); + setHumanState(human.modelUuid, 'idle'); + setHumanPicking(human.modelUuid, false); + setHumanActive(human.modelUuid, false); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + humanStatus(human.modelUuid, 'Reached drop point'); + setPath([]); + } else if (currentPhase === 'drop-pickup') { + setCurrentPhase('picking'); + setHumanState(human.modelUuid, 'idle'); + setHumanPicking(human.modelUuid, true); + setHumanActive(human.modelUuid, false); + setPath([]); + clearCurrentMaterials(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'idle', true, true, true); + humanStatus(human.modelUuid, 'Reached pickup point again, cycle complete'); + } + } + + function animate(currentTime: number) { + if (previousTimeRef.current === null) { + previousTimeRef.current = currentTime; + } + + const deltaTime = (currentTime - previousTimeRef.current) / 1000; + previousTimeRef.current = currentTime; + + if (human.isActive) { + if (!isPausedRef.current) { + activeTimeRef.current += deltaTime * isSpeedRef.current; + } + } else { + if (!isPausedRef.current) { + idleTimeRef.current += deltaTime * isSpeedRef.current; + } + } + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + useEffect(() => { + if (!isPlaying) return + if (!human.isActive) { + const roundedActiveTime = Math.round(activeTimeRef.current); + incrementActiveTime(human.modelUuid, roundedActiveTime); + activeTimeRef.current = 0; + } else { + const roundedIdleTime = Math.round(idleTimeRef.current); + incrementIdleTime(human.modelUuid, roundedIdleTime); + idleTimeRef.current = 0; + } + + if (animationFrameIdRef.current === null) { + animationFrameIdRef.current = requestAnimationFrame(animate); + } + + return () => { + if (animationFrameIdRef.current !== null) { + cancelAnimationFrame(animationFrameIdRef.current); + animationFrameIdRef.current = null; + } + }; + }, [human, isPlaying]); + + function startUnloadingProcess() { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + if (human.point.action.triggers.length > 0) { + const trigger = getTriggerByUuid(selectedProduct.productUuid, human.point.action.triggers[0]?.triggerUuid); + const model = getEventByModelUuid(selectedProduct.productUuid, trigger?.triggeredAsset?.triggeredModel?.modelUuid || ''); + + if (trigger && model) { + if (model.type === 'transfer') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToConveyor(model); + } + } else if (model.type === 'machine') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToMachine(model); + } + } else if (model.type === 'roboticArm') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToArmBot(model); + } + } else if (model.type === 'storageUnit') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToStorageUnit(model); + } + } else if (model.type === 'vehicle') { + const action = getActionByUuid(selectedProduct.productUuid, human.point.action.actionUuid); + if (action) { + handleMaterialDropToVehicle(model); + } + } + } else { + const droppedMaterial = human.currentLoad; + handleMaterialDropByDefault(droppedMaterial); + } + } else { + const droppedMaterial = human.currentLoad; + handleMaterialDropByDefault(droppedMaterial); + } + } else { + requestAnimationFrame(startUnloadingProcess); + } + } + + function handleMaterialDropToStorageUnit(model: StorageEventSchema) { + const humanAsset = getAssetById(human.modelUuid); + if (model && humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + if (model.point.action.actionType === 'store') { + loopMaterialDropToStorage( + human.modelUuid, + human.currentLoad, + model.modelUuid, + model.point.action.storageCapacity, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToStorage( + humanId: string, + humanCurrentLoad: number, + storageUnitId: string, + storageMaxCapacity: number, + action: HumanAction + ) { + const storageUnit = getStorageUnitById(storageUnitId); + const humanAsset = getAssetById(human.modelUuid); + + if (!storageUnit || humanCurrentLoad <= 0 || storageUnit.currentLoad >= storageMaxCapacity) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0 && storageUnit.currentLoad < storageMaxCapacity) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextDrop = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + loopMaterialDropToStorage( + humanId, + humanCurrentLoad, + storageUnitId, + storageMaxCapacity, + action + ); + } else { + requestAnimationFrame(waitForNextDrop); + } + }; + waitForNextDrop(); + } + } + + function handleMaterialDropToConveyor(model: ConveyorEventSchema) { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.isCompleted) { + const conveyor = getConveyorById(model.modelUuid); + if (conveyor) { + loopMaterialDropToConveyor( + human.modelUuid, + human.currentLoad, + conveyor.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToConveyor( + humanId: string, + humanCurrentLoad: number, + conveyorId: string, + action: HumanAction + ) { + const conveyor = getConveyorById(conveyorId); + const humanAsset = getAssetById(human.modelUuid); + + if (!conveyor || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextDrop = () => { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToConveyor( + humanId, + humanCurrentLoad, + conveyorId, + action + ); + } else { + requestAnimationFrame(waitForNextDrop); + } + }; + waitForNextDrop(); + } + } + + function handleMaterialDropToArmBot(model: RoboticArmEventSchema) { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + const armBot = getArmBotById(model.modelUuid); + if (armBot && armBot.state === 'idle' && !armBot.isActive) { + loopMaterialDropToArmBot( + human.modelUuid, + human.currentLoad, + model.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToArmBot( + humanId: string, + humanCurrentLoad: number, + armBotId: string, + action: HumanAction + ) { + const armBot = getArmBotById(armBotId); + const humanAsset = getAssetById(human.modelUuid); + + if (!armBot || armBot.state !== 'idle' || armBot.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentArmBot = getArmBotById(armBotId); + if (currentArmBot && currentArmBot.state === 'idle' && !currentArmBot.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToArmBot( + humanId, + humanCurrentLoad, + armBotId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropToVehicle(model: VehicleEventSchema) { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + const vehicle = getVehicleById(model.modelUuid); + if (vehicle && vehicle.state === 'idle' && !vehicle.isActive) { + loopMaterialDropToVehicle( + human.modelUuid, + human.currentLoad, + model.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToVehicle( + humanId: string, + humanCurrentLoad: number, + vehicleId: string, + action: HumanAction + ) { + const vehicle = getVehicleById(vehicleId); + const humanAsset = getAssetById(human.modelUuid); + + if (!vehicle || vehicle.state !== 'idle' || vehicle.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentVehicle = getVehicleById(vehicleId); + if (currentVehicle && currentVehicle.state === 'idle' && !currentVehicle.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToVehicle( + humanId, + humanCurrentLoad, + vehicleId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropToMachine(model: MachineEventSchema) { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + const checkAnimation = () => { + if (humanAsset?.animationState?.current === 'drop' && humanAsset?.animationState?.isCompleted) { + const machine = getMachineById(model.modelUuid); + if (machine && machine.state === 'idle' && !machine.isActive) { + loopMaterialDropToMachine( + human.modelUuid, + human.currentLoad, + model.modelUuid, + human.point.action + ); + } + } else { + requestAnimationFrame(checkAnimation); + } + }; + checkAnimation(); + } + + function loopMaterialDropToMachine( + humanId: string, + humanCurrentLoad: number, + machineId: string, + action: HumanAction + ) { + const machine = getMachineById(machineId); + const humanAsset = getAssetById(human.modelUuid); + + if (!machine || machine.state !== 'idle' || machine.isActive || humanCurrentLoad <= 0) { + return; + } + + decrementHumanLoad(humanId, 1); + humanCurrentLoad -= 1; + + const material = removeLastMaterial(humanId); + if (material) { + triggerPointActions(action, material.materialId); + } + + if (humanCurrentLoad > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + const waitForNextTransfer = () => { + const currentMachine = getMachineById(machineId); + if (currentMachine && currentMachine.state === 'idle' && !currentMachine.isActive) { + if (humanAsset?.animationState?.isCompleted) { + loopMaterialDropToMachine( + humanId, + humanCurrentLoad, + machineId, + action + ); + } else { + requestAnimationFrame(waitForNextTransfer); + } + } else { + requestAnimationFrame(waitForNextTransfer); + } + }; + waitForNextTransfer(); + } + } + + function handleMaterialDropByDefault(droppedMaterial: number) { + const humanAsset = getAssetById(human.modelUuid); + if (humanAsset?.animationState?.current !== 'drop') { + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + } + + if (humanAsset?.animationState?.isCompleted) { + const remainingMaterials = droppedMaterial - 1; + decrementHumanLoad(human.modelUuid, 1); + const material = removeLastMaterial(human.modelUuid); + + if (material) { + setEndTime(material.materialId, performance.now()); + removeMaterial(material.materialId); + } + + if (remainingMaterials > 0) { + resetAnimation(human.modelUuid); + setCurrentAnimation(human.modelUuid, 'drop', true, false, false); + + requestAnimationFrame(() => handleMaterialDropByDefault(remainingMaterials)); + } + return; + } + + requestAnimationFrame(() => handleMaterialDropByDefault(droppedMaterial)); + } return ( <> + + + + ) } diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx new file mode 100644 index 0000000..64b1f84 --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -0,0 +1,263 @@ +import { useEffect, useRef, useState } from 'react' +import { useGLTF } from '@react-three/drei'; +import { useFrame, useThree } from '@react-three/fiber'; +import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore'; +import { useProductContext } from '../../../products/productContext'; +import { useSceneContext } from '../../../../scene/sceneContext'; +import { Group, Plane, Vector3 } from 'three'; +import { useVersionContext } from '../../../../builder/version/versionContext'; +import { useParams } from 'react-router-dom'; +import startPoint from "../../../../../assets/gltf-glb/ui/arrow_green.glb"; +import startEnd from "../../../../../assets/gltf-glb/ui/arrow_red.glb"; +import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; + +function HumanUi() { + const { scene: startScene } = useGLTF(startPoint) as any; + const { scene: endScene } = useGLTF(startEnd) as any; + const startMarker = useRef(null); + const endMarker = useRef(null); + const outerGroup = useRef(null); + const prevMousePos = useRef({ x: 0, y: 0 }); + const { controls, raycaster } = useThree(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { selectedProductStore } = useProductContext(); + const { humanStore, productStore } = useSceneContext(); + const { selectedProduct } = selectedProductStore(); + const { humans, getHumanById } = humanStore(); + const { updateEvent } = productStore(); + const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]); + const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]); + const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]); + const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]); + const { isDragging, setIsDragging } = useIsDragging(); + const { isRotating, setIsRotating } = useIsRotating(); + const plane = useRef(new Plane(new Vector3(0, 1, 0), 0)); + + const [selectedHumanData, setSelectedHumanData] = useState<{ + position: [number, number, number]; + rotation: [number, number, number]; + }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); + + const { selectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; + + useEffect(() => { + if (!selectedEventSphere) return; + + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + if (!selectedHuman || !selectedHuman.point?.action) return; + + setSelectedHumanData({ + position: selectedHuman.position, + rotation: selectedHuman.rotation, + }); + + const action = selectedHuman.point.action; + + if (action.pickUpPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.pickUpPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setStartPosition([localPosition.x, 0.5, localPosition.z]); + setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]); + } + + if (action.dropPoint?.position && outerGroup.current) { + const worldPos = new Vector3(...action.dropPoint.position); + const localPosition = outerGroup.current.worldToLocal(worldPos.clone()); + setEndPosition([localPosition.x, 0.5, localPosition.z]); + setEndRotation(action.dropPoint.rotation || [0, 0, 0]); + } + }, [selectedEventSphere, outerGroup.current, selectedAction, humans]); + + const handlePointerDown = ( + e: any, + state: "start" | "end", + rotation: "start" | "end" + ) => { + if (e.object.name === "handle") { + const normalizedX = (e.clientX / window.innerWidth) * 2 - 1; + const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1; + prevMousePos.current = { x: normalizedX, y: normalizedY }; + setIsRotating(rotation); + if (controls) (controls as any).enabled = false; + setIsDragging(null); + } else { + setIsDragging(state); + setIsRotating(null); + if (controls) (controls as any).enabled = false; + } + }; + + const handlePointerUp = () => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + + if (selectedEventSphere?.userData.modelUuid && selectedAction.actionId) { + const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid); + + if (selectedHuman && outerGroup.current && startMarker.current && endMarker.current) { + const worldPosStart = new Vector3(...startPosition); + const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone()); + + const worldPosEnd = new Vector3(...endPosition); + const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone()); + + const updatedAction = { + ...selectedHuman.point.action, + pickUpPoint: { + position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number], + rotation: startRotation + }, + dropPoint: { + position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number], + rotation: endRotation + } + }; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventSphere.userData.modelUuid, + { + ...selectedHuman, + point: { + ...selectedHuman.point, + action: updatedAction + } + } + ); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productUuid, + projectId || '', + event + ); + } + } + } + }; + + useFrame(() => { + if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return; + const intersectPoint = new Vector3(); + const intersects = raycaster.ray.intersectPlane( + plane.current, + intersectPoint + ); + if (!intersects) return; + const localPoint = outerGroup?.current.worldToLocal(intersectPoint.clone()); + if (isDragging === "start") { + setStartPosition([localPoint.x, 0.5, localPoint.z]); + } else if (isDragging === "end") { + setEndPosition([localPoint.x, 0.5, localPoint.z]); + } + }); + + useFrame((state) => { + if (!isRotating) return; + const currentPointerX = state.pointer.x; + const deltaX = currentPointerX - prevMousePos.current.x; + prevMousePos.current.x = currentPointerX; + const marker =isRotating === "start" ? startMarker.current : endMarker.current; + if (marker) { + const rotationSpeed = 10; + marker.rotation.y += deltaX * rotationSpeed; + if (isRotating === "start") { + setStartRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); + } else { + setEndRotation([ + marker.rotation.x, + marker.rotation.y, + marker.rotation.z, + ]); + } + } + }); + + useEffect(() => { + const handleGlobalPointerUp = () => { + setIsDragging(null); + setIsRotating(null); + if (controls) (controls as any).enabled = true; + handlePointerUp(); + }; + + if (isDragging || isRotating) { + window.addEventListener("pointerup", handleGlobalPointerUp); + } + + return () => { + window.removeEventListener("pointerup", handleGlobalPointerUp); + }; + }, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation]); + + return ( + <> + {selectedHumanData && ( + + { + e.stopPropagation(); + handlePointerDown(e, "start", "start"); + }} + onPointerMissed={() => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + { + e.stopPropagation(); + handlePointerDown(e, "end", "end"); + }} + onPointerMissed={() => { + (controls as any).enabled = true; + setIsDragging(null); + setIsRotating(null); + }} + /> + + )} + + ) +} + +export default HumanUi \ No newline at end of file diff --git a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts index 8275fb3..58925f3 100644 --- a/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts +++ b/app/src/modules/simulation/simulator/functions/checkActiveRoboticArmsInSubsequence.ts @@ -124,7 +124,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm' + event.type === 'roboticArm' || + event.type === 'human' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts index 7c2f7ee..a87fe6a 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionMachineSequences.ts @@ -16,7 +16,8 @@ export async function determineExecutionMachineSequences(products: productsSchem event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm' + event.type === 'roboticArm' || + event.type === 'human' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts index 1f29ab3..d17e2d7 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionOrder.ts @@ -19,7 +19,9 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[ } else if (event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm') { + event.type === 'roboticArm' || + event.type === 'human' + ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); } diff --git a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts index bb88fb5..3ba5eb2 100644 --- a/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts +++ b/app/src/modules/simulation/simulator/functions/determineExecutionSequences.ts @@ -16,7 +16,9 @@ export async function determineExecutionSequences(products: productsSchema): Pro } else if (event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm') { + event.type === 'roboticArm' || + event.type === 'human' + ) { pointMap.set(event.point.uuid, event.point); allPoints.push(event.point); } diff --git a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts index 8ad5e10..8f812bf 100644 --- a/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts +++ b/app/src/modules/simulation/simulator/functions/getConveyorSequencesInProduct.ts @@ -91,7 +91,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch event.type === 'vehicle' || event.type === 'machine' || event.type === 'storageUnit' || - event.type === 'roboticArm' + event.type === 'roboticArm' || + event.type === 'human' ) { pointToEventMap.set(event.point.uuid, event); allPoints.push(event.point); diff --git a/app/src/modules/simulation/spatialUI/temp.md b/app/src/modules/simulation/spatialUI/temp.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx index 9d889ed..02eb2ec 100644 --- a/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx @@ -43,7 +43,7 @@ const VehicleUI = () => { const outerGroup = useRef(null); const state: Types.ThreeState = useThree(); const controls: any = state.controls; - const [selectedVehicleData, setSelectedVechicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); + const [selectedVehicleData, setSelectedVehicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] }); const CIRCLE_RADIUS = 0.8; const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); @@ -71,7 +71,7 @@ const VehicleUI = () => { ); if (selectedVehicle) { - setSelectedVechicleData({ + setSelectedVehicleData({ position: selectedVehicle.position, rotation: selectedVehicle.rotation, }); diff --git a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts index 5731d9e..0ec6c36 100644 --- a/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts +++ b/app/src/modules/simulation/triggers/triggerHandler/useTriggerHandler.ts @@ -6,9 +6,10 @@ import { useVehicleEventManager } from '../../vehicle/eventManager/useVehicleEve import { useMachineEventManager } from '../../machine/eventManager/useMachineEventManager'; import { useSceneContext } from '../../../scene/sceneContext'; import { useProductContext } from '../../products/productContext'; +import { useHumanEventManager } from '../../human/eventManager/useHumanEventManager'; export function useTriggerHandler() { - const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, productStore } = useSceneContext(); + const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext(); const { selectedProductStore } = useProductContext(); const { handleAction } = useActionHandler(); const { selectedProduct } = selectedProductStore(); @@ -19,7 +20,9 @@ export function useTriggerHandler() { const { addConveyorToMonitor } = useConveyorEventManager(); const { addVehicleToMonitor } = useVehicleEventManager(); const { addMachineToMonitor } = useMachineEventManager(); + const { addHumanToMonitor } = useHumanEventManager(); const { getVehicleById } = vehicleStore(); + const { getHumanById } = humanStore(); const { getMachineById } = machineStore(); const { getStorageUnitById } = storageUnitStore(); const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore(); @@ -149,23 +152,51 @@ export function useTriggerHandler() { const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); if (model?.type === 'vehicle') { - const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); - if (vehicle) { - if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { - // Handle current action from vehicle - setIsPaused(materialId, true); - handleAction(action, materialId); - - } else { - - // Handle current action using Event Manager - setIsPaused(materialId, true); - - addVehicleToMonitor(vehicle.modelUuid, - () => { + const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (armBot) { + if (armBot.isActive === false && armBot.state === 'idle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) } - ) + } + } else { + setIsPaused(materialId, true); + addArmBotToMonitor(armBot.modelUuid, () => { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) + } + } + }) } } } else if (model?.type === 'machine') { @@ -191,26 +222,25 @@ export function useTriggerHandler() { } } else { setIsPaused(materialId, true); - addArmBotToMonitor(armBot.modelUuid, - () => { - const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); - if (machine) { - if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { - setIsPaused(materialId, true); - handleAction(action, materialId); - } else { + addArmBotToMonitor(armBot.modelUuid, () => { + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (machine) { + if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + handleAction(action, materialId); + } else { - // Handle current action using Event Manager - setIsPaused(materialId, true); + // Handle current action using Event Manager + setIsPaused(materialId, true); - addMachineToMonitor(machine.modelUuid, - () => { - handleAction(action, materialId); - } - ) - } + addMachineToMonitor(machine.modelUuid, + () => { + handleAction(action, materialId); + } + ) } } + } ); } } @@ -256,6 +286,242 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'storageUnit') { // Transfer to Storage Unit + } else if (toEvent?.type === 'human') { + // Transfer to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + // Handle current action of the material + handleAction(action, materialId); + + if (material.next) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.next.modelUuid, + pointUuid: material.next.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + if (action) { + + if (human) { + if (action && action.triggers.length > 0 && + action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && + action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid && + action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + if (model?.type === 'vehicle') { + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (human) { + if (human.isActive === false && human.state === 'idle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } else { + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + }) + } + } + } else if (model?.type === 'transfer') { + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (human) { + if (human.isActive === false && human.state === 'idle') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (conveyor) { + if (!conveyor.isPaused) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addConveyorToMonitor(conveyor.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } else { + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + if (!conveyor.isPaused) { + // Handle current action from vehicle + setIsVisible(materialId, false); + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addConveyorToMonitor(conveyor.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + }) + } + } + } else if (model?.type === 'machine') { + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + if (human) { + if (human.isActive === false && human.state === 'idle') { + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (machine) { + if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addMachineToMonitor(machine.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } else { + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, () => { + const machine = getMachineById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (machine) { + if (machine.isActive === false && machine.state === 'idle' && !machine.currentAction) { + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addMachineToMonitor(machine.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId); + } + ) + } + } + } + ); + } + } + } else { + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId) + } + ); + + } + } + } else { + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + setIsPaused(materialId, true); + setIsVisible(materialId, false); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + addHumanToMonitor(human.modelUuid, + () => { + setIsVisible(materialId, false); + handleAction(action, materialId) + } + ); + + } + } + } + } + } + } + } } } else if (fromEvent?.type === 'vehicle') { if (toEvent?.type === 'transfer') { @@ -322,18 +588,25 @@ export function useTriggerHandler() { setNextLocation(material.materialId, null); - setIsVisible(materialId, false); if (action && armBot) { if (armBot.isActive === false && armBot.state === 'idle') { // Handle current action from arm bot + setIsVisible(materialId, false); + handleAction(action, materialId); } else { - // Event Manager Needed + addArmBotToMonitor(armBot.modelUuid, + () => { + setIsVisible(materialId, false); + + handleAction(action, materialId); + } + ) } } @@ -379,6 +652,52 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'human') { + // Vehicle to Human + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + + if (action && human) { + + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + setIsVisible(materialId, false); + + handleAction(action, materialId); + + } else { + + addHumanToMonitor(human.modelUuid, + () => { + setIsVisible(materialId, false); + + handleAction(action, materialId); + } + ) + } + } + } + } } } else if (fromEvent?.type === 'machine') { if (toEvent?.type === 'transfer') { @@ -485,6 +804,135 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'storageUnit') { // Machine to Storage Unit + } else if (toEvent?.type === 'human') { + // Machine to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + + setIsPaused(materialId, true); + + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + + if (action && human) { + + if (human.isActive === false && human.state === 'idle') { + + // Handle current action from arm bot + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); + if (previousModel) { + if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + handleAction(action, materialId) + } else { + addConveyorToMonitor(conveyor.modelUuid, + () => { + handleAction(action, materialId) + } + ) + } + } else { + handleAction(action, materialId) + } + // handleAction(action, materialId) + } + } else if (model?.type === 'vehicle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) + } + } + } else { + handleAction(action, materialId) + } + + } else { + + // Handle current action using Event Manager + + addHumanToMonitor(human.modelUuid, () => { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (conveyor) { + const previousModel = getEventByModelUuid(selectedProduct.productUuid, material.previous?.modelUuid || ''); + if (previousModel) { + if (previousModel.type === 'transfer' && previousModel.modelUuid === model.modelUuid) { + handleAction(action, materialId) + } else { + addConveyorToMonitor(conveyor.modelUuid, + () => { + handleAction(action, materialId) + } + ) + } + } else { + handleAction(action, materialId) + } + } + } else if (model?.type === 'vehicle') { + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || ''); + if (vehicle) { + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + // Handle current action from vehicle + setIsPaused(materialId, true); + handleAction(action, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + handleAction(action, materialId); + } + ) + } + } + } else { + handleAction(action, materialId) + } + } + ); + } + } + } + } } } else if (fromEvent?.type === 'roboticArm') { if (toEvent?.type === 'transfer') { @@ -758,6 +1206,53 @@ export function useTriggerHandler() { } } } + } else if (toEvent?.type === 'human') { + // Robotic Arm to Human + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + setIsPaused(material.materialId, false); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setNextLocation(material.materialId, null); + + if (action) { + + if (human) { + + if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < human.point.action.loadCapacity) { + + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + // Handle current action from human + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + + } + } + } } else if (fromEvent?.type === 'storageUnit') { if (toEvent?.type === 'transfer') { @@ -775,6 +1270,326 @@ export function useTriggerHandler() { } else if (toEvent?.type === 'storageUnit') { // Storage Unit to Storage Unit + } else if (toEvent?.type === 'human') { + // Storage Unit to Human + + } + } else if (fromEvent?.type === 'human') { + if (toEvent?.type === 'transfer') { + // Human to Transfer + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + + if (action && action.triggers.length > 0 && + action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid && + action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid && + action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) { + const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + if (model?.type === 'roboticArm') { + + handleAction(action, material.materialId); + + } else if (model?.type === 'vehicle') { + const nextAction = getActionByUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredAction.actionUuid); + + if (action) { + handleAction(action, material.materialId); + + const vehicle = getVehicleById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset?.triggeredPoint?.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + if (nextAction) { + + if (vehicle) { + + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + setIsPaused(materialId, false); + + // Handle current action from vehicle + handleAction(nextAction, materialId); + + } else { + + // Handle current action using Event Manager + setIsPaused(materialId, true); + + addVehicleToMonitor(vehicle.modelUuid, + () => { + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '', + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid || '', + actionUuid: action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid || '', + }); + + setNextLocation(material.materialId, null); + + setIsPaused(materialId, false); + setIsVisible(materialId, false); + handleAction(nextAction, materialId); + } + ) + } + } + } + } + + } else if (model?.type === 'transfer') { + const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid); + if (conveyor) { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + }) + setIsPaused(material.materialId, false); + setIsVisible(material.materialId, true); + handleAction(action, material.materialId); + } + } else { + setNextLocation(material.materialId, { + modelUuid: action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid, + pointUuid: action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid, + }) + + handleAction(action, material.materialId); + } + } else if (action) { + setNextLocation(material.materialId, null) + + handleAction(action, material.materialId); + } + + } + } + + } else if (toEvent?.type === 'vehicle') { + // Human to Vehicle + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + setIsPaused(material.materialId, false); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const vehicle = getVehicleById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setNextLocation(material.materialId, null); + + if (action) { + + if (vehicle) { + + if (vehicle.isActive === false && vehicle.state === 'idle' && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) { + + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + // Handle current action from vehicle + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + + } + } + + } else if (toEvent?.type === 'machine') { + // Human to Machine + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + + // setIsPaused(material.materialId, false); + + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const machine = getMachineById(trigger.triggeredAsset?.triggeredModel.modelUuid); + setNextLocation(material.materialId, null); + + if (action) { + + if (machine) { + + if (machine.isActive === false && machine.state === 'idle') { + + setIsVisible(materialId, false); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + // Handle current action from machine + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + } + } + } else if (toEvent?.type === 'roboticArm') { + // Human to Robotic Arm + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + + if (action && armBot) { + + if (armBot.isActive === false && armBot.state === 'idle') { + + // Handle current action from arm bot + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + } + + } else if (toEvent?.type === 'storageUnit') { + // Human to Storage Unit + + if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) { + const material = getMaterialById(materialId); + if (material) { + const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid); + const storageUnit = getStorageUnitById(trigger.triggeredAsset?.triggeredModel.modelUuid); + + setPreviousLocation(material.materialId, { + modelUuid: material.current.modelUuid, + pointUuid: material.current.pointUuid, + actionUuid: material.current.actionUuid, + }) + + setCurrentLocation(material.materialId, { + modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid, + pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid, + }); + + setNextLocation(material.materialId, null); + + setIsVisible(materialId, false); + + if (action && storageUnit) { + + if (storageUnit.currentLoad < storageUnit.point.action.storageCapacity) { + + // Handle current action from vehicle + handleAction(action, materialId); + + } else { + + // Event Manager Needed + + } + } + } + } + } else if (toEvent?.type === 'human') { + // Human to Human + } } } diff --git a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx index fb4ee8e..6a6a32a 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/materialAnimator.tsx @@ -3,12 +3,7 @@ import { useThree, useFrame } from '@react-three/fiber'; import * as THREE from 'three'; import { MaterialModel } from '../../../materials/instances/material/materialModel'; -type MaterialAnimatorProps = { - agvDetail: VehicleStatus; -}; - - -const MaterialAnimator = ({ agvDetail }: MaterialAnimatorProps) => { +const MaterialAnimator = ({ agvDetail }: { agvDetail: VehicleStatus }) => { const meshRef = useRef(null!); const [hasLoad, setHasLoad] = useState(false); const { scene } = useThree(); diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 9d45fd8..a5255e0 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -32,7 +32,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai useEffect(() => { if (currentPhase === 'stationed-pickup' && path.length > 0) { - // console.log('path: ', path); setCurrentPath(path); setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation) } else if (currentPhase === 'pickup-drop' && path.length > 0) { @@ -66,84 +65,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } }, [isReset, isPlaying]) - // useFrame((_, delta) => { - // const object = scene.getObjectByProperty('uuid', agvUuid); - // if (!object || currentPath.length < 2) return; - // if (isPaused) return; - - // let totalDistance = 0; - // const distances = []; - // let accumulatedDistance = 0; - // let index = 0; - // const rotationSpeed = 1; - - // 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; - // } - - // while (index < distances.length && progressRef.current > 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 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 * speed * agvDetail.speed) * 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); - // const t = (progressRef.current - accumulatedDistance) / segmentDistance; - // const position = start.clone().lerp(end, t); - // object.position.copy(position); - // } - // } - - // if (progressRef.current >= totalDistance) { - // if (restRotation && objectRotation) { - // const targetEuler = new THREE.Euler( - // objectRotation.x, - // objectRotation.y - (agvDetail.point.action.steeringAngle), - // objectRotation.z - // ); - // const targetQuaternion = new THREE.Quaternion().setFromEuler(targetEuler); - // object.quaternion.slerp(targetQuaternion, delta * (rotationSpeed * speed * agvDetail.speed)); - // if (object.quaternion.angleTo(targetQuaternion) < 0.01) { - // object.quaternion.copy(targetQuaternion); - // object.rotation.copy(targetEuler); - // setRestingRotation(false); - // } - // return; - // } - // } - // if (progressRef.current >= totalDistance) { - // setRestingRotation(true); - // progressRef.current = 0; - // movingForward.current = !movingForward.current; - // setCurrentPath([]); - // handleCallBack(); - // if (currentPhase === 'pickup-drop') { - // requestAnimationFrame(startUnloadingProcess); - // } - // } - // }); const lastTimeRef = useRef(performance.now()); useFrame(() => { diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 0709109..5c49901 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,20 +1,22 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import VehicleAnimator from '../animator/vehicleAnimator'; import * as THREE from 'three'; import { NavMeshQuery } from '@recast-navigation/core'; import { useNavMesh } from '../../../../../store/builder/store'; import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore'; import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler'; -import MaterialAnimator from '../animator/materialAnimator'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useProductContext } from '../../../products/productContext'; +import MaterialAnimator from '../animator/materialAnimator'; +import VehicleAnimator from '../animator/vehicleAnimator'; + function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) { const { navMesh } = useNavMesh(); const { isPlaying } = usePlayButtonStore(); - const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, productStore } = useSceneContext(); + const { materialStore, armBotStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, productStore } = useSceneContext(); const { removeMaterial, setEndTime } = materialStore(); const { getStorageUnitById } = storageUnitStore(); + const { getHumanById } = humanStore(); const { getArmBotById } = armBotStore(); const { getConveyorById } = conveyorStore(); const { triggerPointActions } = useTriggerHandler(); @@ -150,7 +152,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) // eslint-disable-next-line react-hooks/exhaustive-deps }, [vehicles, currentPhase, path, isPlaying]); - function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; @@ -197,7 +198,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) }; }, [agvDetail, isPlaying]); - function handleCallBack() { if (currentPhase === 'stationed-pickup') { setCurrentPhase('picking'); @@ -247,6 +247,11 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) if (action) { handleMaterialDropToStorageUnit(model); } + } else if (model.type === 'human') { + const action = getActionByUuid(selectedProduct.productUuid, agvDetail.point.action.actionUuid); + if (action) { + handleMaterialDropToHuman(model); + } } } else { const droppedMaterial = agvDetail.currentLoad; @@ -260,6 +265,79 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } } + function handleMaterialDropToHuman(model: HumanEventSchema) { + if (model) { + if (model.point.action.actionType === 'worker') { + loopMaterialDropToHuman( + agvDetail.modelUuid, + agvDetail.currentLoad, + agvDetail.point.action.unLoadDuration, + model.modelUuid, + model.point.action.loadCapacity, + agvDetail.point.action + ); + } + } + } + + function loopMaterialDropToHuman( + vehicleId: string, + vehicleCurrentLoad: number, + unLoadDuration: number, + humanId: string, + storageMaxCapacity: number, + action: VehicleAction + ) { + startTime = performance.now(); + const fixedInterval = ((unLoadDuration / vehicleCurrentLoad) * (1000 / isSpeedRef.current)); + + const unloadLoop = () => { + if (isPausedRef.current) { + pauseTimeRef.current ??= performance.now(); + requestAnimationFrame(unloadLoop); + return; + } + + if (pauseTimeRef.current) { + const pauseDuration = performance.now() - pauseTimeRef.current; + startTime += pauseDuration; + pauseTimeRef.current = null; + } + + const elapsedTime = performance.now() - startTime; + const human = getHumanById(humanId); + + if (elapsedTime >= fixedInterval) { + if (human && agvDetail && + human.currentLoad < storageMaxCapacity && + vehicleCurrentLoad > 0) { + + decrementVehicleLoad(vehicleId, 1); + vehicleCurrentLoad -= 1; + + const material = removeLastMaterial(vehicleId); + if (material) { + + triggerPointActions(action, material.materialId); + + } + + if (vehicleCurrentLoad > 0 && human.currentLoad < storageMaxCapacity) { + startTime = performance.now(); + requestAnimationFrame(unloadLoop); + } + } + } else { + requestAnimationFrame(unloadLoop); + } + }; + + const human = getHumanById(humanId); + if (human && vehicleCurrentLoad > 0 && human?.currentLoad < storageMaxCapacity) { + unloadLoop(); + } + } + function handleMaterialDropToStorageUnit(model: StorageEventSchema) { if (model) { if (model.point.action.actionType === 'store') { @@ -527,10 +605,4 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) ); } -export default VehicleInstance; - - - - - - +export default VehicleInstance; \ No newline at end of file diff --git a/app/src/services/simulation/products/UpsertProductOrEventApi.ts b/app/src/services/simulation/products/UpsertProductOrEventApi.ts index 69b6c9e..2382762 100644 --- a/app/src/services/simulation/products/UpsertProductOrEventApi.ts +++ b/app/src/services/simulation/products/UpsertProductOrEventApi.ts @@ -1,38 +1,37 @@ 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/V1/ProductUpsert`, - { - method: "POST", - headers: { - Authorization: "Bearer ", - "Content-Type": "application/json", - token: localStorage.getItem("token") || "", - refresh_token: localStorage.getItem("refreshToken") || "", - }, - body: JSON.stringify(body), - } - ); - const newAccessToken = response.headers.get("x-access-token"); - if (newAccessToken) { - //console.log("New token received:", newAccessToken); - localStorage.setItem("token", newAccessToken); - } + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/ProductUpsert`, + { + method: "POST", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify(body), + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } - if (!response.ok) { - console.error("Failed to add product or event"); - } + if (!response.ok) { + console.error("Failed to add product or event"); + } - const result = await response.json(); - return result; - } catch (error) { - echo.error("Failed to upsert product Or eventApi"); - if (error instanceof Error) { - console.log(error.message); - } else { - console.log("An unknown error occurred"); + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to upsert product Or eventApi"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } } - } }; diff --git a/app/src/store/builder/useAssetStore.ts b/app/src/store/builder/useAssetStore.ts index fec16b4..2223d0a 100644 --- a/app/src/store/builder/useAssetStore.ts +++ b/app/src/store/builder/useAssetStore.ts @@ -22,7 +22,9 @@ interface AssetsStore { // Animation controls setAnimations: (modelUuid: string, animations: string[]) => void; - setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void; + setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean, loopAnimation: boolean, isCompleted: boolean) => void; + setAnimationComplete: (modelUuid: string, isCompleted: boolean) => void; + resetAnimation: (modelUuid: string) => void; addAnimation: (modelUuid: string, animation: string) => void; removeAnimation: (modelUuid: string, animation: string) => void; @@ -149,22 +151,44 @@ export const createAssetStore = () => { if (asset) { asset.animations = animations; if (!asset.animationState) { - asset.animationState = { current: '', playing: false }; + asset.animationState = { current: '', isPlaying: false, loopAnimation: true, isCompleted: true }; } } }); }, - setCurrentAnimation: (modelUuid, current, isPlaying) => { + setCurrentAnimation: (modelUuid, current, isPlaying, loopAnimation, isCompleted) => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); if (asset?.animationState) { asset.animationState.current = current; - asset.animationState.playing = isPlaying; + asset.animationState.isPlaying = isPlaying; + asset.animationState.loopAnimation = loopAnimation; + asset.animationState.isCompleted = isCompleted; } }); }, + setAnimationComplete: (modelUuid, isCompleted) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animationState) { + asset.animationState.isCompleted = isCompleted; + } + }); + }, + + resetAnimation: (modelUuid) => { + set((state) => { + const asset = state.assets.find(a => a.modelUuid === modelUuid); + if (asset?.animationState) { + asset.animationState.current = ''; + asset.animationState.isPlaying = true; + asset.animationState.loopAnimation = true; + asset.animationState.isCompleted = true; } + }); + }, + addAnimation: (modelUuid, animation) => { set((state) => { const asset = state.assets.find(a => a.modelUuid === modelUuid); @@ -184,7 +208,7 @@ export const createAssetStore = () => { if (asset?.animations) { asset.animations = asset.animations.filter(a => a !== animation); if (asset.animationState?.current === animation) { - asset.animationState.playing = false; + asset.animationState.isPlaying = false; asset.animationState.current = ''; } } diff --git a/app/src/store/builder/useBuilderStore.ts b/app/src/store/builder/useBuilderStore.ts index 6147c0c..5be4e3b 100644 --- a/app/src/store/builder/useBuilderStore.ts +++ b/app/src/store/builder/useBuilderStore.ts @@ -15,6 +15,7 @@ interface BuilderState { // Floor Asset selectedFloorAsset: Object3D | null; + loopAnimation: boolean; // Wall Settings selectedWall: Object3D | null; @@ -64,6 +65,7 @@ interface BuilderState { // Setters - Floor Asset setSelectedFloorAsset: (asset: Object3D | null) => void; + setLoopAnimation: (loop: boolean) => void; // Setters - Wall setSelectedWall: (wall: Object3D | null) => void; @@ -118,6 +120,7 @@ export const useBuilderStore = create()( deletableWallAsset: null, selectedFloorAsset: null, + loopAnimation: true, selectedWall: null, wallThickness: 0.5, @@ -197,6 +200,12 @@ export const useBuilderStore = create()( }); }, + setLoopAnimation(loopAnimation: boolean) { + set((state) => { + state.loopAnimation = loopAnimation; + }); + }, + // === Setters: Wall === setSelectedWall: (wall: Object3D | null) => { diff --git a/app/src/store/simulation/useHumanStore.ts b/app/src/store/simulation/useHumanStore.ts index 75b9c7d..496228f 100644 --- a/app/src/store/simulation/useHumanStore.ts +++ b/app/src/store/simulation/useHumanStore.ts @@ -15,6 +15,10 @@ interface HumansStore { setHumanActive: (modelUuid: string, isActive: boolean) => void; setHumanPicking: (modelUuid: string, isPicking: boolean) => void; setHumanLoad: (modelUuid: string, load: number) => void; + setHumanState: ( + modelUuid: string, + newState: HumanStatus["state"] + ) => void; incrementHumanLoad: (modelUuid: string, incrementBy: number) => void; decrementHumanLoad: (modelUuid: string, decrementBy: number) => void; @@ -24,11 +28,6 @@ interface HumansStore { getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; clearCurrentMaterials: (modelUuid: string) => void; - setCurrentAction: ( - modelUuid: string, - action: HumanStatus["currentAction"] - ) => void; - incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void; @@ -111,6 +110,15 @@ export const createHumanStore = () => { }); }, + setHumanState: (modelUuid, newState) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.state = newState; + } + }); + }, + incrementHumanLoad: (modelUuid, incrementBy) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); @@ -152,7 +160,7 @@ export const createHumanStore = () => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); if (human && human.currentMaterials.length > 0) { - removed = human.currentMaterials.pop(); + removed = JSON.parse(JSON.stringify(human.currentMaterials.pop())); } }); return removed; @@ -175,15 +183,6 @@ export const createHumanStore = () => { }); }, - setCurrentAction: (modelUuid, action) => { - set((state) => { - const human = state.humans.find(h => h.modelUuid === modelUuid); - if (human) { - human.currentAction = action; - } - }); - }, - incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const human = state.humans.find(h => h.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 695559d..3b483ea 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] + action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action'] ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss @@ -276,6 +276,15 @@ export const createProductStore = () => { return; } } + } else if (event.type === "human") { + if ('actions' in point) { + const index = point.actions.findIndex((a: any) => a.actionUuid === actionUuid); + if (index !== -1) { + point.actions.splice(index, 1); + updatedEvent = JSON.parse(JSON.stringify(event)); + return; + } + } } else if ('action' in point && point.action?.actionUuid === actionUuid) { point.action = undefined; updatedEvent = JSON.parse(JSON.stringify(event)); diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index 1bbab4c..b6cd076 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -146,6 +146,40 @@ export const useSelectedAction = create()( })) ); +interface SelectedAnimationState { + selectedAnimation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + } | null; + setSelectedAnimation: (animation: { + animationUuid: string; + animationName: string; + animationType: "behaviour" | "animatedTravel"; + animation: string | null; + travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } + }) => void; + clearSelectedAnimation: () => void; +} + +export const useSelectedAnimation = create()( + immer((set) => ({ + selectedAnimation: null, + setSelectedAnimation: (animation) => { + set((state) => { + state.selectedAnimation = animation; + }); + }, + clearSelectedAnimation: () => { + set((state) => { + state.selectedAnimation = null; + }); + }, + })) +); + interface IsDraggingState { isDragging: "start" | "end" | null; setIsDragging: (state: "start" | "end" | null) => void; diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index de7586a..b48cd12 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -26,7 +26,9 @@ interface Asset { animations?: string[]; animationState?: { current: string; - playing: boolean; + isPlaying: boolean; + loopAnimation: boolean; + isCompleted: boolean; }; eventData?: { type: string; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 1d2f364..678f117 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -72,10 +72,10 @@ interface StorageAction { interface HumanAction { actionUuid: string; actionName: string; - actionType: "animation" | "animatedTravel"; - animation: string | null; + actionType: "worker"; + pickUpPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } + dropPoint?: { position: [number, number, number] | null; rotation: [number, number, number] | null; } loadCapacity: number; - travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } triggers: TriggerSchema[]; } @@ -121,7 +121,7 @@ interface HumanPointSchema { uuid: string; position: [number, number, number]; rotation: [number, number, number]; - actions: HumanAction[]; + action: HumanAction; } type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; @@ -157,6 +157,7 @@ interface StorageEventSchema extends AssetEventSchema { interface HumanEventSchema extends AssetEventSchema { type: "human"; + speed: number; point: HumanPointSchema; } @@ -229,6 +230,7 @@ interface HumanStatus extends HumanEventSchema { currentAction?: { actionUuid: string; actionName: string; + animationUuid: string; materialType?: string | null; materialId?: string | null; };