feat: Add human mechanics and event handling, including UI components and action management

This commit is contained in:
2025-07-03 12:09:31 +05:30
parent 424df54ff7
commit b5c69f3335
13 changed files with 469 additions and 48 deletions

View File

@@ -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" && <RoboticArmMechanics />}
{assetType === "machine" && <MachineMechanics />}
{assetType === "storageUnit" && <StorageMechanics />}
{assetType === "human" && <HumanMechanics />}
</>
)}
{!currentEventData && selectedEventSphere && (

View File

@@ -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<string[]>([]);
const [speed, setSpeed] = useState("0.5");
const [currentAction, setCurrentAction] = useState<HumanAction | undefined>();
const [isLoopAnimation, setIsLoopAnimation] = useState(false);
const [selectedPointData, setSelectedPointData] = useState<HumanPointSchema | undefined>();
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 (
<>
<div className="global-props section">
<div className="property-list-container">
<div className="property-item">
<InputWithDropDown
label="Speed"
value={speed}
min={0}
step={0.1}
defaultValue="0.5"
max={10}
activeOption="m/s"
onClick={() => { }}
onChange={handleSpeedChange}
/>
</div>
</div>
</div>
<section>
<ActionsList
selectedPointData={selectedPointData}
multipleAction
handleAddAction={handleAddAction}
handleDeleteAction={handleDeleteAction}
/>
{selectedAction.actionId && currentAction && (
<div className="selected-actions-details">
<div className="selected-actions-header">
<RenameInput
value={selectedAction.actionName || ""}
onRename={handleRenameAction}
/>
</div>
<div className="selected-actions-list">
<LabledDropdown
label="Action Type"
defaultOption={activeOption}
options={availableActions.options}
onSelect={handleSelectActionType}
disabled={true}
/>
<LabledDropdown
label="Animation"
defaultOption={activeAnimationOption}
options={animationOptions}
onSelect={handleSelectAnimation}
disabled={true}
/>
<InputToggle
value={isLoopAnimation}
inputKey=""
label="Loop Animation"
onClick={handleLoopAnimationChange}
/>
{activeOption === 'animatedTravel' &&
<PickAndPlaceAction clearPoints={handleClearPoints} />
}
</div>
<div className="tirgger">
<Trigger
selectedPointData={selectedPointData as any}
type={"Human"}
/>
</div>
</div>
)}
</section>
</>
);
}
export default HumanMechanics;

View File

@@ -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,7 +365,6 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
}
/>
</button>
{triggers.length > 1 && (
<button
id="remove-trigger-button"
className="remove-button"
@@ -376,7 +375,6 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
>
<RemoveIcon />
</button>
)}
</div>
))}
</div>

View File

@@ -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],

View File

@@ -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],

View File

@@ -30,7 +30,7 @@ export const handleAddEventToProduct = ({
projectId: projectId || '',
eventDatas: event
}).then((data) => {
// console.log(data);
console.log(data);
})
if (clearSelectedAsset) {

View File

@@ -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 (
<>

View File

@@ -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 (
<>
<HumanUi human={human} />
</>
)
}

View File

@@ -0,0 +1,10 @@
import React from 'react'
function HumanUi({ human }: { human: HumanStatus }) {
return (
<>
</>
)
}
export default HumanUi

View File

@@ -17,7 +17,6 @@ export const upsertProductOrEventApi = async (body: any) => {
);
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
//console.log("New token received:", newAccessToken);
localStorage.setItem("token", newAccessToken);
}

View File

@@ -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<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']>
updates: Partial<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]>
) => 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));

View File

@@ -158,6 +158,7 @@ interface StorageEventSchema extends AssetEventSchema {
interface HumanEventSchema extends AssetEventSchema {
type: "human";
speed: number;
point: HumanPointSchema;
}