pillar Jig half way completed

This commit is contained in:
2025-08-06 18:19:54 +05:30
parent 76f295d9b9
commit a08cec33ab
27 changed files with 1506 additions and 1031 deletions

View File

@@ -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<string>("");
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
const [filtereredAssets, setFiltereredAssets] = useState<AssetProp[]>([]);
const [categoryList, setCategoryList] = useState<CategoryListProp[]>([]);
const [isLoading, setisLoading] = useState<boolean>(false); // Loading state for assets
const { setSelectedItem } = useSelectedItem();
const [searchValue, setSearchValue] = useState<string>("");
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
const [filtereredAssets, setFiltereredAssets] = useState<AssetProp[]>([]);
const [categoryList, setCategoryList] = useState<CategoryListProp[]>([]);
const [isLoading, setisLoading] = useState<boolean>(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 (
<div className="assets-container-main">
<Search onChange={handleSearchChange} />
<div className="assets-list-section">
<section>
{(() => {
if (isLoading) {
return <SkeletonUI type="asset" />; // 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 (
<div className="assets-result">
<div className="assets-wrapper">
<div className="searched-content">
<p>Results for {searchValue}</p>
</div>
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
<div
key={`${index}-${asset.filename}`}
className="assets"
id={asset.filename}
title={asset.filename}
>
<img
src={asset?.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type:
asset.type === "undefined"
? undefined
: asset.type
});
}}
/>
};
filteredAssets();
}, [categoryAssets]);
<div className="asset-name">
{asset.filename
.split("_")
.map(
(word: any) =>
word.charAt(0).toUpperCase() + word.slice(1)
)
.join(" ")}
</div>
</div>
))}
</div>
</div>
</div>
);
}
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 (
<div className="assets-wrapper">
<h2>
{selectedCategory}
<button
className="back-button"
id="asset-backButtom"
onClick={() => {
setSelectedCategory(null);
setCategoryAssets([]);
}}
>
Back
</button>
</h2>
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
<div
key={`${index}-${asset}`}
className="assets"
id={asset.filename}
title={asset.filename}
>
<img
src={asset?.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type:
asset.type === "undefined"
? undefined
: asset.type,
category: asset.category,
subCategory: asset.subCategory
});
}}
/>
<div className="asset-name">
{asset.filename
.split("_")
.map(
(word: any) =>
word.charAt(0).toUpperCase() + word.slice(1)
)
.join(" ")}
</div>
</div>
))}
{categoryAssets.length === 0 && (
<div className="no-asset">
🚧 The asset shelf is empty. We're working on filling it
up!
</div>
)}
</div>
</div>
);
}
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 (
<div className="assets-wrapper">
<h2>Categories</h2>
<div className="categories-container">
{Array.from(
new Set(categoryList.map((asset) => asset.category))
).map((category, index) => {
const categoryInfo = categoryList.find(
(asset) => asset.category === category
);
return (
<div
key={`${index}-${category}`}
className="category"
id={category}
onClick={() => fetchCategoryAssets(category)}
>
<img
src={categoryInfo?.categoryImage ?? ""}
alt={category}
className="category-image"
draggable={false}
/>
<div className="category-name">{category}</div>
</div>
);
})}
</div>
</div>
);
})()}
</section>
</div>
</div>
);
return (
<div className="assets-container-main">
<Search onChange={handleSearchChange} />
<div className="assets-list-section">
<section>
{(() => {
if (isLoading) {
return <SkeletonUI type="asset" />; // Show skeleton when loading
}
if (searchValue) {
return (
<div className="assets-result">
<div className="assets-wrapper">
<div className="searched-content">
<p>Results for {searchValue}</p>
</div>
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
<div
key={`${index}-${asset.filename}`}
className="assets"
id={asset.filename}
title={asset.filename}
>
<img
src={asset?.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type: asset.type === "undefined" ? undefined : asset.type
});
}}
/>
<div className="asset-name">
{asset.filename
.split("_")
.map(
(word: any) =>
word.charAt(0).toUpperCase() + word.slice(1)
)
.join(" ")}
</div>
</div>
))}
</div>
</div>
</div>
);
}
if (selectedCategory) {
return (
<div className="assets-wrapper">
<h2>
{selectedCategory}
<button
className="back-button"
id="asset-backButtom"
onClick={() => {
setSelectedCategory(null);
setCategoryAssets([]);
}}
>
Back
</button>
</h2>
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
<div
key={`${index}-${asset}`}
className="assets"
id={asset.filename}
title={asset.filename}
>
<img
src={asset?.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type: asset.type === "undefined" ? undefined : asset.type,
category: asset.category,
subType: asset.subType
});
}}
/>
<div className="asset-name">
{asset.filename.split("_").map((word: any) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")}
</div>
</div>
))}
{categoryAssets.length === 0 && (
<div className="no-asset">
🚧 The asset shelf is empty. We're working on filling it up!
</div>
)}
</div>
</div>
);
}
return (
<div className="assets-wrapper">
<h2>Categories</h2>
<div className="categories-container">
{Array.from(
new Set(categoryList.map((asset) => asset.category))
).map((category, index) => {
const categoryInfo = categoryList.find(
(asset) => asset.category === category
);
return (
<div
key={`${index}-${category}`}
className="category"
id={category}
onClick={() => fetchCategoryAssets(category)}
>
<img
src={categoryInfo?.categoryImage ?? ""}
alt={category}
className="category-image"
draggable={false}
/>
<div className="category-name">{category}</div>
</div>
);
})}
</div>
</div>
);
})()}
</section>
</div>
</div>
);
};
export default Assets;

View File

@@ -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({

View File

@@ -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,

View File

@@ -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<THREE.Group>,
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<boolean>,
rightDrag: React.MutableRefObject<boolean>
}) {
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,

View File

@@ -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<GLTF["scene"] | null>(null);
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const [isSelected, setIsSelected] = useState(false);
const groupRef = useRef<THREE.Group>(null);
const [ikData, setIkData] = useState<any>();
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<MouseEvent>, 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<MouseEvent>, asset: Asset) => {
if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
setDeletableFloorItem(null);
}
}, [activeTool, deletableFloorItem]);
const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
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 (
<group
key={asset.modelUuid}

View File

@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import { Group, Vector3 } from "three";
import { CameraControls } from '@react-three/drei';
import { useLimitDistance, useRenderDistance, useSelectedFloorItem } from '../../../../store/builder/store';
import { useLimitDistance, useRenderDistance, useSelectedFloorItem, useToggleView } from '../../../../store/builder/store';
import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore';
import { useSceneContext } from '../../../scene/sceneContext';
@@ -12,7 +12,7 @@ import { GLTFLoader } from "three/examples/jsm/Addons";
const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url));
function Models({ loader }: { loader: GLTFLoader }) {
const { controls, camera, raycaster, pointer, gl } = useThree();
const { controls, camera } = useThree();
const assetGroupRef = useRef<Group>(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 (
<group
name='Asset Group'

View File

@@ -140,7 +140,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
wallUuid: intersect.object.userData.wallUuid,
position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
});
@@ -190,7 +190,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
updateWallAsset(wallAsset.modelUuid, {
wallUuid: intersect.object.userData.wallUuid,
position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z],
position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
});
}

View File

@@ -51,9 +51,9 @@ function WallAssetCreator() {
modelName: selectedItem.name,
modelUuid: MathUtils.generateUUID(),
wallUuid: wall.wallUuid,
wallAssetType: selectedItem.subCategory,
wallAssetType: selectedItem.subType,
assetId: selectedItem.id,
position: [closestPoint.x, selectedItem.subCategory === "fixed-move" ? 0 : intersect.point.y, closestPoint.z],
position: [closestPoint.x, selectedItem.subType === "fixedMove" ? 0 : intersect.point.y, closestPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
isLocked: false,
isVisible: true,

View File

@@ -39,10 +39,7 @@ const CopyPasteControls3D = ({
const [isPasting, setIsPasting] = useState(false);
const [relativePositions, setRelativePositions] = useState<THREE.Vector3[]>([]);
const [centerOffset, setCenterOffset] = useState<THREE.Vector3 | null>(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;

View File

@@ -37,10 +37,7 @@ const DuplicationControls3D = ({
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
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;

View File

@@ -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<HumanEventManagerState>;
@@ -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<HumanEventManagerState>({ 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 (
<SceneContext.Provider value={contextValue}>

View File

@@ -0,0 +1,15 @@
import React from 'react'
import CraneInstances from './instances/craneInstances'
function Crane() {
return (
<>
<CraneInstances />
</>
)
}
export default Crane

View File

@@ -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 (
<PillarJibHelper crane={crane} />
);
}
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 (
<mesh
geometry={geometry}
position={position}
rotation={[Math.PI / 2, 0, 0]}
>
<meshStandardMaterial
color={0x888888}
metalness={0.5}
roughness={0.4}
side={THREE.DoubleSide}
transparent={true}
opacity={0.3}
/>
</mesh>
);
}

View File

@@ -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) => (
<React.Fragment key={crane.modelUuid}>
{crane.subType === "pillarJib" &&
<PillarJibInstance crane={crane} />
}
</React.Fragment>
))}
</>
)
}
export default CraneInstances

View File

@@ -0,0 +1,14 @@
import PillarJibAnimator from '../animator/pillarJibAnimator'
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
return (
<>
<PillarJibAnimator crane={crane} />
</>
)
}
export default PillarJibInstance

View File

@@ -40,7 +40,7 @@ function PointsCalculator(
}
return {
points: [worldTopMiddle]
points: [new THREE.Vector3(worldTopMiddle.x, worldTopMiddle.y + 0.1, worldTopMiddle.z)]
};
}

View File

@@ -18,6 +18,7 @@ function PointInstances() {
machine: "purple",
storageUnit: "red",
human: "white",
crane: "yellow",
};
return (

View File

@@ -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);

View File

@@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom';
import { useVersionContext } from '../../builder/version/versionContext';
function Products() {
const { armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, 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);
}
}
});

View File

@@ -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() {
<Human />
<Crane />
<Simulator />
<SimulationAnalysis />

View File

@@ -3,74 +3,74 @@ import { io } from "socket.io-client";
import * as CONSTANTS from "../../types/world/worldConstants";
export const useSocketStore = create<any>((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<any>((set: any, get: any) => ({
// socket: null,
@@ -128,507 +128,507 @@ export const useSocketStore = create<any>((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<any>((set: any) => ({
organization: "",
setOrganization: (x: any) => set(() => ({ organization: x })),
organization: "",
setOrganization: (x: any) => set(() => ({ organization: x })),
}));
export const useToggleView = create<any>((set: any) => ({
toggleView: false,
setToggleView: (x: any) => set(() => ({ toggleView: x })),
toggleView: false,
setToggleView: (x: any) => set(() => ({ toggleView: x })),
}));
export const useRoomsState = create<any>((set: any) => ({
roomsState: [],
setRoomsState: (x: any) => set(() => ({ roomsState: x })),
roomsState: [],
setRoomsState: (x: any) => set(() => ({ roomsState: x })),
}));
export const useSelectedItem = create<any>((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<any>((set: any) => ({
navMesh: null,
setNavMesh: (x: any) => set({ navMesh: x }),
navMesh: null,
setNavMesh: (x: any) => set({ navMesh: x }),
}));
export const useSelectedAssets = create<any>((set: any) => ({
selectedAssets: [],
setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })),
selectedAssets: [],
setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })),
}));
export const useLayers = create<any>((set: any) => ({
Layers: 1,
setLayers: (x: any) => set(() => ({ Layers: x })),
Layers: 1,
setLayers: (x: any) => set(() => ({ Layers: x })),
}));
export const useCamPosition = create<any>((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<any>((set: any) => ({
menuVisible: false,
setMenuVisible: (x: any) => set(() => ({ menuVisible: x })),
menuVisible: false,
setMenuVisible: (x: any) => set(() => ({ menuVisible: x })),
}));
export const useToolMode = create<any>((set: any) => ({
toolMode: null,
setToolMode: (x: any) => set(() => ({ toolMode: x })),
toolMode: null,
setToolMode: (x: any) => set(() => ({ toolMode: x })),
}));
export const useSelectedWallItem = create<any>((set: any) => ({
selectedWallItem: null,
setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })),
selectedWallItem: null,
setSelectedWallItem: (x: any) => set(() => ({ selectedWallItem: x })),
}));
export const useSelectedFloorItem = create<any>((set: any) => ({
selectedFloorItem: null,
setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })),
selectedFloorItem: null,
setSelectedFloorItem: (x: any) => set(() => ({ selectedFloorItem: x })),
}));
export const useDeletableFloorItem = create<any>((set: any) => ({
deletableFloorItem: null,
setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })),
deletableFloorItem: null,
setDeletableFloorItem: (x: any) => set(() => ({ deletableFloorItem: x })),
}));
export const useSetScale = create<any>((set: any) => ({
scale: null,
setScale: (x: any) => set(() => ({ scale: x })),
scale: null,
setScale: (x: any) => set(() => ({ scale: x })),
}));
export const useRoofVisibility = create<any>((set: any) => ({
roofVisibility: false,
setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })),
roofVisibility: false,
setRoofVisibility: (x: any) => set(() => ({ roofVisibility: x })),
}));
export const useWallVisibility = create<any>((set: any) => ({
wallVisibility: false,
setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })),
wallVisibility: false,
setWallVisibility: (x: any) => set(() => ({ wallVisibility: x })),
}));
export const useShadows = create<any>((set: any) => ({
shadows: false,
setShadows: (x: any) => set(() => ({ shadows: x })),
shadows: false,
setShadows: (x: any) => set(() => ({ shadows: x })),
}));
export const useSunPosition = create<any>((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<any>((set: any) => ({
removeLayer: false,
setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })),
removeLayer: false,
setRemoveLayer: (x: any) => set(() => ({ removeLayer: x })),
}));
export const useRemovedLayer = create<any>((set: any) => ({
removedLayer: null,
setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })),
removedLayer: null,
setRemovedLayer: (x: any) => set(() => ({ removedLayer: x })),
}));
export const useProjectName = create<any>((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<any>((set: any) => ({
activeLayer: 1,
setActiveLayer: (x: any) => set({ activeLayer: x }),
activeLayer: 1,
setActiveLayer: (x: any) => set({ activeLayer: x }),
}));
export const useResetCamera = create<any>((set: any) => ({
resetCamera: false,
setResetCamera: (x: any) => set({ resetCamera: x }),
resetCamera: false,
setResetCamera: (x: any) => set({ resetCamera: x }),
}));
export const useAddAction = create<any>((set: any) => ({
addAction: null,
setAddAction: (x: any) => set({ addAction: x }),
addAction: null,
setAddAction: (x: any) => set({ addAction: x }),
}));
export const useActiveTool = create<any>((set: any) => ({
activeTool: "cursor",
setActiveTool: (x: any) => set({ activeTool: x }),
activeTool: "cursor",
setActiveTool: (x: any) => set({ activeTool: x }),
}));
export const useActiveSubTool = create<any>((set: any) => ({
activeSubTool: "cursor",
setActiveSubTool: (x: any) => set({ activeSubTool: x }),
activeSubTool: "cursor",
setActiveSubTool: (x: any) => set({ activeSubTool: x }),
}));
export const useElevation = create<any>((set: any) => ({
elevation: 45,
setElevation: (x: any) => set({ elevation: x }),
elevation: 45,
setElevation: (x: any) => set({ elevation: x }),
}));
export const useAzimuth = create<any>((set: any) => ({
azimuth: -160,
setAzimuth: (x: any) => set({ azimuth: x }),
azimuth: -160,
setAzimuth: (x: any) => set({ azimuth: x }),
}));
export const useRenderDistance = create<any>((set: any) => ({
renderDistance: 40,
setRenderDistance: (x: any) => set({ renderDistance: x }),
renderDistance: 40,
setRenderDistance: (x: any) => set({ renderDistance: x }),
}));
export const useCamMode = create<any>((set: any) => ({
camMode: "ThirdPerson",
setCamMode: (x: any) => set({ camMode: x }),
camMode: "ThirdPerson",
setCamMode: (x: any) => set({ camMode: x }),
}));
export const useUserName = create<any>((set: any) => ({
userName: "",
setUserName: (x: any) => set({ userName: x }),
userName: "",
setUserName: (x: any) => set({ userName: x }),
}));
export const useRenameModeStore = create<any>((set: any) => ({
isRenameMode: false,
setIsRenameMode: (state: boolean) => set({ isRenameMode: state }),
isRenameMode: false,
setIsRenameMode: (state: boolean) => set({ isRenameMode: state }),
}));
export const useObjectPosition = create<any>((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<any>((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<any>((set: any) => ({
drieTemp: undefined,
setDrieTemp: (x: any) => set({ drieTemp: x }),
drieTemp: undefined,
setDrieTemp: (x: any) => set({ drieTemp: x }),
}));
export const useActiveUsers = create<any>((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<any>((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<any>((set: any) => ({
zoneTarget: [],
setZoneTarget: (x: any) => set({ zoneTarget: x }),
zoneTarget: [],
setZoneTarget: (x: any) => set({ zoneTarget: x }),
}));
export const usezonePosition = create<any>((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<EditPositionState>((set) => ({
Edit: false,
setEdit: (value) => set({ Edit: value }),
Edit: false,
setEdit: (value) => set({ Edit: value }),
}));
export const useAsset3dWidget = create<any>((set: any) => ({
widgetSelect: "",
setWidgetSelect: (x: any) => set({ widgetSelect: x }),
widgetSelect: "",
setWidgetSelect: (x: any) => set({ widgetSelect: x }),
}));
export const useWidgetSubOption = create<any>((set: any) => ({
widgetSubOption: "2D",
setWidgetSubOption: (x: any) => set({ widgetSubOption: x }),
widgetSubOption: "2D",
setWidgetSubOption: (x: any) => set({ widgetSubOption: x }),
}));
export const useLimitDistance = create<any>((set: any) => ({
limitDistance: true,
setLimitDistance: (x: any) => set({ limitDistance: x }),
limitDistance: true,
setLimitDistance: (x: any) => set({ limitDistance: x }),
}));
export const useTileDistance = create<any>((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<any>((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<ZoneAssetState>((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<VersionHistoryState>((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<ShortcutStore>((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<any>((set: any) => ({
machineCount: 0,
setMachineCount: (x: any) => set({ machineCount: x }),
machineCount: 0,
setMachineCount: (x: any) => set({ machineCount: x }),
}));
export const useMachineUptime = create<any>((set: any) => ({
machineActiveTime: 0,
setMachineActiveTime: (x: any) => set({ machineActiveTime: x }),
machineActiveTime: 0,
setMachineActiveTime: (x: any) => set({ machineActiveTime: x }),
}));
export const useMachineDowntime = create<any>((set: any) => ({
machineIdleTime: 0,
setMachineIdleTime: (x: any) => set({ machineIdleTime: x }),
machineIdleTime: 0,
setMachineIdleTime: (x: any) => set({ machineIdleTime: x }),
}));
export const useMaterialCycle = create<any>((set: any) => ({
materialCycleTime: 0,
setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }),
materialCycleTime: 0,
setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }),
}));
export const useThroughPutData = create<any>((set: any) => ({
throughputData: 0,
setThroughputData: (x: any) => set({ throughputData: x }),
throughputData: 0,
setThroughputData: (x: any) => set({ throughputData: x }),
}));
export const useProductionCapacityData = create<any>((set: any) => ({
productionCapacityData: 0,
setProductionCapacityData: (x: any) => set({ productionCapacityData: x }),
productionCapacityData: 0,
setProductionCapacityData: (x: any) => set({ productionCapacityData: x }),
}));
export const useProcessBar = create<any>((set: any) => ({
processBar: [],
setProcessBar: (x: any) => set({ processBar: x }),
processBar: [],
setProcessBar: (x: any) => set({ processBar: x }),
}));
export const useDfxUpload = create<any>((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<string, string>;
setInputValues: (values: Record<string, string>) => void;
updateInputValue: (label: string, value: string) => void; // <- New
inputValues: Record<string, string>;
setInputValues: (values: Record<string, string>) => void;
updateInputValue: (label: string, value: string) => void; // <- New
};
export const useInputValues = create<InputValuesStore>((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<ROISummaryStore>((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<CompareStore>((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<SaveVersionStore>((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<ViewSceneState>((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<any>((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<any>((set: any) => ({
selectedPath: "auto",
setSelectedPath: (x: any) => set({ selectedPath: x }),
selectedPath: "auto",
setSelectedPath: (x: any) => set({ selectedPath: x }),
}));

View File

@@ -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<Omit<CraneStatus, "modelUuid" | "productUuid">>
) => 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<CraneStore>()(
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<typeof createCraneStore>;

View File

@@ -11,24 +11,24 @@ type EventsStore = {
updateEvent: (modelUuid: string, updates: Partial<EventsSchema>) => 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<ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema>
updates: Partial<PointsScheme>
) => 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<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action']>
updates: Partial<ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction>
) => 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;
};

View File

@@ -18,13 +18,13 @@ type ProductsStore = {
updateEvent: (productUuid: string, modelUuid: string, updates: Partial<EventsSchema>) => 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<ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema | HumanPointSchema>
updates: Partial<PointsScheme>
) => 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<ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] | HumanPointSchema['actions'][0]>
updates: Partial<ConveyorAction | VehicleAction | RoboticArmAction | MachineAction | StorageAction | HumanAction | CraneAction>
) => 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));

View File

@@ -32,6 +32,7 @@ interface Asset {
};
eventData?: {
type: string;
subType: string;
point?: {
uuid: string;
position: [number, number, number];

View File

@@ -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;

View File

@@ -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<React.SetStateAction<FloorItem
// Configuration for wall items, including model, scale, position, and rotation
interface WallItem {
type: "fixed-move" | "free-move" | undefined;
type: "fixedMove" | "freeMove" | undefined;
model?: THREE.Group;
modelUuid?: string;
assetId: string;