feat: Implement human simulation features

- Added human event handling in the simulation, including the ability to add, update, and remove human instances.
- Introduced a new Human store to manage human states and actions.
- Updated the simulation context to include human management.
- Enhanced the Points and TriggerConnector components to support human interactions.
- Refactored existing components to integrate human-related functionalities.
- Added HumanInstance and HumanInstances components for rendering human entities in the simulation.
- Updated TypeScript definitions to include human-related types and actions.
This commit is contained in:
2025-07-02 15:07:31 +05:30
parent 3f59f5d2dd
commit 7519aa90c6
22 changed files with 706 additions and 144 deletions

View File

@@ -51,9 +51,8 @@ const AssetProperties: React.FC = () => {
};
const handleAnimationClick = (animation: string) => {
if (selectedFloorItem && selectedFloorItem.animationState) {
const isPlaying = selectedFloorItem.animationState?.playing || false;
setCurrentAnimation(selectedFloorItem.uuid, animation, !isPlaying);
if (selectedFloorItem) {
setCurrentAnimation(selectedFloorItem.uuid, animation, true);
}
}

View File

@@ -28,9 +28,9 @@ const VersionHistory = () => {
const handleSelectVersion = (version: Version) => {
if (!projectId) return;
getVersionDataApi(projectId, version.versionId).then((verdionData) => {
getVersionDataApi(projectId, version.versionId).then((versionData) => {
setSelectedVersion(version);
// console.log(verdionData);
// console.log(versionData);
}).catch((err) => {
// console.log(err);
})

View File

@@ -3,12 +3,14 @@ import { useEffect, useMemo, useRef, useState } from 'react'
import { useThree } from '@react-three/fiber';
import { useActiveLayer, useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi';
import { useParams } from 'react-router-dom';
import { useVersionContext } from '../../version/versionContext';
import { useSceneContext } from '../../../scene/sceneContext';
import ReferenceAisle from './referenceAisle';
import ReferencePoint from '../../point/reference/referencePoint';
import { getUserData } from '../../../../functions/getUserData';
// import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi';
function AisleCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@@ -23,6 +25,7 @@ function AisleCreator() {
const isLeftMouseDown = useRef(false);
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const [tempPoints, setTempPoints] = useState<Point[]>([]);
@@ -106,7 +109,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -129,7 +147,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -151,7 +184,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -172,7 +220,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -195,7 +258,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -217,7 +295,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -238,7 +331,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
@@ -260,7 +368,22 @@ function AisleCreator() {
};
addAisle(aisle);
if (projectId) {
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}

View File

@@ -360,6 +360,40 @@ async function handleModelLoad(
position: storageEvent.point.position,
rotation: storageEvent.point.rotation,
};
} else if (selectedItem.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: newFloorItem.rotation,
state: "idle",
type: "human",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z],
rotation: [0, 0, 0],
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "animation",
animation: null,
loadCapacity: 1,
travelPoints: {
startPoint: null,
endPoint: null,
},
triggers: []
}
]
}
}
addEvent(humanEvent);
eventData.point = {
uuid: humanEvent.point.uuid,
position: humanEvent.point.position,
rotation: humanEvent.point.rotation,
}
}
const completeData = {

View File

@@ -301,20 +301,21 @@ function Model({ asset }: { readonly asset: Asset }) {
useEffect(() => {
const handlePlay = (clipName: string) => {
if (asset.animationState && asset.animationState.playing) {
if (!mixerRef.current) return;
Object.values(actions.current).forEach((action) => action.stop());
const action = actions.current[clipName];
const action = actions.current[asset.animationState.current];
if (action && asset.animationState?.playing) {
action.reset().setLoop(THREE.LoopOnce, 1).play();
}
};
} else {
Object.values(actions.current).forEach((action) => action.stop());
}
handlePlay(asset.animationState?.current || '');
}, [asset])
}, [asset.animationState])
return (
<group
@@ -362,17 +363,6 @@ function Model({ asset }: { readonly asset: Asset }) {
<AssetBoundingBox boundingBox={boundingBox} />
)
)}
{/* <group >
<Html>
<div style={{ position: 'absolute', }}>
{animationNames.map((name) => (
<button key={name} onClick={() => handlePlay(name)} style={{ margin: 4 }}>
{name}
</button>
))}
</div>
</Html>
</group> */}
</group >
);
}

View File

@@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
// import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
// import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
@@ -159,7 +159,22 @@ function Point({ point }: { readonly point: Point }) {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
updatedAisles.forEach((updatedAisle) => {
upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '')
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
})
})
}
} else if (point.pointType === 'Wall') {
@@ -238,7 +253,22 @@ function Point({ point }: { readonly point: Point }) {
if (removedAisles.length > 0) {
removedAisles.forEach(aisle => {
if (projectId) {
deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '')
// API
// deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid
}
socket.emit('v1:model-aisle:delete', data);
}
});
setHoveredPoint(null);

View File

@@ -6,7 +6,7 @@ export default function SocketResponses() {
useEffect(() => {
socket.on("v1:model-asset:response:add", (data: any) => {
console.log('data: ', data);
// console.log('data: ', data);
});
return () => {

View File

@@ -16,6 +16,7 @@ import { createMachineStore, MachineStoreType } from '../../store/simulation/use
import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/useConveyorStore';
import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore';
import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore';
import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore';
type SceneContextValue = {
@@ -35,6 +36,7 @@ type SceneContextValue = {
conveyorStore: ConveyorStoreType;
vehicleStore: VehicleStoreType;
storageUnitStore: StorageUnitStoreType;
humanStore: HumanStoreType;
clearStores: () => void;
@@ -67,6 +69,7 @@ export function SceneProvider({
const conveyorStore = useMemo(() => createConveyorStore(), []);
const vehicleStore = useMemo(() => createVehicleStore(), []);
const storageUnitStore = useMemo(() => createStorageUnitStore(), []);
const humanStore = useMemo(() => createHumanStore(), []);
const clearStores = useMemo(() => () => {
assetStore.getState().clearAssets();
@@ -83,7 +86,8 @@ export function SceneProvider({
conveyorStore.getState().clearConveyors();
vehicleStore.getState().clearVehicles();
storageUnitStore.getState().clearStorageUnits();
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore]);
humanStore.getState().clearHumans();
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]);
const contextValue = useMemo(() => (
{
@@ -101,10 +105,11 @@ export function SceneProvider({
conveyorStore,
vehicleStore,
storageUnitStore,
humanStore,
clearStores,
layout
}
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, clearStores, layout]);
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]);
return (
<SceneContext.Provider value={contextValue}>

View File

@@ -1,19 +1,15 @@
import React, { useEffect } from 'react'
import { useEffect } from 'react'
import { useInputValues, useProductionCapacityData, useThroughPutData } from '../../../../store/builder/store'
import { usePlayButtonStore } from '../../../../store/usePlayButtonStore';
import { useProductContext } from '../../products/productContext';
export default function ProductionCapacityData() {
const { throughputData } = useThroughPutData()
const { productionCapacityData, setProductionCapacityData } = useProductionCapacityData()
const { setProductionCapacityData } = useProductionCapacityData()
const { inputValues } = useInputValues();
const { isPlaying } = usePlayButtonStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
useEffect(() => {
if (!isPlaying) {
setProductionCapacityData(0);
}
}, [isPlaying]);
@@ -26,8 +22,6 @@ export default function ProductionCapacityData() {
const Monthly_working_days = workingDaysPerYear / 12;
const Production_capacity_per_month = throughputData * Monthly_working_days;
setProductionCapacityData(Number(Production_capacity_per_month.toFixed(2)));
}
}, [throughputData, inputValues, isPlaying]);

View File

@@ -1,18 +1,9 @@
import React, { useEffect } from 'react'
import { usePlayButtonStore } from '../../../store/usePlayButtonStore'
import ProductionCapacityData from './productionCapacity/productionCapacityData'
import ThroughPutData from './throughPut/throughPutData'
import ROIData from './ROI/roiData'
function SimulationAnalysis() {
const { isPlaying } = usePlayButtonStore()
// useEffect(()=>{
// if (isPlaying) {
//
// } else {
//
// }
// },[isPlaying])
return (
<>
<ThroughPutData />

View File

@@ -352,6 +352,37 @@ function PointsCreator() {
</mesh>
</group>
);
} else if (usedEvent.type === "human") {
const point = usedEvent.point;
return (
<group
key={`${index}-${usedEvent.modelUuid}`}
position={usedEvent.position}
rotation={usedEvent.rotation}
>
<mesh
name="Event-Sphere"
uuid={point.uuid}
ref={(el) => (sphereRefs.current[point.uuid] = el!)}
onClick={(e) => {
e.stopPropagation();
if (toolMode === 'cursor') {
setSelectedEventSphere(
sphereRefs.current[point.uuid]
);
}
}}
position={new THREE.Vector3(...point.position)}
userData={{
modelUuid: usedEvent.modelUuid,
pointUuid: point.uuid,
}}
>
<sphereGeometry args={[0.1, 16, 16]} />
<meshStandardMaterial color="white" />
</mesh>
</group>
);
} else {
return null;
}

View File

@@ -1,4 +1,3 @@
import React from 'react'
import PointsCreator from './creator/pointsCreator'
function Points() {

View File

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

View File

@@ -0,0 +1,13 @@
import HumanInstances from './instances/humanInstances'
function Human() {
return (
<>
<HumanInstances />
</>
)
}
export default Human

View File

@@ -0,0 +1,20 @@
import React from 'react'
import HumanInstance from './instance/humanInstance';
import { useSceneContext } from '../../../scene/sceneContext';
function HumanInstances() {
const { humanStore } = useSceneContext();
const { humans } = humanStore();
return (
<>
{humans.map((human: HumanStatus) => (
<React.Fragment key={human.modelUuid}>
<HumanInstance human={human} />
</React.Fragment>
))}
</>
)
}
export default HumanInstances

View File

@@ -0,0 +1,15 @@
import { useEffect } from 'react'
function HumanInstance({ human }: { human: HumanStatus }) {
useEffect(() => {
console.log('human: ', human);
}, [human])
return (
<>
</>
)
}
export default HumanInstance

View File

@@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom';
import { useVersionContext } from '../../builder/version/versionContext';
function Products() {
const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, layout, productStore } = useSceneContext();
const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, layout, productStore } = useSceneContext();
const { products, getProductById, addProduct, setProducts } = productStore();
const { selectedProductStore } = useProductContext();
const { setMainProduct } = useMainProduct();
@@ -20,6 +20,7 @@ function Products() {
const { addMachine, clearMachines } = machineStore();
const { addConveyor, clearConveyors } = conveyorStore();
const { setCurrentMaterials, clearStorageUnits, updateCurrentLoad, addStorageUnit } = storageUnitStore();
const { addHuman, clearHumans } = humanStore();
const { isReset } = useResetButtonStore();
const { isPlaying } = usePlayButtonStore();
const { mainProduct } = useMainProduct();
@@ -153,6 +154,20 @@ function Products() {
}
}, [selectedProduct, products, isReset, isPlaying]);
useEffect(() => {
if (selectedProduct.productUuid) {
const product = getProductById(selectedProduct.productUuid);
if (product) {
clearHumans();
product.eventDatas.forEach(events => {
if (events.type === 'human') {
addHuman(selectedProduct.productUuid, events);
}
});
}
}
}, [selectedProduct, products, isReset, isPlaying]);
return (
<>

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import Vehicles from './vehicle/vehicles';
import Points from './events/points/points';
import Conveyor from './conveyor/conveyor';
@@ -6,6 +6,7 @@ import RoboticArm from './roboticArm/roboticArm';
import Materials from './materials/materials';
import Machine from './machine/machine';
import StorageUnit from './storageUnit/storageUnit';
import Human from './human/human';
import Simulator from './simulator/simulator';
import Products from './products/products';
import Trigger from './triggers/trigger';
@@ -52,6 +53,8 @@ function Simulation() {
<StorageUnit />
<Human />
<Simulator />
<SimulationAnalysis />

View File

@@ -4,7 +4,6 @@ import { usePlayButtonStore, useResetButtonStore } from '../../../store/usePlayB
import { determineExecutionOrder } from './functions/determineExecutionOrder';
import { useProductContext } from '../products/productContext';
import { useSceneContext } from '../../scene/sceneContext';
import { useCompareProductDataStore } from '../../../store/builder/store';
function Simulator() {
const { selectedProductStore } = useProductContext();

View File

@@ -5,20 +5,20 @@ import { useSceneContext } from "../../../scene/sceneContext";
import { useViewSceneStore } from "../../../../store/builder/store";
function VehicleInstances() {
const { vehicleStore } = useSceneContext();
const { vehicles } = vehicleStore();
const { viewSceneLabels } = useViewSceneStore();
const { vehicleStore } = useSceneContext();
const { vehicles } = vehicleStore();
const { viewSceneLabels } = useViewSceneStore();
return (
<>
{vehicles.map((vehicle: VehicleStatus) => (
<React.Fragment key={vehicle.modelUuid}>
<VehicleInstance agvDetail={vehicle} />
{viewSceneLabels && <VehicleContentUi vehicle={vehicle} />}
</React.Fragment>
))}
</>
);
return (
<>
{vehicles.map((vehicle: VehicleStatus) => (
<React.Fragment key={vehicle.modelUuid}>
<VehicleInstance agvDetail={vehicle} />
{viewSceneLabels && <VehicleContentUi vehicle={vehicle} />}
</React.Fragment>
))}
</>
);
}
export default VehicleInstances;

View File

@@ -0,0 +1,239 @@
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
interface HumansStore {
humans: HumanStatus[];
addHuman: (productUuid: string, event: HumanEventSchema) => void;
removeHuman: (modelUuid: string) => void;
updateHuman: (
modelUuid: string,
updates: Partial<Omit<HumanStatus, "modelUuid" | "productUuid">>
) => void;
clearHumans: () => void;
setHumanActive: (modelUuid: string, isActive: boolean) => void;
setHumanPicking: (modelUuid: string, isPicking: boolean) => void;
setHumanLoad: (modelUuid: string, load: number) => void;
incrementHumanLoad: (modelUuid: string, incrementBy: number) => void;
decrementHumanLoad: (modelUuid: string, decrementBy: number) => void;
addCurrentMaterial: (modelUuid: string, materialType: string, materialId: string) => void;
setCurrentMaterials: (modelUuid: string, materials: { materialType: string; materialId: string }[]) => void;
removeLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined;
getLastMaterial: (modelUuid: string) => { materialType: string; materialId: string } | undefined;
clearCurrentMaterials: (modelUuid: string) => void;
setCurrentAction: (
modelUuid: string,
action: HumanStatus["currentAction"]
) => void;
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
incrementIdleTime: (modelUuid: string, incrementBy: number) => void;
incrementDistanceTraveled: (modelUuid: string, incrementBy: number) => void;
resetTime: (modelUuid: string) => void;
getHumanById: (modelUuid: string) => HumanStatus | undefined;
getHumansByProduct: (productUuid: string) => HumanStatus[];
getActiveHumans: () => HumanStatus[];
}
export const createHumanStore = () => {
return create<HumansStore>()(
immer((set, get) => ({
humans: [],
addHuman: (productUuid, event) => {
set((state) => {
const exists = state.humans.some(h => h.modelUuid === event.modelUuid);
if (!exists) {
state.humans.push({
...event,
productUuid,
isActive: false,
isPicking: false,
idleTime: 0,
activeTime: 0,
currentLoad: 0,
currentMaterials: [],
distanceTraveled: 0
});
}
});
},
removeHuman: (modelUuid) => {
set((state) => {
state.humans = state.humans.filter(h => h.modelUuid !== modelUuid);
});
},
updateHuman: (modelUuid, updates) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
Object.assign(human, updates);
}
});
},
clearHumans: () => {
set((state) => {
state.humans = [];
});
},
setHumanActive: (modelUuid, isActive) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.isActive = isActive;
}
});
},
setHumanPicking: (modelUuid, isPicking) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.isPicking = isPicking;
}
});
},
setHumanLoad: (modelUuid, load) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentLoad = load;
}
});
},
incrementHumanLoad: (modelUuid, incrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentLoad += incrementBy;
}
});
},
decrementHumanLoad: (modelUuid, decrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentLoad -= decrementBy;
}
});
},
addCurrentMaterial: (modelUuid, materialType, materialId) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentMaterials.push({ materialType, materialId });
}
});
},
setCurrentMaterials: (modelUuid, materials) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentMaterials = materials;
}
});
},
removeLastMaterial: (modelUuid) => {
let removed;
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human && human.currentMaterials.length > 0) {
removed = human.currentMaterials.pop();
}
});
return removed;
},
getLastMaterial: (modelUuid) => {
const human = get().humans.find(h => h.modelUuid === modelUuid);
if (human && human.currentMaterials.length > 0) {
return human.currentMaterials[human.currentMaterials.length - 1];
}
return undefined;
},
clearCurrentMaterials: (modelUuid) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentMaterials = [];
}
});
},
setCurrentAction: (modelUuid, action) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.currentAction = action;
}
});
},
incrementActiveTime: (modelUuid, incrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.activeTime += incrementBy;
}
});
},
incrementIdleTime: (modelUuid, incrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.idleTime += incrementBy;
}
});
},
incrementDistanceTraveled: (modelUuid, incrementBy) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.distanceTraveled += incrementBy;
}
});
},
resetTime: (modelUuid) => {
set((state) => {
const human = state.humans.find(h => h.modelUuid === modelUuid);
if (human) {
human.activeTime = 0;
human.idleTime = 0;
}
});
},
getHumanById: (modelUuid) => {
return get().humans.find(h => h.modelUuid === modelUuid);
},
getHumansByProduct: (productUuid) => {
return get().humans.filter(h => h.productUuid === productUuid);
},
getActiveHumans: () => {
return get().humans.filter(h => h.isActive);
}
}))
);
};
export type HumanStoreType = ReturnType<typeof createHumanStore>;

View File

@@ -1,3 +1,4 @@
// Base Types
interface AssetEventSchema {
modelUuid: string;
modelName: string;
@@ -18,69 +19,7 @@ interface TriggerSchema {
} | null;
}
interface ConveyorPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: ConveyorAction;
}
interface VehiclePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: VehicleAction;
}
interface RoboticArmPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: RoboticArmAction[];
}
interface MachinePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: MachineAction;
}
interface StoragePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: StorageAction;
}
interface ConveyorEventSchema extends AssetEventSchema {
type: "transfer";
speed: number;
points: ConveyorPointSchema[];
}
interface VehicleEventSchema extends AssetEventSchema {
type: "vehicle";
speed: number;
point: VehiclePointSchema;
}
interface RoboticArmEventSchema extends AssetEventSchema {
type: "roboticArm";
speed: number;
point: RoboticArmPointSchema;
}
interface MachineEventSchema extends AssetEventSchema {
type: "machine";
point: MachinePointSchema;
}
interface StorageEventSchema extends AssetEventSchema {
type: "storageUnit";
point: StoragePointSchema;
}
// Actions
interface ConveyorAction {
actionUuid: string;
actionName: string;
@@ -130,19 +69,100 @@ interface StorageAction {
triggers: TriggerSchema[];
}
type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction;
interface HumanAction {
actionUuid: string;
actionName: string;
actionType: "animation" | "animatedTravel";
animation: string | null;
loadCapacity: number;
travelPoints?: { startPoint: [number, number, number] | null; endPoint: [number, number, number] | null; }
triggers: TriggerSchema[];
}
type PointsScheme = ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema;
type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction;
type EventsSchema = ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema;
// Points
interface ConveyorPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: ConveyorAction;
}
type productsSchema = {
productName: string;
productUuid: string;
eventDatas: EventsSchema[];
}[]
interface VehiclePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: VehicleAction;
}
interface RoboticArmPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: RoboticArmAction[];
}
interface MachinePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: MachineAction;
}
interface StoragePointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
action: StorageAction;
}
interface HumanPointSchema {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: HumanAction[];
}
type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema;
// Events
interface ConveyorEventSchema extends AssetEventSchema {
type: "transfer";
speed: number;
points: ConveyorPointSchema[];
}
interface VehicleEventSchema extends AssetEventSchema {
type: "vehicle";
speed: number;
point: VehiclePointSchema;
}
interface RoboticArmEventSchema extends AssetEventSchema {
type: "roboticArm";
speed: number;
point: RoboticArmPointSchema;
}
interface MachineEventSchema extends AssetEventSchema {
type: "machine";
point: MachinePointSchema;
}
interface StorageEventSchema extends AssetEventSchema {
type: "storageUnit";
point: StoragePointSchema;
}
interface HumanEventSchema extends AssetEventSchema {
type: "human";
point: HumanPointSchema;
}
type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema;
// Statuses
interface ConveyorStatus extends ConveyorEventSchema {
productUuid: string;
isActive: boolean;
@@ -197,6 +217,24 @@ interface StorageUnitStatus extends StorageEventSchema {
currentMaterials: { materialType: string; materialId: string; }[];
}
interface HumanStatus extends HumanEventSchema {
productUuid: string;
isActive: boolean;
isPicking: boolean;
idleTime: number;
activeTime: number;
currentLoad: number;
currentMaterials: { materialType: string; materialId: string; }[];
distanceTraveled: number;
currentAction?: {
actionUuid: string;
actionName: string;
materialType?: string | null;
materialId?: string | null;
};
}
// Materials
interface MaterialSchema {
materialId: string;
materialName: string;
@@ -230,6 +268,14 @@ interface MaterialSchema {
type MaterialsSchema = MaterialSchema[];
// Products
type productsSchema = {
productName: string;
productUuid: string;
eventDatas: EventsSchema[];
}[];
// Material History
interface MaterialHistoryEntry {
material: MaterialSchema;
removedAt: string;