diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 1ff41e7..6607937 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -6,146 +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 && selectedFloorItem.animationState) { - const isPlaying = selectedFloorItem.animationState?.playing || false; - setCurrentAnimation(selectedFloorItem.uuid, animation, !isPlaying); + 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/ZoneProperties.tsx b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx index 7415878..d129dd5 100644 --- a/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/ZoneProperties.tsx @@ -12,13 +12,17 @@ import { zoneCameraUpdate } from "../../../../services/visulization/zone/zoneCam import { useParams } from "react-router-dom"; import { getUserData } from "../../../../functions/getUserData"; import { useVersionContext } from "../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../modules/scene/sceneContext"; const ZoneProperties: React.FC = () => { const { Edit, setEdit } = useEditPosition(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { zonePosition, setZonePosition } = usezonePosition(); const { zoneTarget, setZoneTarget } = usezoneTarget(); - const { zones, setZones } = useZones(); + // const { zones, setZones } = useZones(); + const { assetStore, zoneStore } = useSceneContext(); + const { zones, setZoneName } = zoneStore() + const { projectId } = useParams(); const { userName, userId, organization, email } = getUserData(); const { selectedVersionStore } = useVersionContext(); @@ -34,10 +38,11 @@ const ZoneProperties: React.FC = () => { let zonesdata = { zoneUuid: selectedZone.zoneUuid, - viewPortposition: zonePosition, - viewPortCenter: zoneTarget, + viewPortPosition: zonePosition, + viewPortTarget: zoneTarget, }; + let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); // console.log('response: ', response); if (response.message === "zone updated") { @@ -63,13 +68,14 @@ const ZoneProperties: React.FC = () => { let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); if (response.message === "zone updated") { setSelectedZone((prev) => ({ ...prev, zoneName: newName })); - setZones((prevZones: any[]) => - prevZones.map((zone) => - zone.zoneUuid === selectedZone.zoneUuid - ? { ...zone, zoneName: newName } - : zone - ) - ); + setZoneName(selectedZone.zoneUuid, newName) + // setZones((prevZones: any[]) => + // prevZones.map((zone) => + // zone.zoneUuid === selectedZone.zoneUuid + // ? { ...zone, zoneName: newName } + // : zone + // ) + // ); } else { // console.log(response?.message); } @@ -81,6 +87,7 @@ const ZoneProperties: React.FC = () => { setSelectedZone((prev) => ({ ...prev, [key]: newValue })); } const checkZoneNameDuplicate = (name: string) => { + console.log('zones: ', zones); return zones.some( (zone: any) => zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() && diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index eee19b1..252e44d 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"; @@ -60,6 +61,8 @@ const EventProperties: React.FC = () => { return "machine"; case "storageUnit": return "storageUnit"; + case "human": + return "human"; default: return null; } @@ -79,6 +82,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 85a539c..57efd87 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -274,14 +274,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/layout/sidebarRight/versionHisory/VersionHistory.tsx b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx index 9d24ba0..a0255f4 100644 --- a/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx +++ b/app/src/components/layout/sidebarRight/versionHisory/VersionHistory.tsx @@ -28,9 +28,9 @@ const VersionHistory = () => { const handleSelectVersion = (version: Version) => { if (!projectId) return; - getVersionDataApi(projectId, version.versionId).then((verdionData) => { + getVersionDataApi(projectId, version.versionId).then((versionData) => { setSelectedVersion(version); - // console.log(verdionData); + // console.log(versionData); }).catch((err) => { // console.log(err); }) 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/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index 753f154..d75387d 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -44,16 +44,19 @@ const DropDownList: React.FC = ({ remove, }) => { const [isOpen, setIsOpen] = useState(defaultOpen); - const { zones } = useZones(); + // const { zones } = useZones(); const handleToggle = () => { setIsOpen((prev) => !prev); // Toggle the state }; const [zoneDataList, setZoneDataList] = useState([]); - const { assetStore } = useSceneContext(); + // const { assetStore } = useSceneContext(); + const { assetStore, zoneStore } = useSceneContext(); const { assets } = assetStore(); - + const { zones } = zoneStore() + + const isPointInsidePolygon = ( point: [number, number], polygon: [number, number][] @@ -76,7 +79,7 @@ const DropDownList: React.FC = ({ }; useEffect(() => { - const updatedZoneList: ZoneData[] = zones?.map((zone: Zone) => { + const updatedZoneList: ZoneData[] = zones?.map((zone: any) => { const polygon2D = zone.points.map((p: [number, number, number]) => [ p[0], p[2], diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index cf13a4f..dac4b40 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -46,7 +46,7 @@ const List: React.FC = ({ items = [], remove }) => { const { activeModule } = useModuleStore(); const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); - const { zones, setZones } = useZones(); + const { setSubModule } = useSubModuleStore(); const [expandedZones, setExpandedZones] = useState>({}); const { projectId } = useParams(); @@ -55,6 +55,8 @@ const List: React.FC = ({ items = [], remove }) => { const { organization } = getUserData(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const { zoneStore } = useSceneContext(); + const { zones, setZoneName } = zoneStore() useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ @@ -92,8 +94,8 @@ const List: React.FC = ({ items = [], remove }) => { lockedPanels: response?.lockedPanels ?? [], widgets: response?.widgets ?? [], zoneUuid: response?.zoneUuid, - zoneViewPortTarget: response?.viewPortCenter ?? [], - zoneViewPortPosition: response?.viewPortposition ?? [], + zoneViewPortTarget: response?.viewPortTarget ?? [], + zoneViewPortPosition: response?.viewPortPosition ?? [], }); } catch (error) { echo.error("Failed to select zone"); @@ -123,13 +125,14 @@ const List: React.FC = ({ items = [], remove }) => { const response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); if (response.message === "zone updated") { setSelectedZone((prev) => ({ ...prev, zoneName: newName })); - setZones((prevZones: any[]) => - prevZones.map((zone) => - zone.zoneUuid === selectedZone.zoneUuid - ? { ...zone, zoneName: newName } - : zone - ) - ); + setZoneName(selectedZone.zoneUuid, newName) + // setZones((prevZones: any[]) => + // prevZones.map((zone) => + // zone.zoneUuid === selectedZone.zoneUuid + // ? { ...zone, zoneName: newName } + // : zone + // ) + // ); } } diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index 31ed1f6..af08226 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -3,12 +3,14 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useThree } from '@react-three/fiber'; import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; -import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi'; import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../version/versionContext'; import { useSceneContext } from '../../../scene/sceneContext'; import ReferenceAisle from './referenceAisle'; import ReferencePoint from '../../point/reference/referencePoint'; +import { getUserData } from '../../../../functions/getUserData'; + +// import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi'; function AisleCreator() { const { scene, camera, raycaster, gl, pointer } = useThree(); @@ -23,6 +25,7 @@ function AisleCreator() { const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); + const { userId, organization } = getUserData(); const { projectId } = useParams(); const [tempPoints, setTempPoints] = useState([]); @@ -106,7 +109,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -129,7 +147,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -151,7 +184,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -172,7 +220,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -195,7 +258,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -217,7 +295,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -238,7 +331,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } @@ -260,7 +368,22 @@ function AisleCreator() { }; addAisle(aisle); if (projectId) { - upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '') + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid, + points: aisle.points, + type: aisle.type + }) } setTempPoints([newPoint]); } 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 7c85a49..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 = { @@ -360,6 +360,35 @@ async function handleModelLoad( position: storageEvent.point.position, rotation: storageEvent.point.rotation, }; + } else if (selectedItem.type === "Human") { + const humanEvent: HumanEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + 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], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + + } + } + addEvent(humanEvent); + eventData.point = { + uuid: humanEvent.point.uuid, + position: humanEvent.point.position, + rotation: humanEvent.point.rotation, + } } const completeData = { diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 3c76664..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,28 +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(() => { - const handlePlay = (clipName: string) => { - if (!mixerRef.current) return; + if (!asset.animationState || !mixerRef.current) return; + const { current, loopAnimation, isPlaying } = asset.animationState; + const currentAction = actions.current[current]; + const previousAction = previousAnimation ? actions.current[previousAnimation] : null; + + if (isPlaying && currentAction) { + blendFactor.current = 0; + + 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()); + } - const action = actions.current[clipName]; - if (action && asset.animationState?.playing) { - action.reset().setLoop(THREE.LoopOnce, 1).play(); + return () => { + if (mixerRef.current) { + mixerRef.current.removeEventListener('finished', handleAnimationComplete); } }; - - handlePlay(asset.animationState?.current || ''); - - }, [asset]) + }, [asset.animationState?.current, asset.animationState?.isPlaying]); return ( ) )} - {/* - -
- {animationNames.map((name) => ( - - ))} -
- -
*/}
); } diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index 15777aa..73dec77 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom'; import { useVersionContext } from '../version/versionContext'; import { useSceneContext } from '../../scene/sceneContext'; -import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi'; -import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; +// import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi'; +// import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi'; // import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi'; // import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi'; // import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi'; @@ -159,7 +159,22 @@ function Point({ point }: { readonly point: Point }) { const updatedAisles = getAislesByPointId(point.pointUuid); if (updatedAisles.length > 0 && projectId) { updatedAisles.forEach((updatedAisle) => { - upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '') + + // API + + // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + socket.emit('v1:model-aisle:add', { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: updatedAisle.aisleUuid, + points: updatedAisle.points, + type: updatedAisle.type + }) }) } } else if (point.pointType === 'Wall') { @@ -238,7 +253,22 @@ function Point({ point }: { readonly point: Point }) { if (removedAisles.length > 0) { removedAisles.forEach(aisle => { if (projectId) { - deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '') + + // API + + // deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || ''); + + // SOCKET + + const data = { + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization, + aisleUuid: aisle.aisleUuid + } + + socket.emit('v1:model-aisle:delete', data); } }); setHoveredPoint(null); diff --git a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx index 1df200f..f1f1b6a 100644 --- a/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx +++ b/app/src/modules/builder/zone/zoneCreator/zoneCreator.tsx @@ -20,7 +20,7 @@ function ZoneCreator() { const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); const { zoneStore } = useSceneContext(); - const { addZone, getZonePointById, getZoneByPoints } = zoneStore(); + const { zones, addZone, getZonePointById, getZoneByPoints } = zoneStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -32,6 +32,7 @@ function ZoneCreator() { const [isCreating, setIsCreating] = useState(false); const { zoneColor, zoneHeight, snappedPosition, snappedPoint, setSnappedPoint, setSnappedPosition } = useBuilderStore(); + useEffect(() => { const canvasElement = gl.domElement; @@ -92,7 +93,7 @@ function ZoneCreator() { if (tempPoints.length > 2 && isCreating && snappedPoint && snappedPoint.pointUuid === tempPoints[0].pointUuid) { const zone: Zone = { zoneUuid: THREE.MathUtils.generateUUID(), - zoneName: "Zone", + zoneName: `Zone `, points: tempPoints, zoneColor, zoneHeight, diff --git a/app/src/modules/collaboration/socket/socketResponses.dev.tsx b/app/src/modules/collaboration/socket/socketResponses.dev.tsx index 5b8f772..f2ab238 100644 --- a/app/src/modules/collaboration/socket/socketResponses.dev.tsx +++ b/app/src/modules/collaboration/socket/socketResponses.dev.tsx @@ -6,7 +6,7 @@ export default function SocketResponses() { useEffect(() => { socket.on("v1:model-asset:response:add", (data: any) => { - console.log('data: ', data); + // console.log('data: ', data); }); return () => { diff --git a/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx b/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx index 3b9d232..29a3007 100644 --- a/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/copyPasteControls.tsx @@ -332,6 +332,34 @@ const CopyPasteControls = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; + } else if (obj.userData.eventData.type === "Human") { + const humanEvent: HumanEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "human", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], + rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + } + } + addEvent(humanEvent); + eventData.point = { + uuid: humanEvent.point.uuid, + position: humanEvent.point.position, + rotation: humanEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx b/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx index 5194952..b4a1916 100644 --- a/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/duplicationControls.tsx @@ -306,6 +306,34 @@ const DuplicationControls = ({ position: storageEvent.point.position, rotation: storageEvent.point.rotation }; + } else if (obj.userData.eventData.type === "Human") { + const humanEvent: HumanEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "human", + speed: 1, + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], + rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "worker", + loadCapacity: 1, + triggers: [] + } + } + } + addEvent(humanEvent); + eventData.point = { + uuid: humanEvent.point.uuid, + position: humanEvent.point.position, + rotation: humanEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index 4e48e51..67811c4 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -16,6 +16,7 @@ import { createMachineStore, MachineStoreType } from '../../store/simulation/use import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/useConveyorStore'; import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore'; import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore'; +import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore'; type SceneContextValue = { @@ -35,6 +36,7 @@ type SceneContextValue = { conveyorStore: ConveyorStoreType; vehicleStore: VehicleStoreType; storageUnitStore: StorageUnitStoreType; + humanStore: HumanStoreType; clearStores: () => void; @@ -67,6 +69,7 @@ export function SceneProvider({ const conveyorStore = useMemo(() => createConveyorStore(), []); const vehicleStore = useMemo(() => createVehicleStore(), []); const storageUnitStore = useMemo(() => createStorageUnitStore(), []); + const humanStore = useMemo(() => createHumanStore(), []); const clearStores = useMemo(() => () => { assetStore.getState().clearAssets(); @@ -83,7 +86,8 @@ export function SceneProvider({ conveyorStore.getState().clearConveyors(); vehicleStore.getState().clearVehicles(); storageUnitStore.getState().clearStorageUnits(); - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore]); + humanStore.getState().clearHumans(); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); const contextValue = useMemo(() => ( { @@ -101,10 +105,11 @@ export function SceneProvider({ conveyorStore, vehicleStore, storageUnitStore, + humanStore, clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]); return ( diff --git a/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts new file mode 100644 index 0000000..9a1cf99 --- /dev/null +++ b/app/src/modules/simulation/actions/human/actionHandler/useWorkerHandler.ts @@ -0,0 +1,36 @@ +import { useCallback } from "react"; +import { useSceneContext } from "../../../../scene/sceneContext"; +import { useProductContext } from "../../../products/productContext"; + +export function useWorkerHandler() { + const { materialStore, humanStore, productStore } = useSceneContext(); + const { getMaterialById } = materialStore(); + const { getModelUuidByActionUuid } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { incrementHumanLoad, addCurrentMaterial } = humanStore(); + + const workerLogStatus = (materialUuid: string, status: string) => { + 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/analysis/productionCapacity/productionCapacityData.tsx b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx index 888a519..f7891e7 100644 --- a/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx +++ b/app/src/modules/simulation/analysis/productionCapacity/productionCapacityData.tsx @@ -1,19 +1,15 @@ -import React, { useEffect } from 'react' +import { useEffect } from 'react' import { useInputValues, useProductionCapacityData, useThroughPutData } from '../../../../store/builder/store' import { usePlayButtonStore } from '../../../../store/usePlayButtonStore'; -import { useProductContext } from '../../products/productContext'; export default function ProductionCapacityData() { const { throughputData } = useThroughPutData() - const { productionCapacityData, setProductionCapacityData } = useProductionCapacityData() + const { setProductionCapacityData } = useProductionCapacityData() const { inputValues } = useInputValues(); const { isPlaying } = usePlayButtonStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); useEffect(() => { if (!isPlaying) { - setProductionCapacityData(0); } }, [isPlaying]); @@ -26,8 +22,6 @@ export default function ProductionCapacityData() { const Monthly_working_days = workingDaysPerYear / 12; const Production_capacity_per_month = throughputData * Monthly_working_days; - - setProductionCapacityData(Number(Production_capacity_per_month.toFixed(2))); } }, [throughputData, inputValues, isPlaying]); diff --git a/app/src/modules/simulation/analysis/simulationAnalysis.tsx b/app/src/modules/simulation/analysis/simulationAnalysis.tsx index db5a88b..2cfda53 100644 --- a/app/src/modules/simulation/analysis/simulationAnalysis.tsx +++ b/app/src/modules/simulation/analysis/simulationAnalysis.tsx @@ -1,18 +1,9 @@ -import React, { useEffect } from 'react' -import { usePlayButtonStore } from '../../../store/usePlayButtonStore' import ProductionCapacityData from './productionCapacity/productionCapacityData' import ThroughPutData from './throughPut/throughPutData' import ROIData from './ROI/roiData' function SimulationAnalysis() { - const { isPlaying } = usePlayButtonStore() - // useEffect(()=>{ - // if (isPlaying) { - // - // } else { - // - // } - // },[isPlaying]) + return ( <> diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index 9f9cbb4..25e0b3d 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -352,6 +352,37 @@ function PointsCreator() { ); + } else if (usedEvent.type === "human") { + const point = usedEvent.point; + return ( + + (sphereRefs.current[point.uuid] = el!)} + onClick={(e) => { + e.stopPropagation(); + if (toolMode === 'cursor') { + setSelectedEventSphere( + sphereRefs.current[point.uuid] + ); + } + }} + position={new THREE.Vector3(...point.position)} + userData={{ + modelUuid: usedEvent.modelUuid, + pointUuid: point.uuid, + }} + > + + + + + ); } else { return null; } diff --git a/app/src/modules/simulation/events/points/points.tsx b/app/src/modules/simulation/events/points/points.tsx index 2a50f2d..c8b9d6d 100644 --- a/app/src/modules/simulation/events/points/points.tsx +++ b/app/src/modules/simulation/events/points/points.tsx @@ -1,4 +1,3 @@ -import React from 'react' import PointsCreator from './creator/pointsCreator' function Points() { diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 0b8cb2d..ec7406c 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -152,6 +152,22 @@ function TriggerConnector() { }); } } + // Handle Human point + else if (event.type === "human" && 'point' in event) { + const point = event.point; + 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}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger + }); + } + }); + } + } }); setConnections(newConnections); 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 new file mode 100644 index 0000000..767269c --- /dev/null +++ b/app/src/modules/simulation/human/human.tsx @@ -0,0 +1,40 @@ +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 && + + } + + + ) +} + +export default Human \ No newline at end of file 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/humanInstances.tsx b/app/src/modules/simulation/human/instances/humanInstances.tsx new file mode 100644 index 0000000..75849ad --- /dev/null +++ b/app/src/modules/simulation/human/instances/humanInstances.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import HumanInstance from './instance/humanInstance'; +import { useSceneContext } from '../../../scene/sceneContext'; + +function HumanInstances() { + const { humanStore } = useSceneContext(); + const { humans } = humanStore(); + + return ( + <> + {humans.map((human: HumanStatus) => ( + + + + ))} + + ) +} + +export default HumanInstances \ No newline at end of file diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx new file mode 100644 index 0000000..12d2d61 --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -0,0 +1,688 @@ +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(() => { + 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 ( + <> + + + + + + ) +} + +export default HumanInstance \ No newline at end of file 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/products/products.tsx b/app/src/modules/simulation/products/products.tsx index e3ade5e..1478255 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../builder/version/versionContext'; function Products() { - const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, layout, productStore } = useSceneContext(); + const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, layout, productStore } = useSceneContext(); const { products, getProductById, addProduct, setProducts } = productStore(); const { selectedProductStore } = useProductContext(); const { setMainProduct } = useMainProduct(); @@ -20,6 +20,7 @@ function Products() { const { addMachine, clearMachines } = machineStore(); const { addConveyor, clearConveyors } = conveyorStore(); const { setCurrentMaterials, clearStorageUnits, updateCurrentLoad, addStorageUnit } = storageUnitStore(); + const { addHuman, clearHumans } = humanStore(); const { isReset } = useResetButtonStore(); const { isPlaying } = usePlayButtonStore(); const { mainProduct } = useMainProduct(); @@ -153,6 +154,20 @@ function Products() { } }, [selectedProduct, products, isReset, isPlaying]); + useEffect(() => { + if (selectedProduct.productUuid) { + const product = getProductById(selectedProduct.productUuid); + if (product) { + clearHumans(); + product.eventDatas.forEach(events => { + if (events.type === 'human') { + addHuman(selectedProduct.productUuid, events); + } + }); + } + } + }, [selectedProduct, products, isReset, isPlaying]); + return ( <> diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 3429393..bb9d91f 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import Vehicles from './vehicle/vehicles'; import Points from './events/points/points'; import Conveyor from './conveyor/conveyor'; @@ -6,6 +6,7 @@ import RoboticArm from './roboticArm/roboticArm'; import Materials from './materials/materials'; import Machine from './machine/machine'; import StorageUnit from './storageUnit/storageUnit'; +import Human from './human/human'; import Simulator from './simulator/simulator'; import Products from './products/products'; import Trigger from './triggers/trigger'; @@ -52,6 +53,8 @@ function Simulation() { + + 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/simulator/simulator.tsx b/app/src/modules/simulation/simulator/simulator.tsx index 598d3d8..869ffa9 100644 --- a/app/src/modules/simulation/simulator/simulator.tsx +++ b/app/src/modules/simulation/simulator/simulator.tsx @@ -4,7 +4,6 @@ import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayB import { determineExecutionOrder } from './functions/determineExecutionOrder'; import { useProductContext } from '../products/productContext'; import { useSceneContext } from '../../scene/sceneContext'; -import { useCompareProductDataStore } from '../../../store/builder/store'; function Simulator() { const { selectedProductStore } = useProductContext(); 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 2f37503..954beec 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -71,7 +71,6 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } }, [isReset, isPlaying]) - const lastTimeRef = useRef(performance.now()); useFrame(() => { @@ -283,88 +282,6 @@ function DraggableSphere({ ); } -// function DraggableLineSegment({ -// index, -// start, -// end, -// updatePoints, -// isAnyDragging, -// setIsAnyDragging, -// }: { -// index: number; -// start: THREE.Vector3; -// end: THREE.Vector3; -// updatePoints: (i0: number, p0: THREE.Vector3, i1: number, p1: THREE.Vector3) => void; -// isAnyDragging: string; -// setIsAnyDragging: (val: string) => void; -// }) { -// const meshRef = useRef(null); -// const { gl, raycaster, controls } = useThree(); -// const { activeTool } = useActiveTool(); -// const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); -// const dragStart = useRef(null); - - -// const onPointerDown = () => { -// if (activeTool !== 'pen' || isAnyDragging) return; // <-- Skip if dragging sphere -// setIsAnyDragging("line"); -// gl.domElement.style.cursor = 'grabbing'; -// if (controls) (controls as any).enabled = false; -// }; - -// const onPointerMove = (e: ThreeEvent) => { -// if (isAnyDragging !== "line" || activeTool !== 'pen') return; - -// const intersect = new THREE.Vector3(); -// if (raycaster.ray.intersectPlane(plane, intersect)) { -// if (!dragStart.current) dragStart.current = intersect.clone(); - -// const offset = new THREE.Vector3().subVectors(intersect, dragStart.current); -// const newStart = start.clone().add(offset); -// const newEnd = end.clone().add(offset); -// updatePoints(index, newStart, index + 1, newEnd); -// } -// }; - -// const onPointerUp = () => { -// if (activeTool !== 'pen') return; -// setIsAnyDragging(""); -// dragStart.current = null; -// gl.domElement.style.cursor = 'default'; -// if (controls) (controls as any).enabled = true; -// }; -// const noopRaycast = (raycaster: THREE.Raycaster, intersects: THREE.Intersection[]) => { }; - -// return ( -// -// -// -// ); - - -// // return ( -// // null : undefined} -// // raycast={isAnyDragging === "point" ? () => { } : undefined} -// // // pointerEvents={isAnyDragging === "point" ? "none" : "auto"} // ✅ the correct way -// // > -// // -// // -// // ); -// } - function DraggableLineSegment({ index, start, @@ -426,207 +343,3 @@ function DraggableLineSegment({ /> ); } - -// These are recently edited files. Do not suggest code that has been deleted. -// function DraggableSphere({ -// index, -// position, -// onMove, -// isAnyDragging, -// setIsAnyDragging, -// }: { -// index: number; -// position: THREE.Vector3; -// onMove: (index: number, pos: THREE.Vector3) => void; -// isAnyDragging: boolean; -// setIsAnyDragging: (val: boolean) => void; -// }) { -// const meshRef = useRef(null); -// const { gl, controls, raycaster } = useThree(); -// const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); // XZ plane -// const isDragging = useRef(false); -// const { activeTool } = useActiveTool(); - -// const onPointerDown = (e: ThreeEvent) => { -// if (activeTool !== 'pen') return; - -// isDragging.current = true; -// gl.domElement.style.cursor = 'grabbing'; - -// if (controls) { -// (controls as any).enabled = false; -// } -// }; - -// const onPointerMove = (e: ThreeEvent) => { -// if (!isDragging.current || activeTool !== 'pen') return; - -// const intersect = new THREE.Vector3(); -// if (raycaster.ray.intersectPlane(plane, intersect)) { -// meshRef.current!.position.copy(intersect); -// onMove(index, intersect); -// } -// }; - -// const onPointerUp = () => { -// if (activeTool !== 'pen') return; - -// isDragging.current = false; -// gl.domElement.style.cursor = 'default'; -// if (controls) { -// (controls as any).enabled = true; -// } -// }; - -// return ( -// -// -// -// -// ); -// } - -// function DraggableLineSegment({ -// index, -// start, -// end, -// updatePoints, -// isAnyDragging, -// setIsAnyDragging, -// }: { -// index: number; -// start: THREE.Vector3; -// end: THREE.Vector3; -// updatePoints: (i0: number, p0: THREE.Vector3, i1: number, p1: THREE.Vector3) => void; -// isAnyDragging: boolean; -// setIsAnyDragging: (val: boolean) => void; -// }) { -// const meshRef = useRef(null); -// const { gl, camera, controls, raycaster } = useThree(); -// const { activeTool } = useActiveTool(); // 👈 Get active tool -// const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0); -// const isDragging = useRef(false); -// const dragStart = useRef(null); - -// const onPointerDown = () => { -// if (activeTool !== 'pen') return; // 👈 Only allow when tool is 'pen' - -// isDragging.current = true; -// gl.domElement.style.cursor = 'grabbing'; -// if (controls) (controls as any).enabled = false; -// }; - -// const onPointerMove = (e: ThreeEvent) => { -// if (!isDragging.current || activeTool !== 'pen') return; - -// const intersect = new THREE.Vector3(); -// if (raycaster.ray.intersectPlane(plane, intersect)) { -// if (!dragStart.current) { -// dragStart.current = intersect.clone(); -// } - -// const offset = new THREE.Vector3().subVectors(intersect, dragStart.current); -// const newStart = start.clone().add(offset); -// const newEnd = end.clone().add(offset); -// updatePoints(index, newStart, index + 1, newEnd); -// } -// }; - -// const onPointerUp = () => { -// if (activeTool !== 'pen') return; - -// isDragging.current = false; -// dragStart.current = null; -// gl.domElement.style.cursor = 'default'; -// if (controls) (controls as any).enabled = true; -// }; - -// return ( -// -// -// -// ); -// } - - - - - - - - - - - -// return ( -// <> -// {selectedPath === "auto" && -// {/* {currentPath.map((pos, i) => { -// if (i < currentPath.length - 1) { -// return ( -// { -// const updated = [...currentPath]; -// updated[i0] = p0.toArray() as [number, number, number]; -// updated[i1] = p1.toArray() as [number, number, number]; -// setCurrentPath(updated); -// }} -// /> -// ); -// } -// return null; -// })} */} -// {currentPath.length > 0 && ( -// { if (controls) (controls as any).enabled = true; }}> -// -// {currentPath.map((pos, i) => { -// if (i < currentPath.length - 1) { -// return ( -// -// -// {/* { -// const updated = [...currentPath]; -// updated[i0] = p0.toArray() as [number, number, number]; -// updated[i1] = p1.toArray() as [number, number, number]; -// setCurrentPath(updated); -// }} -// /> */} -// -// ) -// } -// } -// )} -// -// ) -// } -// -// } -// -// ); \ No newline at end of file diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 83a803e..3e910a7 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -1,21 +1,23 @@ 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, useSelectedPath } 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 InteractivePoints from '../animator/interactivePoint'; +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(); @@ -149,7 +151,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) } }, [vehicles, currentPhase, path, isPlaying, selectedPath]); - function animate(currentTime: number) { if (previousTimeRef.current === null) { previousTimeRef.current = currentTime; @@ -196,7 +197,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) }; }, [agvDetail, isPlaying]); - function handleCallBack() { if (currentPhase === 'stationed-pickup') { setCurrentPhase('picking'); @@ -246,6 +246,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; @@ -259,6 +264,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/modules/simulation/vehicle/instances/vehicleInstances.tsx b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx index 989242b..8f2b3f3 100644 --- a/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx +++ b/app/src/modules/simulation/vehicle/instances/vehicleInstances.tsx @@ -5,20 +5,20 @@ import { useSceneContext } from "../../../scene/sceneContext"; import { useViewSceneStore } from "../../../../store/builder/store"; function VehicleInstances() { - const { vehicleStore } = useSceneContext(); - const { vehicles } = vehicleStore(); - const { viewSceneLabels } = useViewSceneStore(); + const { vehicleStore } = useSceneContext(); + const { vehicles } = vehicleStore(); + const { viewSceneLabels } = useViewSceneStore(); - return ( - <> - {vehicles.map((vehicle: VehicleStatus) => ( - - - {viewSceneLabels && } - - ))} - - ); + return ( + <> + {vehicles.map((vehicle: VehicleStatus) => ( + + + {viewSceneLabels && } + + ))} + + ); } export default VehicleInstances; diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index 4f24e05..bec92b3 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -17,6 +17,7 @@ import { useWidgetStore } from "../../store/useWidgetStore"; import { useNavigate, useParams } from "react-router-dom"; import { getUserData } from "../../functions/getUserData"; import { useVersionContext } from "../builder/version/versionContext"; +import { useSceneContext } from "../scene/sceneContext"; type Side = "top" | "bottom" | "left" | "right"; @@ -28,6 +29,7 @@ type FormattedZoneData = Record< points: []; lockedPanels: Side[]; zoneUuid: string; + zoneName: string; zoneViewPortTarget: number[]; zoneViewPortPosition: number[]; widgets: Widget[]; @@ -64,6 +66,9 @@ const RealTimeVisulization: React.FC = () => { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const navigate = useNavigate(); + const { zoneStore } = useSceneContext(); + const { zones } = zoneStore(); + OuterClick({ contextClassName: [ @@ -82,6 +87,7 @@ const RealTimeVisulization: React.FC = () => { useEffect(() => { if (!projectId || !selectedVersion) return; getZone2dData(organization, projectId, selectedVersion?.versionId || '').then((response) => { + // console.log('response: ', response); if (!response) return; // if (response.status === 401) { // console.log("force logout"); @@ -94,19 +100,21 @@ const RealTimeVisulization: React.FC = () => { const formattedData = response.reduce( (acc, zone) => { - acc[zone.zoneName] = { + acc[zone.zoneUuid] = { activeSides: [], panelOrder: [], lockedPanels: [], points: zone.points, zoneUuid: zone.zoneUuid, - zoneViewPortTarget: zone.viewPortCenter, - zoneViewPortPosition: zone.viewPortposition, + zoneName: zone.zoneName, + zoneViewPortTarget: zone.viewPortTarget, + zoneViewPortPosition: zone.viewPortPosition, widgets: [], }; return acc; }, {} ); + // console.log('formattedData: ', formattedData); setZonesData(formattedData); }) @@ -119,13 +127,14 @@ const RealTimeVisulization: React.FC = () => { if (!selectedZone) return prev; return { ...prev, - [selectedZone.zoneName]: { - ...prev[selectedZone.zoneName], // Keep existing properties + [selectedZone.zoneUuid]: { + ...prev[selectedZone.zoneUuid], // Keep existing properties activeSides: selectedZone.activeSides || [], panelOrder: selectedZone.panelOrder || [], lockedPanels: selectedZone.lockedPanels || [], points: selectedZone.points || [], zoneUuid: selectedZone.zoneUuid || "", + zoneName: selectedZone.zoneName || "", zoneViewPortTarget: selectedZone.zoneViewPortTarget || [], zoneViewPortPosition: selectedZone.zoneViewPortPosition || [], widgets: selectedZone.widgets || [], diff --git a/app/src/modules/visualization/zone/DisplayZone.tsx b/app/src/modules/visualization/zone/DisplayZone.tsx index b817c1f..42fe4c4 100644 --- a/app/src/modules/visualization/zone/DisplayZone.tsx +++ b/app/src/modules/visualization/zone/DisplayZone.tsx @@ -34,6 +34,7 @@ interface DisplayZoneProps { points: []; widgets: Widget[]; zoneUuid: string; + zoneName: string; zoneViewPortTarget: number[]; zoneViewPortPosition: number[]; }; @@ -111,8 +112,8 @@ const DisplayZone: React.FC = ({ setShowLeftArrow(isOverflowing && canScrollLeft); setShowRightArrow(isOverflowing && canScrollRight); - // console.log('canScrollRight: ', canScrollRight); - // console.log('isOverflowing: ', isOverflowing); + // + // } }, []); @@ -180,9 +181,10 @@ const DisplayZone: React.FC = ({ // setSelectedChartId(null); let response = await getSelect2dZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || ''); - // console.log('response2d: ', response); + + // let res = await getFloatingZoneData(zoneUuid, organization, projectId, selectedVersion?.versionId || ''); - // console.log("resFloating: ", res); + // setFloatingWidget(res); // Set the selected zone in the store @@ -201,8 +203,8 @@ const DisplayZone: React.FC = ({ widgets: response.widgets || [], points: response.points || [], zoneUuid: zoneUuid, - zoneViewPortTarget: response.viewPortCenter || {}, - zoneViewPortPosition: response.viewPortposition || {}, + zoneViewPortTarget: response.viewPortTarget || [], + zoneViewPortPosition: response.viewPortPosition || [], }); } catch (error) { echo.error("Failed to select zone"); @@ -238,20 +240,22 @@ const DisplayZone: React.FC = ({ > {Object.keys(zonesData).length !== 0 ? ( <> - {Object.keys(zonesData).map((zoneName, index) => ( -
{ + {Object.values(zonesData).map((zone, index) => ( + <> + { } +
{ - console.log('zonesData: ', zonesData); - handleSelect2dZoneData(zonesData[zoneName]?.zoneUuid, zoneName) - } - } - > - {zoneName} -
+ handleSelect2dZoneData(zonesData[zone.zoneUuid]?.zoneUuid, zone.zoneName) + } + } + > + {zone.zoneName} +
+ ))} ) : ( diff --git a/app/src/modules/visualization/zone/zoneCameraTarget.tsx b/app/src/modules/visualization/zone/zoneCameraTarget.tsx index 6ac0d3d..82f014a 100644 --- a/app/src/modules/visualization/zone/zoneCameraTarget.tsx +++ b/app/src/modules/visualization/zone/zoneCameraTarget.tsx @@ -10,6 +10,7 @@ import { export default function ZoneCentreTarget() { const { selectedZone } = useSelectedZoneStore(); + // const [previousZoneCentre, setPreviousZoneCentre] = useState( null ); diff --git a/app/src/pages/UserAuth.tsx b/app/src/pages/UserAuth.tsx index fec9a47..cdc609e 100644 --- a/app/src/pages/UserAuth.tsx +++ b/app/src/pages/UserAuth.tsx @@ -56,7 +56,6 @@ const UserAuth: React.FC = () => { try { const projects = await recentlyViewed(organization, res.message.userId); - console.log('projects: ', projects); if (res.message.isShare) { if (Object.values(projects.RecentlyViewed).length > 0) { const firstId = (Object.values(projects?.RecentlyViewed || {})[0] as any)?._id; diff --git a/app/src/services/factoryBuilder/zone/getZonesApi.ts b/app/src/services/factoryBuilder/zone/getZonesApi.ts index f0ea687..2714685 100644 --- a/app/src/services/factoryBuilder/zone/getZonesApi.ts +++ b/app/src/services/factoryBuilder/zone/getZonesApi.ts @@ -1,9 +1,6 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; -export const getZonesApi = async ( - projectId: string, - versionId: string, -) => { +export const getZonesApi = async (projectId: string, versionId: string,) => { try { const response = await fetch(`${url_Backend_dwinzo}/api/V1/zones/${projectId}/${versionId}`, { method: "GET", 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/services/visulization/zone/zoneCameraUpdation.ts b/app/src/services/visulization/zone/zoneCameraUpdation.ts index cfacfcd..9bb3b24 100644 --- a/app/src/services/visulization/zone/zoneCameraUpdation.ts +++ b/app/src/services/visulization/zone/zoneCameraUpdation.ts @@ -3,7 +3,7 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_UR export const zoneCameraUpdate = async (zoneData: {}, organization: string, projectId?: string, versionId?: string) => { try { - const response = await fetch(`${url_Backend_dwinzo}/api/V1/zones`, { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/upsertZone`, { method: "POST", headers: { Authorization: "Bearer ", // Replace with actual token 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 new file mode 100644 index 0000000..496228f --- /dev/null +++ b/app/src/store/simulation/useHumanStore.ts @@ -0,0 +1,238 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +interface HumansStore { + humans: HumanStatus[]; + + addHuman: (productUuid: string, event: HumanEventSchema) => void; + removeHuman: (modelUuid: string) => void; + updateHuman: ( + modelUuid: string, + updates: Partial> + ) => void; + clearHumans: () => void; + + 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; + + addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void; + setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void; + removeLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; + getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; + clearCurrentMaterials: (modelUuid: string) => void; + + incrementActiveTime: (modelUuid: string, incrementBy: number) => void; + incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void; + resetTime: (modelUuid: string) => void; + + getHumanById: (modelUuid: string) => HumanStatus | undefined; + getHumansByProduct: (productUuid: string) => HumanStatus[]; + getActiveHumans: () => HumanStatus[]; +} + +export const createHumanStore = () => { + return create()( + immer((set, get) => ({ + humans: [], + + addHuman: (productUuid, event) => { + set((state) => { + const exists = state.humans.some(h => h.modelUuid === event.modelUuid); + if (!exists) { + state.humans.push({ + ...event, + productUuid, + isActive: false, + isPicking: false, + idleTime: 0, + activeTime: 0, + currentLoad: 0, + currentMaterials: [], + distanceTraveled: 0 + }); + } + }); + }, + + removeHuman: (modelUuid) => { + set((state) => { + state.humans = state.humans.filter(h => h.modelUuid !== modelUuid); + }); + }, + + updateHuman: (modelUuid, updates) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + Object.assign(human, updates); + } + }); + }, + + clearHumans: () => { + set((state) => { + state.humans = []; + }); + }, + + setHumanActive: (modelUuid, isActive) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.isActive = isActive; + } + }); + }, + + setHumanPicking: (modelUuid, isPicking) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.isPicking = isPicking; + } + }); + }, + + setHumanLoad: (modelUuid, load) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentLoad = load; + } + }); + }, + + 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); + if (human) { + human.currentLoad += incrementBy; + } + }); + }, + + decrementHumanLoad: (modelUuid, decrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentLoad -= decrementBy; + } + }); + }, + + addCurrentMaterial: (modelUuid, materialType, materialId) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentMaterials.push({ materialType, materialId }); + } + }); + }, + + setCurrentMaterials: (modelUuid, materials) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentMaterials = materials; + } + }); + }, + + removeLastMaterial: (modelUuid) => { + let removed; + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human && human.currentMaterials.length > 0) { + removed = JSON.parse(JSON.stringify(human.currentMaterials.pop())); + } + }); + return removed; + }, + + getLastMaterial: (modelUuid) => { + const human = get().humans.find(h => h.modelUuid === modelUuid); + if (human && human.currentMaterials.length > 0) { + return human.currentMaterials[human.currentMaterials.length - 1]; + } + return undefined; + }, + + clearCurrentMaterials: (modelUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.currentMaterials = []; + } + }); + }, + + incrementActiveTime: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.activeTime += incrementBy; + } + }); + }, + + incrementIdleTime: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.idleTime += incrementBy; + } + }); + }, + + incrementDistanceTraveled: (modelUuid, incrementBy) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.distanceTraveled += incrementBy; + } + }); + }, + + resetTime: (modelUuid) => { + set((state) => { + const human = state.humans.find(h => h.modelUuid === modelUuid); + if (human) { + human.activeTime = 0; + human.idleTime = 0; + } + }); + }, + + getHumanById: (modelUuid) => { + return get().humans.find(h => h.modelUuid === modelUuid); + }, + + getHumansByProduct: (productUuid) => { + return get().humans.filter(h => h.productUuid === productUuid); + }, + + getActiveHumans: () => { + return get().humans.filter(h => h.isActive); + } + })) + ); +}; + +export type HumanStoreType = ReturnType; 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 d5d2f00..678f117 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -1,3 +1,4 @@ +// Base Types interface AssetEventSchema { modelUuid: string; modelName: string; @@ -18,69 +19,7 @@ interface TriggerSchema { } | null; } -interface ConveyorPointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: ConveyorAction; -} - -interface VehiclePointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: VehicleAction; -} - -interface RoboticArmPointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - actions: RoboticArmAction[]; -} - -interface MachinePointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: MachineAction; -} - -interface StoragePointSchema { - uuid: string; - position: [number, number, number]; - rotation: [number, number, number]; - action: StorageAction; -} - -interface ConveyorEventSchema extends AssetEventSchema { - type: "transfer"; - speed: number; - points: ConveyorPointSchema[]; -} - -interface VehicleEventSchema extends AssetEventSchema { - type: "vehicle"; - speed: number; - point: VehiclePointSchema; -} - -interface RoboticArmEventSchema extends AssetEventSchema { - type: "roboticArm"; - speed: number; - point: RoboticArmPointSchema; -} - -interface MachineEventSchema extends AssetEventSchema { - type: "machine"; - point: MachinePointSchema; -} - -interface StorageEventSchema extends AssetEventSchema { - type: "storageUnit"; - point: StoragePointSchema; -} - +// Actions interface ConveyorAction { actionUuid: string; actionName: string; @@ -130,19 +69,101 @@ interface StorageAction { triggers: TriggerSchema[]; } -type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction; +interface HumanAction { + actionUuid: string; + actionName: string; + 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; + triggers: TriggerSchema[]; +} -type PointsScheme = ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema; +type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction; -type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema; +// Points +interface ConveyorPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: ConveyorAction; +} -type productsSchema = { - productName: string; - productUuid: string; - eventDatas: EventsSchema[]; -}[] +interface VehiclePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: VehicleAction; +} +interface RoboticArmPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: RoboticArmAction[]; +} +interface MachinePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: MachineAction; +} + +interface StoragePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: StorageAction; +} + +interface HumanPointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + action: HumanAction; +} + +type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; + +// Events +interface ConveyorEventSchema extends AssetEventSchema { + type: "transfer"; + speed: number; + points: ConveyorPointSchema[]; +} + +interface VehicleEventSchema extends AssetEventSchema { + type: "vehicle"; + speed: number; + point: VehiclePointSchema; +} + +interface RoboticArmEventSchema extends AssetEventSchema { + type: "roboticArm"; + speed: number; + point: RoboticArmPointSchema; +} + +interface MachineEventSchema extends AssetEventSchema { + type: "machine"; + point: MachinePointSchema; +} + +interface StorageEventSchema extends AssetEventSchema { + type: "storageUnit"; + point: StoragePointSchema; +} + +interface HumanEventSchema extends AssetEventSchema { + type: "human"; + speed: number; + point: HumanPointSchema; +} + +type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema; + +// Statuses interface ConveyorStatus extends ConveyorEventSchema { productUuid: string; isActive: boolean; @@ -197,6 +218,25 @@ interface StorageUnitStatus extends StorageEventSchema { currentMaterials: { materialType: string; materialId: string; }[]; } +interface HumanStatus extends HumanEventSchema { + productUuid: string; + isActive: boolean; + isPicking: boolean; + idleTime: number; + activeTime: number; + currentLoad: number; + currentMaterials: { materialType: string; materialId: string; }[]; + distanceTraveled: number; + currentAction?: { + actionUuid: string; + actionName: string; + animationUuid: string; + materialType?: string | null; + materialId?: string | null; + }; +} + +// Materials interface MaterialSchema { materialId: string; materialName: string; @@ -230,6 +270,14 @@ interface MaterialSchema { type MaterialsSchema = MaterialSchema[]; +// Products +type productsSchema = { + productName: string; + productUuid: string; + eventDatas: EventsSchema[]; +}[]; + +// Material History interface MaterialHistoryEntry { material: MaterialSchema; removedAt: string;