feat: Add human mechanics and event handling, including UI components and action management
This commit is contained in:
@@ -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 && (
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -30,7 +30,7 @@ export const handleAddEventToProduct = ({
|
||||
projectId: projectId || '',
|
||||
eventDatas: event
|
||||
}).then((data) => {
|
||||
// console.log(data);
|
||||
console.log(data);
|
||||
})
|
||||
|
||||
if (clearSelectedAsset) {
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
|
||||
@@ -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} />
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
function HumanUi({ human }: { human: HumanStatus }) {
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default HumanUi
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
1
app/src/types/simulationTypes.d.ts
vendored
1
app/src/types/simulationTypes.d.ts
vendored
@@ -158,6 +158,7 @@ interface StorageEventSchema extends AssetEventSchema {
|
||||
|
||||
interface HumanEventSchema extends AssetEventSchema {
|
||||
type: "human";
|
||||
speed: number;
|
||||
point: HumanPointSchema;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user