feat: Refactor human action handling to support animated travel and streamline action structure

This commit is contained in:
2025-07-03 12:11:58 +05:30
parent b5c69f3335
commit 98f4d48db2
10 changed files with 75 additions and 282 deletions

View File

@@ -1,31 +1,25 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { MathUtils } from "three";
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown"; import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
import RenameInput from "../../../../../ui/inputs/RenameInput"; import RenameInput from "../../../../../ui/inputs/RenameInput";
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
import Trigger from "../trigger/Trigger"; import Trigger from "../trigger/Trigger";
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore"; import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
import PickAndPlaceAction from "../actions/PickAndPlaceAction"; import PickAndPlaceAction from "../actions/PickAndPlaceAction";
import ActionsList from "../components/ActionsList";
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi"; import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useProductContext } from "../../../../../../modules/simulation/products/productContext"; import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useVersionContext } from "../../../../../../modules/builder/version/versionContext"; import { useVersionContext } from "../../../../../../modules/builder/version/versionContext";
import { useSceneContext } from "../../../../../../modules/scene/sceneContext"; import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
import InputToggle from "../../../../../ui/inputs/InputToggle"; import ActionsList from "../components/ActionsList";
function HumanMechanics() { function HumanMechanics() {
const [activeOption, setActiveOption] = useState<"animation" | "animatedTravel">("animation"); const [activeOption, setActiveOption] = useState<"animatedTravel">("animatedTravel");
const [activeAnimationOption, setActiveAnimationOption] = useState("");
const [animationOptions, setAnimationOptions] = useState<string[]>([]);
const [speed, setSpeed] = useState("0.5"); const [speed, setSpeed] = useState("0.5");
const [currentAction, setCurrentAction] = useState<HumanAction | undefined>(); const [currentAction, setCurrentAction] = useState<HumanAction | undefined>();
const [isLoopAnimation, setIsLoopAnimation] = useState(false);
const [selectedPointData, setSelectedPointData] = useState<HumanPointSchema | undefined>(); const [selectedPointData, setSelectedPointData] = useState<HumanPointSchema | undefined>();
const { selectedEventData } = useSelectedEventData(); const { selectedEventData } = useSelectedEventData();
const { productStore, assetStore } = useSceneContext(); const { productStore } = useSceneContext();
const { getAssetById } = assetStore(); const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = productStore();
const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction, addAction, removeAction } = productStore();
const { selectedProductStore } = useProductContext(); const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore(); const { selectedProduct } = selectedProductStore();
const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction(); const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction();
@@ -41,15 +35,10 @@ function HumanMechanics() {
selectedEventData.selectedPoint selectedEventData.selectedPoint
) as HumanPointSchema | undefined; ) as HumanPointSchema | undefined;
if (point?.actions) { if (point?.action) {
setSelectedPointData(point); setSelectedPointData(point);
if (point.actions.length > 0) { const action = point.action;
setSelectedAction(point.actions[0].actionUuid, point.actions[0].actionName); setSelectedAction(action.actionUuid, action.actionName);
const asset = getAssetById(selectedEventData.data.modelUuid);
if (asset && asset.animations) {
setAnimationOptions(asset.animations)
}
}
} }
} else { } else {
clearSelectedAction(); clearSelectedAction();
@@ -73,15 +62,11 @@ function HumanMechanics() {
selectedEventData.selectedPoint selectedEventData.selectedPoint
) as HumanPointSchema | undefined; ) as HumanPointSchema | undefined;
if (point?.actions) { if (point?.action) {
setSelectedPointData(point); setSelectedPointData(point);
const action = point.actions.find((a) => a.actionUuid === selectedAction.actionId); const action = point.action;
if (action) { setCurrentAction(action);
setCurrentAction(action); setActiveOption(action.actionType as "animatedTravel");
setIsLoopAnimation(action.loopAnimation ?? false);
setActiveOption(action.actionType as "animation" | "animatedTravel");
setActiveAnimationOption(action.animation || '')
}
} }
} else { } else {
clearSelectedAction(); clearSelectedAction();
@@ -106,73 +91,18 @@ function HumanMechanics() {
}; };
const handleSelectActionType = (actionType: string) => { const handleSelectActionType = (actionType: string) => {
if (!selectedAction.actionId) return; if (!currentAction) return;
setActiveOption(actionType as "animation" | "animatedTravel"); setActiveOption(actionType as "animatedTravel");
const event = updateAction( const event = updateAction(
selectedProduct.productUuid, selectedProduct.productUuid,
selectedAction.actionId, currentAction.actionUuid,
{ actionType: actionType as "animation" | "animatedTravel" } { actionType: actionType as "animatedTravel" }
); );
if (selectedPointData) { if (event && selectedPointData) {
const updatedActions = selectedPointData.actions.map((action) => const updatedAction = { ...selectedPointData.action, actionType: actionType as "animatedTravel" };
action.actionUuid === selectedAction.actionId setSelectedPointData({ ...selectedPointData, action: updatedAction });
? { ...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) { if (event) {
@@ -181,20 +111,17 @@ function HumanMechanics() {
}; };
const handleRenameAction = (newName: string) => { const handleRenameAction = (newName: string) => {
if (!selectedAction.actionId) return; if (!currentAction) return;
const event = updateAction( const event = updateAction(
selectedProduct.productUuid, selectedProduct.productUuid,
selectedAction.actionId, currentAction.actionUuid,
{ actionName: newName } { actionName: newName }
); );
if (selectedPointData) { if (event && selectedPointData) {
const updatedActions = selectedPointData.actions.map((action) => const updatedAction = { ...selectedPointData.action, actionName: newName };
action.actionUuid === selectedAction.actionId setSelectedPointData({ ...selectedPointData, action: updatedAction });
? { ...action, actionName: newName }
: action
);
setSelectedPointData({ ...selectedPointData, actions: updatedActions });
} }
if (event) { if (event) {
@@ -217,11 +144,11 @@ function HumanMechanics() {
}; };
const handleClearPoints = () => { const handleClearPoints = () => {
if (!selectedAction.actionId || !selectedPointData) return; if (!currentAction) return;
const event = updateAction( const event = updateAction(
selectedProduct.productUuid, selectedProduct.productUuid,
selectedAction.actionId, currentAction.actionUuid,
{ {
travelPoints: { travelPoints: {
startPoint: null, startPoint: null,
@@ -235,66 +162,9 @@ function HumanMechanics() {
} }
}; };
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 = { const availableActions = {
defaultOption: "animatedTravel", defaultOption: "animatedTravel",
options: ["animation", "animatedTravel"], options: ["animatedTravel"],
}; };
return ( return (
@@ -316,19 +186,20 @@ function HumanMechanics() {
</div> </div>
</div> </div>
</div> </div>
<section>
<ActionsList
selectedPointData={selectedPointData}
multipleAction
handleAddAction={handleAddAction}
handleDeleteAction={handleDeleteAction}
/>
{selectedAction.actionId && currentAction && ( {currentAction && (
<section>
<ActionsList
selectedPointData={selectedPointData}
multipleAction={false}
handleAddAction={() => { }}
handleDeleteAction={() => { }}
/>
<div className="selected-actions-details"> <div className="selected-actions-details">
<div className="selected-actions-header"> <div className="selected-actions-header">
<RenameInput <RenameInput
value={selectedAction.actionName || ""} value={currentAction.actionName || ""}
onRename={handleRenameAction} onRename={handleRenameAction}
/> />
</div> </div>
@@ -340,32 +211,19 @@ function HumanMechanics() {
onSelect={handleSelectActionType} onSelect={handleSelectActionType}
disabled={true} disabled={true}
/> />
<LabledDropdown {activeOption === 'animatedTravel' && (
label="Animation"
defaultOption={activeAnimationOption}
options={animationOptions}
onSelect={handleSelectAnimation}
disabled={true}
/>
<InputToggle
value={isLoopAnimation}
inputKey=""
label="Loop Animation"
onClick={handleLoopAnimationChange}
/>
{activeOption === 'animatedTravel' &&
<PickAndPlaceAction clearPoints={handleClearPoints} /> <PickAndPlaceAction clearPoints={handleClearPoints} />
} )}
</div> </div>
<div className="tirgger"> <div className="tirgger">
<Trigger <Trigger
selectedPointData={selectedPointData as any} selectedPointData={selectedPointData as any}
type={"Human"} type="Human"
/> />
</div> </div>
</div> </div>
)} </section>
</section> )}
</> </>
); );
} }

View File

@@ -256,21 +256,18 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), 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], 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], rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0],
actions: [ action: {
{ actionUuid: THREE.MathUtils.generateUUID(),
actionUuid: THREE.MathUtils.generateUUID(), actionName: "Action 1",
actionName: "Action 1", actionType: "animatedTravel",
actionType: "animation", loadCapacity: 1,
animation: null, travelPoints: {
loopAnimation: true, startPoint: null,
loadCapacity: 1, endPoint: null,
travelPoints: { },
startPoint: null, triggers: []
endPoint: null, }
},
triggers: []
}
]
} }
} }
addEvent(humanEvent); addEvent(humanEvent);

View File

@@ -374,21 +374,18 @@ async function handleModelLoad(
uuid: THREE.MathUtils.generateUUID(), uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z], position: [data.points[0].x, data.points[0].y, data.points[0].z],
rotation: [0, 0, 0], rotation: [0, 0, 0],
actions: [ action: {
{ actionUuid: THREE.MathUtils.generateUUID(),
actionUuid: THREE.MathUtils.generateUUID(), actionName: "Action 1",
actionName: "Action 1", actionType: "animatedTravel",
actionType: "animation", loadCapacity: 1,
animation: null, travelPoints: {
loopAnimation: true, startPoint: null,
loadCapacity: 1, endPoint: null,
travelPoints: { },
startPoint: null, triggers: []
endPoint: null, }
},
triggers: []
}
]
} }
} }
addEvent(humanEvent); addEvent(humanEvent);

View File

@@ -1,34 +0,0 @@
import { useCallback } from "react";
import { useSceneContext } from "../../../../scene/sceneContext";
import { useProductContext } from "../../../products/productContext";
export function useAnimationHandler() {
const { materialStore, humanStore, productStore } = useSceneContext();
const { getMaterialById } = materialStore();
const { } = humanStore();
const { getModelUuidByActionUuid } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const animationLogStatus = (materialUuid: string, status: string) => {
echo.info(`${materialUuid}, ${status}`);
}
const handleAnimation = useCallback((action: HumanAction, materialId?: string) => {
if (!action || action.actionType !== 'animation' || !materialId) return;
const material = getMaterialById(materialId);
if (!material) return;
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
if (!modelUuid) return;
animationLogStatus(material.materialName, `performing animation`);
}, [getMaterialById]);
return {
handleAnimation,
};
}

View File

@@ -1,15 +1,9 @@
import { useEffect, useCallback } from 'react'; import { useEffect, useCallback } from 'react';
import { useAnimationHandler } from './actionHandler/useAnimationHandler';
import { useAnimatedTravelHandler } from './actionHandler/useAnimatedTravelHandler'; import { useAnimatedTravelHandler } from './actionHandler/useAnimatedTravelHandler';
export function useHumanActions() { export function useHumanActions() {
const { handleAnimation } = useAnimationHandler();
const { handleAnimatedTravel } = useAnimatedTravelHandler(); const { handleAnimatedTravel } = useAnimatedTravelHandler();
const handleAnimationAction = useCallback((action: HumanAction, materialId: string) => {
handleAnimation(action, materialId);
}, [handleAnimation]);
const handleAnimatedTravelAction = useCallback((action: HumanAction) => { const handleAnimatedTravelAction = useCallback((action: HumanAction) => {
handleAnimatedTravel(action); handleAnimatedTravel(action);
}, [handleAnimatedTravel]); }, [handleAnimatedTravel]);
@@ -18,16 +12,13 @@ export function useHumanActions() {
if (!action) return; if (!action) return;
switch (action.actionType) { switch (action.actionType) {
case 'animation':
handleAnimationAction(action, materialId);
break;
case 'animatedTravel': case 'animatedTravel':
handleAnimatedTravelAction(action); handleAnimatedTravelAction(action);
break; break;
default: default:
console.warn(`Unknown Human action type: ${action.actionType}`); console.warn(`Unknown Human action type: ${action.actionType}`);
} }
}, [handleAnimationAction, handleAnimatedTravelAction]); }, [handleAnimatedTravelAction]);
const cleanup = useCallback(() => { const cleanup = useCallback(() => {
}, []); }, []);

View File

@@ -39,7 +39,7 @@ export function useActionHandler() {
case 'store': case 'retrieve': case 'store': case 'retrieve':
handleStorageAction(action as StorageAction, materialId as string); handleStorageAction(action as StorageAction, materialId as string);
break; break;
case 'animation': case 'animatedTravel': case 'animatedTravel':
handleHumanAction(action as HumanAction, materialId as string); handleHumanAction(action as HumanAction, materialId as string);
break; break;
default: default:

View File

@@ -155,8 +155,8 @@ function TriggerConnector() {
// Handle Human point // Handle Human point
else if (event.type === "human" && 'point' in event) { else if (event.type === "human" && 'point' in event) {
const point = event.point; const point = event.point;
point.actions?.forEach(action => { if (point.action?.triggers) {
action.triggers?.forEach(trigger => { point.action.triggers.forEach(trigger => {
if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) { if (trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint) {
newConnections.push({ newConnections.push({
id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`,
@@ -166,7 +166,7 @@ function TriggerConnector() {
}); });
} }
}); });
}); }
} }
}); });

View File

@@ -24,11 +24,6 @@ interface HumansStore {
getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined; getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined;
clearCurrentMaterials: (modelUuid: string) => void; clearCurrentMaterials: (modelUuid: string) => void;
setCurrentAction: (
modelUuid: string,
action: HumanStatus["currentAction"]
) => void;
incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
incrementIdleTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void; incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void;
@@ -175,15 +170,6 @@ export const createHumanStore = () => {
}); });
}, },
setCurrentAction: (modelUuid, action) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentAction = action;
}
});
},
incrementActiveTime: (modelUuid, incrementBy) => { incrementActiveTime: (modelUuid, incrementBy) => {
set((state) => { set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid); const human = state.humans.find(h => h.modelUuid === modelUuid);

View File

@@ -32,13 +32,13 @@ type ProductsStore = {
productUuid: string, productUuid: string,
modelUuid: string, modelUuid: string,
pointUuid: string, pointUuid: string,
action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action']
) => EventsSchema | undefined; ) => EventsSchema | undefined;
removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined;
updateAction: ( updateAction: (
productUuid: string, productUuid: string,
actionUuid: string, actionUuid: string,
updates: Partial<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]> updates: Partial<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['action']>
) => EventsSchema | undefined; ) => EventsSchema | undefined;
// Trigger-level actionss // Trigger-level actionss

View File

@@ -72,9 +72,7 @@ interface StorageAction {
interface HumanAction { interface HumanAction {
actionUuid: string; actionUuid: string;
actionName: string; actionName: string;
actionType: "animation" | "animatedTravel"; actionType: "animatedTravel";
animation: string | null;
loopAnimation: boolean;
loadCapacity: number; loadCapacity: number;
travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; } travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; }
triggers: TriggerSchema[]; triggers: TriggerSchema[];
@@ -122,7 +120,7 @@ interface HumanPointSchema {
uuid: string; uuid: string;
position: [number, number, number]; position: [number, number, number];
rotation: [number, number, number]; rotation: [number, number, number];
actions: HumanAction[]; action: HumanAction;
} }
type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema;