From b5c69f3335d8e6f7914b0109343a39dbdb004a9b Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Thu, 3 Jul 2025 12:09:31 +0530 Subject: [PATCH] feat: Add human mechanics and event handling, including UI components and action management --- .../eventProperties/EventProperties.tsx | 4 + .../mechanics/humanMechanics.tsx | 373 ++++++++++++++++++ .../eventProperties/trigger/Trigger.tsx | 26 +- app/src/modules/builder/asset/assetsGroup.tsx | 1 + .../builder/asset/functions/addAssetModel.ts | 1 + .../functions/handleAddEventToProduct.ts | 2 +- app/src/modules/simulation/human/human.tsx | 21 + .../instances/instance/humanInstance.tsx | 4 + .../human/instances/instance/humanUi.tsx | 10 + app/src/modules/simulation/spatialUI/temp.md | 0 .../products/UpsertProductOrEventApi.ts | 61 ++- app/src/store/simulation/useProductStore.ts | 13 +- app/src/types/simulationTypes.d.ts | 1 + 13 files changed, 469 insertions(+), 48 deletions(-) create mode 100644 app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx create mode 100644 app/src/modules/simulation/human/instances/instance/humanUi.tsx delete mode 100644 app/src/modules/simulation/spatialUI/temp.md diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx index ed799e5..ed9ef9f 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/EventProperties.tsx @@ -8,6 +8,7 @@ import VehicleMechanics from "./mechanics/vehicleMechanics"; import RoboticArmMechanics from "./mechanics/roboticArmMechanics"; import MachineMechanics from "./mechanics/machineMechanics"; import StorageMechanics from "./mechanics/storageMechanics"; +import HumanMechanics from "./mechanics/humanMechanics"; import { AddIcon } from "../../../../icons/ExportCommonIcons"; import { handleAddEventToProduct } from "../../../../../modules/simulation/events/points/functions/handleAddEventToProduct"; import { useProductContext } from "../../../../../modules/simulation/products/productContext"; @@ -61,6 +62,8 @@ const EventProperties: React.FC = () => { return "machine"; case "storageUnit": return "storageUnit"; + case "human": + return "human"; default: return null; } @@ -80,6 +83,7 @@ const EventProperties: React.FC = () => { {assetType === "roboticArm" && } {assetType === "machine" && } {assetType === "storageUnit" && } + {assetType === "human" && } )} {!currentEventData && selectedEventSphere && ( diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx new file mode 100644 index 0000000..8c5a348 --- /dev/null +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/humanMechanics.tsx @@ -0,0 +1,373 @@ +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 { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; +import PickAndPlaceAction from "../actions/PickAndPlaceAction"; +import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; +import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; +import { useParams } from "react-router-dom"; +import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; +import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; +import InputToggle from "../../../../../ui/inputs/InputToggle"; + +function HumanMechanics() { + const [activeOption, setActiveOption] = useState<"animation" | "animatedTravel">("animation"); + const [activeAnimationOption, setActiveAnimationOption] = useState(""); + const [animationOptions, setAnimationOptions] = useState([]); + const [speed, setSpeed] = useState("0.5"); + const [currentAction, setCurrentAction] = useState(); + const [isLoopAnimation, setIsLoopAnimation] = useState(false); + const [selectedPointData, setSelectedPointData] = useState(); + const { selectedEventData } = useSelectedEventData(); + const { productStore, assetStore } = useSceneContext(); + const { getAssetById } = assetStore(); + const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + const { projectId } = useParams(); + + useEffect(() => { + if (selectedEventData) { + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as HumanPointSchema | undefined; + + if (point?.actions) { + setSelectedPointData(point); + if (point.actions.length > 0) { + setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); + const asset = getAssetById(selectedEventData.data.modelUuid); + if (asset && asset.animations) { + setAnimationOptions(asset.animations) + } + } + } + } else { + clearSelectedAction(); + } + }, [selectedEventData, selectedProduct]); + + useEffect(() => { + if (selectedEventData && selectedProduct.productUuid) { + const event = getEventByModelUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid + ) as HumanEventSchema | undefined; + + if (event?.speed !== undefined) { + setSpeed(event.speed.toString()); + } + + const point = getPointByUuid( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint + ) as HumanPointSchema | undefined; + + if (point?.actions) { + setSelectedPointData(point); + const action = point.actions.find((a) => a.actionUuid === selectedAction.actionId); + if (action) { + setCurrentAction(action); + setIsLoopAnimation(action.loopAnimation ?? false); + setActiveOption(action.actionType as "animation" | "animatedTravel"); + setActiveAnimationOption(action.animation || '') + } + } + } else { + clearSelectedAction(); + setCurrentAction(undefined); + setSpeed("0.5"); + } + }, [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) return; + setActiveOption(actionType as "animation" | "animatedTravel"); + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { actionType: actionType as "animation" | "animatedTravel" } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionType: actionType as "animation" | "animatedTravel" } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + } + + const handleSelectAnimation = (animationOption: string) => { + if (!selectedAction.actionId) return; + setActiveAnimationOption(animationOption); + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { animation: animationOption } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, animation: animationOption } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + } + + const handleLoopAnimationChange = () => { + if (!selectedAction.actionId || !currentAction) return; + + const updatedValue = !isLoopAnimation; + setIsLoopAnimation(updatedValue); + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { loopAnimation: updatedValue } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, loopAnimation: updatedValue } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + setCurrentAction(updatedActions.find((a) => a.actionUuid === selectedAction.actionId)); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleRenameAction = (newName: string) => { + if (!selectedAction.actionId) return; + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { actionName: newName } + ); + + if (selectedPointData) { + const updatedActions = selectedPointData.actions.map((action) => + action.actionUuid === selectedAction.actionId + ? { ...action, actionName: newName } + : action + ); + setSelectedPointData({ ...selectedPointData, actions: updatedActions }); + } + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleSpeedChange = (value: string) => { + if (!selectedEventData) return; + + const event = updateEvent( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + { speed: parseFloat(value) } + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleClearPoints = () => { + if (!selectedAction.actionId || !selectedPointData) return; + + const event = updateAction( + selectedProduct.productUuid, + selectedAction.actionId, + { + travelPoints: { + startPoint: null, + endPoint: null, + }, + } + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + }; + + const handleAddAction = () => { + if (!selectedEventData || !selectedPointData) return; + + const newAction: HumanAction = { + actionUuid: MathUtils.generateUUID(), + actionName: `Action ${selectedPointData.actions.length + 1}`, + actionType: "animation", + animation: null, + loadCapacity: 1, + loopAnimation: true, + travelPoints: { + startPoint: null, + endPoint: null, + }, + triggers: [], + }; + + const event = addAction( + selectedProduct.productUuid, + selectedEventData.data.modelUuid, + selectedEventData.selectedPoint, + newAction + ); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const updatedPoint = { ...selectedPointData, actions: [...selectedPointData.actions, newAction] }; + setSelectedPointData(updatedPoint); + setSelectedAction(newAction.actionUuid, newAction.actionName); + }; + + const handleDeleteAction = (actionUuid: string) => { + if (!selectedPointData) return; + + const event = removeAction(selectedProduct.productUuid, actionUuid); + + if (event) { + updateBackend(selectedProduct.productName, selectedProduct.productUuid, projectId || '', event); + } + + const index = selectedPointData.actions.findIndex((a) => a.actionUuid === actionUuid); + const newActions = selectedPointData.actions.filter((a) => a.actionUuid !== actionUuid); + const updatedPoint = { ...selectedPointData, actions: newActions }; + setSelectedPointData(updatedPoint); + + if (selectedAction.actionId === actionUuid) { + const nextAction = newActions[index] || newActions[index - 1]; + if (nextAction) { + setSelectedAction(nextAction.actionUuid, nextAction.actionName); + } else { + clearSelectedAction(); + } + } + }; + + const availableActions = { + defaultOption: "animatedTravel", + options: ["animation", "animatedTravel"], + }; + + return ( + <> +
+
+
+ { }} + onChange={handleSpeedChange} + /> +
+
+
+
+ + + {selectedAction.actionId && currentAction && ( +
+
+ +
+
+ + + + {activeOption === 'animatedTravel' && + + } +
+
+ +
+
+ )} +
+ + ); +} + +export default HumanMechanics; 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/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 6fcdc7c..461bf50 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -251,6 +251,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { 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], diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 9fa104c..47274a2 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -369,6 +369,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "human", + speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], diff --git a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts index 0ba13a7..9439c3c 100644 --- a/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts +++ b/app/src/modules/simulation/events/points/functions/handleAddEventToProduct.ts @@ -30,7 +30,7 @@ export const handleAddEventToProduct = ({ projectId: projectId || '', eventDatas: event }).then((data) => { - // console.log(data); + console.log(data); }) if (clearSelectedAsset) { diff --git a/app/src/modules/simulation/human/human.tsx b/app/src/modules/simulation/human/human.tsx index 30608d4..6a552c6 100644 --- a/app/src/modules/simulation/human/human.tsx +++ b/app/src/modules/simulation/human/human.tsx @@ -1,6 +1,27 @@ import HumanInstances from './instances/humanInstances' +import { useEffect, useState } from "react"; +import { useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { useSceneContext } from "../../scene/sceneContext"; function Human() { + const { humanStore } = useSceneContext(); + const { getHumanById } = humanStore(); + const { selectedEventSphere } = useSelectedEventSphere(); + const { isPlaying } = usePlayButtonStore(); + const [isHumanSelected, setIsHumanSelected] = useState(false); + + useEffect(() => { + if (selectedEventSphere) { + const selectedVehicle = getHumanById(selectedEventSphere.userData.modelUuid); + if (selectedVehicle) { + setIsHumanSelected(true); + } else { + setIsHumanSelected(false); + } + } + }, [getHumanById, selectedEventSphere]) + return ( <> diff --git a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx index d8dc023..89e2b10 100644 --- a/app/src/modules/simulation/human/instances/instance/humanInstance.tsx +++ b/app/src/modules/simulation/human/instances/instance/humanInstance.tsx @@ -1,4 +1,5 @@ import { useEffect } from 'react' +import HumanUi from './humanUi'; function HumanInstance({ human }: { human: HumanStatus }) { @@ -8,6 +9,9 @@ function HumanInstance({ human }: { human: HumanStatus }) { return ( <> + + + ) } diff --git a/app/src/modules/simulation/human/instances/instance/humanUi.tsx b/app/src/modules/simulation/human/instances/instance/humanUi.tsx new file mode 100644 index 0000000..7a2bb9c --- /dev/null +++ b/app/src/modules/simulation/human/instances/instance/humanUi.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +function HumanUi({ human }: { human: HumanStatus }) { + return ( + <> + + ) +} + +export default HumanUi \ No newline at end of file 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/services/simulation/products/UpsertProductOrEventApi.ts b/app/src/services/simulation/products/UpsertProductOrEventApi.ts index 69b6c9e..2382762 100644 --- a/app/src/services/simulation/products/UpsertProductOrEventApi.ts +++ b/app/src/services/simulation/products/UpsertProductOrEventApi.ts @@ -1,38 +1,37 @@ let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; export const upsertProductOrEventApi = async (body: any) => { - try { - const response = await fetch( - `${url_Backend_dwinzo}/api/V1/ProductUpsert`, - { - method: "POST", - headers: { - Authorization: "Bearer ", - "Content-Type": "application/json", - token: localStorage.getItem("token") || "", - refresh_token: localStorage.getItem("refreshToken") || "", - }, - body: JSON.stringify(body), - } - ); - const newAccessToken = response.headers.get("x-access-token"); - if (newAccessToken) { - //console.log("New token received:", newAccessToken); - localStorage.setItem("token", newAccessToken); - } + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/ProductUpsert`, + { + method: "POST", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify(body), + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + localStorage.setItem("token", newAccessToken); + } - if (!response.ok) { - console.error("Failed to add product or event"); - } + if (!response.ok) { + console.error("Failed to add product or event"); + } - const result = await response.json(); - return result; - } catch (error) { - echo.error("Failed to upsert product Or eventApi"); - if (error instanceof Error) { - console.log(error.message); - } else { - console.log("An unknown error occurred"); + const result = await response.json(); + return result; + } catch (error) { + echo.error("Failed to upsert product Or eventApi"); + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } } - } }; diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 695559d..18d636b 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['actions'][0] ) => 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/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 26802f8..be7cf16 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -158,6 +158,7 @@ interface StorageEventSchema extends AssetEventSchema { interface HumanEventSchema extends AssetEventSchema { type: "human"; + speed: number; point: HumanPointSchema; }