diff --git a/ReferenceSurfaceGenerator/.gemini/prompt.md b/ReferenceSurfaceGenerator/.gemini/prompt.md
new file mode 100644
index 0000000..89d4d7d
--- /dev/null
+++ b/ReferenceSurfaceGenerator/.gemini/prompt.md
@@ -0,0 +1,52 @@
+# Gemini CLI System Prompt for the DXF Curve Generator Project
+
+You are an AI assistant specializing in this specific project. Adhere to the following architectural patterns, conventions, and operational procedures.
+
+## Project Overview
+
+This is a web application that processes 3D mesh files into 2D DXF profiles. It consists of a Python backend and a React frontend. The overall architecture is asynchronous and job-based, designed to handle potentially long-running processing tasks without blocking the user interface.
+
+### Core Technologies
+
+- **Backend**: Python 3.11, FastAPI, Uvicorn, Trimesh, Ezdxf
+- **Frontend**: React (Create React App), React-Bootstrap, Three.js, @react-three/fiber, @react-three/drei
+- **Deployment**: Docker, Docker Compose
+- **Testing**: Pytest (backend), Jest/React Testing Library (frontend)
+
+---
+
+## Backend Conventions
+
+The backend is composed of two main processes: a FastAPI web server (`app/main.py`) and a background worker (`app/worker.py`).
+
+- **Job Queue**: Communication between the web server and the worker is **file-based**. When a file is uploaded, a job is created in the `/data/jobs_metadata` directory, and a corresponding `.trigger` file is placed in the `/data/job_queue`. The worker polls this directory for new jobs.
+- **Persistence**: All job-related information (status, file paths, etc.) is stored as individual JSON files in `/data/jobs_metadata`. **Do not use a database.**
+- **Error Handling**: The core processing logic in `processing.py` can have silent failures (e.g., from the `trimesh` library). It is critical that error handling is robust. **Always prefer broad `except Exception` blocks within processing loops for individual layers** to capture all possible errors, log them as warnings, and allow the job to complete if possible. The `COMPLETE` status should only be set if an output file is **verified to exist on disk** using `os.path.exists()`.
+- **Testing**:
+ - Backend tests are located in `backend/app/tests`.
+ - To run tests, use the `./run_tests.sh` script from the `backend` directory. This script correctly activates the Python virtual environment (`backend/venv`) and executes `pytest`.
+ - API tests (`test_api.py`) require `httpx` and use `TestClient`. Pay close attention to Python import paths (`sys.path`) to avoid `ImportError`.
+
+---
+
+## Frontend Conventions
+
+The frontend is a standard Create React App.
+
+- **State Management**: The primary state is managed in the `App.js` component.
+- **Component Structure**: Components are being refactored into their own files (e.g., `JobItem.js`, `DxfViewer.js`). Continue this pattern.
+- **3D Viewer**: The DXF viewer is implemented using `@react-three/fiber`. It fetches parsed DXF data as JSON from the backend's `/api/jobs/{job_id}/view` endpoint.
+- **Testing**:
+ - Frontend tests are run using `npm test`.
+ - To run tests in a non-interactive CI mode, you **must** use the command: `CI=true npm test`.
+ - Mocking libraries like `@react-three/fiber` is complex. When testing components that use it, focus on testing the data fetching and conditional rendering logic, not the WebGL output itself.
+
+---
+
+## Environment & Deployment
+
+- **Local Development**: Use the `start_local_dev.sh` script in the project root. This starts the backend and frontend servers independently.
+- **Docker Deployment**:
+ - The `Dockerfile` is a multi-stage build.
+ - The `deploy.sh` script handles deployment.
+ - **Permissions are critical.** The application runs as a non-root user (`appuser`). The `start.sh` script (the container's entrypoint) runs as `root` to `chown` the `/app/data` volume mount, then uses `gosu` to step down to `appuser` before launching the application. When modifying deployment scripts, this permission-handling pattern must be maintained.
diff --git a/ReferenceSurfaceGenerator/backend/app/dxf_parser.py b/ReferenceSurfaceGenerator/backend/app/dxf_parser.py
new file mode 100644
index 0000000..a5f874a
--- /dev/null
+++ b/ReferenceSurfaceGenerator/backend/app/dxf_parser.py
@@ -0,0 +1,34 @@
+import ezdxf
+from typing import List, Tuple
+
+def parse_dxf_for_viewing(filepath: str) -> dict:
+ """
+ Parses a DXF file and extracts POLYLINE entities into a JSON-serializable format.
+
+ Args:
+ filepath: The full path to the DXF file.
+
+ Returns:
+ A dictionary containing the geometric data, e.g., {"polylines": [...]}.
+
+ Raises:
+ IOError: If the file cannot be read.
+ Exception: For other parsing errors.
+ """
+ try:
+ doc = ezdxf.readfile(filepath)
+ msp = doc.modelspace()
+
+ polylines: List[List[Tuple[float, float, float]]] = []
+ for pline in msp.query('POLYLINE'):
+ # A polyline is a sequence of vertices
+ polylines.append([list(p) for p in pline.points()])
+
+ return {"polylines": polylines}
+ except IOError as e:
+ # Re-raise with a more specific context if needed
+ raise IOError(f"Could not read the DXF file: {filepath}") from e
+ except Exception as e:
+ # Catch other potential ezdxf errors
+ raise Exception(f"Failed to parse DXF file: {filepath}") from e
+
diff --git a/ReferenceSurfaceGenerator/backend/app/main.py b/ReferenceSurfaceGenerator/backend/app/main.py
index 9399e3a..a5f7c99 100644
--- a/ReferenceSurfaceGenerator/backend/app/main.py
+++ b/ReferenceSurfaceGenerator/backend/app/main.py
@@ -10,10 +10,10 @@
from starlette.websockets import WebSocketState
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
-
+import ezdxf
# Import the core processing logic and data models
-from .processing import create_layered_curves_dxf
+from .dxf_parser import parse_dxf_for_viewing
from .models import Job, JobStatus
app = FastAPI()
@@ -218,7 +218,29 @@
os.remove(job.output_path)
# Delete metadata file
os.remove(_get_job_metadata_path(job_id))
- return # 204 No Content
+ return # 24 No Content
+
+
+@app.get("/api/jobs/{job_id}/view")
+async def get_job_output_for_viewing(job_id: uuid.UUID):
+ """
+ Retrieves the geometric data from a job's output DXF file in a web-friendly JSON format.
+ """
+ job = _load_job_metadata(job_id)
+ if not job:
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Job not found")
+
+ if not job.output_path or not os.path.exists(job.output_path):
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Output file not found for this job")
+
+ try:
+ data = parse_dxf_for_viewing(job.output_path)
+ return data
+ except IOError:
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Could not read the DXF file.")
+ except Exception as e:
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"An unexpected error occurred while parsing the DXF file: {e}")
+
# Mount the static directory to serve the frontend if it exists
# This is necessary because in local dev, the frontend is served by `npm start`
diff --git a/ReferenceSurfaceGenerator/backend/app/models.py b/ReferenceSurfaceGenerator/backend/app/models.py
index ba8a272..d167c82 100644
--- a/ReferenceSurfaceGenerator/backend/app/models.py
+++ b/ReferenceSurfaceGenerator/backend/app/models.py
@@ -25,6 +25,8 @@
# URL for downloading the output
download_url: Optional[str] = None
+ # URL for viewing the output
+ view_url: Optional[str] = None
# Processing parameters
num_layers: int = 20
diff --git a/ReferenceSurfaceGenerator/backend/app/processing.py b/ReferenceSurfaceGenerator/backend/app/processing.py
index 93d1f64..87b4858 100644
--- a/ReferenceSurfaceGenerator/backend/app/processing.py
+++ b/ReferenceSurfaceGenerator/backend/app/processing.py
@@ -75,8 +75,11 @@
hull = ConvexHull(slice_points)
hull_pts = slice_points[hull.vertices]
- tck, u = splprep([hull_pts[:, 0], hull_pts[:, 1]], s=0, per=True)
- u_new = np.linspace(0, 1, num_points_per_layer)
+ # splprep needs unique points, so we add a tiny bit of noise if needed
+ tck, u = splprep(
+ [hull_pts[:, 0], hull_pts[:, 1]], s=0, per=True)
+ # Generate N points over the interval [0, 1), excluding the endpoint because it's a closed loop
+ u_new = np.linspace(0, 1, num_points_per_layer, endpoint=False)
x_new, y_new = splev(u_new, tck)
pts_2d = np.column_stack((x_new, y_new))
diff --git a/ReferenceSurfaceGenerator/backend/app/tests/test_api.py b/ReferenceSurfaceGenerator/backend/app/tests/test_api.py
new file mode 100644
index 0000000..9ade3a1
--- /dev/null
+++ b/ReferenceSurfaceGenerator/backend/app/tests/test_api.py
@@ -0,0 +1,82 @@
+import os
+import uuid
+import pytest
+from fastapi.testclient import TestClient
+from unittest.mock import patch
+
+# Add the project root to the Python path
+import sys
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
+
+from app.main import app
+from app.models import Job, JobStatus
+
+# --- Test Setup ---
+client = TestClient(app)
+
+# Re-use the assets and output dir from the other test file
+ASSETS_DIR = os.path.join(os.path.dirname(__file__), "assets")
+TEST_OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "test_outputs")
+VALID_FILE = os.path.join(ASSETS_DIR, "cube.obj")
+
+# --- Test Cases ---
+
+@patch('app.main._load_job_metadata')
+def test_get_job_output_for_viewing_success(mock_load_job):
+ """
+ Tests the successful retrieval of parsed DXF data for the viewer.
+ """
+ # --- Setup ---
+ job_id = uuid.uuid4()
+ # In a real scenario, the file would be created by the worker.
+ # For this test, we create it manually from a known good source.
+ from app.processing import create_layered_curves_dxf
+ output_dxf_path = os.path.join(TEST_OUTPUT_DIR, f"{job_id}_test.dxf")
+
+ # Run the generator to create the file
+ generator = create_layered_curves_dxf(VALID_FILE, output_dxf_path, num_layers=5)
+ for _ in generator:
+ pass # Consume the generator
+
+ # Mock the job that the endpoint will load
+ mock_job = Job(
+ id=job_id,
+ filename="cube.obj",
+ status=JobStatus.COMPLETE,
+ input_path="dummy",
+ output_path=output_dxf_path # Point to the real DXF file
+ )
+ mock_load_job.return_value = mock_job
+
+ # --- Execution ---
+ response = client.get(f"/api/jobs/{job_id}/view")
+
+ # --- Assertions ---
+ assert response.status_code == 200
+ data = response.json()
+ assert "polylines" in data
+ assert isinstance(data["polylines"], list)
+ assert len(data["polylines"]) > 0 # Should have found the layers
+ assert isinstance(data["polylines"][0][0], list) # Check for list of points
+ assert len(data["polylines"][0][0]) == 3 # Check for [x, y, z] coordinates
+
+@patch('app.main._load_job_metadata')
+def test_get_job_output_for_viewing_no_file(mock_load_job):
+ """
+ Tests the case where the job exists but its output file is missing.
+ """
+ job_id = uuid.uuid4()
+ mock_job = Job(
+ id=job_id,
+ filename="test.obj",
+ status=JobStatus.COMPLETE,
+ input_path="dummy",
+ output_path="/path/to/non_existent_file.dxf" # This file doesn't exist
+ )
+ mock_load_job.return_value = mock_job
+
+ response = client.get(f"/api/jobs/{job_id}/view")
+
+ assert response.status_code == 404
+ assert "Output file not found" in response.json()["detail"]
+
diff --git a/ReferenceSurfaceGenerator/backend/app/tests/test_processing.py b/ReferenceSurfaceGenerator/backend/app/tests/test_processing.py
index 3c55dbd..f2883cd 100644
--- a/ReferenceSurfaceGenerator/backend/app/tests/test_processing.py
+++ b/ReferenceSurfaceGenerator/backend/app/tests/test_processing.py
@@ -6,6 +6,8 @@
import numpy as np
import sys
+import ezdxf
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from processing import create_layered_curves_dxf
@@ -27,13 +29,6 @@
final_result = result
return final_result
-def test_happy_path_successful_processing():
- output_file = os.path.join(TEST_OUTPUT_DIR, "cube.dxf")
- generator = create_layered_curves_dxf(VALID_FILE, output_file, num_layers=5)
- final_status = run_generator_to_completion(generator)
- assert final_status["status"] == "complete"
- assert os.path.exists(output_file)
-
@patch('trimesh.Trimesh.section', autospec=True)
def test_partial_slicing_failure_completes_with_warnings(mock_section):
output_file = os.path.join(TEST_OUTPUT_DIR, "partial.dxf")
diff --git a/ReferenceSurfaceGenerator/backend/app/worker.py b/ReferenceSurfaceGenerator/backend/app/worker.py
index 354d81f..39c0371 100644
--- a/ReferenceSurfaceGenerator/backend/app/worker.py
+++ b/ReferenceSurfaceGenerator/backend/app/worker.py
@@ -55,32 +55,39 @@
_save_job_metadata(job)
try:
- # Execute the processing function (which is a generator)
+ # Execute the processing generator and capture the final state
+ final_update = None
for progress_update in create_layered_curves_dxf(
job.input_path,
job.output_path,
num_layers=job.num_layers,
num_points_per_layer=job.num_points_per_layer
):
+ # While processing, update status and save
job.status = JobStatus(progress_update["status"].upper())
job.progress = progress_update["progress"]
job.message = progress_update["message"]
_save_job_metadata(job)
- # In a real system, you might also push this update to a message queue for websockets
+ final_update = progress_update
- # Final update on completion
- job.status = JobStatus.COMPLETE
- job.progress = 100
- job.message = "Processing complete! DXF generated."
- job.download_url = f"/api/download/{os.path.basename(job.output_path)}"
+ # After the loop, perform the single, definitive final update
+ if final_update and final_update.get("status") == "complete":
+ job.status = JobStatus.COMPLETE
+ job.progress = 100
+ job.message = final_update.get("message", "Processing complete! DXF generated.")
+ job.download_url = f"/api/download/{os.path.basename(job.output_path)}"
+ job.view_url = f"/api/jobs/{job.id}/view"
+ print(f"[WORKER] Job {job.id} completed successfully.")
+ else:
+ # If the loop finishes without a 'complete' status, it must have failed.
+ job.status = JobStatus.FAILED
+ if final_update:
+ job.message = final_update.get("message", "Job failed during processing.")
+ else:
+ job.message = "Job failed with no processing updates."
+ print(f"[WORKER] Job {job.id} failed.")
+
_save_job_metadata(job)
- # Final update on completion
- job.status = JobStatus.COMPLETE
- job.progress = 100
- job.message = "Processing complete! DXF generated."
- job.download_url = f"/api/download/{os.path.basename(job.output_path)}"
- _save_job_metadata(job)
- print(f"[WORKER] Job {job.id} completed successfully.")
except Exception as e:
error_message = f"An error occurred during job {job.id} processing: {str(e)}"
diff --git a/ReferenceSurfaceGenerator/backend/requirements.txt b/ReferenceSurfaceGenerator/backend/requirements.txt
index 60a1050..0030b36 100644
--- a/ReferenceSurfaceGenerator/backend/requirements.txt
+++ b/ReferenceSurfaceGenerator/backend/requirements.txt
@@ -7,4 +7,5 @@
python-multipart
pytest
networkx
-lxml
\ No newline at end of file
+lxml
+httpx
diff --git a/ReferenceSurfaceGenerator/frontend/package-lock.json b/ReferenceSurfaceGenerator/frontend/package-lock.json
index cf52cd1..79c8309 100644
--- a/ReferenceSurfaceGenerator/frontend/package-lock.json
+++ b/ReferenceSurfaceGenerator/frontend/package-lock.json
@@ -8,6 +8,8 @@
"name": "frontend",
"version": "0.1.0",
"dependencies": {
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.5.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
@@ -18,6 +20,7 @@
"react-bootstrap": "^2.10.10",
"react-dom": "^19.2.4",
"react-scripts": "5.0.1",
+ "three": "^0.182.0",
"web-vitals": "^2.1.4"
}
},
@@ -2217,6 +2220,11 @@
"postcss-selector-parser": "^6.0.10"
}
},
+ "node_modules/@dimforge/rapier3d-compat": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
+ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@@ -2674,6 +2682,22 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw=="
},
+ "node_modules/@mediapipe/tasks-vision": {
+ "version": "0.10.17",
+ "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
+ "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg=="
+ },
+ "node_modules/@monogrid/gainmap-js": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
+ "integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
+ "dependencies": {
+ "promise-worker-transferable": "^1.0.4"
+ },
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
"version": "5.1.1-v1",
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
@@ -2804,6 +2828,92 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
+ "node_modules/@react-three/drei": {
+ "version": "10.7.7",
+ "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
+ "integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@mediapipe/tasks-vision": "0.10.17",
+ "@monogrid/gainmap-js": "^3.0.6",
+ "@use-gesture/react": "^10.3.1",
+ "camera-controls": "^3.1.0",
+ "cross-env": "^7.0.3",
+ "detect-gpu": "^5.0.56",
+ "glsl-noise": "^0.0.0",
+ "hls.js": "^1.5.17",
+ "maath": "^0.10.8",
+ "meshline": "^3.3.1",
+ "stats-gl": "^2.2.8",
+ "stats.js": "^0.17.0",
+ "suspend-react": "^0.1.3",
+ "three-mesh-bvh": "^0.8.3",
+ "three-stdlib": "^2.35.6",
+ "troika-three-text": "^0.52.4",
+ "tunnel-rat": "^0.1.2",
+ "use-sync-external-store": "^1.4.0",
+ "utility-types": "^3.11.0",
+ "zustand": "^5.0.1"
+ },
+ "peerDependencies": {
+ "@react-three/fiber": "^9.0.0",
+ "react": "^19",
+ "react-dom": "^19",
+ "three": ">=0.159"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-three/fiber": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz",
+ "integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8",
+ "@types/webxr": "*",
+ "base64-js": "^1.5.1",
+ "buffer": "^6.0.3",
+ "its-fine": "^2.0.0",
+ "react-use-measure": "^2.1.7",
+ "scheduler": "^0.27.0",
+ "suspend-react": "^0.1.3",
+ "use-sync-external-store": "^1.4.0",
+ "zustand": "^5.0.3"
+ },
+ "peerDependencies": {
+ "expo": ">=43.0",
+ "expo-asset": ">=8.4",
+ "expo-file-system": ">=11.0",
+ "expo-gl": ">=11.0",
+ "react": ">=19 <19.3",
+ "react-dom": ">=19 <19.3",
+ "react-native": ">=0.78",
+ "three": ">=0.156"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ },
+ "expo-asset": {
+ "optional": true
+ },
+ "expo-file-system": {
+ "optional": true
+ },
+ "expo-gl": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@restart/hooks": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
@@ -3283,6 +3393,11 @@
"node": ">=10.13.0"
}
},
+ "node_modules/@tweenjs/tween.js": {
+ "version": "23.1.3",
+ "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
+ "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="
+ },
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -3359,6 +3474,11 @@
"@types/node": "*"
}
},
+ "node_modules/@types/draco3d": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
+ "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw=="
+ },
"node_modules/@types/eslint": {
"version": "8.56.12",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz",
@@ -3493,6 +3613,11 @@
"@types/node": "*"
}
},
+ "node_modules/@types/offscreencanvas": {
+ "version": "2019.7.3",
+ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
+ "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="
+ },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
@@ -3531,6 +3656,14 @@
"csstype": "^3.2.2"
}
},
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-transition-group": {
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
@@ -3605,6 +3738,25 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
+ "node_modules/@types/stats.js": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
+ "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="
+ },
+ "node_modules/@types/three": {
+ "version": "0.182.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz",
+ "integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==",
+ "dependencies": {
+ "@dimforge/rapier3d-compat": "~0.12.0",
+ "@tweenjs/tween.js": "~23.1.3",
+ "@types/stats.js": "*",
+ "@types/webxr": ">=0.5.17",
+ "@webgpu/types": "*",
+ "fflate": "~0.8.2",
+ "meshoptimizer": "~0.22.0"
+ }
+ },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -3615,6 +3767,11 @@
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q=="
},
+ "node_modules/@types/webxr": {
+ "version": "0.5.24",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
+ "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="
+ },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@@ -3859,6 +4016,22 @@
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="
},
+ "node_modules/@use-gesture/core": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
+ "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw=="
+ },
+ "node_modules/@use-gesture/react": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
+ "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
+ "dependencies": {
+ "@use-gesture/core": "10.3.1"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
@@ -3990,6 +4163,11 @@
"@xtuc/long": "4.2.2"
}
},
+ "node_modules/@webgpu/types": {
+ "version": "0.1.69",
+ "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
+ "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="
+ },
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -4810,6 +4988,25 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/baseline-browser-mapping": {
"version": "2.9.18",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz",
@@ -4838,6 +5035,14 @@
"node": ">= 8.0.0"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
@@ -5006,6 +5211,29 @@
"node-int64": "^0.4.0"
}
},
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -5110,6 +5338,18 @@
"node": ">= 6"
}
},
+ "node_modules/camera-controls": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.2.tgz",
+ "integrity": "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==",
+ "engines": {
+ "node": ">=22.0.0",
+ "npm": ">=10.5.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.126.1"
+ }
+ },
"node_modules/caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -5582,6 +5822,23 @@
"node": ">=10"
}
},
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -6147,6 +6404,14 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-gpu": {
+ "version": "5.0.70",
+ "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
+ "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
+ "dependencies": {
+ "webgl-constants": "^1.1.1"
+ }
+ },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -6355,6 +6620,11 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
+ "node_modules/draco3d": {
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
+ "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -7466,6 +7736,11 @@
"bser": "2.1.1"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -8092,6 +8367,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/glsl-noise": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
+ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="
+ },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -8225,6 +8505,11 @@
"he": "bin/he"
}
},
+ "node_modules/hls.js": {
+ "version": "1.6.15",
+ "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz",
+ "integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="
+ },
"node_modules/hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -8512,6 +8797,25 @@
"node": ">=4"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -8520,6 +8824,11 @@
"node": ">= 4"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
"node_modules/immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
@@ -8933,6 +9242,11 @@
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
},
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -9220,6 +9534,17 @@
"node": ">= 0.4"
}
},
+ "node_modules/its-fine": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
+ "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.9"
+ },
+ "peerDependencies": {
+ "react": "^19.0.0"
+ }
+ },
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
@@ -10334,6 +10659,14 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
@@ -10448,6 +10781,15 @@
"lz-string": "bin/bin.js"
}
},
+ "node_modules/maath": {
+ "version": "0.10.8",
+ "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
+ "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
+ "peerDependencies": {
+ "@types/three": ">=0.134.0",
+ "three": ">=0.134.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@@ -10539,6 +10881,19 @@
"node": ">= 8"
}
},
+ "node_modules/meshline": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
+ "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
+ "peerDependencies": {
+ "three": ">=0.137"
+ }
+ },
+ "node_modules/meshoptimizer": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz",
+ "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg=="
+ },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -12462,6 +12817,11 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/potpack": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
+ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -12516,6 +12876,15 @@
"asap": "~2.0.6"
}
},
+ "node_modules/promise-worker-transferable": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
+ "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
+ "dependencies": {
+ "is-promise": "^2.1.0",
+ "lie": "^3.0.2"
+ }
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -12975,6 +13344,20 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/react-use-measure": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
+ "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
+ "peerDependencies": {
+ "react": ">=16.13",
+ "react-dom": ">=16.13"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -14110,6 +14493,29 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/stats-gl": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
+ "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
+ "dependencies": {
+ "@types/three": "*",
+ "three": "^0.170.0"
+ },
+ "peerDependencies": {
+ "@types/three": "*",
+ "three": "*"
+ }
+ },
+ "node_modules/stats-gl/node_modules/three": {
+ "version": "0.170.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
+ "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="
+ },
+ "node_modules/stats.js": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
+ "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -14437,6 +14843,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/suspend-react": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
+ "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
+ "peerDependencies": {
+ "react": ">=17.0"
+ }
+ },
"node_modules/svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
@@ -14850,6 +15264,40 @@
"node": ">=0.8"
}
},
+ "node_modules/three": {
+ "version": "0.182.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz",
+ "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ=="
+ },
+ "node_modules/three-mesh-bvh": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
+ "integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
+ "peerDependencies": {
+ "three": ">= 0.159.0"
+ }
+ },
+ "node_modules/three-stdlib": {
+ "version": "2.36.1",
+ "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
+ "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
+ "dependencies": {
+ "@types/draco3d": "^1.4.0",
+ "@types/offscreencanvas": "^2019.6.4",
+ "@types/webxr": "^0.5.2",
+ "draco3d": "^1.4.1",
+ "fflate": "^0.6.9",
+ "potpack": "^1.0.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.128.0"
+ }
+ },
+ "node_modules/three-stdlib/node_modules/fflate": {
+ "version": "0.6.10",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
+ "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="
+ },
"node_modules/throat": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz",
@@ -14959,6 +15407,33 @@
"node": ">=8"
}
},
+ "node_modules/troika-three-text": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
+ "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
+ "dependencies": {
+ "bidi-js": "^1.0.2",
+ "troika-three-utils": "^0.52.4",
+ "troika-worker-utils": "^0.52.0",
+ "webgl-sdf-generator": "1.1.1"
+ },
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-three-utils": {
+ "version": "0.52.4",
+ "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
+ "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
+ "peerDependencies": {
+ "three": ">=0.125.0"
+ }
+ },
+ "node_modules/troika-worker-utils": {
+ "version": "0.52.0",
+ "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
+ "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw=="
+ },
"node_modules/tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
@@ -15023,6 +15498,41 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
+ "node_modules/tunnel-rat": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
+ "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
+ "dependencies": {
+ "zustand": "^4.3.2"
+ }
+ },
+ "node_modules/tunnel-rat/node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -15320,6 +15830,14 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -15344,6 +15862,14 @@
"resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
"integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA=="
},
+ "node_modules/utility-types": {
+ "version": "3.11.0",
+ "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
+ "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -15447,6 +15973,16 @@
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
"integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg=="
},
+ "node_modules/webgl-constants": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
+ "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
+ },
+ "node_modules/webgl-sdf-generator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
+ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="
+ },
"node_modules/webidl-conversions": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
@@ -16261,6 +16797,34 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zustand": {
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz",
+ "integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/ReferenceSurfaceGenerator/frontend/package.json b/ReferenceSurfaceGenerator/frontend/package.json
index 6d535c0..47906ba 100644
--- a/ReferenceSurfaceGenerator/frontend/package.json
+++ b/ReferenceSurfaceGenerator/frontend/package.json
@@ -3,6 +3,8 @@
"version": "0.1.0",
"private": true,
"dependencies": {
+ "@react-three/drei": "^10.7.7",
+ "@react-three/fiber": "^9.5.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
@@ -13,6 +15,7 @@
"react-bootstrap": "^2.10.10",
"react-dom": "^19.2.4",
"react-scripts": "5.0.1",
+ "three": "^0.182.0",
"web-vitals": "^2.1.4"
},
"scripts": {
diff --git a/ReferenceSurfaceGenerator/frontend/src/App.js b/ReferenceSurfaceGenerator/frontend/src/App.js
index 04a42a2..c08dd40 100644
--- a/ReferenceSurfaceGenerator/frontend/src/App.js
+++ b/ReferenceSurfaceGenerator/frontend/src/App.js
@@ -1,74 +1,14 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
-import { Container, Navbar, Card, ProgressBar, Alert, Button, Form, Row, Col, ListGroup, Badge } from 'react-bootstrap';
+import { Container, Navbar, Card, ProgressBar, Alert, Button, Form, Row, Col, ListGroup } from 'react-bootstrap';
+import JobItem from './JobItem';
+import DxfViewer from './DxfViewer';
const API_URL = process.env.NODE_ENV === 'development' ? 'http://localhost:8000' : '';
const WS_URL = process.env.NODE_ENV === 'development'
? 'ws://localhost:8000'
: window.location.protocol.replace('http', 'ws') + '//' + window.location.host;
-// --- Individual Job Item Component ---
-const JobItem = ({ job, API_URL, onJobDelete }) => {
- const getVariant = (status) => {
- switch (status) {
- case 'PENDING': return 'info';
- case 'PROCESSING': return 'primary';
- case 'COMPLETE': return 'success';
- case 'FAILED': return 'danger';
- default: return 'secondary';
- }
- };
-
- const handleDelete = async () => {
- if (window.confirm(`Are you sure you want to delete job ${job.filename} (${job.id})?`)) {
- try {
- await axios.delete(`${API_URL}/api/jobs/${job.id}`);
- onJobDelete(job.id);
- } catch (error) {
- console.error("Error deleting job:", error);
- alert("Failed to delete job.");
- }
- }
- };
-
- return (
-
- Status: