merged to main
|
@ -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 project’s 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 don’t 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.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 480 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 1.2 MiB |
|
@ -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;
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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 && (
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
const Mesh: React.FC = () => {
|
||||
return <mesh></mesh>;
|
||||
};
|
||||
|
||||
export default Mesh;
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,6 +631,7 @@ input {
|
|||
|
||||
input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
|
||||
&::placeholder {
|
||||
|
|
|
@ -130,7 +130,6 @@
|
|||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
|
||||
.widget-left-sideBar {
|
||||
.widgets-wrapper {
|
||||
|
||||
|
@ -542,7 +541,6 @@
|
|||
|
||||
.floating {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1074,24 +1072,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 {
|
||||
|
@ -1118,30 +1154,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }[];
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
|
|