From a08cec33ab2ff1c5acf942b096ea91405b739775 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Wed, 6 Aug 2025 18:19:54 +0530 Subject: [PATCH] pillar Jig half way completed --- .../components/layout/sidebarLeft/Assets.tsx | 461 ++++++------ app/src/modules/builder/asset/assetsGroup.tsx | 31 + .../builder/asset/functions/addAssetModel.ts | 66 +- .../model/eventHandlers/useEventHandlers.ts | 128 ++-- .../builder/asset/models/model/model.tsx | 429 +++--------- .../modules/builder/asset/models/models.tsx | 26 +- .../Instances/Instances/wallAssetInstance.tsx | 4 +- .../builder/wallAsset/wallAssetCreator.tsx | 4 +- .../selection3D/copyPasteControls3D.tsx | 42 +- .../selection3D/duplicationControls3D.tsx | 42 +- app/src/modules/scene/sceneContext.tsx | 9 +- app/src/modules/simulation/crane/crane.tsx | 15 + .../instances/animator/pillarJibAnimator.tsx | 133 ++++ .../crane/instances/craneInstances.tsx | 24 + .../instances/instance/pillarJibInstance.tsx | 14 + .../points/functions/pointsCalculator.ts | 2 +- .../points/instances/pointInstances.tsx | 1 + .../triggerConnections/triggerConnector.tsx | 16 + .../modules/simulation/products/products.tsx | 25 +- app/src/modules/simulation/simulation.tsx | 3 + app/src/store/builder/store.ts | 656 +++++++++--------- app/src/store/simulation/useCraneStore.ts | 263 +++++++ app/src/store/simulation/useEventsStore.ts | 12 +- app/src/store/simulation/useProductStore.ts | 23 +- app/src/types/builderTypes.d.ts | 1 + app/src/types/simulationTypes.d.ts | 104 ++- app/src/types/world/worldTypes.d.ts | 3 +- 27 files changed, 1506 insertions(+), 1031 deletions(-) create mode 100644 app/src/modules/simulation/crane/crane.tsx create mode 100644 app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx create mode 100644 app/src/modules/simulation/crane/instances/craneInstances.tsx create mode 100644 app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx create mode 100644 app/src/store/simulation/useCraneStore.ts diff --git a/app/src/components/layout/sidebarLeft/Assets.tsx b/app/src/components/layout/sidebarLeft/Assets.tsx index 0420d2a..8c8b4d7 100644 --- a/app/src/components/layout/sidebarLeft/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/Assets.tsx @@ -17,255 +17,242 @@ import SkeletonUI from "../../templates/SkeletonUI"; // ------------------------------------- interface AssetProp { - filename: string; - thumbnail?: string; - category: string; - description?: string; - tags: string; - url?: string; - uploadDate?: number; - isArchieve?: boolean; - animated?: boolean; - price?: number; - CreatedBy?: string; + filename: string; + thumbnail?: string; + category: string; + description?: string; + tags: string; + url?: string; + uploadDate?: number; + isArchieve?: boolean; + animated?: boolean; + price?: number; + CreatedBy?: string; } interface CategoryListProp { - assetImage?: string; - assetName?: string; - categoryImage: string; - category: string; + assetImage?: string; + assetName?: string; + categoryImage: string; + category: string; } const Assets: React.FC = () => { - const { setSelectedItem } = useSelectedItem(); - const [searchValue, setSearchValue] = useState(""); - const [selectedCategory, setSelectedCategory] = useState(null); - const [categoryAssets, setCategoryAssets] = useState([]); - const [filtereredAssets, setFiltereredAssets] = useState([]); - const [categoryList, setCategoryList] = useState([]); - const [isLoading, setisLoading] = useState(false); // Loading state for assets + const { setSelectedItem } = useSelectedItem(); + const [searchValue, setSearchValue] = useState(""); + const [selectedCategory, setSelectedCategory] = useState(null); + const [categoryAssets, setCategoryAssets] = useState([]); + const [filtereredAssets, setFiltereredAssets] = useState([]); + const [categoryList, setCategoryList] = useState([]); + const [isLoading, setisLoading] = useState(false); // Loading state for assets - const handleSearchChange = (value: string) => { - const searchTerm = value.toLowerCase(); - setSearchValue(value); - if (searchTerm.trim() === "" && !selectedCategory) { - setCategoryAssets([]); - return; - } - const filteredModels = filtereredAssets?.filter((model) => { - if (!model?.tags || !model?.filename || !model?.category) return false; - if (searchTerm.startsWith(":") && searchTerm.length > 1) { - const tagSearchTerm = searchTerm.slice(1); - return model.tags.toLowerCase().includes(tagSearchTerm); - } else if (selectedCategory) { - return ( - model.category - .toLowerCase() - .includes(selectedCategory.toLowerCase()) && - model.filename.toLowerCase().includes(searchTerm) - ); - } else { - return model.filename.toLowerCase().includes(searchTerm); - } - }); + const handleSearchChange = (value: string) => { + const searchTerm = value.toLowerCase(); + setSearchValue(value); + if (searchTerm.trim() === "" && !selectedCategory) { + setCategoryAssets([]); + return; + } + const filteredModels = filtereredAssets?.filter((model) => { + if (!model?.tags || !model?.filename || !model?.category) return false; + if (searchTerm.startsWith(":") && searchTerm.length > 1) { + const tagSearchTerm = searchTerm.slice(1); + return model.tags.toLowerCase().includes(tagSearchTerm); + } else if (selectedCategory) { + return ( + model.category + .toLowerCase() + .includes(selectedCategory.toLowerCase()) && + model.filename.toLowerCase().includes(searchTerm) + ); + } else { + return model.filename.toLowerCase().includes(searchTerm); + } + }); - setCategoryAssets(filteredModels); - }; - - useEffect(() => { - const filteredAssets = async () => { - try { - const filt = await fetchAssets(); - setFiltereredAssets(filt); - } catch { - echo.error("Filter asset not found"); - } + setCategoryAssets(filteredModels); }; - filteredAssets(); - }, [categoryAssets]); - useEffect(() => { - setCategoryList([ - { category: "Fenestration", categoryImage: feneration }, - { category: "Vehicles", categoryImage: vehicle }, - { category: "Workstation", categoryImage: workStation }, - { category: "Machines", categoryImage: machines }, - { category: "Workers", categoryImage: worker }, - { category: "Storage", categoryImage: storage }, - { category: "Safety", categoryImage: safety }, - { category: "Office", categoryImage: office }, - ]); - }, []); - - const fetchCategoryAssets = async (asset: any) => { - setisLoading(true); - setSelectedCategory(asset); - try { - const res = await getCategoryAsset(asset); - setCategoryAssets(res); - setFiltereredAssets(res); - setisLoading(false); // End loading - // eslint-disable-next-line - } catch (error) { - echo.error("failed to fetch assets"); - setisLoading(false); - } - }; - - return ( -
- -
-
- {(() => { - if (isLoading) { - return ; // Show skeleton when loading + useEffect(() => { + const filteredAssets = async () => { + try { + const filt = await fetchAssets(); + setFiltereredAssets(filt); + } catch { + echo.error("Filter asset not found"); } - if (searchValue) { - return ( -
-
-
-

Results for {searchValue}

-
-
- {categoryAssets?.map((asset: any, index: number) => ( -
- {asset.filename} { - setSelectedItem({ - name: asset.filename, - id: asset.AssetID, - type: - asset.type === "undefined" - ? undefined - : asset.type - }); - }} - /> + }; + filteredAssets(); + }, [categoryAssets]); -
- {asset.filename - .split("_") - .map( - (word: any) => - word.charAt(0).toUpperCase() + word.slice(1) - ) - .join(" ")} -
-
- ))} -
-
-
- ); - } + useEffect(() => { + setCategoryList([ + { category: "Fenestration", categoryImage: feneration }, + { category: "Vehicles", categoryImage: vehicle }, + { category: "Workstation", categoryImage: workStation }, + { category: "Machines", categoryImage: machines }, + { category: "Workers", categoryImage: worker }, + { category: "Storage", categoryImage: storage }, + { category: "Safety", categoryImage: safety }, + { category: "Office", categoryImage: office }, + ]); + }, []); - if (selectedCategory) { - return ( -
-

- {selectedCategory} - -

-
- {categoryAssets?.map((asset: any, index: number) => ( -
- {asset.filename} { - setSelectedItem({ - name: asset.filename, - id: asset.AssetID, - type: - asset.type === "undefined" - ? undefined - : asset.type, - category: asset.category, - subCategory: asset.subCategory - }); - }} - /> -
- {asset.filename - .split("_") - .map( - (word: any) => - word.charAt(0).toUpperCase() + word.slice(1) - ) - .join(" ")} -
-
- ))} - {categoryAssets.length === 0 && ( -
- 🚧 The asset shelf is empty. We're working on filling it - up! -
- )} -
-
- ); - } + const fetchCategoryAssets = async (asset: any) => { + setisLoading(true); + setSelectedCategory(asset); + try { + const res = await getCategoryAsset(asset); + setCategoryAssets(res); + setFiltereredAssets(res); + setisLoading(false); // End loading + // eslint-disable-next-line + } catch (error) { + echo.error("failed to fetch assets"); + setisLoading(false); + } + }; - return ( -
-

Categories

-
- {Array.from( - new Set(categoryList.map((asset) => asset.category)) - ).map((category, index) => { - const categoryInfo = categoryList.find( - (asset) => asset.category === category - ); - return ( -
fetchCategoryAssets(category)} - > - {category} -
{category}
-
- ); - })} -
-
- ); - })()} -
-
-
- ); + return ( +
+ +
+
+ {(() => { + if (isLoading) { + return ; // Show skeleton when loading + } + if (searchValue) { + return ( +
+
+
+

Results for {searchValue}

+
+
+ {categoryAssets?.map((asset: any, index: number) => ( +
+ {asset.filename} { + setSelectedItem({ + name: asset.filename, + id: asset.AssetID, + type: asset.type === "undefined" ? undefined : asset.type + }); + }} + /> + +
+ {asset.filename + .split("_") + .map( + (word: any) => + word.charAt(0).toUpperCase() + word.slice(1) + ) + .join(" ")} +
+
+ ))} +
+
+
+ ); + } + + if (selectedCategory) { + return ( +
+

+ {selectedCategory} + +

+
+ {categoryAssets?.map((asset: any, index: number) => ( +
+ {asset.filename} { + setSelectedItem({ + name: asset.filename, + id: asset.AssetID, + type: asset.type === "undefined" ? undefined : asset.type, + category: asset.category, + subType: asset.subType + }); + }} + /> +
+ {asset.filename.split("_").map((word: any) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")} +
+
+ ))} + {categoryAssets.length === 0 && ( +
+ 🚧 The asset shelf is empty. We're working on filling it up! +
+ )} +
+
+ ); + } + + return ( +
+

Categories

+
+ {Array.from( + new Set(categoryList.map((asset) => asset.category)) + ).map((category, index) => { + const categoryInfo = categoryList.find( + (asset) => asset.category === category + ); + return ( +
fetchCategoryAssets(category)} + > + {category} +
{category}
+
+ ); + })} +
+
+ ); + })()} +
+
+
+ ); }; export default Assets; diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 6c06be2..072c9a7 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -119,6 +119,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "vehicle", + subType: item.eventData.subType as VehicleEventSchema['subType'] || '', speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -151,6 +152,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "transfer", + subType: item.eventData.subType || '', speed: 1, points: item.eventData.points?.map((point: any, index: number) => ({ uuid: point.uuid || THREE.MathUtils.generateUUID(), @@ -177,6 +179,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "machine", + subType: item.eventData.subType || '', 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], @@ -200,6 +203,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "roboticArm", + subType: item.eventData.subType || '', speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -228,6 +232,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "storageUnit", + subType: item.eventData.subType || '', 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], @@ -250,6 +255,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { rotation: [item.rotation.x, item.rotation.y, item.rotation.z], state: "idle", type: "human", + subType: item.eventData.subType || '', speed: 1, point: { uuid: item.eventData.point?.uuid || THREE.MathUtils.generateUUID(), @@ -270,6 +276,31 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { } } addEvent(humanEvent); + } else if (item.eventData.type === 'Crane') { + const craneEvent: CraneEventSchema = { + modelUuid: item.modelUuid, + modelName: item.modelName, + position: item.position, + rotation: [item.rotation.x, item.rotation.y, item.rotation.z], + state: "idle", + type: "crane", + subType: item.eventData.subType as CraneEventSchema['subType'] || 'pillarJib', + 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], + rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); } } else { assets.push({ diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index bb66c6a..c64064a 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -1,13 +1,13 @@ import * as THREE from "three"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; -import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; import * as Types from "../../../../types/world/worldTypes"; import { retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils"; -// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; import { Socket } from "socket.io-client"; import * as CONSTANTS from "../../../../types/world/worldConstants"; import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator"; + import { getUserData } from "../../../../functions/getUserData"; +// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi'; async function addAssetModel( scene: THREE.Scene, @@ -165,7 +165,7 @@ async function handleModelLoad( if (!data || !data.points) return; - const eventData: any = { type: selectedItem.type }; + const eventData: any = { type: selectedItem.type, subType: selectedItem.subType }; if (selectedItem.type === "Conveyor") { const ConveyorEvent: ConveyorEventSchema = { @@ -175,6 +175,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "transfer", + subType: selectedItem.subType || '', speed: 1, points: data.points.map((point: THREE.Vector3, index: number) => { const triggers: TriggerSchema[] = []; @@ -243,6 +244,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "vehicle", + subType: selectedItem.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -280,6 +282,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "roboticArm", + subType: selectedItem.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -313,6 +316,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "machine", + subType: selectedItem.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], @@ -341,6 +345,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "storageUnit", + subType: selectedItem.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [data.points[0].x, data.points[0].y, data.points[0].z], @@ -368,6 +373,7 @@ async function handleModelLoad( rotation: newFloorItem.rotation, state: "idle", type: "human", + subType: selectedItem.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -393,6 +399,36 @@ async function handleModelLoad( position: humanEvent.point.position, rotation: humanEvent.point.rotation, } + } else if (selectedItem.type === "Crane") { + const craneEvent: CraneEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: newFloorItem.rotation, + state: "idle", + type: "crane", + subType: selectedItem.subType || '', + 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: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); + eventData.point = { + uuid: craneEvent.point.uuid, + position: craneEvent.point.position, + rotation: craneEvent.point.rotation, + } } const completeData = { @@ -401,11 +437,7 @@ async function handleModelLoad( modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { - x: model.rotation.x, - y: model.rotation.y, - z: model.rotation.z, - }, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, isLocked: false, isVisible: true, socketId: socket.id, @@ -422,11 +454,7 @@ async function handleModelLoad( modelName: completeData.modelName, assetId: completeData.assetId, position: completeData.position, - rotation: [ - completeData.rotation.x, - completeData.rotation.y, - completeData.rotation.z, - ] as [number, number, number], + rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z,] as [number, number, number], isLocked: completeData.isLocked, isCollidable: false, isVisible: completeData.isVisible, @@ -442,11 +470,7 @@ async function handleModelLoad( modelName: newFloorItem.modelName, assetId: newFloorItem.assetId, position: newFloorItem.position, - rotation: { - x: model.rotation.x, - y: model.rotation.y, - z: model.rotation.z, - }, + rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, }, isLocked: false, isVisible: true, socketId: socket.id, @@ -462,11 +486,7 @@ async function handleModelLoad( modelName: data.modelName, assetId: data.assetId, position: data.position, - rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [ - number, - number, - number - ], + rotation: [data.rotation.x, data.rotation.y, data.rotation.z] as [number, number, number], isLocked: data.isLocked, isCollidable: false, isVisible: data.isVisible, diff --git a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts index d678382..c08144c 100644 --- a/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts +++ b/app/src/modules/builder/asset/models/model/eventHandlers/useEventHandlers.ts @@ -1,12 +1,9 @@ import * as THREE from 'three'; import { CameraControls } from '@react-three/drei'; -import { ThreeEvent } from '@react-three/fiber'; -import { useCallback } from 'react'; -import { ProductStoreType } from '../../../../../../store/simulation/useProductStore'; -import { EventStoreType } from '../../../../../../store/simulation/useEventsStore'; -import { Socket } from 'socket.io-client'; +import { ThreeEvent, useThree } from '@react-three/fiber'; +import { useCallback, useEffect, useRef } from 'react'; -import { useActiveTool, useToolMode } from '../../../../../../store/builder/store'; +import { useActiveTool, useDeletableFloorItem, useSelectedFloorItem, useToggleView } from '../../../../../../store/builder/store'; import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore'; import { useSocketStore } from '../../../../../../store/builder/store'; import { useSceneContext } from '../../../../../scene/sceneContext'; @@ -14,60 +11,60 @@ import { useProductContext } from '../../../../../simulation/products/productCon import { useVersionContext } from '../../../../version/versionContext'; import { useParams } from 'react-router-dom'; import { getUserData } from '../../../../../../functions/getUserData'; +import { useLeftData, useTopData } from '../../../../../../store/visualization/useZone3DWidgetStore'; +import { useSelectedAsset } from '../../../../../../store/simulation/useSimulationStore'; +import { upsertProductOrEventApi } from '../../../../../../services/simulation/products/UpsertProductOrEventApi'; // import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi'; export function useModelEventHandlers({ - controls, boundingBox, groupRef, - toggleView, - deletableFloorItem, - setDeletableFloorItem, - setSelectedFloorItem, - gl, - setTop, - setLeft, - getIsEventInProduct, - getEventByModelUuid, - setSelectedAsset, - clearSelectedAsset, - removeAsset, - updateBackend, - leftDrag, - rightDrag }: { - controls: CameraControls | any, boundingBox: THREE.Box3 | null, groupRef: React.RefObject, - toggleView: boolean, - deletableFloorItem: THREE.Object3D | null, - setDeletableFloorItem: (item: THREE.Object3D | null) => void, - setSelectedFloorItem: (item: THREE.Object3D | null) => void, - gl: THREE.WebGLRenderer, - setTop: (top: number) => void, - setLeft: (left: number) => void, - getIsEventInProduct: (productUuid: string, modelUuid: string) => boolean, - getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined, - setSelectedAsset: (EventData: EventsSchema) => void, - clearSelectedAsset: () => void, - removeAsset: (modelUuid: string) => void, - updateBackend: (productName: string, productUuid: string, projectId: string, event: EventsSchema) => void, - leftDrag: React.MutableRefObject, - rightDrag: React.MutableRefObject }) { - + const { controls, gl } = useThree(); const { activeTool } = useActiveTool(); const { activeModule } = useModuleStore(); + const { toggleView } = useToggleView(); const { subModule } = useSubModuleStore(); const { socket } = useSocketStore(); - const { eventStore, productStore } = useSceneContext(); + const { eventStore, productStore, assetStore } = useSceneContext(); + const { removeAsset } = assetStore(); + const { removeEvent } = eventStore(); + const { getIsEventInProduct, addPoint, deleteEvent } = productStore(); + const { getEventByModelUuid } = eventStore(); + const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); + const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); + const { setSelectedFloorItem } = useSelectedFloorItem(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { userId, organization } = getUserData(); + const leftDrag = useRef(false); + const isLeftMouseDown = useRef(false); + const rightDrag = useRef(false); + const isRightMouseDown = useRef(false); + const { setTop } = useTopData(); + const { setLeft } = useLeftData(); + + const updateBackend = ( + productName: string, + productUuid: string, + projectId: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productUuid: productUuid, + projectId: projectId, + eventDatas: eventData, + versionId: selectedVersion?.versionId || '', + }); + }; const handleDblClick = (asset: Asset) => { if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { @@ -117,8 +114,8 @@ export function useModelEventHandlers({ const response = socket.emit('v1:model-asset:delete', data) - eventStore.getState().removeEvent(asset.modelUuid); - const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid); + removeEvent(asset.modelUuid); + const updatedEvents = deleteEvent(asset.modelUuid); updatedEvents.forEach((event) => { updateBackend( @@ -157,7 +154,7 @@ export function useModelEventHandlers({ } } - const event = productStore.getState().addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); + const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); if (event) { updateBackend( @@ -169,7 +166,6 @@ export function useModelEventHandlers({ } } } - } }; @@ -228,6 +224,50 @@ export function useModelEventHandlers({ } } + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerDown = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = true; + leftDrag.current = false; + } + if (evt.button === 2) { + isRightMouseDown.current = true; + rightDrag.current = false; + } + }; + + const onPointerMove = () => { + if (isLeftMouseDown.current) { + leftDrag.current = true; + } + if (isRightMouseDown.current) { + rightDrag.current = true; + } + }; + + const onPointerUp = (evt: any) => { + if (evt.button === 0) { + isLeftMouseDown.current = false; + } + if (evt.button === 2) { + isRightMouseDown.current = false; + } + }; + + canvasElement.addEventListener('pointerdown', onPointerDown); + canvasElement.addEventListener('pointermove', onPointerMove); + canvasElement.addEventListener('pointerup', onPointerUp); + + return () => { + canvasElement.removeEventListener('pointerdown', onPointerDown); + canvasElement.removeEventListener('pointermove', onPointerMove); + canvasElement.removeEventListener('pointerup', onPointerUp); + } + + }, [gl]) + return { handleDblClick, handleClick, diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 96076f9..9767138 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -1,76 +1,34 @@ import * as THREE from 'three'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; -import { ThreeEvent, useThree } from '@react-three/fiber'; -import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; +import { useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; -import { CameraControls } from '@react-three/drei'; -import useModuleStore, { useSubModuleStore } from '../../../../../store/useModuleStore'; -import { useLeftData, useTopData } from '../../../../../store/visualization/useZone3DWidgetStore'; -import { useSelectedAsset } from '../../../../../store/simulation/useSimulationStore'; -import { useProductContext } from '../../../../simulation/products/productContext'; -import { useParams } from 'react-router-dom'; -import { getUserData } from '../../../../../functions/getUserData'; +import useModuleStore from '../../../../../store/useModuleStore'; import { useSceneContext } from '../../../../scene/sceneContext'; -import { useVersionContext } from '../../../version/versionContext'; import { SkeletonUtils } from 'three-stdlib'; -import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi'; import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs'; import { ModelAnimator } from './animator/modelAnimator'; +import { useModelEventHandlers } from './eventHandlers/useEventHandlers'; function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendered: boolean, loader: GLTFLoader }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const savedTheme: string = localStorage.getItem("theme") || "light"; - const { controls, gl } = useThree(); - const { activeTool } = useActiveTool(); const { toolMode } = useToolMode(); const { toggleView } = useToggleView(); - const { subModule } = useSubModuleStore(); const { activeModule } = useModuleStore(); - const { assetStore, eventStore, productStore } = useSceneContext(); - const { removeAsset, resetAnimation } = assetStore(); - const { setTop } = useTopData(); - const { setLeft } = useLeftData(); - const { getIsEventInProduct, addPoint } = productStore(); - const { getEventByModelUuid } = eventStore(); - const { selectedProductStore } = useProductContext(); - const { selectedProduct } = selectedProductStore(); - const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset(); - const { socket } = useSocketStore(); - const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem(); + const { assetStore } = useSceneContext(); + const { resetAnimation } = assetStore(); + const { setDeletableFloorItem } = useDeletableFloorItem(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); - const leftDrag = useRef(false); - const isLeftMouseDown = useRef(false); - const rightDrag = useRef(false); - const isRightMouseDown = useRef(false); const [gltfScene, setGltfScene] = useState(null); const [boundingBox, setBoundingBox] = useState(null); const [isSelected, setIsSelected] = useState(false); const groupRef = useRef(null); const [ikData, setIkData] = useState(); - const { selectedVersionStore } = useVersionContext(); - const { selectedVersion } = selectedVersionStore(); - const { userId, organization } = getUserData(); - const { projectId } = useParams(); const { selectedAssets } = useSelectedAssets(); - const updateBackend = ( - productName: string, - productUuid: string, - projectId: string, - eventData: EventsSchema - ) => { - upsertProductOrEventApi({ - productName: productName, - productUuid: productUuid, - projectId: projectId, - eventDatas: eventData, - versionId: selectedVersion?.versionId || '', - }); - }; - useEffect(() => { if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') { getAssetIksApi(asset.assetId).then((data) => { @@ -82,17 +40,6 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [asset.modelUuid, ikData]) - useEffect(() => { - if (gltfScene) { - gltfScene.traverse((child: any) => { - if (child.isMesh) { - child.castShadow = true; - child.receiveShadow = true; - } - }) - } - }, [gltfScene]); - useEffect(() => { setDeletableFloorItem(null); if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) { @@ -106,285 +53,6 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [isRendered, selectedFloorItem]) - useEffect(() => { - const loadModel = async () => { - try { - - // Check Cache - const assetId = asset.assetId; - const cachedModel = THREE.Cache.get(assetId); - if (cachedModel) { - const clone: any = SkeletonUtils.clone(cachedModel.scene); - clone.animations = cachedModel.animations || []; - setGltfScene(clone); - calculateBoundingBox(clone); - return; - } - - // Check IndexedDB - const indexedDBModel = await retrieveGLTF(assetId); - if (indexedDBModel) { - const blobUrl = URL.createObjectURL(indexedDBModel); - loader.load(blobUrl, (gltf) => { - URL.revokeObjectURL(blobUrl); - THREE.Cache.remove(blobUrl); - THREE.Cache.add(assetId, gltf); - setGltfScene(gltf.scene.clone()); - calculateBoundingBox(gltf.scene); - }, - undefined, - (error) => { - echo.error(`[IndexedDB] Error loading ${asset.modelName}:`); - URL.revokeObjectURL(blobUrl); - } - ); - return; - } - - // Fetch from Backend - const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`; - const handleBackendLoad = async (gltf: GLTF) => { - try { - const response = await fetch(modelUrl); - const modelBlob = await response.blob(); - await storeGLTF(assetId, modelBlob); - THREE.Cache.add(assetId, gltf); - setGltfScene(gltf.scene.clone()); - calculateBoundingBox(gltf.scene); - } catch (error) { - console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error); - } - }; - loader.load(modelUrl, - handleBackendLoad, - undefined, - (error) => { - echo.error(`[Backend] Error loading ${asset.modelName}:`); - } - ); - } catch (err) { - console.error("Failed to load model:", asset.assetId, err); - } - }; - - const calculateBoundingBox = (scene: THREE.Object3D) => { - const box = new THREE.Box3().setFromObject(scene); - setBoundingBox(box); - }; - - loadModel(); - - }, []); - - const handleDblClick = (asset: Asset) => { - if (asset) { - if (activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') { - const size = boundingBox.getSize(new THREE.Vector3()); - const center = boundingBox.getCenter(new THREE.Vector3()); - - const front = new THREE.Vector3(0, 0, 1); - groupRef.current.localToWorld(front); - front.sub(groupRef.current.position).normalize(); - - const distance = Math.max(size.x, size.y, size.z) * 2; - const newPosition = center.clone().addScaledVector(front, distance); - - (controls as CameraControls).setPosition( - newPosition.x, - newPosition.y, - newPosition.z, - true - ); - (controls as CameraControls).setTarget(center.x, center.y, center.z, true); - (controls as CameraControls).fitToBox(groupRef.current, true, { - cover: true, - paddingTop: 5, - paddingLeft: 5, - paddingBottom: 5, - paddingRight: 5, - }); - setSelectedFloorItem(groupRef.current); - } - } - }; - - const handleClick = (evt: ThreeEvent, asset: Asset) => { - if (leftDrag.current || toggleView) return; - if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { - - //REST - - // const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName); - - //SOCKET - - const data = { - organization, - modelUuid: asset.modelUuid, - modelName: asset.modelName, - socketId: socket.id, - userId, - versionId: selectedVersion?.versionId || '', - projectId - } - - const response = socket.emit('v1:model-asset:delete', data) - - eventStore.getState().removeEvent(asset.modelUuid); - const updatedEvents = productStore.getState().deleteEvent(asset.modelUuid); - - updatedEvents.forEach((event) => { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - }) - - if (response) { - - removeAsset(asset.modelUuid); - - echo.success("Model Removed!"); - } - - } else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') { - if (asset.eventData && asset.eventData.type === 'Conveyor') { - const intersectedPoint = evt.point; - const localPosition = groupRef.current?.worldToLocal(intersectedPoint.clone()); - if (localPosition) { - const conveyorPoint: ConveyorPointSchema = { - uuid: THREE.MathUtils.generateUUID(), - position: [localPosition?.x, localPosition?.y, localPosition?.z], - rotation: [0, 0, 0], - action: { - actionUuid: THREE.MathUtils.generateUUID(), - actionName: `Action 1`, - actionType: 'default', - material: 'Default Material', - delay: 0, - spawnInterval: 5, - spawnCount: 1, - triggers: [] - } - } - - const event = addPoint(selectedProduct.productUuid, asset.modelUuid, conveyorPoint); - - if (event) { - updateBackend( - selectedProduct.productName, - selectedProduct.productUuid, - projectId || '', - event - ); - } - } - } - - } - } - - const handlePointerOver = useCallback((asset: Asset) => { - if (activeTool === "delete" && activeModule === 'builder') { - if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { - return; - } else { - setDeletableFloorItem(groupRef.current); - } - } - }, [activeTool, activeModule, deletableFloorItem]); - - const handlePointerOut = useCallback((evt: ThreeEvent, asset: Asset) => { - if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) { - setDeletableFloorItem(null); - } - }, [activeTool, deletableFloorItem]); - - const handleContextMenu = (asset: Asset, evt: ThreeEvent) => { - if (rightDrag.current || toggleView) return; - if (activeTool === "cursor" && subModule === 'simulations') { - if (asset.modelUuid) { - const canvasElement = gl.domElement; - const isInProduct = getIsEventInProduct(selectedProduct.productUuid, asset.modelUuid); - if (isInProduct) { - const event = getEventByModelUuid(asset.modelUuid); - if (event) { - setSelectedAsset(event); - const canvasRect = canvasElement.getBoundingClientRect(); - const relativeX = evt.clientX - canvasRect.left; - const relativeY = evt.clientY - canvasRect.top; - setTop(relativeY); - setLeft(relativeX); - } else { - clearSelectedAsset(); - } - } else { - const event = getEventByModelUuid(asset.modelUuid); - if (event) { - setSelectedAsset(event) - const canvasRect = canvasElement.getBoundingClientRect(); - const relativeX = evt.clientX - canvasRect.left; - const relativeY = evt.clientY - canvasRect.top; - setTop(relativeY); - setLeft(relativeX); - } else { - clearSelectedAsset() - } - } - } else { - clearSelectedAsset() - } - } else { - clearSelectedAsset() - } - } - - useEffect(() => { - const canvasElement = gl.domElement; - - const onPointerDown = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown.current = true; - leftDrag.current = false; - } - if (evt.button === 2) { - isRightMouseDown.current = true; - rightDrag.current = false; - } - }; - - const onPointerMove = () => { - if (isLeftMouseDown.current) { - leftDrag.current = true; - } - if (isRightMouseDown.current) { - rightDrag.current = true; - } - }; - - const onPointerUp = (evt: any) => { - if (evt.button === 0) { - isLeftMouseDown.current = false; - } - if (evt.button === 2) { - isRightMouseDown.current = false; - } - }; - - canvasElement.addEventListener('pointerdown', onPointerDown); - canvasElement.addEventListener('pointermove', onPointerMove); - canvasElement.addEventListener('pointerup', onPointerUp); - - return () => { - canvasElement.removeEventListener('pointerdown', onPointerDown); - canvasElement.removeEventListener('pointermove', onPointerMove); - canvasElement.removeEventListener('pointerup', onPointerUp); - } - - }, [gl]) - useEffect(() => { if (selectedAssets.length > 0) { if (selectedAssets.some((selectedAsset: THREE.Object3D) => selectedAsset.userData.modelUuid === asset.modelUuid)) { @@ -397,6 +65,89 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere } }, [selectedAssets]) + useEffect(() => { + if (gltfScene) { + gltfScene.traverse((child: any) => { + if (child.isMesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }) + } + }, [gltfScene]); + + useEffect(() => { + // Calculate Bounding Box + const calculateBoundingBox = (scene: THREE.Object3D) => { + const box = new THREE.Box3().setFromObject(scene); + setBoundingBox(box); + }; + + // Check Cache + const assetId = asset.assetId; + const cachedModel = THREE.Cache.get(assetId); + if (cachedModel) { + const clone: any = SkeletonUtils.clone(cachedModel.scene); + clone.animations = cachedModel.animations || []; + setGltfScene(clone); + calculateBoundingBox(clone); + return; + } + + // Check IndexedDB + retrieveGLTF(assetId).then((indexedDBModel) => { + if (indexedDBModel) { + const blobUrl = URL.createObjectURL(indexedDBModel); + loader.load( + blobUrl, + (gltf) => { + URL.revokeObjectURL(blobUrl); + THREE.Cache.remove(blobUrl); + THREE.Cache.add(assetId, gltf); + setGltfScene(gltf.scene.clone()); + calculateBoundingBox(gltf.scene); + }, + undefined, + (error) => { + echo.error(`[IndexedDB] Error loading ${asset.modelName}:`); + URL.revokeObjectURL(blobUrl); + } + ); + return; + } + + // Fetch from Backend + const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${assetId}`; + loader.load( + modelUrl, + (gltf: GLTF) => { + fetch(modelUrl) + .then((response) => response.blob()) + .then((modelBlob) => storeGLTF(assetId, modelBlob)) + .then(() => { + THREE.Cache.add(assetId, gltf); + setGltfScene(gltf.scene.clone()); + calculateBoundingBox(gltf.scene); + }) + .catch((error) => { + console.error( + `[Backend] Error storing/loading ${asset.modelName}:`, + error + ); + }); + }, + undefined, + (error) => { + echo.error(`[Backend] Error loading ${asset.modelName}:`); + } + ); + }).catch((err) => { + console.error("Failed to load model:", asset.assetId, err); + }); + }, []); + + const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef }); + return ( (null); const { assetStore } = useSceneContext(); const { assets } = assetStore(); @@ -43,28 +43,6 @@ function Models({ loader }: { loader: GLTFLoader }) { } }); - useEffect(() => { - // const canvasElement = gl.domElement; - - // const onClick = () => { - // if (!assetGroupRef.current || assetGroupRef.current.children.length === 0) return; - // raycaster.setFromCamera(pointer, camera); - - // const intersects = raycaster.intersectObjects(assetGroupRef.current.children, true); - - // if (intersects.length > 0) { - // console.log('intersects: ', intersects); - // } - // } - - // canvasElement.addEventListener('click', onClick); - - // return () => { - // canvasElement.removeEventListener('click', onClick); - // } - - }, [camera]) - return ( ([]); const [centerOffset, setCenterOffset] = useState(null); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => { if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] }; @@ -227,6 +224,7 @@ const CopyPasteControls3D = ({ const eventData: any = { type: pastedAsset.userData.eventData.type, + subType: pastedAsset.userData.eventData.subType, }; if (pastedAsset.userData.eventData.type === "Conveyor") { @@ -237,6 +235,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: 'transfer', + subType: pastedAsset.userData.eventData.subType || '', speed: 1, points: updatedEventData.points.map((point: any, index: number) => ({ uuid: THREE.MathUtils.generateUUID(), @@ -269,6 +268,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "vehicle", + subType: pastedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -307,6 +307,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "roboticArm", + subType: pastedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -341,6 +342,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "machine", + subType: pastedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -369,6 +371,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "storageUnit", + subType: pastedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -396,6 +399,7 @@ const CopyPasteControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "human", + subType: pastedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -421,6 +425,36 @@ const CopyPasteControls3D = ({ position: humanEvent.point.position, rotation: humanEvent.point.rotation }; + } else if (pastedAsset.userData.eventData.type === "Crane") { + const craneEvent: CraneEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "crane", + subType: pastedAsset.userData.eventData.subType || '', + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], + rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); + eventData.point = { + uuid: craneEvent.point.uuid, + position: craneEvent.point.position, + rotation: craneEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx index 55e88fb..f494025 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx @@ -37,10 +37,7 @@ const DuplicationControls3D = ({ const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [isDuplicating, setIsDuplicating] = useState(false); - const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ - left: false, - right: false, - }); + const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, }); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { const pointPosition = new THREE.Vector3().copy(point.position); @@ -228,6 +225,7 @@ const DuplicationControls3D = ({ const eventData: any = { type: duplicatedAsset.userData.eventData.type, + subType: duplicatedAsset.userData.eventData.subType, }; if (duplicatedAsset.userData.eventData.type === "Conveyor") { @@ -238,6 +236,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: 'transfer', + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, points: updatedEventData.points.map((point: any, index: number) => ({ uuid: THREE.MathUtils.generateUUID(), @@ -270,6 +269,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "vehicle", + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -308,6 +308,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "roboticArm", + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -342,6 +343,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "machine", + subType: duplicatedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -370,6 +372,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "storageUnit", + subType: duplicatedAsset.userData.eventData.subType || '', point: { uuid: THREE.MathUtils.generateUUID(), position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], @@ -397,6 +400,7 @@ const DuplicationControls3D = ({ rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], state: "idle", type: "human", + subType: duplicatedAsset.userData.eventData.subType || '', speed: 1, point: { uuid: THREE.MathUtils.generateUUID(), @@ -422,6 +426,36 @@ const DuplicationControls3D = ({ position: humanEvent.point.position, rotation: humanEvent.point.rotation }; + } else if (duplicatedAsset.userData.eventData.type === "Crane") { + const craneEvent: CraneEventSchema = { + modelUuid: newFloorItem.modelUuid, + modelName: newFloorItem.modelName, + position: newFloorItem.position, + rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z], + state: "idle", + type: "crane", + subType: duplicatedAsset.userData.eventData.subType || '', + point: { + uuid: THREE.MathUtils.generateUUID(), + position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]], + rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]], + actions: [ + { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: "Action 1", + actionType: "pickAndDrop", + maxPickUpCount: 1, + triggers: [] + } + ] + } + } + addEvent(craneEvent); + eventData.point = { + uuid: craneEvent.point.uuid, + position: craneEvent.point.position, + rotation: craneEvent.point.rotation + }; } newFloorItem.eventData = eventData; diff --git a/app/src/modules/scene/sceneContext.tsx b/app/src/modules/scene/sceneContext.tsx index bf65c36..ac17198 100644 --- a/app/src/modules/scene/sceneContext.tsx +++ b/app/src/modules/scene/sceneContext.tsx @@ -19,6 +19,7 @@ import { createConveyorStore, ConveyorStoreType } from '../../store/simulation/u import { createVehicleStore, VehicleStoreType } from '../../store/simulation/useVehicleStore'; import { createStorageUnitStore, StorageUnitStoreType } from '../../store/simulation/useStorageUnitStore'; import { createHumanStore, HumanStoreType } from '../../store/simulation/useHumanStore'; +import { createCraneStore, CraneStoreType } from '../../store/simulation/useCraneStore'; type SceneContextValue = { @@ -41,6 +42,7 @@ type SceneContextValue = { vehicleStore: VehicleStoreType; storageUnitStore: StorageUnitStoreType; humanStore: HumanStoreType; + craneStore: CraneStoreType; humanEventManagerRef: React.RefObject; @@ -78,6 +80,7 @@ export function SceneProvider({ const vehicleStore = useMemo(() => createVehicleStore(), []); const storageUnitStore = useMemo(() => createStorageUnitStore(), []); const humanStore = useMemo(() => createHumanStore(), []); + const craneStore = useMemo(() => createCraneStore(), []); const humanEventManagerRef = useRef({ humanStates: [] }); @@ -98,8 +101,9 @@ export function SceneProvider({ vehicleStore.getState().clearVehicles(); storageUnitStore.getState().clearStorageUnits(); humanStore.getState().clearHumans(); + craneStore.getState().clearCranes(); humanEventManagerRef.current.humanStates = []; - }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore]); + }, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]); const contextValue = useMemo(() => ( { @@ -119,11 +123,12 @@ export function SceneProvider({ vehicleStore, storageUnitStore, humanStore, + craneStore, humanEventManagerRef, clearStores, layout } - ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, clearStores, layout]); + ), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]); return ( diff --git a/app/src/modules/simulation/crane/crane.tsx b/app/src/modules/simulation/crane/crane.tsx new file mode 100644 index 0000000..fdbb430 --- /dev/null +++ b/app/src/modules/simulation/crane/crane.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import CraneInstances from './instances/craneInstances' + +function Crane() { + + return ( + <> + + + + + ) +} + +export default Crane \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx new file mode 100644 index 0000000..95a531c --- /dev/null +++ b/app/src/modules/simulation/crane/instances/animator/pillarJibAnimator.tsx @@ -0,0 +1,133 @@ +import * as THREE from 'three'; +import { useEffect, useMemo } from 'react'; +import { useThree } from '@react-three/fiber'; + +function PillarJibAnimator({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + useEffect(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + + if (model) { + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (base && trolley && hook) { + let trolleyDir = 1; + let hookDir = 1; + + const trolleySpeed = 0.01; + const hookSpeed = 0.01; + const rotationSpeed = 0.005; + + const trolleyMinOffset = -1; + const trolleyMaxOffset = 1.75; + const hookMinOffset = 0.25; + const hookMaxOffset = -1.5; + + const originalTrolleyX = trolley.position.x; + const originalHookY = hook.position.y; + + const animate = () => { + if (base) { + base.rotation.y += rotationSpeed; + } + + if (trolley) { + trolley.position.x += trolleyDir * trolleySpeed; + if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset || + trolley.position.x <= originalTrolleyX + trolleyMinOffset) { + trolleyDir *= -1; + } + } + + if (hook) { + hook.position.y += hookDir * hookSpeed; + if (hook.position.y >= originalHookY + hookMinOffset || + hook.position.y <= originalHookY + hookMaxOffset) { + hookDir *= -1; + } + } + + requestAnimationFrame(animate); + }; + + animate(); + } + } + }, [crane, scene]); + + return ( + + ); +} + +export default PillarJibAnimator; + +function PillarJibHelper({ crane }: { crane: CraneStatus }) { + const { scene } = useThree(); + + const { geometry, position } = useMemo(() => { + const model = scene.getObjectByProperty('uuid', crane.modelUuid); + if (!model) return { geometry: null, position: null }; + + const base = model.getObjectByName('base'); + const trolley = model.getObjectByName('trolley'); + const hook = model.getObjectByName('hook'); + + if (!base || !trolley || !hook) return { geometry: null, position: null }; + + const baseWorld = new THREE.Vector3(); + base.getWorldPosition(baseWorld); + + const trolleyWorld = new THREE.Vector3(); + trolley.getWorldPosition(trolleyWorld); + + const hookWorld = new THREE.Vector3(); + hook.getWorldPosition(hookWorld); + + const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length(); + const outerRadius = distFromBase + 1.75; + const innerRadius = Math.max(distFromBase - 1, 0.05); + const height = (0.25 - (-1.5)); + const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2; + + const shape = new THREE.Shape(); + shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false); + + const hole = new THREE.Path(); + hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true); + shape.holes.push(hole); + + const extrudeSettings = { + depth: height, + bevelEnabled: false, + steps: 1 + }; + + const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z]; + + return { geometry, position }; + }, [scene, crane.modelUuid]); + + if (!geometry || !position) return null; + + return ( + + + + ); +} diff --git a/app/src/modules/simulation/crane/instances/craneInstances.tsx b/app/src/modules/simulation/crane/instances/craneInstances.tsx new file mode 100644 index 0000000..bd6a96b --- /dev/null +++ b/app/src/modules/simulation/crane/instances/craneInstances.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import PillarJibInstance from './instance/pillarJibInstance'; +import { useSceneContext } from '../../../scene/sceneContext'; + +function CraneInstances() { + const { craneStore } = useSceneContext(); + const { cranes } = craneStore(); + + return ( + <> + {cranes.map((crane: CraneStatus) => ( + + + {crane.subType === "pillarJib" && + + } + + + ))} + + ) +} + +export default CraneInstances \ No newline at end of file diff --git a/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx new file mode 100644 index 0000000..bef6a17 --- /dev/null +++ b/app/src/modules/simulation/crane/instances/instance/pillarJibInstance.tsx @@ -0,0 +1,14 @@ +import PillarJibAnimator from '../animator/pillarJibAnimator' + +function PillarJibInstance({ crane }: { crane: CraneStatus }) { + + return ( + <> + + + + + ) +} + +export default PillarJibInstance \ No newline at end of file diff --git a/app/src/modules/simulation/events/points/functions/pointsCalculator.ts b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts index ccc2dbb..c59a070 100644 --- a/app/src/modules/simulation/events/points/functions/pointsCalculator.ts +++ b/app/src/modules/simulation/events/points/functions/pointsCalculator.ts @@ -40,7 +40,7 @@ function PointsCalculator( } return { - points: [worldTopMiddle] + points: [new THREE.Vector3(worldTopMiddle.x, worldTopMiddle.y + 0.1, worldTopMiddle.z)] }; } diff --git a/app/src/modules/simulation/events/points/instances/pointInstances.tsx b/app/src/modules/simulation/events/points/instances/pointInstances.tsx index 0b7ca60..edd1243 100644 --- a/app/src/modules/simulation/events/points/instances/pointInstances.tsx +++ b/app/src/modules/simulation/events/points/instances/pointInstances.tsx @@ -18,6 +18,7 @@ function PointInstances() { machine: "purple", storageUnit: "red", human: "white", + crane: "yellow", }; return ( diff --git a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx index 1469d7f..736667b 100644 --- a/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx +++ b/app/src/modules/simulation/events/triggerConnections/triggerConnector.tsx @@ -173,6 +173,22 @@ function TriggerConnector() { }); }); } + // Handle Human point + else if (event.type === "crane" && '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); diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index eeaf7fc..32c1162 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom'; import { useVersionContext } from '../../builder/version/versionContext'; function Products() { - const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, layout, productStore } = useSceneContext(); + const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, layout, productStore } = useSceneContext(); const { products, getProductById, addProduct, setProducts } = productStore(); const { selectedProductStore } = useProductContext(); const { setMainProduct } = useMainProduct(); @@ -20,7 +20,8 @@ function Products() { const { addMachine, clearMachines } = machineStore(); const { addConveyor, clearConveyors } = conveyorStore(); const { setCurrentMaterials, clearStorageUnits, updateCurrentLoad, addStorageUnit } = storageUnitStore(); - const { addHuman, addCurrentAction, clearHumans } = humanStore(); + const { addHuman, addCurrentAction: addCurrentActionHuman, clearHumans } = humanStore(); + const { addCrane, addCurrentAction: addCurrentActionCrane, clearCranes } = craneStore(); const { isReset } = useResetButtonStore(); const { isPlaying } = usePlayButtonStore(); const { mainProduct } = useMainProduct(); @@ -164,7 +165,25 @@ function Products() { addHuman(selectedProduct.productUuid, events); if (events.point.actions.length > 0) { - addCurrentAction(events.modelUuid, events.point.actions[0].actionUuid); + addCurrentActionHuman(events.modelUuid, events.point.actions[0].actionUuid); + } + } + }); + } + } + }, [selectedProduct, products, isReset, isPlaying]); + + useEffect(() => { + if (selectedProduct.productUuid) { + const product = getProductById(selectedProduct.productUuid); + if (product) { + clearCranes(); + product.eventDatas.forEach(events => { + if (events.type === 'crane') { + addCrane(selectedProduct.productUuid, events); + + if (events.point.actions.length > 0) { + addCurrentActionCrane(events.modelUuid, events.point.actions[0].actionUuid); } } }); diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index bb9d91f..4490aeb 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -7,6 +7,7 @@ import Materials from './materials/materials'; import Machine from './machine/machine'; import StorageUnit from './storageUnit/storageUnit'; import Human from './human/human'; +import Crane from './crane/crane'; import Simulator from './simulator/simulator'; import Products from './products/products'; import Trigger from './triggers/trigger'; @@ -55,6 +56,8 @@ function Simulation() { + + diff --git a/app/src/store/builder/store.ts b/app/src/store/builder/store.ts index 12c231a..aa1499b 100644 --- a/app/src/store/builder/store.ts +++ b/app/src/store/builder/store.ts @@ -3,74 +3,74 @@ import { io } from "socket.io-client"; import * as CONSTANTS from "../../types/world/worldConstants"; export const useSocketStore = create((set: any, get: any) => ({ - socket: null, - initializeSocket: ( - email?: string, - organization?: string, - token?: string, - refreshToken?: string - ) => { - const existingSocket = get().socket; - if (existingSocket) { - return; - } + socket: null, + initializeSocket: ( + email?: string, + organization?: string, + token?: string, + refreshToken?: string + ) => { + const existingSocket = get().socket; + if (existingSocket) { + return; + } - const socket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); + const socket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Builder_v1`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); - const visualizationSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); + const visualizationSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/Visualization_v1`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); - const dashBoardSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/dashboard`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); - const projectSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); - const threadSocket = io( - `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, - { - reconnection: true, - auth: { token, refreshToken }, - } - ); + const dashBoardSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/dashboard`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); + const projectSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/project`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); + const threadSocket = io( + `http://${process.env.REACT_APP_SERVER_SOCKET_API_BASE_URL}/thread`, + { + reconnection: true, + auth: { token, refreshToken }, + } + ); - set({ - socket, - visualizationSocket, - dashBoardSocket, - projectSocket, - threadSocket, - }); - }, - disconnectSocket: () => { - set((state: any) => { - state.socket?.disconnect(); - state.visualizationSocket?.disconnect(); - state.dashBoardSocket?.disconnect(); - state.projectSocket?.disconnect(); - state.threadSocket?.disconnect(); - return { socket: null }; - }); - }, + set({ + socket, + visualizationSocket, + dashBoardSocket, + projectSocket, + threadSocket, + }); + }, + disconnectSocket: () => { + set((state: any) => { + state.socket?.disconnect(); + state.visualizationSocket?.disconnect(); + state.dashBoardSocket?.disconnect(); + state.projectSocket?.disconnect(); + state.threadSocket?.disconnect(); + return { socket: null }; + }); + }, })); // export const useSocketStore = create((set: any, get: any) => ({ // socket: null, @@ -128,507 +128,507 @@ export const useSocketStore = create((set: any, get: any) => ({ // }, // })); export const useLoadingProgress = create<{ - loadingProgress: number; - setLoadingProgress: (x: number) => void; + loadingProgress: number; + setLoadingProgress: (x: number) => void; }>((set) => ({ - loadingProgress: 1, - setLoadingProgress: (x: number) => set({ loadingProgress: x }), + loadingProgress: 1, + setLoadingProgress: (x: number) => set({ loadingProgress: x }), })); export const useOrganization = create((set: any) => ({ - organization: "", - setOrganization: (x: any) => set(() => ({ organization: x })), + organization: "", + setOrganization: (x: any) => set(() => ({ organization: x })), })); export const useToggleView = create((set: any) => ({ - toggleView: false, - setToggleView: (x: any) => set(() => ({ toggleView: x })), + toggleView: false, + setToggleView: (x: any) => set(() => ({ toggleView: x })), })); export const useRoomsState = create((set: any) => ({ - roomsState: [], - setRoomsState: (x: any) => set(() => ({ roomsState: x })), + roomsState: [], + setRoomsState: (x: any) => set(() => ({ roomsState: x })), })); export const useSelectedItem = create((set: any) => ({ - selectedItem: { - name: "", - id: "", - type: undefined, - category: "", - subCatergory: "", - }, - setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), + selectedItem: { + name: "", + id: "", + type: undefined, + category: "", + subType: "", + }, + setSelectedItem: (x: any) => set(() => ({ selectedItem: x })), })); export const useNavMesh = create((set: any) => ({ - navMesh: null, - setNavMesh: (x: any) => set({ navMesh: x }), + navMesh: null, + setNavMesh: (x: any) => set({ navMesh: x }), })); export const useSelectedAssets = create((set: any) => ({ - selectedAssets: [], - setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), + selectedAssets: [], + setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), })); export const useLayers = create((set: any) => ({ - Layers: 1, - setLayers: (x: any) => set(() => ({ Layers: x })), + Layers: 1, + setLayers: (x: any) => set(() => ({ Layers: x })), })); export const useCamPosition = create((set: any) => ({ - camPosition: { x: undefined, y: undefined, z: undefined }, - setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), + camPosition: { x: undefined, y: undefined, z: undefined }, + setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }), })); export const useMenuVisible = create((set: any) => ({ - menuVisible: false, - setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), + menuVisible: false, + setMenuVisible: (x: any) => set(() => ({ menuVisible: x })), })); export const useToolMode = create((set: any) => ({ - toolMode: null, - setToolMode: (x: any) => set(() => ({ toolMode: x })), + toolMode: null, + setToolMode: (x: any) => set(() => ({ toolMode: x })), })); export const useSelectedWallItem = create((set: any) => ({ - selectedWallItem: null, - setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), + selectedWallItem: null, + setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })), })); export const useSelectedFloorItem = create((set: any) => ({ - selectedFloorItem: null, - setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), + selectedFloorItem: null, + setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })), })); export const useDeletableFloorItem = create((set: any) => ({ - deletableFloorItem: null, - setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })), + deletableFloorItem: null, + setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })), })); export const useSetScale = create((set: any) => ({ - scale: null, - setScale: (x: any) => set(() => ({ scale: x })), + scale: null, + setScale: (x: any) => set(() => ({ scale: x })), })); export const useRoofVisibility = create((set: any) => ({ - roofVisibility: false, - setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })), + roofVisibility: false, + setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })), })); export const useWallVisibility = create((set: any) => ({ - wallVisibility: false, - setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })), + wallVisibility: false, + setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })), })); export const useShadows = create((set: any) => ({ - shadows: false, - setShadows: (x: any) => set(() => ({ shadows: x })), + shadows: false, + setShadows: (x: any) => set(() => ({ shadows: x })), })); export const useSunPosition = create((set: any) => ({ - sunPosition: { x: undefined, y: undefined, z: undefined }, - setSunPosition: (newSuntPosition: any) => - set({ sunPosition: newSuntPosition }), + sunPosition: { x: undefined, y: undefined, z: undefined }, + setSunPosition: (newSuntPosition: any) => + set({ sunPosition: newSuntPosition }), })); export const useRemoveLayer = create((set: any) => ({ - removeLayer: false, - setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })), + removeLayer: false, + setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })), })); export const useRemovedLayer = create((set: any) => ({ - removedLayer: null, - setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })), + removedLayer: null, + setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })), })); export const useProjectName = create((set: any) => ({ - projectName: "Creating Your Project", - setProjectName: (x: any) => set({ projectName: x }), + projectName: "Creating Your Project", + setProjectName: (x: any) => set({ projectName: x }), })); export const useActiveLayer = create((set: any) => ({ - activeLayer: 1, - setActiveLayer: (x: any) => set({ activeLayer: x }), + activeLayer: 1, + setActiveLayer: (x: any) => set({ activeLayer: x }), })); export const useResetCamera = create((set: any) => ({ - resetCamera: false, - setResetCamera: (x: any) => set({ resetCamera: x }), + resetCamera: false, + setResetCamera: (x: any) => set({ resetCamera: x }), })); export const useAddAction = create((set: any) => ({ - addAction: null, - setAddAction: (x: any) => set({ addAction: x }), + addAction: null, + setAddAction: (x: any) => set({ addAction: x }), })); export const useActiveTool = create((set: any) => ({ - activeTool: "cursor", - setActiveTool: (x: any) => set({ activeTool: x }), + activeTool: "cursor", + setActiveTool: (x: any) => set({ activeTool: x }), })); export const useActiveSubTool = create((set: any) => ({ - activeSubTool: "cursor", - setActiveSubTool: (x: any) => set({ activeSubTool: x }), + activeSubTool: "cursor", + setActiveSubTool: (x: any) => set({ activeSubTool: x }), })); export const useElevation = create((set: any) => ({ - elevation: 45, - setElevation: (x: any) => set({ elevation: x }), + elevation: 45, + setElevation: (x: any) => set({ elevation: x }), })); export const useAzimuth = create((set: any) => ({ - azimuth: -160, - setAzimuth: (x: any) => set({ azimuth: x }), + azimuth: -160, + setAzimuth: (x: any) => set({ azimuth: x }), })); export const useRenderDistance = create((set: any) => ({ - renderDistance: 40, - setRenderDistance: (x: any) => set({ renderDistance: x }), + renderDistance: 40, + setRenderDistance: (x: any) => set({ renderDistance: x }), })); export const useCamMode = create((set: any) => ({ - camMode: "ThirdPerson", - setCamMode: (x: any) => set({ camMode: x }), + camMode: "ThirdPerson", + setCamMode: (x: any) => set({ camMode: x }), })); export const useUserName = create((set: any) => ({ - userName: "", - setUserName: (x: any) => set({ userName: x }), + userName: "", + setUserName: (x: any) => set({ userName: x }), })); export const useRenameModeStore = create((set: any) => ({ - isRenameMode: false, - setIsRenameMode: (state: boolean) => set({ isRenameMode: state }), + isRenameMode: false, + setIsRenameMode: (state: boolean) => set({ isRenameMode: state }), })); export const useObjectPosition = create((set: any) => ({ - objectPosition: { x: undefined, y: undefined, z: undefined }, - setObjectPosition: (newObjectPosition: any) => - set({ objectPosition: newObjectPosition }), + objectPosition: { x: undefined, y: undefined, z: undefined }, + setObjectPosition: (newObjectPosition: any) => + set({ objectPosition: newObjectPosition }), })); export const useObjectRotation = create((set: any) => ({ - objectRotation: { x: undefined, y: undefined, z: undefined }, - setObjectRotation: (newObjectRotation: any) => - set({ objectRotation: newObjectRotation }), + objectRotation: { x: undefined, y: undefined, z: undefined }, + setObjectRotation: (newObjectRotation: any) => + set({ objectRotation: newObjectRotation }), })); export const useDrieTemp = create((set: any) => ({ - drieTemp: undefined, - setDrieTemp: (x: any) => set({ drieTemp: x }), + drieTemp: undefined, + setDrieTemp: (x: any) => set({ drieTemp: x }), })); export const useActiveUsers = create((set: any) => ({ - activeUsers: [], - setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => - set((state: { activeUsers: any[] }) => ({ - activeUsers: - typeof callback === "function" ? callback(state.activeUsers) : callback, - })), + activeUsers: [], + setActiveUsers: (callback: (prev: any[]) => any[] | any[]) => + set((state: { activeUsers: any[] }) => ({ + activeUsers: + typeof callback === "function" ? callback(state.activeUsers) : callback, + })), })); export const useDrieUIValue = create((set: any) => ({ - drieUIValue: { touch: null, temperature: null, humidity: null }, + drieUIValue: { touch: null, temperature: null, humidity: null }, - setDrieUIValue: (x: any) => - set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), + setDrieUIValue: (x: any) => + set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), - setTouch: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, touch: value }, - })), - setTemperature: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, temperature: value }, - })), - setHumidity: (value: any) => - set((state: any) => ({ - drieUIValue: { ...state.drieUIValue, humidity: value }, - })), + setTouch: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, touch: value }, + })), + setTemperature: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, temperature: value }, + })), + setHumidity: (value: any) => + set((state: any) => ({ + drieUIValue: { ...state.drieUIValue, humidity: value }, + })), })); export const usezoneTarget = create((set: any) => ({ - zoneTarget: [], - setZoneTarget: (x: any) => set({ zoneTarget: x }), + zoneTarget: [], + setZoneTarget: (x: any) => set({ zoneTarget: x }), })); export const usezonePosition = create((set: any) => ({ - zonePosition: [], - setZonePosition: (x: any) => set({ zonePosition: x }), + zonePosition: [], + setZonePosition: (x: any) => set({ zonePosition: x }), })); interface EditPositionState { - Edit: boolean; - setEdit: (value: boolean) => void; + Edit: boolean; + setEdit: (value: boolean) => void; } export const useEditPosition = create((set) => ({ - Edit: false, - setEdit: (value) => set({ Edit: value }), + Edit: false, + setEdit: (value) => set({ Edit: value }), })); export const useAsset3dWidget = create((set: any) => ({ - widgetSelect: "", - setWidgetSelect: (x: any) => set({ widgetSelect: x }), + widgetSelect: "", + setWidgetSelect: (x: any) => set({ widgetSelect: x }), })); export const useWidgetSubOption = create((set: any) => ({ - widgetSubOption: "2D", - setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), + widgetSubOption: "2D", + setWidgetSubOption: (x: any) => set({ widgetSubOption: x }), })); export const useLimitDistance = create((set: any) => ({ - limitDistance: true, - setLimitDistance: (x: any) => set({ limitDistance: x }), + limitDistance: true, + setLimitDistance: (x: any) => set({ limitDistance: x }), })); export const useTileDistance = create((set: any) => ({ - gridValue: { - size: CONSTANTS.gridConfig.size, - divisions: CONSTANTS.gridConfig.divisions, - }, - planeValue: { - height: CONSTANTS.planeConfig.height, - width: CONSTANTS.planeConfig.width, - }, + gridValue: { + size: CONSTANTS.gridConfig.size, + divisions: CONSTANTS.gridConfig.divisions, + }, + planeValue: { + height: CONSTANTS.planeConfig.height, + width: CONSTANTS.planeConfig.width, + }, - setGridValue: (value: any) => - set((state: any) => ({ - gridValue: { ...state.gridValue, ...value }, - })), + setGridValue: (value: any) => + set((state: any) => ({ + gridValue: { ...state.gridValue, ...value }, + })), - setPlaneValue: (value: any) => - set((state: any) => ({ - planeValue: { ...state.planeValue, ...value }, - })), + setPlaneValue: (value: any) => + set((state: any) => ({ + planeValue: { ...state.planeValue, ...value }, + })), })); export const usePlayAgv = create((set, get) => ({ - PlayAgv: [], - setPlayAgv: (updateFn: (prev: any[]) => any[]) => - set({ PlayAgv: updateFn(get().PlayAgv) }), + PlayAgv: [], + setPlayAgv: (updateFn: (prev: any[]) => any[]) => + set({ PlayAgv: updateFn(get().PlayAgv) }), })); // Define the Asset type type Asset = { - id: string; - name: string; - position?: [number, number, number]; // Optional: 3D position - rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation + id: string; + name: string; + position?: [number, number, number]; // Optional: 3D position + rotation?: { x: number; y: number; z: number }; // Optional: Euler rotation }; // Zustand store type type ZoneAssetState = { - zoneAssetId: Asset | null; - setZoneAssetId: (asset: Asset | null) => void; + zoneAssetId: Asset | null; + setZoneAssetId: (asset: Asset | null) => void; }; // Zustand store export const useZoneAssetId = create((set) => ({ - zoneAssetId: null, - setZoneAssetId: (asset) => set({ zoneAssetId: asset }), + zoneAssetId: null, + setZoneAssetId: (asset) => set({ zoneAssetId: asset }), })); // version visible hidden interface VersionHistoryState { - viewVersionHistory: boolean; - setVersionHistoryVisible: (value: boolean) => void; + viewVersionHistory: boolean; + setVersionHistoryVisible: (value: boolean) => void; } const useVersionHistoryVisibleStore = create((set) => ({ - viewVersionHistory: false, - setVersionHistoryVisible: (value) => set({ viewVersionHistory: value }), + viewVersionHistory: false, + setVersionHistoryVisible: (value) => set({ viewVersionHistory: value }), })); export default useVersionHistoryVisibleStore; interface ShortcutStore { - showShortcuts: boolean; - setShowShortcuts: (value: boolean) => void; - toggleShortcuts: () => void; + showShortcuts: boolean; + setShowShortcuts: (value: boolean) => void; + toggleShortcuts: () => void; } export const useShortcutStore = create((set) => ({ - showShortcuts: false, - setShowShortcuts: (value) => set({ showShortcuts: value }), - toggleShortcuts: () => - set((state) => ({ showShortcuts: !state.showShortcuts })), + showShortcuts: false, + setShowShortcuts: (value) => set({ showShortcuts: value }), + toggleShortcuts: () => + set((state) => ({ showShortcuts: !state.showShortcuts })), })); export const useMachineCount = create((set: any) => ({ - machineCount: 0, - setMachineCount: (x: any) => set({ machineCount: x }), + machineCount: 0, + setMachineCount: (x: any) => set({ machineCount: x }), })); export const useMachineUptime = create((set: any) => ({ - machineActiveTime: 0, - setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), + machineActiveTime: 0, + setMachineActiveTime: (x: any) => set({ machineActiveTime: x }), })); export const useMachineDowntime = create((set: any) => ({ - machineIdleTime: 0, - setMachineIdleTime: (x: any) => set({ machineIdleTime: x }), + machineIdleTime: 0, + setMachineIdleTime: (x: any) => set({ machineIdleTime: x }), })); export const useMaterialCycle = create((set: any) => ({ - materialCycleTime: 0, - setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), + materialCycleTime: 0, + setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }), })); export const useThroughPutData = create((set: any) => ({ - throughputData: 0, - setThroughputData: (x: any) => set({ throughputData: x }), + throughputData: 0, + setThroughputData: (x: any) => set({ throughputData: x }), })); export const useProductionCapacityData = create((set: any) => ({ - productionCapacityData: 0, - setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), + productionCapacityData: 0, + setProductionCapacityData: (x: any) => set({ productionCapacityData: x }), })); export const useProcessBar = create((set: any) => ({ - processBar: [], - setProcessBar: (x: any) => set({ processBar: x }), + processBar: [], + setProcessBar: (x: any) => set({ processBar: x }), })); export const useDfxUpload = create((set: any) => ({ - dfxuploaded: [], - dfxWallGenerate: [], - objValue: { x: 0, y: 0, z: 0 }, - setDfxUploaded: (x: any) => set({ dfxuploaded: x }), - setDxfWallGenerate: (x: any) => set({ dfxWallGenerate: x }), - setObjValue: (x: any) => set({ objValue: x }), + dfxuploaded: [], + dfxWallGenerate: [], + objValue: { x: 0, y: 0, z: 0 }, + setDfxUploaded: (x: any) => set({ dfxuploaded: x }), + setDxfWallGenerate: (x: any) => set({ dfxWallGenerate: x }), + setObjValue: (x: any) => set({ objValue: x }), })); type InputValuesStore = { - inputValues: Record; - setInputValues: (values: Record) => void; - updateInputValue: (label: string, value: string) => void; // <- New + inputValues: Record; + setInputValues: (values: Record) => void; + updateInputValue: (label: string, value: string) => void; // <- New }; export const useInputValues = create((set) => ({ - inputValues: {}, - setInputValues: (values) => set({ inputValues: values }), - updateInputValue: (label, value) => - set((state) => ({ - inputValues: { - ...state.inputValues, - [label]: value, - }, - })), + inputValues: {}, + setInputValues: (values) => set({ inputValues: values }), + updateInputValue: (label, value) => + set((state) => ({ + inputValues: { + ...state.inputValues, + [label]: value, + }, + })), })); export interface ROISummaryData { - productName: string; - roiPercentage: number; - paybackPeriod: number; - totalCost: number; - revenueGenerated: number; - netProfit: number; - netLoss: number; + productName: string; + roiPercentage: number; + paybackPeriod: number; + totalCost: number; + revenueGenerated: number; + netProfit: number; + netLoss: number; } interface ROISummaryStore { - roiSummary: ROISummaryData; - setRoiSummaryData: (values: ROISummaryData) => void; + roiSummary: ROISummaryData; + setRoiSummaryData: (values: ROISummaryData) => void; } export const useROISummaryData = create((set) => ({ - roiSummary: { - productName: "", - roiPercentage: 0, - paybackPeriod: 0, - totalCost: 0, - revenueGenerated: 0, - netProfit: 0, - netLoss: 0, - }, - setRoiSummaryData: (values) => set({ roiSummary: values }), + roiSummary: { + productName: "", + roiPercentage: 0, + paybackPeriod: 0, + totalCost: 0, + revenueGenerated: 0, + netProfit: 0, + netLoss: 0, + }, + setRoiSummaryData: (values) => set({ roiSummary: values }), })); interface CompareStore { - comparePopUp: boolean; - setComparePopUp: (value: boolean) => void; - toggleComparePopUp: () => void; + comparePopUp: boolean; + setComparePopUp: (value: boolean) => void; + toggleComparePopUp: () => void; } export const useCompareStore = create((set) => ({ - comparePopUp: false, - setComparePopUp: (value) => set({ comparePopUp: value }), - toggleComparePopUp: () => - set((state) => ({ comparePopUp: !state.comparePopUp })), + comparePopUp: false, + setComparePopUp: (value) => set({ comparePopUp: value }), + toggleComparePopUp: () => + set((state) => ({ comparePopUp: !state.comparePopUp })), })); // Save state store interface SaveVersionStore { - isVersionSaved: boolean; - setIsVersionSaved: (value: boolean) => void; + isVersionSaved: boolean; + setIsVersionSaved: (value: boolean) => void; } export const useSaveVersion = create((set) => ({ - isVersionSaved: false, - setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), + isVersionSaved: false, + setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), })); interface ViewSceneState { - viewSceneLabels: boolean; - setViewSceneLabels: (value: boolean | ((prev: boolean) => boolean)) => void; + viewSceneLabels: boolean; + setViewSceneLabels: (value: boolean | ((prev: boolean) => boolean)) => void; } export const useViewSceneStore = create((set) => ({ - viewSceneLabels: getInitialViewSceneLabels(), - setViewSceneLabels: (value) => { - set((state) => { - const newValue = - typeof value === "function" ? value(state.viewSceneLabels) : value; + viewSceneLabels: getInitialViewSceneLabels(), + setViewSceneLabels: (value) => { + set((state) => { + const newValue = + typeof value === "function" ? value(state.viewSceneLabels) : value; - // Store in localStorage manually - localStorage.setItem("viewSceneLabels", JSON.stringify(newValue)); + // Store in localStorage manually + localStorage.setItem("viewSceneLabels", JSON.stringify(newValue)); - return { viewSceneLabels: newValue }; - }); - }, + return { viewSceneLabels: newValue }; + }); + }, })); function getInitialViewSceneLabels(): boolean { - if (typeof window === "undefined") return false; // SSR safety - const saved = localStorage.getItem("viewSceneLabels"); - return saved ? JSON.parse(saved) : false; + if (typeof window === "undefined") return false; // SSR safety + const saved = localStorage.getItem("viewSceneLabels"); + return saved ? JSON.parse(saved) : false; } export interface CompareProduct { - productUuid: string; - productName: string; - simulationData: { - // costPerUnit: number; - // workingDaysPerYear: number; - // shiftLength: number; - // shiftsPerDay: number; - roiPercentage: number; - // paybackPeriod: number; - // totalCost: number; - // revenueGenerated: number; - netProfit: number; - productionCapacity: number; - paybackPeriod: number; - // netLoss: number; - machineIdleTime: number; - machineActiveTime: number; - throughputData: number; - }; + productUuid: string; + productName: string; + simulationData: { + // costPerUnit: number; + // workingDaysPerYear: number; + // shiftLength: number; + // shiftsPerDay: number; + roiPercentage: number; + // paybackPeriod: number; + // totalCost: number; + // revenueGenerated: number; + netProfit: number; + productionCapacity: number; + paybackPeriod: number; + // netLoss: number; + machineIdleTime: number; + machineActiveTime: number; + throughputData: number; + }; } export const useCompareProductDataStore = create<{ - compareProductsData: CompareProduct[]; - setCompareProductsData: (x: CompareProduct[]) => void; + compareProductsData: CompareProduct[]; + setCompareProductsData: (x: CompareProduct[]) => void; }>((set) => ({ - compareProductsData: [], - setCompareProductsData: (x) => set({ compareProductsData: x }), + compareProductsData: [], + setCompareProductsData: (x) => set({ compareProductsData: x }), })); export const useSelectedComment = create((set: any) => ({ - selectedComment: null, - setSelectedComment: (x: any) => set({ selectedComment: x }), - position2Dstate: {}, - setPosition2Dstate: (x: any) => set({ position2Dstate: x }), - commentPositionState: null, - setCommentPositionState: (x: any) => set({ commentPositionState: x }), + selectedComment: null, + setSelectedComment: (x: any) => set({ selectedComment: x }), + position2Dstate: {}, + setPosition2Dstate: (x: any) => set({ position2Dstate: x }), + commentPositionState: null, + setCommentPositionState: (x: any) => set({ commentPositionState: x }), })); export const useSelectedPath = create((set: any) => ({ - selectedPath: "auto", - setSelectedPath: (x: any) => set({ selectedPath: x }), + selectedPath: "auto", + setSelectedPath: (x: any) => set({ selectedPath: x }), })); diff --git a/app/src/store/simulation/useCraneStore.ts b/app/src/store/simulation/useCraneStore.ts new file mode 100644 index 0000000..d8389e2 --- /dev/null +++ b/app/src/store/simulation/useCraneStore.ts @@ -0,0 +1,263 @@ +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +interface CraneStore { + cranes: CraneStatus[]; + + addCrane: (productUuid: string, event: CraneEventSchema) => void; + removeCrane: (modelUuid: string) => void; + updateCrane: ( + modelUuid: string, + updates: Partial> + ) => void; + clearCranes: () => void; + + setCurrentPhase: (modelUuid: string, phase: string) => void; + + addCurrentAction: (modelUuid: string, actionUuid: string) => void; + removeCurrentAction: (modelUuid: string) => void; + + setCraneActive: (modelUuid: string, isActive: boolean) => void; + setCraneScheduled: (modelUuid: string, isScheduled: boolean) => void; + setCraneLoad: (modelUuid: string, load: number) => void; + setCraneState: (modelUuid: string, newState: CraneStatus["state"]) => void; + incrementCraneLoad: (modelUuid: string, incrementBy: number) => void; + decrementCraneLoad: (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; + + incrementActiveTime: (modelUuid: string, incrementBy: number) => void; + incrementIdleTime: (modelUuid: string, incrementBy: number) => void; + resetTime: (modelUuid: string) => void; + + getCraneById: (modelUuid: string) => CraneStatus | undefined; + getCranesByProduct: (productUuid: string) => CraneStatus[]; + getActiveCranes: () => CraneStatus[]; +} + +export const createCraneStore = () => { + return create()( + immer((set, get) => ({ + cranes: [], + + addCrane: (productUuid, event) => { + set((state) => { + const exists = state.cranes.some(c => c.modelUuid === event.modelUuid); + if (!exists) { + state.cranes.push({ + ...event, + productUuid, + currentPhase: 'init', + isActive: false, + isScheduled: false, + idleTime: 0, + activeTime: 0, + currentLoad: 0, + currentMaterials: [] + }); + } + }); + }, + + removeCrane: (modelUuid) => { + set((state) => { + state.cranes = state.cranes.filter(c => c.modelUuid !== modelUuid); + }); + }, + + updateCrane: (modelUuid, updates) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + Object.assign(crane, updates); + } + }); + }, + + clearCranes: () => { + set((state) => { + state.cranes = []; + }); + }, + + setCurrentPhase: (modelUuid, phase) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentPhase = phase; + } + }); + }, + + addCurrentAction: (modelUuid, actionUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + const action = crane.point.actions.find(a => a.actionUuid === actionUuid); + if (action) { + crane.currentAction = { + actionUuid: action.actionUuid, + actionName: action.actionName + }; + } + } + }); + }, + + removeCurrentAction: (modelUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentAction = undefined; + } + }); + }, + + setCraneActive: (modelUuid, isActive) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.isActive = isActive; + } + }); + }, + + setCraneScheduled: (modelUuid, isScheduled) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.isScheduled = isScheduled; + } + }); + }, + + setCraneLoad: (modelUuid, load) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentLoad = load; + } + }); + }, + + setCraneState: (modelUuid, newState) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.state = newState; + } + }); + }, + + incrementCraneLoad: (modelUuid, incrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentLoad += incrementBy; + } + }); + }, + + decrementCraneLoad: (modelUuid, decrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentLoad = Math.max(0, crane.currentLoad - decrementBy); + } + }); + }, + + addCurrentMaterial: (modelUuid, materialType, materialId) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentMaterials.push({ materialType, materialId }); + } + }); + }, + + setCurrentMaterials: (modelUuid, materials) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentMaterials = materials; + } + }); + }, + + removeLastMaterial: (modelUuid) => { + let removed; + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane && crane.currentMaterials.length > 0) { + removed = JSON.parse(JSON.stringify(crane.currentMaterials.pop())); + } + }); + return removed; + }, + + getLastMaterial: (modelUuid) => { + const crane = get().cranes.find(c => c.modelUuid === modelUuid); + if (crane && crane.currentMaterials.length > 0) { + return crane.currentMaterials[crane.currentMaterials.length - 1]; + } + return undefined; + }, + + clearCurrentMaterials: (modelUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.currentMaterials = []; + } + }); + }, + + incrementActiveTime: (modelUuid, incrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.activeTime += incrementBy; + } + }); + }, + + incrementIdleTime: (modelUuid, incrementBy) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.idleTime += incrementBy; + } + }); + }, + + resetTime: (modelUuid) => { + set((state) => { + const crane = state.cranes.find(c => c.modelUuid === modelUuid); + if (crane) { + crane.activeTime = 0; + crane.idleTime = 0; + } + }); + }, + + getCraneById: (modelUuid) => { + return get().cranes.find(c => c.modelUuid === modelUuid); + }, + + getCranesByProduct: (productUuid) => { + return get().cranes.filter(c => c.productUuid === productUuid); + }, + + getActiveCranes: () => { + return get().cranes.filter(c => c.isActive); + } + })) + ); +}; + +export type CraneStoreType = ReturnType; \ No newline at end of file diff --git a/app/src/store/simulation/useEventsStore.ts b/app/src/store/simulation/useEventsStore.ts index becf55d..76661a8 100644 --- a/app/src/store/simulation/useEventsStore.ts +++ b/app/src/store/simulation/useEventsStore.ts @@ -11,24 +11,24 @@ type EventsStore = { updateEvent: (modelUuid: string, updates: Partial) => EventsSchema | undefined; // Point-level actions - addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void; + addPoint: (modelUuid: string, point: PointsScheme) => void; removePoint: (modelUuid: string, pointUuid: string) => void; updatePoint: ( modelUuid: string, pointUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Action-level actions addAction: ( modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] + action: ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction ) => void; removeAction: (actionUuid: string) => void; updateAction: ( actionUuid: string, - updates: Partial + updates: Partial ) => void; // Trigger-level actions @@ -38,8 +38,8 @@ type EventsStore = { // Helper functions getEventByModelUuid: (modelUuid: string) => EventsSchema | undefined; - getPointByUuid: (modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | undefined; - getActionByUuid: (actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']) | undefined; + getPointByUuid: (modelUuid: string, pointUuid: string) => PointsScheme | undefined; + getActionByUuid: (actionUuid: string) => (ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction) | undefined; getTriggerByUuid: (triggerUuid: string) => TriggerSchema | undefined; }; diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 258a8cb..f9533a1 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -18,13 +18,13 @@ type ProductsStore = { updateEvent: (productUuid: string, modelUuid: string, updates: Partial) => EventsSchema | undefined; // Point-level actions - addPoint: (productUuid: string, modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema) => EventsSchema | undefined; + addPoint: (productUuid: string, modelUuid: string, point: PointsScheme) => EventsSchema | undefined; removePoint: (productUuid: string, modelUuid: string, pointUuid: string) => EventsSchema | undefined; updatePoint: ( productUuid: string, modelUuid: string, pointUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Action-level actions @@ -32,13 +32,13 @@ type ProductsStore = { productUuid: string, modelUuid: string, pointUuid: string, - action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0] + action: ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction ) => EventsSchema | undefined; removeAction: (productUuid: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( productUuid: string, actionUuid: string, - updates: Partial + updates: Partial ) => EventsSchema | undefined; // Trigger-level actionss @@ -65,9 +65,9 @@ type ProductsStore = { getEventByActionUuid: (productUuid: string, actionUuid: string) => EventsSchema | undefined; getEventByTriggerUuid: (productUuid: string, triggerUuid: string) => EventsSchema | undefined; getEventByPointUuid: (productUuid: string, pointUuid: string) => EventsSchema | undefined; - getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema | undefined; - getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; - getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]) | undefined; + getPointByUuid: (productUuid: string, modelUuid: string, pointUuid: string) => PointsScheme | undefined; + getActionByUuid: (productUuid: string, actionUuid: string) => (ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction) | undefined; + getActionByPointUuid: (productUuid: string, pointUuid: string) => (ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction) | undefined; getModelUuidByPointUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getModelUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; getPointUuidByActionUuid: (productUuid: string, actionUuid: string) => (string) | undefined; @@ -375,6 +375,15 @@ export const createProductStore = () => { return; } } + } else if (event.type === "crane") { + 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)); diff --git a/app/src/types/builderTypes.d.ts b/app/src/types/builderTypes.d.ts index 318050a..63ed97b 100644 --- a/app/src/types/builderTypes.d.ts +++ b/app/src/types/builderTypes.d.ts @@ -32,6 +32,7 @@ interface Asset { }; eventData?: { type: string; + subType: string; point?: { uuid: string; position: [number, number, number]; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index 01a1603..e257ed8 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -1,4 +1,5 @@ // Base Types + interface AssetEventSchema { modelUuid: string; modelName: string; @@ -19,7 +20,9 @@ interface TriggerSchema { } | null; } + // Actions + interface ConveyorAction { actionUuid: string; actionName: string; @@ -107,9 +110,19 @@ interface HumanAction { triggers: TriggerSchema[]; } -type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction; +interface CraneAction { + actionUuid: string; + actionName: string; + actionType: "pickAndDrop"; + maxPickUpCount: number; + triggers: TriggerSchema[]; +} + +type Action = ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction; + // Points + interface ConveyorPointSchema { uuid: string; position: [number, number, number]; @@ -152,46 +165,69 @@ interface HumanPointSchema { actions: HumanAction[]; } -type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema; +interface CranePointSchema { + uuid: string; + position: [number, number, number]; + rotation: [number, number, number]; + actions: CraneAction[]; +} + +type PointsScheme = | ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema | CranePointSchema; + // Events + interface ConveyorEventSchema extends AssetEventSchema { type: "transfer"; + subType: string; speed: number; points: ConveyorPointSchema[]; } interface VehicleEventSchema extends AssetEventSchema { type: "vehicle"; + subType: "manual" | "automatic" | "semiAutomatic" | ''; speed: number; point: VehiclePointSchema; } interface RoboticArmEventSchema extends AssetEventSchema { type: "roboticArm"; + subType: string; speed: number; point: RoboticArmPointSchema; } interface MachineEventSchema extends AssetEventSchema { type: "machine"; + subType: string; point: MachinePointSchema; } interface StorageEventSchema extends AssetEventSchema { type: "storageUnit"; + subType: string; point: StoragePointSchema; } interface HumanEventSchema extends AssetEventSchema { type: "human"; + subType: string; speed: number; point: HumanPointSchema; } -type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema; +interface CraneEventSchema extends AssetEventSchema { + type: "crane"; + subType: "pillarJib" | ''; + point: CranePointSchema; +} + +type EventsSchema = | ConveyorEventSchema | VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema | HumanEventSchema | CraneEventSchema; + // Statuses + interface ConveyorStatus extends ConveyorEventSchema { productUuid: string; isActive: boolean; @@ -262,6 +298,24 @@ interface HumanStatus extends HumanEventSchema { }; } +interface CraneStatus extends CraneEventSchema { + productUuid: string; + currentPhase: string; + isActive: boolean; + isScheduled: boolean; + idleTime: number; + activeTime: number; + currentLoad: number; + currentMaterials: { materialType: string; materialId: string; }[]; + currentAction?: { + actionUuid: string; + actionName: string; + }; +} + + +// Event Manager + type HumanEventState = { humanId: string; actionQueue: { @@ -282,7 +336,9 @@ type HumanEventManagerState = { humanStates: HumanEventState[]; }; + // Materials + interface MaterialSchema { materialId: string; materialName: string; @@ -316,14 +372,18 @@ interface MaterialSchema { type MaterialsSchema = MaterialSchema[]; + // Products + type productsSchema = { productName: string; productUuid: string; eventDatas: EventsSchema[]; }[]; + // Material History + interface MaterialHistoryEntry { material: MaterialSchema; removedAt: string; @@ -331,7 +391,8 @@ interface MaterialHistoryEntry { type MaterialHistorySchema = MaterialHistoryEntry[]; -//IK + +// IK Constraints type Link = { index: number; @@ -350,3 +411,38 @@ type IK = { maxheight?: number; minheight?: number; }; + +// Conveyor Spline Points + +type NormalConveyor = { + type: 'normal'; + points: [number, number, number][][]; +} + +type YJunctionConveyor = { + type: 'y-junction'; + points: [number, number, number][][]; +} + +type CurvedConveyor = { + type: 'curved'; + points: [number, number, number][][]; +} + +type ConveyorPoints = NormalConveyor | YJunctionConveyor | CurvedConveyor; + + +// Crane Constraints + + +type PillarJibCrane = { + trolleySpeed: number; + hookSpeed: number; + rotationSpeed: number; + trolleyMinOffset: number + trolleyMaxOffset: number; + hookMinOffset: number; + hookMaxOffset: number; +} + +type CraneConstraints = PillarJibCrane; \ No newline at end of file diff --git a/app/src/types/world/worldTypes.d.ts b/app/src/types/world/worldTypes.d.ts index da92fd9..6b4552b 100644 --- a/app/src/types/world/worldTypes.d.ts +++ b/app/src/types/world/worldTypes.d.ts @@ -198,6 +198,7 @@ export type FloorItemType = { isVisible: boolean; eventData?: { type: string; + subType: string; point?: { uuid: string; position: [number, number, number]; @@ -221,7 +222,7 @@ export type setFloorItemSetState = React.Dispatch