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,18 +365,16 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
}
/>
</button>
{triggers.length > 1 && (
<button
id="remove-trigger-button"
className="remove-button"
onClick={(e) => {
e.stopPropagation();
handleRemoveTrigger(trigger.triggerUuid);
}}
>
<RemoveIcon />
</button>
)}
<button
id="remove-trigger-button"
className="remove-button"
onClick={(e) => {
e.stopPropagation();
handleRemoveTrigger(trigger.triggerUuid);
}}
>
<RemoveIcon />
</button>
</div>
))}
</div>