Merge branch 'main' into rtViz

This commit is contained in:
Gomathi 2025-04-10 18:13:14 +05:30
commit 0e9c9fbd3e
44 changed files with 2715 additions and 1803 deletions

View File

@ -1,10 +1,13 @@
# Project Folder Structure
# **Project Folder Structure Maintenance**
This document provides a detailed description of the purpose of each folder in the project by root level, along with the folder hierarchy.
This document outlines the projects folder structure, describing the purpose of each folder and file. It also includes guidelines for development, collaboration, and scalability.
## Folder Hierarchy
---
## **Folder Hierarchy**
```
📁 .github
📁 src
├── 📁 assets
├── 📁 components
@ -14,94 +17,195 @@ This document provides a detailed description of the purpose of each folder in t
│ ├── 📁 builder
│ ├── 📁 simulation
│ └── 📁 visualization
├── 📁 pages
├── 📁 services
├── 📁 store
├── 📁 styles
├── 📁 tests
├── 📁 types
├── 📁 utils
├── 📁 temp
├── App.css
├── App.tsx
├── index.css
├── main.tsx
└── vite-env.d.ts
├── Dockerfile
└── nginx.conf
```
---
## Description of Each Folder
## **Folder and File Descriptions**
### 📁 `src`
The root directory for all source code related to the application.
#### 📁 `assets`
- **Purpose:** Contains static assets such as images, icons, fonts, or other resources.
- **Example:**
- `react.svg`: A static SVG file used in the project.
#### 📁 `components`
- **Purpose:** Contains reusable React components, serving as building blocks for the user interface.
- **Example:**
- Buttons, modals, headers, and forms.
#### 📁 `functions`
- **Purpose:** Stores pure functions or logic that can be reused across the application.
- **Example:**
- Utility functions for data transformation or computation.
#### 📁 `hooks`
- **Purpose:** Holds custom React hooks for managing specific logic or behaviors.
- **Example:**
- Hooks for state management, API calls, or reusable effects.
#### 📁 `modules`
- **Purpose:** Organizes high-level, feature-specific code into subdirectories.
- **📁 builder:** Manages functionalities and components related to building or configuring features.
- **📁 simulation:** Handles processes or logic related to simulations or dynamic scenarios.
- **📁 visualization:** Focuses on displaying data visually, such as charts, graphs, or interactive UI elements.
#### 📁 `services`
- **Purpose:** Encapsulates external service interactions, such as API calls or library integrations.
- **Example:**
- REST API clients or authentication handlers.
#### 📁 `store`
- **Purpose:** Contains state management logic and configurations for tools like Redux or Zustand.
- **Example:**
- Redux slices, context providers, or global state stores.
#### 📁 `styles`
- **Purpose:** Includes global CSS, SCSS, or theming resources.
- **Example:**
- Global styles, theme variables, or resets.
#### 📁 `tests`
- **Purpose:** Stores test files for unit testing, integration testing, and mock data.
- **Example:**
- Test files (`*.test.tsx`, `*.spec.ts`) or mock utilities.
#### 📁 `types`
- **Purpose:** Contains shared TypeScript type definitions and interfaces.
- **Example:**
- Type declarations for props, data models, or API responses.
#### 📁 `utils`
- **Purpose:** Includes general-purpose utility files that dont fit into other specific folders.
- **Example:**
- Helper functions like debouncers or date formatters.
### 📁 **`src`**
The root directory for all application source code.
---
## Root-Level Files
### 📁 **`assets`**
- **Purpose:** Static assets like images, fonts, and icons.
- **Examples:**
- Subfolders for `images`, `icons`, and `fonts`.
- Use descriptive file names (e.g., `logo.svg`, `Roboto-Regular.ttf`).
### `App.tsx`
- **Purpose:** The root React component, initializing the main application layout and logic.
---
### `index.css`
- **Purpose:** Contains global styles applied throughout the application.
### 📁 **`components`**
- **Purpose:** Reusable React components forming the building blocks of the UI.
- **Examples:**
- Buttons, modals, input fields, and headers.
- Organize larger components into folders (e.g., `Button/`).
### `main.tsx`
- **Purpose:** The entry point of the app, rendering the React application and setting up the React DOM.
---
### `vite-env.d.ts`
- **Purpose:** TypeScript environment configuration file for Vite.
### 📁 **`functions`**
- **Purpose:** Pure functions and reusable logic.
- **Examples:**
- `formatDate.ts`: Format dates into readable strings.
- `calculateTotal.ts`: Logic for cart total calculation.
---
### 📁 **`hooks`**
- **Purpose:** Custom React hooks encapsulating reusable logic.
- **Examples:**
- `useAuth.ts`: Manages authentication logic.
- `useFetch.ts`: Fetches data from APIs.
---
### 📁 **`modules`**
- **Purpose:** Feature-specific code organized into subfolders.
- **Subfolders:**
- **`builder`**: For UI components and logic related to building features.
- **`simulation`**: Code for running simulations.
- **`visualization`**: Visualization components like charts and graphs.
---
### 📁 **`pages`**
- **Purpose:** Pages that represent routes in the application.
- **Examples:**
- `HomePage.tsx`: Home route.
- `DashboardPage.tsx`: Dashboard route.
---
### 📁 **`services`**
- **Purpose:** External API interactions and third-party service logic.
- **Examples:**
- `apiClient.ts`: Wrapper for HTTP requests.
- `authService.ts`: Authentication services.
---
### 📁 **`store`**
- **Purpose:** State management logic using tools like Redux or Context API.
- **Examples:**
- `authSlice.ts`: Authentication-related state management.
- `cartSlice.ts`: Shopping cart state management.
---
### 📁 **`styles`**
- **Purpose:** Global and modular styles.
- **Examples:**
- `global.css`: Global styles.
- `theme.scss`: Theme variables.
---
### 📁 **`tests`**
- **Purpose:** Test files for unit, integration, and E2E testing.
- **Examples:**
- `Button.test.tsx`: Tests for the `Button` component.
- `mockData.ts`: Mock API responses.
---
### 📁 **`types`**
- **Purpose:** Shared TypeScript type definitions and interfaces.
- **Examples:**
- `User.ts`: User-related type definitions.
- `ApiResponse.ts`: API response types.
---
### 📁 **`utils`**
- **Purpose:** General-purpose utility functions and constants.
- **Examples:**
- `debounce.ts`: Debounce function.
- `constants.ts`: Shared constants.
---
### 📁 **`temp`**
- **Purpose:** A temporary directory for experimental or work-in-progress code.
- **Guidelines:**
- This folder is included in `.gitignore` to prevent its contents from affecting the main project.
- Move any experimental work here to isolate it from production-ready code.
---
### **Root-Level Files**
- **`App.tsx`**: Root React component.
- **`main.tsx`**: Entry point for the app.
- **`Dockerfile`**: Docker configuration for containerizing the application.
- **`nginx.conf`**: Configuration for the Nginx server.
---
## **Development Guidelines**
### **Environment Management**
- **`.env.local`**: Use this file for testing and development-specific variables.
- **`.env`**: Should only contain variables required for deployed services (e.g., API base URLs).
### **Collaboration Best Practices**
1. **Use the `temp` Folder for Experiments:**
Any experimental work should be isolated in the `temp` folder. Avoid committing temporary code to the main repository.
2. **Documentation:**
- Read the shared documentation in the `docify` and `.github` folders before starting work.
- Follow any guidelines or standards outlined in these documents.
3. **Branching Rules:**
- Do not merge other branches into your branch without proper code review or necessity.
- Use meaningful branch names (e.g., `feature/auth-module`, `fix/header-bug`).
4. **Code Reviews:**
- Ensure all code undergoes peer review before merging.
- Use PR templates provided in the `.github` folder to document changes.
---
## **Additional Instructions for Large-Scale Projects**
1. **Folder Depth:**
- Avoid excessive nesting of folders to maintain simplicity.
- Refactor large folders into modules or domains as the project scales.
2. **Dependency Management:**
- Regularly review and update dependencies.
- Remove unused or deprecated libraries to reduce technical debt.
3. **Automate Workflows:**
- Use CI/CD pipelines to automate testing, building, and deployment processes.
- Integrate tools like Prettier, ESLint, and Husky to enforce code quality.
4. **Versioning and Change Logs:**
- Maintain a changelog to track major updates.
- Use semantic versioning (`x.y.z`) for releases.
5. **Performance Monitoring:**
- Regularly monitor and optimize app performance.
- Use tools like Lighthouse, React DevTools, and browser profilers.
6. **Error Handling:**
- Centralize error handling using utilities or middleware.
- Log errors in both the frontend and backend for debugging.
7. **Documentation:**
- Continuously update this document to reflect structural or procedural changes.
- Ensure all team members are familiar with the documentation.

98
app/package-lock.json generated
View File

@ -29,6 +29,7 @@
"chartjs-plugin-annotation": "^3.1.0",
"glob": "^11.0.0",
"gsap": "^3.12.5",
"html2canvas": "^1.4.1",
"leva": "^0.10.0",
"mqtt": "^5.10.4",
"postprocessing": "^6.36.4",
@ -2019,7 +2020,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"devOptional": true,
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@ -2031,7 +2032,7 @@
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"devOptional": true,
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
@ -4134,26 +4135,6 @@
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@testing-library/jest-dom": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
@ -4265,25 +4246,25 @@
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"devOptional": true
"dev": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"devOptional": true
"dev": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"devOptional": true
"dev": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"devOptional": true
"dev": true
},
"node_modules/@turf/along": {
"version": "7.2.0",
@ -8047,6 +8028,15 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@ -9017,7 +9007,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
"dev": true
},
"node_modules/cross-env": {
"version": "7.0.3",
@ -9102,6 +9092,15 @@
"postcss": "^8.4"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-loader": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz",
@ -9885,7 +9884,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=0.3.1"
}
@ -12489,6 +12488,19 @@
}
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/htmlparser2": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@ -15235,7 +15247,7 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"devOptional": true
"dev": true
},
"node_modules/makeerror": {
"version": "1.0.12",
@ -20434,6 +20446,15 @@
"node": "*"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -20694,7 +20715,7 @@
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"devOptional": true,
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@ -20737,7 +20758,7 @@
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"devOptional": true,
"dev": true,
"dependencies": {
"acorn": "^8.11.0"
},
@ -20749,7 +20770,7 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"devOptional": true
"dev": true
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
@ -21220,6 +21241,15 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@ -21236,7 +21266,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"devOptional": true
"dev": true
},
"node_modules/v8-to-istanbul": {
"version": "8.1.1",
@ -22295,7 +22325,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">=6"
}

View File

@ -24,6 +24,7 @@
"chartjs-plugin-annotation": "^3.1.0",
"glob": "^11.0.0",
"gsap": "^3.12.5",
"html2canvas": "^1.4.1",
"leva": "^0.10.0",
"mqtt": "^5.10.4",
"postprocessing": "^6.36.4",

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,16 +1,20 @@
import React, { useEffect, useMemo, useState } from "react";
import Search from "../../ui/inputs/Search";
import vehicle from "../../../assets/image/vehicles.png";
import workStation from "../../../assets/image/workStation.png";
import machines from "../../../assets/image/machines.png";
import feneration from "../../../assets/image/feneration.png";
import worker from "../../../assets/image/worker.png";
import { getCategoryAsset } from "../../../services/factoryBuilder/assest/assets/getCategoryAsset";
import arch from "../../../assets/gltf-glb/arch.glb";
import door from "../../../assets/gltf-glb/door.glb";
import window from "../../../assets/gltf-glb/window.glb";
import { fetchAssets } from "../../../services/marketplace/fetchAssets";
import { useSelectedItem } from "../../../store/store";
// images -------------------
import vehicle from "../../../assets/image/categories/vehicles.png";
import workStation from "../../../assets/image/categories/workStation.png";
import machines from "../../../assets/image/categories/machines.png";
import feneration from "../../../assets/image/categories/feneration.png";
import worker from "../../../assets/image/categories/worker.png";
// -------------------------------------
interface AssetProp {
filename: string;
thumbnail?: string;

View File

@ -115,17 +115,10 @@ const Tools: React.FC = () => {
setOpenDrop(false); // Close the dropdown
}
};
const handleEscKeyPress = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setIsPlaying(false); // Set isPlaying to false when Escape key is pressed
}
};
document.addEventListener("mousedown", handleOutsideClick);
document.addEventListener("keydown", handleEscKeyPress); // Listen for ESC key
return () => {
document.removeEventListener("mousedown", handleOutsideClick);
document.removeEventListener("keydown", handleEscKeyPress); // Clean up the event listener
};
}, []);
useEffect(() => {

View File

@ -1,6 +1,10 @@
import { useEffect, useState } from "react";
import { Line } from "@react-three/drei";
import { useNavMesh, useSimulationStates } from "../../../store/store";
import {
useNavMesh,
usePlayAgv,
useSimulationStates,
} from "../../../store/store";
import PathNavigator from "./pathNavigator";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
@ -11,28 +15,64 @@ type PathPoints = {
points: { x: number; y: number; z: number }[];
hitCount: number;
};
interface ProcessContainerProps {
processes: any[];
agvRef: any;
MaterialRef: any;
}
const Agv = () => {
const Agv: React.FC<ProcessContainerProps> = ({
processes,
agvRef,
MaterialRef,
}) => {
const [pathPoints, setPathPoints] = useState<PathPoints[]>([]);
const { simulationStates } = useSimulationStates();
const { navMesh } = useNavMesh();
const { isPlaying } = usePlayButtonStore();
const { PlayAgv, setPlayAgv } = usePlayAgv();
useEffect(() => {
if (simulationStates.length > 0) {
const agvModels = simulationStates.filter(
(val) => val.modelName === "agv" && val.type === "Vehicle"
);
const agvModels = simulationStates.filter((val) => val.modelName === "agv" && val.type === "Vehicle");
const newPathPoints = agvModels.filter((model: any) => model.points && model.points.actions && typeof model.points.actions.start === "object" && typeof model.points.actions.end === "object" && "x" in model.points.actions.start && "y" in model.points.actions.start && "x" in model.points.actions.end && "y" in model.points.actions.end)
const newPathPoints = agvModels
.filter(
(model: any) =>
model.points &&
model.points.actions &&
typeof model.points.actions.start === "object" &&
typeof model.points.actions.end === "object" &&
"x" in model.points.actions.start &&
"y" in model.points.actions.start &&
"x" in model.points.actions.end &&
"y" in model.points.actions.end
)
.map((model: any) => ({
modelUuid: model.modeluuid,
modelSpeed: model.points.speed,
bufferTime: model.points.actions.buffer,
hitCount: model.points.actions.hitCount,
points: [
{ x: model.position[0], y: model.position[1], z: model.position[2] },
{ x: model.points.actions.start.x, y: 0, z: model.points.actions.start.y },
{ x: model.points.actions.end.x, y: 0, z: model.points.actions.end.y },
{
x: model.position[0],
y: model.position[1],
z: model.position[2],
},
{
x: model.points.actions.start.x,
y: 0,
z: model.points.actions.start.y,
},
{
x: model.points.actions.end.x,
y: 0,
z: model.points.actions.end.y,
},
],
}));
@ -51,6 +91,9 @@ const Agv = () => {
speed={pair.modelSpeed}
bufferTime={pair.bufferTime}
hitCount={pair.hitCount}
processes={processes}
agvRef={agvRef}
MaterialRef={MaterialRef}
/>
{pair.points.slice(1).map((point, idx) => (

View File

@ -1,9 +1,11 @@
import React, { useEffect, useState, useRef } from "react";
import React, { useEffect, useState, useRef, useMemo } from "react";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { NavMeshQuery } from "@recast-navigation/core";
import { Line } from "@react-three/drei";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store";
import crate from "../../../assets/gltf-glb/crate_box.glb";
interface PathNavigatorProps {
navMesh: any;
@ -12,44 +14,72 @@ interface PathNavigatorProps {
speed: number;
bufferTime: number;
hitCount: number;
processes: any[];
agvRef: any;
MaterialRef: any;
}
interface AGVData {
processId: string;
vehicleId: string;
hitCount: number;
totalHits: number;
}
type Phase = "initial" | "toDrop" | "toPickup";
type MaterialType = "Box" | "Crate";
export default function PathNavigator({
navMesh,
pathPoints,
id,
speed,
bufferTime,
hitCount
hitCount,
processes,
agvRef,
MaterialRef,
}: PathNavigatorProps) {
const [currentPhase, setCurrentPhase] = useState<Phase>("initial");
//
const [path, setPath] = useState<[number, number, number][]>([]);
const [currentPhase, setCurrentPhase] = useState<'initial' | 'loop'>('initial');
const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>([]);
const [pickupDropPath, setPickupDropPath] = useState<[number, number, number][]>([]);
const [dropPickupPath, setDropPickupPath] = useState<[number, number, number][]>([]);
const [initialPosition, setInitialPosition] = useState<THREE.Vector3 | null>(null);
const [initialRotation, setInitialRotation] = useState<THREE.Euler | null>(null);
const [targetPosition] = useState(new THREE.Vector3());
const [smoothPosition] = useState(new THREE.Vector3());
const [targetQuaternion] = useState(new THREE.Quaternion());
const PickUpDrop = useRef([]);
// const [currentPhase, setCurrentPhase] = useState<"initial" | "loop">(
// "initial"
// );
const [toPickupPath, setToPickupPath] = useState<[number, number, number][]>(
[]
);
const [pickupDropPath, setPickupDropPath] = useState<
[number, number, number][]
>([]);
const [dropPickupPath, setDropPickupPath] = useState<
[number, number, number][]
>([]);
const [initialPosition, setInitialPosition] = useState<THREE.Vector3 | null>(
null
);
const [initialRotation, setInitialRotation] = useState<THREE.Euler | null>(
null
);
const [boxVisible, setBoxVisible] = useState(false);
const distancesRef = useRef<number[]>([]);
const totalDistanceRef = useRef(0);
const progressRef = useRef(0);
const isWaiting = useRef(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const pathTransitionProgress = useRef(0);
const hasStarted = useRef(false);
const { scene } = useThree();
const { isPlaying } = usePlayButtonStore();
const { PlayAgv, setPlayAgv } = usePlayAgv();
useEffect(() => {
const object = scene.getObjectByProperty("uuid", id);
if (object) {
setInitialPosition(object.position.clone());
setInitialRotation(object.rotation.clone());
smoothPosition.copy(object.position.clone());
targetPosition.copy(object.position.clone());
targetQuaternion.setFromEuler(object.rotation.clone());
}
}, [scene, id]);
@ -57,7 +87,11 @@ export default function PathNavigator({
try {
const navMeshQuery = new NavMeshQuery(navMesh);
const { path: segmentPath } = navMeshQuery.computePath(start, end);
return segmentPath?.map(({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]) || [];
return (
segmentPath?.map(
({ x, y, z }) => [x, y + 0.1, z] as [number, number, number]
) || []
);
} catch {
return [];
}
@ -70,24 +104,23 @@ export default function PathNavigator({
}
setPath([]);
setCurrentPhase('initial');
setCurrentPhase("initial");
setPickupDropPath([]);
setDropPickupPath([]);
distancesRef.current = [];
totalDistanceRef.current = 0;
progressRef.current = 0;
isWaiting.current = false;
pathTransitionProgress.current = 0;
if (initialPosition && initialRotation) {
const object = scene.getObjectByProperty("uuid", id);
if (object && initialPosition && initialRotation) {
if (object) {
object.position.copy(initialPosition);
object.rotation.copy(initialRotation);
smoothPosition.copy(initialPosition);
targetPosition.copy(initialPosition);
targetQuaternion.setFromEuler(initialRotation);
}
}
};
useEffect(() => {
if (!isPlaying) {
resetState();
@ -96,30 +129,44 @@ export default function PathNavigator({
if (!navMesh || pathPoints.length < 2) return;
const [pickup, drop] = pathPoints.slice(-2);
PickUpDrop.current = pathPoints.slice(-2);
const object = scene.getObjectByProperty("uuid", id);
if (!object) return;
const currentPosition = { x: object.position.x, y: object.position.y, z: object.position.z };
const currentPosition = {
x: object.position.x,
y: object.position.y,
z: object.position.z,
};
const toPickupPath = computePath(currentPosition, pickup);
const pickupToDropPath = computePath(pickup, drop);
const dropToPickupPath = computePath(drop, pickup);
if (toPickupPath.length && pickupToDropPath.length && dropToPickupPath.length) {
if (
toPickupPath.length &&
pickupToDropPath.length &&
dropToPickupPath.length
) {
setPickupDropPath(pickupToDropPath);
setDropPickupPath(dropToPickupPath);
setToPickupPath(toPickupPath);
setPath(toPickupPath);
setCurrentPhase('initial');
setCurrentPhase("initial");
}
}, [navMesh, pathPoints, hitCount, isPlaying]);
}, [navMesh, pathPoints, hitCount, isPlaying, PlayAgv]);
useEffect(() => {
if (path.length < 2) return;
let total = 0;
const segmentDistances = path.slice(0, -1).map((point, i) => {
const dist = new THREE.Vector3(...point).distanceTo(new THREE.Vector3(...path[i + 1]));
const dist = new THREE.Vector3(...point).distanceTo(
new THREE.Vector3(...path[i + 1])
);
total += dist;
return dist;
});
@ -130,19 +177,186 @@ export default function PathNavigator({
isWaiting.current = false;
}, [path]);
// Add these refs outside the useFrame if not already present:
const startPointReached = useRef(false);
const baseMaterials = useMemo(
() => ({
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
// Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
Default: new THREE.MeshStandardMaterial(),
}),
[]
);
// Example usage:
const targetModelUUID = id; // Replace with your target ID
const matchedProcess = findProcessByTargetModelUUID(
processes,
targetModelUUID
);
function findProcessByTargetModelUUID(processes: any, targetModelUUID: any) {
for (const process of processes) {
for (const path of process.paths) {
for (const point of path.points) {
if (
point.connections?.targets?.some(
(target: any) => target.modelUUID === targetModelUUID
)
) {
//
return process.id; // Return the process if a match is found
}
}
}
}
return null; // Return null if no match is found
}
useFrame((_, delta) => {});
const boxRef = useRef<THREE.Mesh | null>(null);
useEffect(() => {
if (!scene || !boxRef || !processes) return;
// 1. Find the existing object by UUID
const existingObject = scene.getObjectByProperty("uuid", id);
if (!existingObject) return;
// 2. Find the process that targets this object's modelUUID
if (boxVisible) {
const matchedProcess = findProcessByTargetModelUUID(processes, id);
// 3. Determine the material (from materialref) if a process is matched
let materialType = "Default";
if (matchedProcess) {
const materialEntry = MaterialRef.current.find((item: any) =>
item.objects.some((obj: any) => obj.processId === matchedProcess)
);
console.log("materialEntry: ", materialEntry);
if (materialEntry) {
materialType = materialEntry.material; // "Box" or "Crate"
}
}
// 4. Create the box mesh with the assigned material
const boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const boxMaterial = baseMaterials[materialType as MaterialType];
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
boxMesh.position.y = 1;
boxMesh.name = `box-${id}`;
// 5. Add to scene and cleanup
existingObject.add(boxMesh);
boxRef.current = boxMesh;
}
if (!startPointReached.current && boxVisible) {
setBoxVisible(false);
}
return () => {
if (boxRef.current?.parent) {
boxRef.current.parent.remove(boxRef.current);
}
boxRef.current = null;
};
}, [
processes,
MaterialRef,
boxVisible,
findProcessByTargetModelUUID,
startPointReached,
]);
useFrame((_, delta) => {
if (!isPlaying || path.length < 2 || !scene || !id) return;
const currentAgv = (agvRef.current || []).find(
(agv: AGVData) => agv.vehicleId === id
);
if (!scene || !id || !isPlaying) return;
const object = scene.getObjectByProperty("uuid", id);
if (!object) return;
const speedFactor = speed;
progressRef.current += delta * speedFactor;
if (isPlaying && !hasStarted.current) {
hasStarted.current = false;
startPointReached.current = false;
progressRef.current = 0;
isWaiting.current = false;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
}
const isAgvReady = () => {
if (!agvRef.current || agvRef.current.length === 0) return false;
if (!currentAgv) return false;
return hitCount === currentAgv.expectedHitCount;
};
// Step 1: Snap to start point on first play
if (isPlaying && !hasStarted.current && toPickupPath.length > 0) {
setBoxVisible(false);
const startPoint = new THREE.Vector3(...toPickupPath[0]);
object.position.copy(startPoint);
if (toPickupPath.length > 1) {
const nextPoint = new THREE.Vector3(...toPickupPath[1]);
const direction = nextPoint.clone().sub(startPoint).normalize();
object.rotation.y = Math.atan2(direction.x, direction.z);
}
hasStarted.current = true;
startPointReached.current = true;
progressRef.current = 0;
setToPickupPath(toPickupPath.slice(-1));
return;
}
// Step 2: Wait at start point for AGV readiness
if (isPlaying && startPointReached.current && path.length === 0) {
if (!isAgvReady()) {
return; // Prevent transitioning to the next phase if AGV is not ready
}
setBoxVisible(true);
setPath([...toPickupPath]); // Start path transition once the AGV is ready
setCurrentPhase("toDrop");
progressRef.current = 0;
console.log("startPointReached: ", startPointReached.current);
startPointReached.current = false;
return;
}
if (path.length < 2) return;
progressRef.current += delta * speed;
let covered = progressRef.current;
let accumulated = 0;
let index = 0;
if (distancesRef.current.length !== path.length - 1) {
distancesRef.current = [];
totalDistanceRef.current = 0;
for (let i = 0; i < path.length - 1; i++) {
const start = new THREE.Vector3(...path[i]);
const end = new THREE.Vector3(...path[i + 1]);
const distance = start.distanceTo(end);
distancesRef.current.push(distance);
totalDistanceRef.current += distance;
}
}
while (
index < distancesRef.current.length &&
covered > accumulated + distancesRef.current[index]
@ -151,43 +365,91 @@ export default function PathNavigator({
index++;
}
// AGV has completed its path
if (index >= distancesRef.current.length) {
progressRef.current = totalDistanceRef.current;
if (!isWaiting.current) {
isWaiting.current = true;
timeoutRef.current = setTimeout(() => {
if (currentPhase === 'initial') {
setPath(pickupDropPath);
setCurrentPhase('loop');
} else {
setPath(prevPath =>
prevPath === pickupDropPath ? dropPickupPath : pickupDropPath
);
}
progressRef.current = 0;
if (!isAgvReady()) {
isWaiting.current = false;
}, bufferTime * 1000);
}
return;
}
let nextPath = [];
let nextPhase = currentPhase;
if (currentPhase === "toDrop") {
nextPath = dropPickupPath;
nextPhase = "toPickup";
setBoxVisible(false);
} else if (currentPhase === "toPickup") {
// When returning to start point (toPickup phase completed)
// Set position to toPickupPath[1] instead of [0]
if (toPickupPath.length > 1) {
object.position.copy(new THREE.Vector3(...toPickupPath[1]));
// Also set rotation towards the next point if available
if (toPickupPath.length > 2) {
const nextPoint = new THREE.Vector3(...toPickupPath[2]);
const direction = nextPoint
.clone()
.sub(object.position)
.normalize();
object.rotation.y = Math.atan2(direction.x, direction.z);
}
}
// Reset the path and mark start point as reached
setPath([]);
startPointReached.current = true;
progressRef.current = 0;
distancesRef.current = [];
// Stop the AGV by setting isplaying to false
if (currentAgv) {
currentAgv.isplaying = false;
}
// Clear timeout and return to prevent further movement
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
return;
} else {
nextPath = pickupDropPath;
nextPhase = "toDrop";
setBoxVisible(true);
}
setPath([...nextPath]);
setCurrentPhase(nextPhase);
progressRef.current = 0;
isWaiting.current = false;
distancesRef.current = [];
// Reset hit count for the next cycle
if (agvRef.current) {
agvRef.current = agvRef.current.map((agv: AGVData) =>
agv.vehicleId === id ? { ...agv, hitCount: 0 } : agv
);
}
}, bufferTime * 1000);
return;
}
// Step 4: Interpolate position and rotation
const start = new THREE.Vector3(...path[index]);
const end = new THREE.Vector3(...path[index + 1]);
const dist = distancesRef.current[index];
if (!dist || dist === 0) return;
const t = THREE.MathUtils.clamp((covered - accumulated) / dist, 0, 1);
targetPosition.copy(start).lerp(end, t);
smoothPosition.lerp(targetPosition, 0.1);
object.position.copy(smoothPosition);
object.position.copy(start.clone().lerp(end, t));
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const targetRotationY = Math.atan2(direction.x, direction.z);
targetQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), targetRotationY);
object.quaternion.slerp(targetQuaternion, 0.1);
object.rotation.y = Math.atan2(direction.x, direction.z);
});
useEffect(() => {
@ -199,9 +461,16 @@ export default function PathNavigator({
}, []);
return (
<group name="path-navigator-lines" visible={!isPlaying} >
<group name="path-navigator-lines" visible={!isPlaying}>
{toPickupPath.length > 0 && (
<Line points={toPickupPath} color="blue" lineWidth={3} dashed dashSize={0.75} dashScale={2} />
<Line
points={toPickupPath}
color="blue"
lineWidth={3}
dashed
dashSize={0.75}
dashScale={2}
/>
)}
{pickupDropPath.length > 0 && (

View File

@ -150,6 +150,7 @@ async function handleModelLoad(
const organization = email ? email.split("@")[1].split(".")[0] : "";
getAssetEventType(selectedItem.id, organization).then(async (res) => {
console.log('res: ', res);
if (res.type === "Conveyor") {
const pointUUIDs = res.points.map(() => THREE.MathUtils.generateUUID());
@ -224,6 +225,7 @@ async function handleModelLoad(
eventData as Types.ConveyorEventsSchema
]);
console.log('data: ', data);
socket.emit("v2:model-asset:add", data);
} else if (res.type === "Vehicle") {
@ -453,6 +455,7 @@ async function handleModelLoad(
return updatedItems;
});
console.log('data: ', data);
socket.emit("v2:model-asset:add", data);
}

View File

@ -20,6 +20,7 @@ async function loadInitialFloorItems(
const organization = (email!.split("@")[1]).split(".")[0];
const items = await getFloorAssets(organization);
console.log('items: ', items);
localStorage.setItem("FloorItems", JSON.stringify(items));
await initializeDB();

View File

@ -5,6 +5,7 @@ import { useCamMode, useToggleView } from '../../../store/store';
import { useKeyboardControls } from '@react-three/drei';
import switchToThirdPerson from './switchToThirdPerson';
import switchToFirstPerson from './switchToFirstPerson';
import { detectModifierKeys } from '../../../utils/shortcutkeys/detectModifierKeys';
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();
@ -37,7 +38,9 @@ const CamMode: React.FC = () => {
const handleKeyPress = async (event: any) => {
if (!state.controls) return;
if (event.key === "/" && !isTransitioning && !toggleView) {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "/" && !isTransitioning && !toggleView) {
setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
@ -81,9 +84,7 @@ const CamMode: React.FC = () => {
}
});
return (
<></>
);
return null; // This component does not render any UI
};
export default CamMode;

View File

@ -5,6 +5,7 @@ import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore,
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -38,10 +39,12 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.ctrlKey && event.key.toLowerCase() === "c" && movedObjects.length === 0 && rotatedObjects.length === 0) {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+C" && movedObjects.length === 0 && rotatedObjects.length === 0) {
copySelection();
}
if (event.ctrlKey && event.key.toLowerCase() === "v" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
};
@ -263,7 +266,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
@ -341,7 +344,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
@ -419,7 +422,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
@ -570,9 +573,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
setSelectedAssets([]);
}
return (
<></>
);
return null; // No visible output, but the component handles copy-paste functionality
};
export default CopyPasteControls;

View File

@ -6,6 +6,7 @@ import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -39,12 +40,11 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === "d") {
event.preventDefault();
if (event.ctrlKey && event.key.toLowerCase() === "d" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
}
};
if (!toggleView) {
@ -245,7 +245,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
@ -323,7 +323,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
@ -401,7 +401,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
return {
uuid: pointUUID,
position: vehiclePoint?.position,
rotation: vehiclePoint?.rotation,
// rotation: vehiclePoint?.rotation,
actions: hasActions
? {
...vehiclePoint.actions,
@ -552,9 +552,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
setSelectedAssets([]);
}
return (
<></>
);
return null; // This component does not render any UI
};
export default DuplicationControls;

View File

@ -5,6 +5,7 @@ import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore,
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -56,14 +57,16 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (event.key.toLowerCase() === "g") {
if (keyCombination === "G") {
if (selectedAssets.length > 0) {
moveAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (event.key.toLowerCase() === "escape") {
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
@ -453,9 +456,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
setSelectedAssets([]);
}
return (
<></>
)
return null; // No need to return anything, as this component is used for its side effects
}
export default MoveControls

View File

@ -457,9 +457,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
setSelectedAssets([]);
}
return (
<></>
)
return null; // No need to return anything, as this component is used for its side effects
}
export default RotateControls

View File

@ -14,6 +14,7 @@ import CopyPasteControls from "./copyPasteControls";
import MoveControls from "./moveControls";
import RotateControls from "./rotateControls";
import useModuleStore from "../../../../store/useModuleStore";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const SelectionControls: React.FC = () => {
const { camera, controls, gl, scene, pointer } = useThree();
@ -101,12 +102,13 @@ const SelectionControls: React.FC = () => {
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
if (event.key.toLowerCase() === "escape") {
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
}
if (event.key.toLowerCase() === "delete") {
if (keyCombination === "DELETE") {
event.preventDefault();
deleteSelection();
}

View File

@ -16,6 +16,7 @@ import { useFrame, useThree } from "@react-three/fiber";
import { useSubModuleStore } from "../../../store/useModuleStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject<THREE.Group>; }) {
const { isPlaying } = usePlayButtonStore();
@ -40,11 +41,12 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
useEffect(() => {
setTransformMode(null);
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedActionSphere) return;
if (e.key === "g") {
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (e.key === "r") {
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};
@ -333,7 +335,6 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();
@ -387,7 +388,6 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
key={path.modeluuid}
ref={(el) => (groupRefs.current[path.modeluuid] = el!)}
position={path.position}
rotation={path.rotation}
onClick={(e) => {
if (isConnecting || eyeDropMode) return;
e.stopPropagation();

View File

@ -1,7 +0,0 @@
import React from "react";
const Mesh: React.FC = () => {
return <mesh></mesh>;
};
export default Mesh;

View File

@ -1,502 +1,119 @@
import React, { useRef, useState, useEffect, useMemo } from "react";
import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { GLTFLoader } from "three-stdlib";
import React, { useRef, useEffect, useMemo } from "react";
import { useLoader, useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three-stdlib";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import crate from "../../../assets/gltf-glb/crate_box.glb";
import box from "../../../assets/gltf-glb/box.glb";
interface PointAction {
uuid: string;
name: string;
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
objectType: string;
material: string;
delay: string | number;
spawnInterval: string | number;
isUsed: boolean;
import { useProcessAnimation } from "./useProcessAnimations";
import ProcessObject from "./processObject";
import { ProcessData } from "./types";
import { useSimulationStates } from "../../../store/store";
import { retrieveGLTF } from "../../../utils/indexDB/idbUtils";
interface ProcessContainerProps {
processes: ProcessData[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
MaterialRef: any;
}
interface ProcessPoint {
uuid: string;
position: number[];
rotation: number[];
actions: PointAction[];
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
}
interface ProcessPath {
modeluuid: string;
modelName: string;
points: ProcessPoint[];
pathPosition: number[];
pathRotation: number[];
speed: number;
}
interface ProcessData {
id: string;
paths: ProcessPath[];
animationPath: { x: number; y: number; z: number }[];
pointActions: PointAction[][];
speed: number;
customMaterials?: Record<string, THREE.Material>;
renderAs?: "box" | "custom";
}
interface AnimationState {
currentIndex: number;
progress: number;
isAnimating: boolean;
speed: number;
isDelaying: boolean;
delayStartTime: number;
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
}
interface SpawnedObject {
ref: React.RefObject<THREE.Group | THREE.Mesh>;
state: AnimationState;
visible: boolean;
material: THREE.Material;
spawnTime: number;
currentMaterialType: string;
position: THREE.Vector3;
}
interface ProcessAnimationState {
spawnedObjects: { [objectId: string]: SpawnedObject };
nextSpawnTime: number;
objectIdCounter: number;
isProcessDelaying: boolean;
processDelayStartTime: number;
processDelayDuration: number;
}
const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const ProcessAnimator: React.FC<ProcessContainerProps> = ({
processes,
setProcesses,
agvRef,
MaterialRef,
}) => {
const gltf = useLoader(GLTFLoader, crate) as GLTF;
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore();
const groupRef = useRef<THREE.Group>(null);
const debugRef = useRef<boolean>(false);
const clockRef = useRef<THREE.Clock>(new THREE.Clock());
const pauseTimeRef = useRef<number>(0);
const elapsedBeforePauseRef = useRef<number>(0);
const animationStatesRef = useRef<Record<string, ProcessAnimationState>>({});
const { speed, setSpeed } = useAnimationPlaySpeed();
const prevIsPlaying = useRef<boolean | null>(null);
const [internalResetFlag, setInternalResetFlag] = useState(false);
const [animationStates, setAnimationStates] = useState<
Record<string, ProcessAnimationState>
>({});
// Store the speed in a ref to access the latest value in animation frames
const speedRef = useRef<number>(speed);
const {
animationStates,
setAnimationStates,
clockRef,
elapsedBeforePauseRef,
speedRef,
debugRef,
findSpawnPoint,
createSpawnedObject,
handlePointActions,
hasNonInheritActions,
getPointDataForAnimationIndex,
processes: processedProcesses,
checkAndCountTriggers,
} = useProcessAnimation(processes, setProcesses, agvRef);
// Update the ref when speed changes
useEffect(() => {
speedRef.current = speed;
}, [speed]);
useEffect(() => {
if (prevIsPlaying.current !== null) {
setAnimationStates({});
}
// Update ref to current isPlaying after effect
prevIsPlaying.current = isPlaying;
// setAnimationStates({});
}, [isPlaying]);
// Sync ref with state
useEffect(() => {
animationStatesRef.current = animationStates;
}, [animationStates]);
// Base materials
const baseMaterials = useMemo(
() => ({
Box: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
Crate: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
// Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
Default: new THREE.MeshStandardMaterial(),
}),
[]
);
// Replace your reset effect with this:
useEffect(() => {
if (isReset) {
// 1. Mark that we're doing an internal reset
setInternalResetFlag(true);
// 2. Pause the animation first
setIsPlaying(false);
setIsPaused(false);
// 3. Reset all animation states
setAnimationStates({});
animationStatesRef.current = {};
// 4. Reset timing references
clockRef.current = new THREE.Clock();
elapsedBeforePauseRef.current = 0;
pauseTimeRef.current = 0;
// 5. Clear the external reset flag
setReset(false);
// 6. After state updates are complete, restart
setTimeout(() => {
setInternalResetFlag(false);
setIsPlaying(true);
}, 0);
}
}, [isReset, setReset, setIsPlaying, setIsPaused]);
// Handle pause state changes
useEffect(() => {
if (isPaused) {
pauseTimeRef.current = clockRef.current.getElapsedTime();
} else if (pauseTimeRef.current > 0) {
const pausedDuration =
clockRef.current.getElapsedTime() - pauseTimeRef.current;
elapsedBeforePauseRef.current += pausedDuration;
}
}, [isPaused]);
// Initialize animation states when processes or play state changes
useEffect(() => {
if (isPlaying && !internalResetFlag) {
const newStates: Record<string, ProcessAnimationState> = {};
processes.forEach((process) => {
newStates[process.id] = {
spawnedObjects: {},
nextSpawnTime: 0,
objectIdCounter: 0,
isProcessDelaying: false,
processDelayStartTime: 0,
processDelayDuration: 0,
// Update material references for all spawned objects
Object.entries(animationStates).forEach(([processId, processState]) => {
Object.keys(processState.spawnedObjects).forEach((objectId) => {
const entry = {
processId,
objectId,
};
});
setAnimationStates(newStates);
animationStatesRef.current = newStates;
clockRef.current.start();
}
}, [isPlaying, processes, internalResetFlag]);
const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
for (const path of process.paths || []) {
for (const point of path.points || []) {
const spawnAction = point.actions?.find(
(a) => a.isUsed && a.type === "Spawn"
const materialType =
processState.spawnedObjects[objectId]?.currentMaterialType;
if (!materialType) {
return;
}
const matRefArray = MaterialRef.current;
// Find existing material group
const existing = matRefArray.find(
(entryGroup: { material: string; objects: any[] }) =>
entryGroup.material === materialType
);
if (spawnAction) {
return point;
}
}
}
return null;
};
const findAnimationPathPoint = (
process: ProcessData,
spawnPoint: ProcessPoint
): THREE.Vector3 => {
if (process.animationPath && process.animationPath.length > 0) {
let pointIndex = 0;
for (const path of process.paths || []) {
for (let i = 0; i < (path.points?.length || 0); i++) {
const point = path.points?.[i];
if (point && point.uuid === spawnPoint.uuid) {
if (process.animationPath[pointIndex]) {
const p = process.animationPath[pointIndex];
return new THREE.Vector3(p.x, p.y, p.z);
}
}
pointIndex++;
}
}
}
return new THREE.Vector3(
spawnPoint.position[0],
spawnPoint.position[1],
spawnPoint.position[2]
if (existing) {
// Check if this processId + objectId already exists
const alreadyExists = existing.objects.some(
(o: any) =>
o.processId === entry.processId && o.objectId === entry.objectId
);
};
const createSpawnedObject = (
process: ProcessData,
currentTime: number,
materialType: string,
spawnPoint: ProcessPoint
): SpawnedObject => {
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const spawnPosition = findAnimationPathPoint(process, spawnPoint);
const material =
processMaterials[materialType as keyof typeof processMaterials] ||
baseMaterials.Default;
if (debugRef.current) {
console.log(`Creating object with material: ${materialType}`, material);
if (!alreadyExists) {
existing.objects.push(entry);
}
return {
ref: React.createRef(),
state: {
currentIndex: 0,
progress: 0,
isAnimating: true,
speed: process.speed || 1, // Process base speed (will be multiplied by global speed)
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
},
visible: true,
material: material,
currentMaterialType: materialType,
spawnTime: currentTime,
position: spawnPosition,
};
};
const handleMaterialSwap = (
processId: string,
objectId: string,
materialType: string
) => {
if (debugRef.current) {
console.log(`Attempting material swap to: ${materialType}`);
}
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || !processState.spawnedObjects[objectId]) {
if (debugRef.current) console.log("Object not found for swap");
return prev;
}
const process = processes.find((p) => p.id === processId);
if (!process) {
if (debugRef.current) console.log("Process not found");
return prev;
}
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const newMaterial =
processMaterials[materialType as keyof typeof processMaterials];
if (!newMaterial) {
if (debugRef.current) console.log(`Material ${materialType} not found`);
return prev;
}
if (debugRef.current) {
console.log(`Swapping material for ${objectId} to ${materialType}`);
}
return {
...prev,
[processId]: {
...processState,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
material: newMaterial,
currentMaterialType: materialType,
},
},
},
};
} else {
// Create new group for this material type
matRefArray.push({
material: materialType,
objects: [entry],
});
};
const handlePointActions = (
processId: string,
objectId: string,
actions: PointAction[] = [],
currentTime: number
): boolean => {
let shouldStopAnimation = false;
actions.forEach((action) => {
if (!action.isUsed) return;
if (debugRef.current) {
console.log(`Processing action: ${action.type} for ${objectId}`);
}
switch (action.type) {
case "Delay":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || processState.isProcessDelaying) {
return prev;
}
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay as string) || 0;
if (delayDuration > 0) {
return {
...prev,
[processId]: {
...processState,
isProcessDelaying: true,
processDelayStartTime: currentTime,
processDelayDuration: delayDuration,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
state: {
...processState.spawnedObjects[objectId].state,
isAnimating: false,
isDelaying: true,
delayStartTime: currentTime,
currentDelayDuration: delayDuration,
delayComplete: false,
},
},
},
},
};
}
return prev;
});
shouldStopAnimation = true;
break;
case "Despawn":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState) return prev;
const newSpawnedObjects = { ...processState.spawnedObjects };
delete newSpawnedObjects[objectId];
return {
...prev,
[processId]: {
...processState,
spawnedObjects: newSpawnedObjects,
},
};
});
shouldStopAnimation = true;
break;
case "Swap":
if (action.material) {
handleMaterialSwap(processId, objectId, action.material);
}
break;
default:
break;
}
});
});
}, [animationStates, MaterialRef, agvRef]);
return shouldStopAnimation;
};
const hasNonInheritActions = (actions: PointAction[] = []): boolean => {
return actions.some((action) => action.isUsed && action.type !== "Inherit");
};
const getPointDataForAnimationIndex = (
process: ProcessData,
index: number
): ProcessPoint | null => {
if (!process.paths) return null;
let cumulativePoints = 0;
for (const path of process.paths) {
const pointCount = path.points?.length || 0;
if (index < cumulativePoints + pointCount) {
const pointIndex = index - cumulativePoints;
return path.points?.[pointIndex] || null;
}
cumulativePoints += pointCount;
}
return null;
};
// In processAnimator.tsx - only the relevant spawn logic part that needs fixes
useFrame(() => {
if (!isPlaying || isPaused) return;
// Spawn logic frame
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
processes.forEach((process) => {
processedProcesses.forEach((process) => {
const processState = newStates[process.id];
if (!processState) return;
if (processState.isProcessDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
if (
currentTime - processState.processDelayStartTime >=
effectiveDelayTime
) {
newStates[process.id] = {
...processState,
isProcessDelaying: false,
spawnedObjects: Object.entries(
processState.spawnedObjects
).reduce(
(acc, [id, obj]) => ({
...acc,
[id]: {
...obj,
state: {
...obj.state,
isDelaying: false,
delayComplete: true,
isAnimating: true,
progress:
obj.state.progress === 0 ? 0.001 : obj.state.progress,
},
},
}),
{}
),
};
}
// Existing delay handling logic...
return;
}
@ -513,7 +130,14 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
? spawnAction.spawnInterval
: parseFloat(spawnAction.spawnInterval as string) || 0;
// Apply global speed to spawn intervals (faster speed = more frequent spawns)
// Check if this is a zero interval spawn and we already spawned an object
if (
spawnInterval === 0 &&
processState.hasSpawnedZeroIntervalObject === true
) {
return; // Don't spawn more objects for zero interval
}
const effectiveSpawnInterval = spawnInterval / speedRef.current;
if (currentTime >= processState.nextSpawnTime) {
@ -522,9 +146,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
process,
currentTime,
spawnAction.material || "Default",
spawnPoint
spawnPoint,
baseMaterials
);
// Update state with the new object and flag for zero interval
newStates[process.id] = {
...processState,
spawnedObjects: {
@ -533,6 +159,11 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
},
objectIdCounter: processState.objectIdCounter + 1,
nextSpawnTime: currentTime + effectiveSpawnInterval,
// Mark that we've spawned an object for zero interval case
hasSpawnedZeroIntervalObject:
spawnInterval === 0
? true
: processState.hasSpawnedZeroIntervalObject,
};
}
});
@ -542,20 +173,18 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
});
useFrame((_, delta) => {
if (!isPlaying || isPaused) return;
// Animation logic frame
const currentTime =
clockRef.current.getElapsedTime() - elapsedBeforePauseRef.current;
setAnimationStates((prev) => {
const newStates = { ...prev };
processes.forEach((process) => {
processedProcesses.forEach((process) => {
const processState = newStates[process.id];
if (!processState) return;
if (processState.isProcessDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
processState.processDelayDuration / speedRef.current;
@ -617,7 +246,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const stateRef = obj.state;
if (stateRef.isDelaying) {
// Apply global speed to delays (faster speed = shorter delays)
const effectiveDelayTime =
stateRef.currentDelayDuration / speedRef.current;
@ -654,18 +282,15 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
stateRef.currentIndex
);
// Handle point actions when first arriving at point
if (stateRef.progress === 0 && currentPointData?.actions) {
if (debugRef.current) {
console.log(
`At point ${stateRef.currentIndex} with actions:`,
currentPointData.actions
);
}
const shouldStop = handlePointActions(
process.id,
objectId,
currentPointData.actions,
currentTime
currentTime,
processedProcesses,
baseMaterials
);
if (shouldStop) {
updatedObjects[objectId] = { ...obj, state: { ...stateRef } };
@ -676,14 +301,39 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const nextPointIdx = stateRef.currentIndex + 1;
const isLastPoint = nextPointIdx >= path.length;
// if (isLastPoint) {
// if (currentPointData?.actions) {
// const shouldStop = !hasNonInheritActions(
// currentPointData.actions
// );
// if (shouldStop) {
// return;
// }
// }
// }
if (isLastPoint) {
if (currentPointData?.actions) {
const shouldStop = !hasNonInheritActions(
const hasNonInherit = hasNonInheritActions(
currentPointData.actions
);
if (shouldStop) {
if (!hasNonInherit) {
// Remove the object if all actions are inherit
updatedObjects[objectId] = {
...obj,
visible: false,
state: { ...stateRef, isAnimating: false },
};
return;
}
} else {
// No actions at last point - remove the object
updatedObjects[objectId] = {
...obj,
visible: false,
state: { ...stateRef, isAnimating: false },
};
return;
}
}
@ -691,8 +341,6 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
const nextPoint = path[nextPointIdx];
const distance =
path[stateRef.currentIndex].distanceTo(nextPoint);
// Apply both process-specific speed and global speed multiplier
const effectiveSpeed = stateRef.speed * speedRef.current;
const movement = effectiveSpeed * delta;
@ -708,17 +356,19 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
stateRef.progress = 0;
currentRef.position.copy(nextPoint);
// TRIGGER CHECK - When object arrives at new point
checkAndCountTriggers(
process.id,
objectId,
stateRef.currentIndex, // The new point index
processedProcesses,
currentTime
);
const newPointData = getPointDataForAnimationIndex(
process,
stateRef.currentIndex
);
if (newPointData?.actions && debugRef.current) {
console.log(
`Reached new point with actions:`,
newPointData.actions
);
}
} else {
currentRef.position.lerpVectors(
path[stateRef.currentIndex],
@ -742,56 +392,32 @@ const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
});
});
if (!processes || processes.length === 0) {
if (!processedProcesses || processedProcesses.length === 0) {
return null;
}
return (
<>
<group ref={groupRef}>
{Object.entries(animationStates).flatMap(([processId, processState]) =>
Object.entries(processState.spawnedObjects)
.filter(([_, obj]) => obj.visible)
.map(([objectId, obj]) => {
const process = processes.find((p) => p.id === processId);
const process = processedProcesses.find((p) => p.id === processId);
const renderAs = process?.renderAs || "custom";
if (renderAs === "box") {
return (
<mesh
<ProcessObject
key={objectId}
ref={obj.ref as React.RefObject<THREE.Mesh>}
material={obj.material}
position={obj.position}
>
<boxGeometry args={[1, 1, 1]} />
</mesh>
objectId={objectId}
obj={obj}
renderAs={renderAs}
gltf={gltf}
/>
);
}
if (gltf?.scene) {
// Clone the scene and apply the material to all meshes
const clonedScene = gltf.scene.clone();
clonedScene.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.material = obj.material;
}
});
return (
<group
key={objectId}
ref={obj.ref as React.RefObject<THREE.Group>}
position={obj.position}
>
<primitive object={clonedScene} />
</group>
);
}
return null;
})
)}
</>
</group>
);
};

View File

@ -2,15 +2,28 @@ import React, { useState } from "react";
import ProcessCreator from "./processCreator";
import ProcessAnimator from "./processAnimator";
const ProcessContainer: React.FC = () => {
const [processes, setProcesses] = useState<any[]>([]);
interface ProcessContainerProps {
processes: any[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
MaterialRef: any;
}
const ProcessContainer: React.FC<ProcessContainerProps> = ({
processes,
setProcesses,
agvRef,
MaterialRef,
}) => {
return (
<>
<ProcessCreator onProcessesCreated={setProcesses} />
{processes.length > 0 && <ProcessAnimator processes={processes} />}
<ProcessAnimator
processes={processes}
setProcesses={setProcesses}
agvRef={agvRef}
MaterialRef={MaterialRef}
/>
</>
);
};

View File

@ -24,16 +24,26 @@
// isUsed: boolean;
// }
// export interface PointTrigger {
// uuid: string;
// bufferTime: number;
// name: string;
// type: string;
// isUsed: boolean;
// }
// export interface PathPoint {
// uuid: string;
// position: [number, number, number];
// actions: PointAction[];
// triggers: PointTrigger[];
// connections: {
// targets: Array<{ modelUUID: string }>;
// };
// }
// export interface SimulationPath {
// type: string;
// modeluuid: string;
// points: PathPoint[];
// pathPosition: [number, number, number];
@ -45,7 +55,9 @@
// paths: SimulationPath[];
// animationPath: THREE.Vector3[];
// pointActions: PointAction[][];
// pointTriggers: PointTrigger[][];
// speed: number;
// isplaying: boolean;
// }
// interface ProcessCreatorProps {
@ -58,18 +70,33 @@
// ): SimulationPath {
// const { modeluuid } = path;
// // Simplified normalizeAction function that preserves exact original properties
// // Normalized action handler
// const normalizeAction = (action: any): PointAction => {
// return { ...action }; // Return exact copy with no modifications
// };
// // Normalized trigger handler
// const normalizeTrigger = (trigger: any): PointTrigger => {
// return { ...trigger }; // Return exact copy with no modifications
// };
// if (path.type === "Conveyor") {
// return {
// type: path.type,
// modeluuid,
// points: path.points.map((point) => ({
// uuid: point.uuid,
// position: point.position,
// actions: point.actions.map(normalizeAction), // Preserve exact actions
// actions: Array.isArray(point.actions)
// ? point.actions.map(normalizeAction)
// : point.actions
// ? [normalizeAction(point.actions)]
// : [],
// triggers: Array.isArray(point.triggers)
// ? point.triggers.map(normalizeTrigger)
// : point.triggers
// ? [normalizeTrigger(point.triggers)]
// : [],
// connections: {
// targets: point.connections.targets.map((target) => ({
// modelUUID: target.modelUUID,
@ -83,44 +110,44 @@
// : path.speed || 1,
// };
// } else {
// // For vehicle paths, handle the case where triggers might not exist
// return {
// type: path.type,
// modeluuid,
// points: [
// {
// uuid: path.point.uuid,
// position: path.point.position,
// actions: Array.isArray(path.point.actions)
// ? path.point.actions.map(normalizeAction)
// : [normalizeAction(path.point.actions)],
// uuid: path.points.uuid,
// position: path.points.position,
// actions: Array.isArray(path.points.actions)
// ? path.points.actions.map(normalizeAction)
// : path.points.actions
// ? [normalizeAction(path.points.actions)]
// : [],
// // For vehicle paths, since triggers might not exist in the schema,
// // we always define default to an empty array
// triggers: [],
// connections: {
// targets: path.point.connections.targets.map((target) => ({
// targets: path.points.connections.targets.map((target) => ({
// modelUUID: target.modelUUID,
// })),
// },
// },
// ],
// pathPosition: path.position,
// speed: path.point.speed || 1,
// speed: path.points.speed || 1,
// };
// }
// }
// // Custom shallow comparison for arrays
// const areArraysEqual = (a: any[], b: any[]) => {
// if (a.length !== b.length) return false;
// for (let i = 0; i < a.length; i++) {
// if (a[i] !== b[i]) return false;
// }
// return true;
// };
// // Helper function to create an empty process
// const createEmptyProcess = (): Process => ({
// id: `process-${Math.random().toString(36).substring(2, 11)}`,
// paths: [],
// animationPath: [],
// pointActions: [],
// pointTriggers: [], // Added point triggers array
// speed: 1,
// isplaying: false,
// });
// // Enhanced connection checking function
@ -156,12 +183,38 @@
// return connectsToLast && !connectsToFirst;
// }
// // Check if a point has a spawn action
// function hasSpawnAction(point: PathPoint): boolean {
// return point.actions.some((action) => action.type.toLowerCase() === "spawn");
// }
// // Ensure spawn point is always at the beginning of the path
// function ensureSpawnPointIsFirst(path: SimulationPath): SimulationPath {
// if (path.points.length !== 3) return path;
// // If the third point has spawn action and first doesn't, reverse the array
// if (hasSpawnAction(path.points[2]) && !hasSpawnAction(path.points[0])) {
// return {
// ...path,
// points: [...path.points].reverse(),
// };
// }
// return path;
// }
// // Updated path adjustment function
// function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
// if (paths.length < 2) return paths;
// if (paths.length < 1) return paths;
// const adjustedPaths = [...paths];
// // First ensure all paths have spawn points at the beginning
// for (let i = 0; i < adjustedPaths.length; i++) {
// adjustedPaths[i] = ensureSpawnPointIsFirst(adjustedPaths[i]);
// }
// // Then handle connections between paths
// for (let i = 0; i < adjustedPaths.length - 1; i++) {
// const currentPath = adjustedPaths[i];
// const nextPath = adjustedPaths[i + 1];
@ -189,6 +242,7 @@
// const [processes, setProcesses] = useState<Process[]>([]);
// const hasSpawnAction = useCallback((path: SimulationPath): boolean => {
// if (path.type !== "Conveyor") return false;
// return path.points.some((point) =>
// point.actions.some((action) => action.type.toLowerCase() === "spawn")
// );
@ -202,10 +256,12 @@
// const animationPath: THREE.Vector3[] = [];
// const pointActions: PointAction[][] = [];
// const pointTriggers: PointTrigger[][] = []; // Added point triggers collection
// const processSpeed = paths[0]?.speed || 1;
// for (const path of paths) {
// for (const point of path.points) {
// if (path.type === "Conveyor") {
// const obj = scene.getObjectByProperty("uuid", point.uuid);
// if (!obj) {
// console.warn(`Object with UUID ${point.uuid} not found in scene`);
@ -215,6 +271,8 @@
// const position = obj.getWorldPosition(new THREE.Vector3());
// animationPath.push(position.clone());
// pointActions.push(point.actions);
// pointTriggers.push(point.triggers); // Collect triggers for each point
// }
// }
// }
@ -223,7 +281,9 @@
// paths,
// animationPath,
// pointActions,
// pointTriggers,
// speed: processSpeed,
// isplaying: false,
// };
// },
// [scene]
@ -326,21 +386,35 @@
// );
// }, [simulationStates]);
// // Enhanced dependency tracking that includes action and trigger types
// const pathsDependency = useMemo(() => {
// if (!convertedPaths) return null;
// return convertedPaths.map((path) => ({
// id: path.modeluuid,
// hasSpawn: path.points.some((p: PathPoint) =>
// p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn")
// ),
// // Track all action types for each point
// actionSignature: path.points
// .map((point, index) =>
// point.actions.map((action) => `${index}-${action.type}`).join("|")
// )
// .join(","),
// // Track all trigger types for each point
// triggerSignature: path.points
// .map((point, index) =>
// point.triggers
// .map((trigger) => `${index}-${trigger.type}`)
// .join("|")
// )
// .join(","),
// connections: path.points
// .flatMap((p: PathPoint) =>
// p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
// )
// .join(","),
// isplaying: false,
// }));
// }, [convertedPaths]);
// // Force process recreation when paths change
// useEffect(() => {
// if (!convertedPaths || convertedPaths.length === 0) {
// if (prevProcessesRef.current.length > 0) {
@ -350,42 +424,16 @@
// return;
// }
// if (areArraysEqual(prevPathsRef.current, convertedPaths)) {
// return;
// }
// prevPathsRef.current = convertedPaths;
// // Always regenerate processes if the pathsDependency has changed
// // This ensures action and trigger type changes will be detected
// const newProcesses = createProcessesFromPaths(convertedPaths);
// prevPathsRef.current = convertedPaths;
// // console.log("--- Action Types in Paths ---");
// // convertedPaths.forEach((path) => {
// // path.points.forEach((point) => {
// // point.actions.forEach((action) => {
// // console.log(
// // `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}`
// // );
// // });
// // });
// // });
// // console.log("New processes:", newProcesses);
// if (
// newProcesses.length !== prevProcessesRef.current.length ||
// !newProcesses.every(
// (proc, i) =>
// proc.paths.length === prevProcessesRef.current[i]?.paths.length &&
// proc.paths.every(
// (path, j) =>
// path.modeluuid ===
// prevProcessesRef.current[i]?.paths[j]?.modeluuid
// )
// )
// ) {
// // Always update processes when action or trigger types change
// onProcessesCreated(newProcesses);
// // prevProcessesRef.current = newProcesses;
// }
// prevProcessesRef.current = newProcesses;
// }, [
// pathsDependency,
// pathsDependency, // This now includes action and trigger types
// onProcessesCreated,
// convertedPaths,
// createProcessesFromPaths,
@ -423,10 +471,19 @@ export interface PointAction {
isUsed: boolean;
}
export interface PointTrigger {
uuid: string;
bufferTime: number;
name: string;
type: string;
isUsed: boolean;
}
export interface PathPoint {
uuid: string;
position: [number, number, number];
actions: PointAction[];
triggers: PointTrigger[];
connections: {
targets: Array<{ modelUUID: string }>;
};
@ -438,6 +495,7 @@ export interface SimulationPath {
points: PathPoint[];
pathPosition: [number, number, number];
speed?: number;
isplaying: boolean;
}
export interface Process {
@ -445,7 +503,9 @@ export interface Process {
paths: SimulationPath[];
animationPath: THREE.Vector3[];
pointActions: PointAction[][];
pointTriggers: PointTrigger[][];
speed: number;
isplaying: boolean;
}
interface ProcessCreatorProps {
@ -458,11 +518,16 @@ function convertToSimulationPath(
): SimulationPath {
const { modeluuid } = path;
// Simplified normalizeAction function that preserves exact original properties
// Normalized action handler
const normalizeAction = (action: any): PointAction => {
return { ...action }; // Return exact copy with no modifications
};
// Normalized trigger handler
const normalizeTrigger = (trigger: any): PointTrigger => {
return { ...trigger }; // Return exact copy with no modifications
};
if (path.type === "Conveyor") {
return {
type: path.type,
@ -470,7 +535,16 @@ function convertToSimulationPath(
points: path.points.map((point) => ({
uuid: point.uuid,
position: point.position,
actions: point.actions.map(normalizeAction), // Preserve exact actions
actions: Array.isArray(point.actions)
? point.actions.map(normalizeAction)
: point.actions
? [normalizeAction(point.actions)]
: [],
triggers: Array.isArray(point.triggers)
? point.triggers.map(normalizeTrigger)
: point.triggers
? [normalizeTrigger(point.triggers)]
: [],
connections: {
targets: point.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
@ -482,8 +556,10 @@ function convertToSimulationPath(
typeof path.speed === "string"
? parseFloat(path.speed) || 1
: path.speed || 1,
isplaying: false, // Added missing property
};
} else {
// For vehicle paths, handle the case where triggers might not exist
return {
type: path.type,
modeluuid,
@ -493,7 +569,10 @@ function convertToSimulationPath(
position: path.points.position,
actions: Array.isArray(path.points.actions)
? path.points.actions.map(normalizeAction)
: [normalizeAction(path.points.actions)],
: path.points.actions
? [normalizeAction(path.points.actions)]
: [],
triggers: [],
connections: {
targets: path.points.connections.targets.map((target) => ({
modelUUID: target.modelUUID,
@ -503,26 +582,20 @@ function convertToSimulationPath(
],
pathPosition: path.position,
speed: path.points.speed || 1,
isplaying: false,
};
}
}
// Custom shallow comparison for arrays
const areArraysEqual = (a: any[], b: any[]) => {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
};
// Helper function to create an empty process
const createEmptyProcess = (): Process => ({
id: `process-${Math.random().toString(36).substring(2, 11)}`,
paths: [],
animationPath: [],
pointActions: [],
pointTriggers: [], // Added point triggers array
speed: 1,
isplaying: false,
});
// Enhanced connection checking function
@ -631,10 +704,12 @@ export function useProcessCreation() {
const animationPath: THREE.Vector3[] = [];
const pointActions: PointAction[][] = [];
const pointTriggers: PointTrigger[][] = []; // Added point triggers collection
const processSpeed = paths[0]?.speed || 1;
for (const path of paths) {
for (const point of path.points) {
if (path.type === "Conveyor") {
const obj = scene.getObjectByProperty("uuid", point.uuid);
if (!obj) {
console.warn(`Object with UUID ${point.uuid} not found in scene`);
@ -644,6 +719,8 @@ export function useProcessCreation() {
const position = obj.getWorldPosition(new THREE.Vector3());
animationPath.push(position.clone());
pointActions.push(point.actions);
pointTriggers.push(point.triggers); // Collect triggers for each point
}
}
}
@ -652,7 +729,9 @@ export function useProcessCreation() {
paths,
animationPath,
pointActions,
pointTriggers,
speed: processSpeed,
isplaying: false,
};
},
[scene]
@ -755,7 +834,7 @@ const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
);
}, [simulationStates]);
// Enhanced dependency tracking that includes action types
// Enhanced dependency tracking that includes action and trigger types
const pathsDependency = useMemo(() => {
if (!convertedPaths) return null;
return convertedPaths.map((path) => ({
@ -766,11 +845,20 @@ const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
point.actions.map((action) => `${index}-${action.type}`).join("|")
)
.join(","),
// Track all trigger types for each point
triggerSignature: path.points
.map((point, index) =>
point.triggers
.map((trigger) => `${index}-${trigger.type}`)
.join("|")
)
.join(","),
connections: path.points
.flatMap((p: PathPoint) =>
p.connections.targets.map((t: { modelUUID: string }) => t.modelUUID)
)
.join(","),
isplaying: false,
}));
}, [convertedPaths]);
@ -785,15 +873,15 @@ const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
}
// Always regenerate processes if the pathsDependency has changed
// This ensures action type changes will be detected
// This ensures action and trigger type changes will be detected
const newProcesses = createProcessesFromPaths(convertedPaths);
prevPathsRef.current = convertedPaths;
// Always update processes when action types change
// Always update processes when action or trigger types change
onProcessesCreated(newProcesses);
prevProcessesRef.current = newProcesses;
}, [
pathsDependency, // This now includes action types
pathsDependency, // This now includes action and trigger types
onProcessesCreated,
convertedPaths,
createProcessesFromPaths,

View File

@ -0,0 +1,58 @@
import React, { useMemo } from "react";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import { SpawnedObject } from "./types";
interface ProcessObjectProps {
objectId: string;
obj: SpawnedObject;
renderAs?: "box" | "custom";
gltf?: GLTF;
}
const ProcessObject: React.FC<ProcessObjectProps> = ({
objectId,
obj,
renderAs = "custom",
gltf,
}) => {
const renderedObject = useMemo(() => {
if (renderAs === "box") {
return (
<mesh
key={objectId}
ref={obj.ref as React.RefObject<THREE.Mesh>}
material={obj.material}
position={obj.position}
>
<boxGeometry args={[1, 1, 1]} />
</mesh>
);
}
if (gltf?.scene) {
const clonedScene = gltf.scene.clone();
clonedScene.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.material = obj.material;
}
});
return (
<group
key={objectId}
ref={obj.ref as React.RefObject<THREE.Group>}
position={obj.position}
>
<primitive object={clonedScene} />
</group>
);
}
return null;
}, [objectId, obj, renderAs, gltf]);
return renderedObject;
};
export default ProcessObject;

View File

@ -1,114 +0,0 @@
import React, { useRef, useEffect } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { GLTF } from "three-stdlib";
import { Box3Helper } from "three";
import { SpawnedObject, ProcessData } from "./types";
interface ProcessObjectRendererProps {
objectId: string;
object: SpawnedObject;
process: ProcessData;
gltf: GLTF;
showBoundingBox?: boolean;
}
export const ProcessObjectRenderer: React.FC<ProcessObjectRendererProps> = ({
objectId,
object,
process,
gltf,
showBoundingBox = false,
}) => {
const meshRef = useRef<THREE.Mesh>(null);
const boxHelperRef = useRef<THREE.Box3Helper | null>(null);
const boundingBoxRef = useRef<THREE.Box3>(new THREE.Box3());
// Issue 1: Can't assign to ref.current as it's read-only
useEffect(() => {
if (object.ref && meshRef.current) {
// Instead of direct assignment, we need to store the mesh reference another way
// Option 1: If you can modify the SpawnedObject interface, add a setMesh method
if (typeof (object as any).setMesh === 'function') {
(object as any).setMesh(meshRef.current);
}
// Option 2: Store the mesh in a property that isn't ref.current
// This requires modifying your SpawnedObject interface to include this property
(object as any).meshInstance = meshRef.current;
// Option 3: If you need to maintain compatibility, you could use Object.defineProperty
// But this is a hack and not recommended
// Object.defineProperty(object.ref, 'current', { value: meshRef.current, writable: true });
}
}, [object.ref]);
// Create a bounding box helper for visualization
useFrame(() => {
if (meshRef.current && showBoundingBox) {
// Update the bounding box to match the mesh position
if (!boxHelperRef.current) {
// Get the size of the mesh
const size = new THREE.Vector3(1, 1, 1);
// If the mesh has geometry, use its dimensions
if (meshRef.current.geometry) {
const box = new THREE.Box3().setFromObject(meshRef.current);
box.getSize(size);
}
// Create a new bounding box centered on the mesh
boundingBoxRef.current = new THREE.Box3().setFromCenterAndSize(
meshRef.current.position,
size
);
// Create a helper to visualize the box
boxHelperRef.current = new Box3Helper(
boundingBoxRef.current,
new THREE.Color(0xff0000)
);
// Add the helper to the scene
meshRef.current.parent?.add(boxHelperRef.current);
} else {
// Update the box position to match the mesh
boundingBoxRef.current.setFromCenterAndSize(
meshRef.current.position,
boundingBoxRef.current.getSize(new THREE.Vector3())
);
// Force the helper to update
boxHelperRef.current.updateMatrixWorld(true);
}
}
});
if (gltf?.scene) {
return (
<primitive
ref={meshRef}
object={gltf.scene.clone()}
position={[0, 0, 0]}
material={object.material}
/>
);
}
// Issue 2: Material color type problem
return (
<mesh ref={meshRef}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial
// Fix the color property access
color={
object.material && 'color' in object.material
? (object.material as THREE.MeshStandardMaterial).color
: "#00ff00"
}
metalness={0.5}
roughness={0.3}
/>
</mesh>
);
};

View File

@ -1,44 +1,59 @@
import * as THREE from "three";
import React from "react";
export interface Trigger {
uuid: string;
name: string;
type: string;
bufferTime: number;
isUsed: boolean;
}
export interface PointAction {
uuid: string;
name: string;
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
objectType: string;
material: string;
delay: string | number;
spawnInterval: string | number;
isUsed: boolean;
hitCount?: number;
}
export interface ProcessPoint {
uuid: string;
position: number[];
actions?: PointAction[];
rotation: number[];
actions: PointAction[];
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
triggers?: Trigger[];
}
export interface PointAction {
type: string;
isUsed: boolean;
spawnInterval?: number | string;
material?: string;
delay?: number | string;
export interface ProcessPath {
modeluuid: string;
modelName: string;
points: ProcessPoint[];
pathPosition: number[];
pathRotation: number[];
speed: number;
type: "Conveyor" | "Vehicle";
isplaying: boolean
}
export interface ProcessData {
id: string;
name: string;
paths?: {
points?: ProcessPoint[];
}[];
animationPath?: { x: number; y: number; z: number }[];
speed?: number;
paths: ProcessPath[];
animationPath: { x: number; y: number; z: number }[];
pointActions: PointAction[][];
speed: number;
customMaterials?: Record<string, THREE.Material>;
renderAs?: "box" | "custom";
pointTriggers: [];
}
export interface ProcessAnimationState {
spawnedObjects: Record<string, SpawnedObject | SpawnedObjectWithCollision>;
nextSpawnTime: number;
objectIdCounter: number;
isProcessDelaying: boolean;
processDelayStartTime: number;
processDelayDuration: number;
isCollisionPaused?: boolean;
}
export interface SpawnedObject {
ref: React.RefObject<THREE.Object3D>;
state: {
export interface AnimationState {
currentIndex: number;
progress: number;
isAnimating: boolean;
@ -48,32 +63,24 @@ export interface SpawnedObject {
currentDelayDuration: number;
delayComplete: boolean;
currentPathIndex: number;
};
visible: boolean;
material: THREE.Material;
currentMaterialType: string;
spawnTime: number;
position: THREE.Vector3;
collision?: {
boundingBox: THREE.Box3;
isColliding: boolean;
colliding: boolean; // Added this property
};
}
// For use in your processAnimator.tsx
// Update the CollisionState interface to include all required properties
interface CollisionState {
boundingBox: THREE.Box3;
isColliding: boolean;
colliding: boolean; // This was missing
collidingWith: string[];
}
export interface SpawnedObjectWithCollision extends SpawnedObject {
collision: {
boundingBox: THREE.Box3;
isColliding: boolean;
colliding: boolean;
collidingWith: string[];
};
}
export interface SpawnedObject {
ref: React.RefObject<THREE.Group | THREE.Mesh>;
state: AnimationState;
visible: boolean;
material: THREE.Material;
spawnTime: number;
currentMaterialType: string;
position: THREE.Vector3;
}
export interface ProcessAnimationState {
spawnedObjects: { [objectId: string]: SpawnedObject };
nextSpawnTime: number;
objectIdCounter: number;
isProcessDelaying: boolean;
processDelayStartTime: number;
processDelayDuration: number;
hasSpawnedZeroIntervalObject?: boolean;
}

View File

@ -0,0 +1,540 @@
import { useCallback, useEffect, useRef, useState } from "react";
import * as THREE from "three";
import {
ProcessData,
ProcessAnimationState,
SpawnedObject,
AnimationState,
ProcessPoint,
PointAction,
Trigger,
} from "./types";
import {
useAnimationPlaySpeed,
usePauseButtonStore,
usePlayButtonStore,
useResetButtonStore,
} from "../../../store/usePlayButtonStore";
import { usePlayAgv } from "../../../store/store";
// Enhanced ProcessAnimationState with trigger tracking
interface EnhancedProcessAnimationState extends ProcessAnimationState {
triggerCounts: Record<string, number>;
triggerLogs: Array<{
timestamp: number;
pointId: string;
objectId: string;
triggerId: string;
}>;
}
interface ProcessContainerProps {
processes: ProcessData[];
setProcesses: React.Dispatch<React.SetStateAction<any[]>>;
agvRef: any;
}
interface PlayAgvState {
playAgv: Record<string, any>;
setPlayAgv: (data: any) => void;
}
export const useProcessAnimation = (
processes: ProcessData[],
setProcesses: React.Dispatch<React.SetStateAction<any[]>>,
agvRef: any
) => {
// State and refs initialization
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { isPaused, setIsPaused } = usePauseButtonStore();
const { isReset, setReset } = useResetButtonStore();
const debugRef = useRef<boolean>(false);
const clockRef = useRef<THREE.Clock>(new THREE.Clock());
const pauseTimeRef = useRef<number>(0);
const elapsedBeforePauseRef = useRef<number>(0);
const animationStatesRef = useRef<
Record<string, EnhancedProcessAnimationState>
>({});
const { speed } = useAnimationPlaySpeed();
const prevIsPlaying = useRef<boolean | null>(null);
const [internalResetFlag, setInternalResetFlag] = useState(false);
const [animationStates, setAnimationStates] = useState<
Record<string, EnhancedProcessAnimationState>
>({});
const speedRef = useRef<number>(speed);
const { PlayAgv, setPlayAgv } = usePlayAgv();
// Effect hooks
useEffect(() => {
speedRef.current = speed;
}, [speed]);
useEffect(() => {
if (prevIsPlaying.current !== null) {
setAnimationStates({});
}
prevIsPlaying.current = isPlaying;
}, [isPlaying]);
useEffect(() => {
animationStatesRef.current = animationStates;
}, [animationStates]);
// Reset handler
useEffect(() => {
if (isReset) {
setInternalResetFlag(true);
setIsPlaying(false);
setIsPaused(false);
setAnimationStates({});
animationStatesRef.current = {};
clockRef.current = new THREE.Clock();
elapsedBeforePauseRef.current = 0;
pauseTimeRef.current = 0;
setReset(false);
setTimeout(() => {
setInternalResetFlag(false);
setIsPlaying(true);
}, 0);
}
}, [isReset, setReset, setIsPlaying, setIsPaused]);
// Pause handler
useEffect(() => {
if (isPaused) {
pauseTimeRef.current = clockRef.current.getElapsedTime();
} else if (pauseTimeRef.current > 0) {
const pausedDuration =
clockRef.current.getElapsedTime() - pauseTimeRef.current;
elapsedBeforePauseRef.current += pausedDuration;
}
}, [isPaused]);
// Initialize animation states with trigger tracking
useEffect(() => {
if (isPlaying && !internalResetFlag) {
const newStates: Record<string, EnhancedProcessAnimationState> = {};
processes.forEach((process) => {
const triggerCounts: Record<string, number> = {};
// Initialize trigger counts for all On-Hit triggers
process.paths?.forEach((path) => {
path.points?.forEach((point) => {
point.triggers?.forEach((trigger: Trigger) => {
if (trigger.type === "On-Hit" && trigger.isUsed) {
triggerCounts[`${point.uuid}-${trigger.uuid}`] = 0;
}
});
});
});
newStates[process.id] = {
spawnedObjects: {},
nextSpawnTime: 0,
objectIdCounter: 0,
isProcessDelaying: false,
processDelayStartTime: 0,
processDelayDuration: 0,
triggerCounts,
triggerLogs: [],
};
});
setAnimationStates(newStates);
animationStatesRef.current = newStates;
clockRef.current.start();
}
}, [isPlaying, processes, internalResetFlag]);
// Helper functions
const findSpawnPoint = (process: ProcessData): ProcessPoint | null => {
for (const path of process.paths || []) {
for (const point of path.points || []) {
const spawnAction = point.actions?.find(
(a) => a.isUsed && a.type === "Spawn"
);
if (spawnAction) {
return point;
}
}
}
return null;
};
const findAnimationPathPoint = (
process: ProcessData,
spawnPoint: ProcessPoint
): THREE.Vector3 => {
if (process.animationPath && process.animationPath.length > 0) {
let pointIndex = 0;
for (const path of process.paths || []) {
for (let i = 0; i < (path.points?.length || 0); i++) {
const point = path.points?.[i];
if (point && point.uuid === spawnPoint.uuid) {
if (process.animationPath[pointIndex]) {
const p = process.animationPath[pointIndex];
return new THREE.Vector3(p.x, p.y, p.z);
}
}
pointIndex++;
}
}
}
return new THREE.Vector3(
spawnPoint.position[0],
spawnPoint.position[1],
spawnPoint.position[2]
);
};
// Optimized object creation
const createSpawnedObject = useCallback(
(
process: ProcessData,
currentTime: number,
materialType: string,
spawnPoint: ProcessPoint,
baseMaterials: Record<string, THREE.Material>
): SpawnedObject => {
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const spawnPosition = findAnimationPathPoint(process, spawnPoint);
const material =
processMaterials[materialType as keyof typeof processMaterials] ||
baseMaterials.Default;
return {
ref: { current: null },
state: {
currentIndex: 0,
progress: 0,
isAnimating: true,
speed: process.speed || 1,
isDelaying: false,
delayStartTime: 0,
currentDelayDuration: 0,
delayComplete: false,
currentPathIndex: 0,
},
visible: true,
material: material,
currentMaterialType: materialType,
spawnTime: currentTime,
position: spawnPosition,
};
},
[]
);
// Material handling
const handleMaterialSwap = useCallback(
(
processId: string,
objectId: string,
materialType: string,
processes: ProcessData[],
baseMaterials: Record<string, THREE.Material>
) => {
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || !processState.spawnedObjects[objectId])
return prev;
const process = processes.find((p) => p.id === processId);
if (!process) return prev;
const processMaterials = {
...baseMaterials,
...(process.customMaterials || {}),
};
const newMaterial =
processMaterials[materialType as keyof typeof processMaterials];
if (!newMaterial) return prev;
return {
...prev,
[processId]: {
...processState,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
material: newMaterial,
currentMaterialType: materialType,
},
},
},
};
});
},
[]
);
// Point action handler with trigger counting
const handlePointActions = useCallback(
(
processId: string,
objectId: string,
actions: PointAction[] = [],
currentTime: number,
processes: ProcessData[],
baseMaterials: Record<string, THREE.Material>
): boolean => {
let shouldStopAnimation = false;
actions.forEach((action) => {
if (!action.isUsed) return;
switch (action.type) {
case "Delay":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState || processState.isProcessDelaying) return prev;
const delayDuration =
typeof action.delay === "number"
? action.delay
: parseFloat(action.delay as string) || 0;
if (delayDuration > 0) {
return {
...prev,
[processId]: {
...processState,
isProcessDelaying: true,
processDelayStartTime: currentTime,
processDelayDuration: delayDuration,
spawnedObjects: {
...processState.spawnedObjects,
[objectId]: {
...processState.spawnedObjects[objectId],
state: {
...processState.spawnedObjects[objectId].state,
isAnimating: false,
isDelaying: true,
delayStartTime: currentTime,
currentDelayDuration: delayDuration,
delayComplete: false,
},
},
},
},
};
}
return prev;
});
shouldStopAnimation = true;
break;
case "Despawn":
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState) return prev;
const newSpawnedObjects = { ...processState.spawnedObjects };
delete newSpawnedObjects[objectId];
return {
...prev,
[processId]: {
...processState,
spawnedObjects: newSpawnedObjects,
},
};
});
shouldStopAnimation = true;
break;
case "Swap":
if (action.material) {
handleMaterialSwap(
processId,
objectId,
action.material,
processes,
baseMaterials
);
}
break;
default:
break;
}
});
return shouldStopAnimation;
},
[handleMaterialSwap]
);
// Trigger counting system
const checkAndCountTriggers = useCallback(
(
processId: string,
objectId: string,
currentPointIndex: number,
processes: ProcessData[],
currentTime: number
) => {
setAnimationStates((prev) => {
const processState = prev[processId];
if (!processState) return prev;
const process = processes.find((p) => p.id === processId);
if (!process) return prev;
const point = getPointDataForAnimationIndex(process, currentPointIndex);
if (!point?.triggers) return prev;
const onHitTriggers = point.triggers.filter(
(t: Trigger) => t.type === "On-Hit" && t.isUsed
);
if (onHitTriggers.length === 0) return prev;
const newTriggerCounts = { ...processState.triggerCounts };
const newTriggerLogs = [...processState.triggerLogs];
let shouldLog = false;
// Update counts for all valid triggers
onHitTriggers.forEach((trigger: Trigger) => {
const triggerKey = `${point.uuid}-${trigger.uuid}`;
newTriggerCounts[triggerKey] =
(newTriggerCounts[triggerKey] || 0) + 1;
shouldLog = true;
newTriggerLogs.push({
timestamp: currentTime,
pointId: point.uuid,
objectId,
triggerId: trigger.uuid,
});
});
const processTotalHits = Object.values(newTriggerCounts).reduce(
(a, b) => a + b,
0
);
if (shouldLog) {
const vehiclePaths = process.paths.filter(
(path) => path.type === "Vehicle"
);
vehiclePaths.forEach((vehiclePath) => {
if (vehiclePath.points?.length > 0) {
const vehiclePoint = vehiclePath.points[0];
const action = vehiclePoint.actions?.[0];
let expectedHitCount = action?.hitCount;
if (expectedHitCount !== undefined) {
const vehicleId = vehiclePath.modeluuid;
let vehicleEntry = agvRef.current.find(
(v: any) =>
v.vehicleId === vehicleId && v.processId === processId
);
if (!vehicleEntry) {
vehicleEntry = {
processId,
vehicleId,
expectedHitCount,
isplaying: false,
hitCount: 0, // Initialize hitCount
};
agvRef.current.push(vehicleEntry);
}
// Increment expectedHitCount if not playing
if (!vehicleEntry.isplaying) {
vehicleEntry.expectedHitCount = processTotalHits;
}
// Set vehicle's hitCount to the processTotalHits
vehicleEntry.hitCount = processTotalHits;
vehicleEntry.lastUpdated = currentTime;
// If hitCount matches expectedHitCount, set isplaying to true
if (vehicleEntry.hitCount >= vehicleEntry.expectedHitCount) {
vehicleEntry.isplaying = true;
}
}
}
});
}
return {
...prev,
[processId]: {
...processState,
triggerCounts: newTriggerCounts,
triggerLogs: newTriggerLogs,
totalHits: processTotalHits,
},
};
});
},
[]
);
// Utility functions
const hasNonInheritActions = useCallback(
(actions: PointAction[] = []): boolean => {
return actions.some(
(action) => action.isUsed && action.type !== "Inherit"
);
},
[]
);
const getPointDataForAnimationIndex = useCallback(
(process: ProcessData, index: number): ProcessPoint | null => {
if (!process.paths) return null;
let cumulativePoints = 0;
for (const path of process.paths) {
const pointCount = path.points?.length || 0;
if (index < cumulativePoints + pointCount) {
const pointIndex = index - cumulativePoints;
return path.points?.[pointIndex] || null;
}
cumulativePoints += pointCount;
}
return null;
},
[]
);
const getTriggerCounts = useCallback((processId: string) => {
return animationStatesRef.current[processId]?.triggerCounts || {};
}, []);
const getTriggerLogs = useCallback((processId: string) => {
return animationStatesRef.current[processId]?.triggerLogs || [];
}, []);
return {
animationStates,
setAnimationStates,
clockRef,
elapsedBeforePauseRef,
speedRef,
debugRef,
findSpawnPoint,
createSpawnedObject,
handlePointActions,
hasNonInheritActions,
getPointDataForAnimationIndex,
checkAndCountTriggers,
getTriggerCounts,
getTriggerLogs,
processes,
};
};

View File

@ -16,23 +16,9 @@ function Simulation() {
const { activeModule } = useModuleStore();
const pathsGroupRef = useRef() as React.MutableRefObject<THREE.Group>;
const { simulationStates, setSimulationStates } = useSimulationStates();
const [processes, setProcesses] = useState([]);
useEffect(() => {
// console.log('simulationStates: ', simulationStates);
}, [simulationStates]);
// useEffect(() => {
// if (selectedActionSphere) {
// console.log('selectedActionSphere: ', selectedActionSphere);
// }
// }, [selectedActionSphere]);
// useEffect(() => {
// if (selectedPath) {
// console.log('selectedPath: ', selectedPath);
// }
// }, [selectedPath]);
const [processes, setProcesses] = useState<any[]>([]);
const agvRef = useRef([]);
const MaterialRef = useRef([]);
return (
<>
@ -41,8 +27,17 @@ function Simulation() {
<>
<PathCreation pathsGroupRef={pathsGroupRef} />
<PathConnector pathsGroupRef={pathsGroupRef} />
<ProcessContainer />
<Agv />
<ProcessContainer
processes={processes}
setProcesses={setProcesses}
agvRef={agvRef}
MaterialRef={MaterialRef}
/>
<Agv
processes={processes}
agvRef={agvRef}
MaterialRef={MaterialRef}
/>
</>
)}
</>

View File

@ -70,9 +70,8 @@ export const useZones = create<any>((set: any) => ({
zones: [],
setZones: (callback: any) =>
set((state: any) => ({
zones:
typeof callback === 'function' ? callback(state.zones) : callback
}))
zones: typeof callback === "function" ? callback(state.zones) : callback,
})),
}));
interface ZonePointsState {
@ -314,9 +313,7 @@ export const useActiveUsers = create<any>((set: any) => ({
setActiveUsers: (callback: (prev: any[]) => any[] | any[]) =>
set((state: { activeUsers: any[] }) => ({
activeUsers:
typeof callback === "function"
? callback(state.activeUsers)
: callback,
typeof callback === "function" ? callback(state.activeUsers) : callback,
})),
}));
@ -356,12 +353,33 @@ export const useSelectedPath = create<any>((set: any) => ({
}));
interface SimulationPathsStore {
simulationStates: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[];
simulationStates: (
| Types.ConveyorEventsSchema
| Types.VehicleEventsSchema
| Types.StaticMachineEventsSchema
| Types.ArmBotEventsSchema
)[];
setSimulationStates: (
paths:
| (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]
| ((prev: (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[]
) => (Types.ConveyorEventsSchema | Types.VehicleEventsSchema | Types.StaticMachineEventsSchema | Types.ArmBotEventsSchema)[])
| (
| Types.ConveyorEventsSchema
| Types.VehicleEventsSchema
| Types.StaticMachineEventsSchema
| Types.ArmBotEventsSchema
)[]
| ((
prev: (
| Types.ConveyorEventsSchema
| Types.VehicleEventsSchema
| Types.StaticMachineEventsSchema
| Types.ArmBotEventsSchema
)[]
) => (
| Types.ConveyorEventsSchema
| Types.VehicleEventsSchema
| Types.StaticMachineEventsSchema
| Types.ArmBotEventsSchema
)[])
) => void;
}
@ -372,7 +390,7 @@ export const useSimulationStates = create<SimulationPathsStore>((set) => ({
simulationStates:
typeof paths === "function" ? paths(state.simulationStates) : paths,
})),
}))
}));
export const useNavMesh = create<any>((set: any) => ({
navMesh: null,
@ -456,6 +474,11 @@ export const useTileDistance = create<any>((set: any) => ({
})),
}));
export const usePlayAgv = create<any>((set, get) => ({
PlayAgv: [],
setPlayAgv: (updateFn: (prev: any[]) => any[]) =>
set({ PlayAgv: updateFn(get().PlayAgv) }),
}));
// Define the Asset type
type Asset = {
id: string;

View File

@ -9,3 +9,25 @@
justify-content: space-between;
align-items: center;
}
// Array of base colors
$colors: (
#f5550b,
#1bac1b,
#0099ff,
#d4c927,
#8400ff,
#13e9b3,
#df1dcf
);
@mixin gradient-by-child($index) {
// Get the color based on the index passed
$base-color: nth($colors, $index);
// Apply gradient using the same color with different alpha values
background: linear-gradient(
144.19deg,
rgba($base-color, 0.2) 16.62%, // 20% opacity
rgba($base-color, 0.08) 85.81% // 80% opacity
);
}

View File

@ -7,14 +7,28 @@ input {
width: 100%;
padding: 2px 4px;
border-radius: #{$border-radius-small};
border: 1px solid var(--border-color);
outline: none;
outline: 2px solid var(--border-color);
outline-offset: -2px;
border: none;
background: transparent;
color: var(--input-text-color);
&:focus,
&:active {
border: 1px solid var(--accent-color);
outline: 1px solid var(--accent-color);
}
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
// Text styles
-webkit-text-fill-color: var(--input-text-color) !important;
caret-color: var(--input-text-color);
// Background styles
background-color: var(--background-color) !important;
-webkit-box-shadow: 0 0 0px 1000px var(--background-color) inset !important;
}
}
@ -615,6 +629,7 @@ input {
input {
border: none;
outline: none;
background: transparent;
&::placeholder {

View File

@ -130,7 +130,6 @@
grid-column: 1 / -1;
}
.widget-left-sideBar {
min-height: 50vh;
max-height: 60vh;
@ -538,7 +537,6 @@
.floating {
width: 100%;
}
}
@ -1070,24 +1068,62 @@
.category-name {
position: relative;
z-index: 3;
font-size: var(--font-size-large);
font-size: var(--font-size-regular);
// -webkit-text-fill-color: transparent;
// -webkit-text-stroke: 1px black;
}
&::after {
content: "";
width: 50px;
height: 50px;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: var(--circle-color, #000);
position: absolute;
top: 50%;
top: 60%;
right: -10px;
transform: translate(0, -50%);
background: linear-gradient(144.19deg,
#f1e7cd 16.62%,
#fffaef 85.81%);
}
&:nth-child(1), &:nth-child(9) {
&::after {
@include gradient-by-child(1); // First child uses the first color
}
}
&:nth-child(2), &:nth-child(10) {
&::after {
@include gradient-by-child(2); // Second child uses the second color
}
}
&:nth-child(3), &:nth-child(11) {
&::after {
@include gradient-by-child(3); // Third child uses the third color
}
}
&:nth-child(4), &:nth-child(12) {
&::after {
@include gradient-by-child(4); // Fourth child uses the fourth color
}
}
&:nth-child(5), &:nth-child(13) {
&::after {
@include gradient-by-child(5); // Fifth child uses the fifth color
}
}
&:nth-child(6), &:nth-child(14) {
&::after {
@include gradient-by-child(6); // Fifth child uses the fifth color
}
}
&:nth-child(7), &:nth-child(15) {
&::after {
@include gradient-by-child(7); // Fifth child uses the fifth color
}
}
.category-image {
@ -1114,30 +1150,47 @@
.assets {
width: 117px;
height: 95px;
border-radius: 3.59px;
border-radius: #{$border-radius-small};
background-color: var(--background-color-gray);
padding: 8px;
padding-top: 12px;
font-weight: $medium-weight;
position: relative;
overflow: hidden;
padding: 0;
&:hover {
.asset-name {
opacity: 1;
}
}
.asset-name {
position: relative;
position: absolute;
top: 0;
z-index: 3;
padding: 8px;
width: 100%;
font-size: var(--font-size-regular);
background: color-mix(
in srgb,
var(--background-color) 40%,
transparent
);
backdrop-filter: blur(5px);
opacity: 0;
transition: opacity 0.3s ease;
/* Added properties for ellipsis */
display: -webkit-box; /* Necessary for multiline truncation */
-webkit-line-clamp: 2; /* Number of lines to show */
-webkit-box-orient: vertical; /* Box orientation for the ellipsis */
overflow: hidden; /* Hide overflowing content */
text-overflow: ellipsis; /* Add ellipsis for truncated content */
}
.asset-image {
height: 100%;
width: 100%;
position: absolute;
// top: 50%;
// right: 5px;
// transform: translate(0, -50%);
top: 0;
left: 0;
z-index: 2;
object-fit: cover;
}
}
}

View File

@ -765,20 +765,19 @@
.editWidgetOptions {
position: absolute;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%);
background-color: var(--background-color);
z-index: 3;
display: flex;
flex-direction: column;
border-radius: 6px;
overflow: hidden;
padding: 4px;
min-width: 150px;
.option {
padding: 8px 10px;
padding: 4px 10px;
border-radius: #{$border-radius-small};
color: var(--text-color);
cursor: pointer;
@ -791,7 +790,8 @@
color: #f65648;
&:hover {
background-color: #ffe3e0;
background-color: #f65648;
color: white;
}
}
}

View File

@ -1,13 +1,13 @@
// Importing core classes and types from THREE.js and @react-three/fiber
import * as THREE from "three";
import { TransformControls } from 'three/examples/jsm/controls/TransformControls';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { DragControls } from 'three/examples/jsm/controls/DragControls';
import { IntersectionEvent } from '@react-three/fiber/dist/declarations/src/core/events';
import { TransformControls } from "three/examples/jsm/controls/TransformControls";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { DragControls } from "three/examples/jsm/controls/DragControls";
import { IntersectionEvent } from "@react-three/fiber/dist/declarations/src/core/events";
import { ThreeEvent } from "@react-three/fiber/dist/declarations/src/core/events";
import { RootState } from "@react-three/fiber";
import { CSM } from "three/examples/jsm/csm/CSM";
import { CSMHelper } from 'three/examples/jsm/csm/CSMHelper';
import { CSMHelper } from "three/examples/jsm/csm/CSMHelper";
import { CameraControls } from "@react-three/drei";
/** Core THREE.js and React-Fiber Event Types **/
@ -15,7 +15,6 @@ import { CameraControls } from "@react-three/drei";
// Event type specific to pointer events in @react-three/fiber
export type ThreeEvent = ThreeEvent<PointerEvent>;
/** Vector and Reference Types **/
// 2D Vector type from THREE.js
@ -33,7 +32,6 @@ export type RefVector3 = React.MutableRefObject<THREE.Vector3 | null>;
// Quaternion type for rotations, using the base structure from THREE.js
export type QuaternionType = THREE.QuaternionLike;
/** Basic Object Types for Scene Management **/
// THREE.js mesh object
@ -49,7 +47,9 @@ export type Shape = THREE.Shape;
export type IntersectionEvent = THREE.Intersection;
// Array type for intersections with objects in the scene
export type IntersectsType = THREE.Intersection<THREE.Object3D<THREE.Object3DEventMap>>[];
export type IntersectsType = THREE.Intersection<
THREE.Object3D<THREE.Object3DEventMap>
>[];
// Event type for mesh interactions
export type MeshEvent = IntersectionEvent<MouseEvent>;
@ -60,7 +60,6 @@ export type DragEvent = DragEvent;
// Generic type for user data attached to objects
export type UserData = any;
/** React Mutable References for Scene Objects **/
// Mutable reference to the scene, used in React-based projects
@ -92,7 +91,6 @@ export type Vector3Array = THREE.Vector3[];
export type DragControl = DragControls | null;
export type RefDragControl = React.MutableRefObject<DragControls | null>;
/** Primitive Types with Mutable References **/
export type String = string;
@ -109,15 +107,15 @@ export type NumberArray = number[];
export type RefRaycaster = React.MutableRefObject<THREE.Raycaster>;
// Camera reference, supporting both perspective and basic cameras
export type RefCamera = React.MutableRefObject<THREE.Camera | THREE.PerspectiveCamera>;
export type RefCamera = React.MutableRefObject<
THREE.Camera | THREE.PerspectiveCamera
>;
/** Three.js Root State Management **/
// Root state of the @react-three/fiber instance, providing context of the scene
export type ThreeState = RootState;
/** Point and Line Types for Spatial Geometry **/
// Defines a point in 3D space with metadata for unique identification
@ -132,11 +130,16 @@ export type RefLine = React.MutableRefObject<Line | [Point] | []>;
// Collection of lines for structured geometry
export type Lines = Array<Line>;
/** Wall and Room Types for 3D Space Management **/
// Defines a wall with its geometry, position, rotation, material, and layer information
export type Wall = [THREE.ExtrudeGeometry, [number, number, number], [number, number, number], string, number];
export type Wall = [
THREE.ExtrudeGeometry,
[number, number, number],
[number, number, number],
string,
number
];
// Collection of walls, useful in scene construction
export type Walls = Array<Wall>;
@ -145,15 +148,22 @@ export type Walls = Array<Wall>;
export type RefWalls = React.MutableRefObject<Walls>;
// Room type, containing coordinates and layer metadata for spatial management
export type Rooms = Array<{ coordinates: Array<{ position: THREE.Vector3, uuid: string }>, layer: number }>;
export type Rooms = Array<{
coordinates: Array<{ position: THREE.Vector3; uuid: string }>;
layer: number;
}>;
// Reference for room objects, enabling updates within React components
export type RefRooms = React.MutableRefObject<Array<{ coordinates: Array<{ position: THREE.Vector3, uuid: string }>, layer: number }>>;
export type RefRooms = React.MutableRefObject<
Array<{
coordinates: Array<{ position: THREE.Vector3; uuid: string }>;
layer: number;
}>
>;
// Reference for lines, supporting React-based state changes
export type RefLines = React.MutableRefObject<Lines>;
/** Floor Line Types for Layered Structures **/
// Floor line type for single lines on the floor level
@ -168,7 +178,6 @@ export type OnlyFloorLines = Array<Lines>;
// Reference for multi-level floor lines, allowing structured updates
export type RefOnlyFloorLines = React.MutableRefObject<OnlyFloorLines>;
/** GeoJSON Line Integration **/
// Structure for representing GeoJSON lines, integrating external data sources
@ -179,7 +188,6 @@ export type GeoJsonLine = {
type: string;
};
/** State Management Types for React Components **/
// Dispatch types for number and boolean states, commonly used in React hooks
@ -189,7 +197,6 @@ export type BooleanState = React.Dispatch<React.SetStateAction<boolean>>;
// Mutable reference for TubeGeometry, allowing dynamic geometry updates
export type RefTubeGeometry = React.MutableRefObject<THREE.TubeGeometry | null>;
/** Floor Item Configuration **/
// Type for individual items placed on the floor, with positioning and rotation metadata
@ -207,8 +214,9 @@ export type FloorItemType = {
export type FloorItems = Array<FloorItemType>;
// Dispatch type for setting floor item state in React
export type setFloorItemSetState = React.Dispatch<React.SetStateAction<FloorItems | null | undefined>>;
export type setFloorItemSetState = React.Dispatch<
React.SetStateAction<FloorItems | null | undefined>
>;
/** Asset Configuration for Loading and Positioning **/
@ -227,14 +235,13 @@ export type AssetConfigurations = {
[key: string]: AssetConfiguration;
};
/** Wall Item Configuration **/
// Configuration for wall items, including model, scale, position, and rotation
interface WallItem {
type: "Fixed-Move" | "Free-Move" | undefined;
model?: THREE.Group;
modeluuid?: string
modeluuid?: string;
modelname?: string;
scale?: [number, number, number];
csgscale?: [number, number, number];
@ -247,10 +254,14 @@ interface WallItem {
export type wallItems = Array<WallItem>;
// Dispatch for setting wall item state in React
export type setWallItemSetState = React.Dispatch<React.SetStateAction<WallItem[]>>;
export type setWallItemSetState = React.Dispatch<
React.SetStateAction<WallItem[]>
>;
// Dispatch for setting vector3 state in React
export type setVector3State = React.Dispatch<React.SetStateAction<THREE.Vector3>>;
export type setVector3State = React.Dispatch<
React.SetStateAction<THREE.Vector3>
>;
// Dispatch for setting euler state in React
export type setEulerState = React.Dispatch<React.SetStateAction<THREE.Euler>>;
@ -258,13 +269,14 @@ export type setEulerState = React.Dispatch<React.SetStateAction<THREE.Euler>>;
// Reference type for wall items, allowing direct access to the mutable array
export type RefWallItems = React.MutableRefObject<WallItem[]>;
/** Wall and Item Selection State Management **/
// State management for selecting, removing, and indexing wall items
export type setRemoveLayerSetState = (layer: number | null) => void;
export type setSelectedWallItemSetState = (item: THREE.Object3D | null) => void;
export type setSelectedFloorItemSetState = (item: THREE.Object3D | null) => void;
export type setSelectedFloorItemSetState = (
item: THREE.Object3D | null
) => void;
export type setSelectedItemsIndexSetState = (index: number | null) => void;
export type RefCSM = React.MutableRefObject<CSM>;
@ -289,14 +301,35 @@ interface ConnectionStore {
interface ConveyorEventsSchema {
modeluuid: string;
modelName: string;
type: 'Conveyor';
type: "Conveyor";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | [];
triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | [];
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
actions:
| {
uuid: string;
name: string;
type: string;
material: string;
delay: number | string;
spawnInterval: number | string;
isUsed: boolean;
}[]
| [];
triggers:
| {
uuid: string;
name: string;
type: string;
isUsed: boolean;
bufferTime: number;
}[]
| [];
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
}[];
position: [number, number, number];
rotation: [number, number, number];
@ -306,49 +339,73 @@ interface ConveyorEventsSchema {
interface VehicleEventsSchema {
modeluuid: string;
modelName: string;
type: 'Vehicle';
type: "Vehicle";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number };
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
speed: number;
actions: {
uuid: string;
name: string;
type: string;
start: { x: number; y: number } | {};
hitCount: number;
end: { x: number; y: number } | {};
buffer: number;
};
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
speed: number;
isPlaying: boolean;
};
position: [number, number, number];
rotation: [number, number, number];
}
interface StaticMachineEventsSchema {
modeluuid: string;
modelName: string;
type: 'StaticMachine';
type: "StaticMachine";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; buffer: number; material: string; };
actions: {
uuid: string;
name: string;
buffer: number | string;
material: string;
isUsed: boolean;
};
triggers: { uuid: string; name: string; type: string };
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
};
position: [number, number, number];
rotation: [number, number, number];
}
interface ArmBotEventsSchema {
modeluuid: string;
modelName: string;
type: 'ArmBot';
type: "ArmBot";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[] };
actions: {
uuid: string;
name: string;
speed: number;
processes: { triggerId: string; startPoint: string; endPoint: string }[];
};
triggers: { uuid: string; name: string; type: string };
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
};
position: [number, number, number];
rotation: [number, number, number];
}
export type EventData = {
@ -359,46 +416,103 @@ export type EventData = {
modelfileID: string;
isLocked: boolean;
isVisible: boolean;
eventData?: {
type: 'Conveyor';
eventData?:
| {
type: "Conveyor";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; type: string; material: string; delay: number | string; spawnInterval: number | string; isUsed: boolean }[] | [];
triggers: { uuid: string; name: string; type: string; isUsed: boolean; bufferTime: number }[] | [];
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
actions:
| {
uuid: string;
name: string;
type: string;
material: string;
delay: number | string;
spawnInterval: number | string;
isUsed: boolean;
}[]
| [];
triggers:
| {
uuid: string;
name: string;
type: string;
isUsed: boolean;
bufferTime: number;
}[]
| [];
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
}[];
speed: number | string;
} | {
type: 'Vehicle';
}
| {
type: "Vehicle";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; type: string; start: { x: number, y: number } | {}, hitCount: number, end: { x: number, y: number } | {}, buffer: number };
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
actions: {
uuid: string;
name: string;
type: string;
start: { x: number; y: number } | {};
hitCount: number;
end: { x: number; y: number } | {};
buffer: number;
};
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
speed: number;
};
} | {
type: 'StaticMachine';
}
| {
type: "StaticMachine";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; buffer: number; material: string; };
triggers: { uuid: string; name: string; type: string };
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
actions: {
uuid: string;
name: string;
buffer: number;
material: string;
};
} | {
type: 'ArmBot';
triggers: { uuid: string; name: string; type: string };
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
};
}
| {
type: "ArmBot";
points: {
uuid: string;
position: [number, number, number];
rotation: [number, number, number];
actions: { uuid: string; name: string; speed: number; processes: { triggerId: string; startPoint: string; endPoint: string }[] };
actions: {
uuid: string;
name: string;
speed: number;
processes: {
triggerId: string;
startPoint: string;
endPoint: string;
}[];
};
triggers: { uuid: string; name: string; type: string };
connections: { source: { modelUUID: string; pointUUID: string }; targets: { modelUUID: string; pointUUID: string }[] };
connections: {
source: { modelUUID: string; pointUUID: string };
targets: { modelUUID: string; pointUUID: string }[];
};
};
}
};
};

View File

@ -0,0 +1,30 @@
// Function to detect if Shift, Ctrl, Alt, or combinations are pressed
// and return the corresponding key combination string
export const detectModifierKeys = (event: KeyboardEvent): string => {
const modifiers = [
event.ctrlKey ? "Ctrl" : "",
event.altKey ? "Alt" : "",
event.shiftKey ? "Shift" : "",
event.metaKey ? "Meta" : "" // Add support for Command/Win key
].filter(Boolean);
// Ignore modifier keys when they're pressed alone
const isModifierKey = [
"Control", "Shift", "Alt", "Meta",
"Ctrl", "AltGraph", "OS" // Additional modifier key aliases
].includes(event.key);
const mainKey = isModifierKey ? "" : event.key.toUpperCase();
// Handle special cases for keys with different representations
const normalizedKey = mainKey === " " ? "Space" : mainKey;
// Build the combination string
if (modifiers.length > 0 && normalizedKey) {
return `${modifiers.join("+")}+${normalizedKey}`;
} else if (modifiers.length > 0) {
return modifiers.join("+");
} else {
return normalizedKey;
}
};

View File

@ -1,85 +1,70 @@
// Importing React and useEffect from React library
import React, { useEffect } from "react";
// Importing the necessary hooks and types from React and TypeScript
// Importing the necessary hooks and types from the application's state management stores
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
import useToggleStore from "../../store/useUIToggleStore";
import { useActiveSubTool, useActiveTool, useAddAction, useDeleteTool, useSelectedWallItem, useToggleView, useToolMode } from "../../store/store";
import {
useActiveSubTool,
useActiveTool,
useAddAction,
useDeleteTool,
useSelectedWallItem,
useToggleView,
useToolMode,
} from "../../store/store";
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
// Utility function to detect modifier keys (Ctrl, Alt, Shift) and key combinations
import { detectModifierKeys } from "./detectModifierKeys";
// KeyPressListener component to handle global keyboard shortcuts
const KeyPressListener: React.FC = () => {
// Function to detect if Shift, Ctrl, Alt, or combinations are pressed
const detectModifierKeys = (event: KeyboardEvent): string => {
const modifiers = [
event.ctrlKey ? "Ctrl" : "",
event.altKey ? "Alt" : "",
event.shiftKey ? "Shift" : "",
event.metaKey ? "Meta" : "" // Add support for Command/Win key
].filter(Boolean);
// Accessing state and actions from different stores
const { activeModule, setActiveModule } = useModuleStore(); // Module management (e.g., builder, simulation, visualization)
const { setActiveSubTool } = useActiveSubTool(); // Sub-tool management
const { toggleUI, setToggleUI } = useToggleStore(); // UI visibility toggle
const { setToggleThreeD } = useThreeDStore(); // 3D view toggle
const { setToolMode } = useToolMode(); // Tool mode management
const { setIsPlaying } = usePlayButtonStore(); // Play button state management
// Ignore modifier keys when they're pressed alone
const isModifierKey = [
"Control", "Shift", "Alt", "Meta",
"Ctrl", "AltGraph", "OS" // Additional modifier key aliases
].includes(event.key);
const mainKey = isModifierKey ? "" : event.key.toUpperCase();
// Handle special cases for keys with different representations
const normalizedKey = mainKey === " " ? "Space" : mainKey;
// Build the combination string
if (modifiers.length > 0 && normalizedKey) {
return `${modifiers.join("+")}+${normalizedKey}`;
} else if (modifiers.length > 0) {
return modifiers.join("+");
} else {
return normalizedKey;
}
};
// Importing the necessary hooks from the store
const { activeModule, setActiveModule } = useModuleStore();
const { setActiveSubTool } = useActiveSubTool();
const { toggleUI, setToggleUI } = useToggleStore();
const { setToggleThreeD } = useThreeDStore();
const { setToolMode } = useToolMode();
const { setIsPlaying } = usePlayButtonStore();
// wall options
const { toggleView, setToggleView } = useToggleView();
const { setDeleteTool } = useDeleteTool();
const { setAddAction } = useAddAction();
const { setSelectedWallItem } = useSelectedWallItem();
const { setActiveTool } = useActiveTool();
// Wall and tool-related actions
const { toggleView, setToggleView } = useToggleView(); // 2D/3D toggle state
const { setDeleteTool } = useDeleteTool(); // Delete tool action
const { setAddAction } = useAddAction(); // Add action management
const { setSelectedWallItem } = useSelectedWallItem(); // Selected wall item management
const { setActiveTool } = useActiveTool(); // Active tool management
// useEffect to manage global keyboard shortcuts
useEffect(() => {
// Function to handle keydown events
const handleKeyPress = (event: KeyboardEvent) => {
// Identify the currently focused element
const activeElement = document.activeElement;
// Check if the user is typing in an input field, textarea, or contenteditable element
const isTyping =
activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement ||
(activeElement && activeElement.getAttribute('contenteditable') === 'true');
(activeElement && activeElement.getAttribute("contenteditable") === "true");
if (isTyping) {
return; // Don't trigger shortcuts while typing
return; // Skip shortcut handling while typing
}
// Detect the combination of keys pressed
const keyCombination = detectModifierKeys(event);
// Allow default behavior for F5 and F12
// Allow browser default behavior for specific keys (e.g., F5 for refresh)
if (["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") {
return;
}
// Prevent default action for the key press
// Prevent the default browser action for other handled key presses
event.preventDefault();
// Detect the key combination pressed
if (keyCombination) {
// Check for specific key combinations to switch modules
// Switch between different modules (e.g., builder, simulation)
if (keyCombination === "1" && !toggleView) {
setActiveTool("cursor");
setActiveSubTool("cursor");
@ -102,12 +87,12 @@ const KeyPressListener: React.FC = () => {
setActiveModule("market");
}
// sidebar toggle
// Toggle UI visibility
if (keyCombination === "Ctrl+." && activeModule !== "market") {
setToggleUI(!toggleUI);
}
// tools toggle
// Tool selection shortcuts
if (keyCombination === "V") {
setActiveTool("cursor");
setActiveSubTool("cursor");
@ -121,14 +106,15 @@ const KeyPressListener: React.FC = () => {
setActiveSubTool("free-hand");
}
// player toggle
// Toggle play mode
if (keyCombination === "Ctrl+P" && !toggleView) {
setIsPlaying(true);
}
// builder key combination
// Builder-specific shortcuts
if (activeModule === "builder") {
if (keyCombination === "TAB") {
// Switch between 2D and 3D views
if (toggleView) {
setToggleView(false);
setToggleThreeD(true);
@ -142,7 +128,8 @@ const KeyPressListener: React.FC = () => {
setActiveTool("cursor");
}
}
// builder tools
// Wall-related tools
if (toggleView) {
if (keyCombination === "Q" || keyCombination === "6") {
setActiveTool("draw-wall");
@ -161,21 +148,34 @@ const KeyPressListener: React.FC = () => {
setToolMode("Floor");
}
}
// Measurement tool
if (keyCombination === "M") {
setActiveTool("measure");
setToolMode("MeasurementScale");
}
}
// Undo redo
// Undo and redo actions
if (keyCombination === "Ctrl+Z") {
// Handle undo action here
// Handle undo action
}
if (keyCombination === "Ctrl+Y" || keyCombination === "Ctrl+Shift+Z") {
// Handle redo action here
// Handle redo action
}
// cleanup function to remove event listener
// Helper actions
if (keyCombination === "Ctrl+H") {
// Open help
}
if (keyCombination === "Ctrl+F") {
// Open find functionality
}
if (keyCombination === "Ctrl+?") {
// Show shortcuts info
}
// Reset to cursor tool and stop play mode
if (keyCombination === "ESCAPE") {
setActiveTool("cursor");
setIsPlaying(false);
@ -183,16 +183,16 @@ const KeyPressListener: React.FC = () => {
}
};
// Add event listener for keydown
// Add keydown event listener
window.addEventListener("keydown", handleKeyPress);
// Cleanup function to remove event listener
// Cleanup function to remove the event listener
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [activeModule, toggleUI, toggleView]); // Empty dependency array ensures this runs only once
}, [activeModule, toggleUI, toggleView]); // Dependencies to reapply effect if these values change
return null; // No UI component to render, so return null
return null; // This component does not render any UI
};
export default KeyPressListener;