meshvault 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Laurent-Philippe Albou (contact@abstractcore.ai)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: meshvault
3
+ Version: 0.1.0
4
+ Summary: A professional 3D asset browser for rapid browsing, previewing, and managing OBJ/FBX files
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: 3d,asset-browser,obj,fbx,three.js,viewer,meshvault
8
+ Author: Laurent-Philippe Albou
9
+ Author-email: contact@abstractcore.ai
10
+ Requires-Python: >=3.10,<4.0
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
25
+ Classifier: Topic :: Multimedia :: Graphics :: Viewers
26
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
27
+ Requires-Dist: fastapi (>=0.115.0,<0.116.0)
28
+ Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
29
+ Requires-Dist: rarfile (>=4.2,<5.0)
30
+ Requires-Dist: trimesh (>=4.4.0,<5.0.0)
31
+ Requires-Dist: uvicorn[standard] (>=0.30.0,<0.31.0)
32
+ Project-URL: Documentation, https://github.com/lpalbou/meshvault/tree/main/docs
33
+ Project-URL: Homepage, https://github.com/lpalbou/meshvault
34
+ Project-URL: Repository, https://github.com/lpalbou/meshvault
35
+ Description-Content-Type: text/markdown
36
+
37
+ # MeshVault
38
+
39
+ A professional, local web-based tool for rapidly browsing, previewing, and managing 3D assets (`.obj`, `.fbx`, `.gltf`, `.glb`, `.stl`) across your filesystem — including assets buried inside `.zip` and `.rar` archives.
40
+
41
+ [![CI](https://github.com/lpalbou/meshvault/actions/workflows/ci.yml/badge.svg)](https://github.com/lpalbou/meshvault/actions)
42
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://python.org)
43
+ [![FastAPI](https://img.shields.io/badge/backend-FastAPI-009688)](https://fastapi.tiangolo.com)
44
+ [![Three.js](https://img.shields.io/badge/3D-Three.js%20r170-black)](https://threejs.org)
45
+ [![PyPI](https://img.shields.io/pypi/v/meshvault)](https://pypi.org/project/meshvault/)
46
+ [![npm](https://img.shields.io/npm/v/meshvault)](https://www.npmjs.com/package/meshvault)
47
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
48
+
49
+ ---
50
+
51
+ ## Features
52
+
53
+ | Feature | Description |
54
+ |---------|-------------|
55
+ | **Folder Browsing** | Navigate your filesystem with a clean sidebar tree. Go up, go home, double-click to enter. List/grid view toggle. |
56
+ | **Search & Filter** | Real-time search input to filter folders and assets in the current directory. |
57
+ | **3D Asset Detection** | Finds `.obj`, `.fbx`, `.gltf`, `.glb`, `.stl` files — including inside `.zip` and `.rar` archives. |
58
+ | **Interactive 3D Viewer** | Click an asset to load it with high-quality PBR rendering, SSAO, soft shadows, tone mapping. |
59
+ | **Orbit / FPV Toggle** | Orbit mode (mouse orbit/zoom/pan, right-click pivot) and FPV drone mode (WASD fly, A/D yaw, mouse look). |
60
+ | **Viewer Toolbar** | Toggle grid, XYZ axes (colored + labeled), wireframe, and light controls from the top-right toolbar. |
61
+ | **Light Controls** | Adjustable key/fill/ambient intensity, light direction (azimuth/elevation), and exposure. |
62
+ | **Background Presets** | 12 background color swatches (dark, gray, light, tinted) for evaluating models on any backdrop. |
63
+ | **Model Transforms** | Center at origin, ground on Y=0, auto-orient via PCA, reset to original. All without moving the camera. |
64
+ | **Model Scaling** | Real-time scale slider (0.25×–2.0×). |
65
+ | **Modified Export** | Export applies all transforms (center, ground, orient, scale) — saves modified OBJ via Three.js OBJExporter. |
66
+ | **FBX Auto-Conversion** | Old FBX files (version < 7000) are auto-converted to OBJ via a built-in binary parser. |
67
+ | **Persistent Settings** | Scene settings (wireframe, grid, axes, background) persist across model loads. |
68
+
69
+ ## Quick Start
70
+
71
+ ### Prerequisites
72
+
73
+ - **Python 3.10+**
74
+ - **Poetry** ([install guide](https://python-poetry.org/docs/#installation))
75
+ - For `.rar` support, one of: `bsdtar`, `unrar`, `7z`, or `unar` (auto-detected)
76
+
77
+ ### Install & Run
78
+
79
+ ```bash
80
+ git clone https://github.com/lpalbou/meshvault.git
81
+ cd meshvault
82
+ poetry install --no-root
83
+ poetry run meshvault
84
+ ```
85
+
86
+ Then open **http://localhost:8420** in your browser.
87
+
88
+ ### Install from PyPI
89
+
90
+ ```bash
91
+ pip install meshvault
92
+ meshvault
93
+ ```
94
+
95
+ ### Install from NPM
96
+
97
+ ```bash
98
+ npx meshvault
99
+ ```
100
+
101
+ ## Usage
102
+
103
+ 1. **Browse**: Navigate folders in the sidebar. Toggle list/grid view. Filter by name.
104
+ 2. **Preview**: Click any 3D asset to load it in the viewer (green=OBJ, orange=FBX, cyan=GLTF, purple=STL/archived).
105
+ 3. **Navigate**: Orbit mode (left-drag orbit, scroll zoom, right-drag pan, right-click pivot) or FPV drone mode (W/Shift forward, S/Ctrl backward, A/D yaw, E/Q altitude). Spacebar resets camera.
106
+ 4. **Scene tools**: Toggle grid, axes (XYZ), wireframe, and lighting from the toolbar. Pick background color from swatches.
107
+ 5. **Transform**: Center model at origin, ground it on Y=0, or auto-orient via PCA. Reset undoes all transforms.
108
+ 6. **Export**: Set name and path in the top bar, click Export. Modified models (centered/oriented/scaled) are exported as OBJ with baked transforms.
109
+
110
+ ## Project Structure
111
+
112
+ ```
113
+ meshvault/
114
+ ├── backend/
115
+ │ ├── app.py # FastAPI server + routes
116
+ │ ├── file_browser.py # Filesystem navigation + asset discovery
117
+ │ ├── archive_inspector.py # ZIP/RAR inspection + multi-tool extraction
118
+ │ ├── export_manager.py # Asset export with renaming
119
+ │ └── fbx_converter.py # FBX 6100 binary parser + OBJ converter
120
+ ├── frontend/
121
+ │ ├── index.html # Main HTML page
122
+ │ ├── css/styles.css # Dark professional theme
123
+ │ └── js/
124
+ │ ├── app.js # Main orchestrator
125
+ │ ├── file_browser.js # File browser + search + grid/list
126
+ │ ├── viewer_3d.js # Three.js 3D viewer
127
+ │ └── export_panel.js # Rename/export controls
128
+ ├── tests/
129
+ │ └── test_file_browser.py # Backend unit tests
130
+ ├── docs/ # Full documentation
131
+ ├── pyproject.toml # Poetry / PyPI configuration
132
+ ├── package.json # NPM configuration
133
+ └── poetry.lock
134
+ ```
135
+
136
+ ## Documentation
137
+
138
+ - [Getting Started](docs/getting_started.md) — Installation, first run, basic usage
139
+ - [Architecture](docs/architecture.md) — System design, components, design decisions
140
+ - [API Reference](docs/api.md) — REST API endpoints, request/response schemas
141
+ - [FAQ](docs/faq.md) — Common questions and troubleshooting
142
+
143
+ ## Tests
144
+
145
+ ```bash
146
+ poetry run pytest tests/ -v
147
+ ```
148
+
149
+ ## Contributing
150
+
151
+ Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/lpalbou/meshvault).
152
+
153
+ ## License
154
+
155
+ MIT License — see [LICENSE](LICENSE) for details.
156
+
157
+ © 2026 Laurent-Philippe Albou — contact@abstractcore.ai
158
+
@@ -0,0 +1,121 @@
1
+ # MeshVault
2
+
3
+ A professional, local web-based tool for rapidly browsing, previewing, and managing 3D assets (`.obj`, `.fbx`, `.gltf`, `.glb`, `.stl`) across your filesystem — including assets buried inside `.zip` and `.rar` archives.
4
+
5
+ [![CI](https://github.com/lpalbou/meshvault/actions/workflows/ci.yml/badge.svg)](https://github.com/lpalbou/meshvault/actions)
6
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://python.org)
7
+ [![FastAPI](https://img.shields.io/badge/backend-FastAPI-009688)](https://fastapi.tiangolo.com)
8
+ [![Three.js](https://img.shields.io/badge/3D-Three.js%20r170-black)](https://threejs.org)
9
+ [![PyPI](https://img.shields.io/pypi/v/meshvault)](https://pypi.org/project/meshvault/)
10
+ [![npm](https://img.shields.io/npm/v/meshvault)](https://www.npmjs.com/package/meshvault)
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
12
+
13
+ ---
14
+
15
+ ## Features
16
+
17
+ | Feature | Description |
18
+ |---------|-------------|
19
+ | **Folder Browsing** | Navigate your filesystem with a clean sidebar tree. Go up, go home, double-click to enter. List/grid view toggle. |
20
+ | **Search & Filter** | Real-time search input to filter folders and assets in the current directory. |
21
+ | **3D Asset Detection** | Finds `.obj`, `.fbx`, `.gltf`, `.glb`, `.stl` files — including inside `.zip` and `.rar` archives. |
22
+ | **Interactive 3D Viewer** | Click an asset to load it with high-quality PBR rendering, SSAO, soft shadows, tone mapping. |
23
+ | **Orbit / FPV Toggle** | Orbit mode (mouse orbit/zoom/pan, right-click pivot) and FPV drone mode (WASD fly, A/D yaw, mouse look). |
24
+ | **Viewer Toolbar** | Toggle grid, XYZ axes (colored + labeled), wireframe, and light controls from the top-right toolbar. |
25
+ | **Light Controls** | Adjustable key/fill/ambient intensity, light direction (azimuth/elevation), and exposure. |
26
+ | **Background Presets** | 12 background color swatches (dark, gray, light, tinted) for evaluating models on any backdrop. |
27
+ | **Model Transforms** | Center at origin, ground on Y=0, auto-orient via PCA, reset to original. All without moving the camera. |
28
+ | **Model Scaling** | Real-time scale slider (0.25×–2.0×). |
29
+ | **Modified Export** | Export applies all transforms (center, ground, orient, scale) — saves modified OBJ via Three.js OBJExporter. |
30
+ | **FBX Auto-Conversion** | Old FBX files (version < 7000) are auto-converted to OBJ via a built-in binary parser. |
31
+ | **Persistent Settings** | Scene settings (wireframe, grid, axes, background) persist across model loads. |
32
+
33
+ ## Quick Start
34
+
35
+ ### Prerequisites
36
+
37
+ - **Python 3.10+**
38
+ - **Poetry** ([install guide](https://python-poetry.org/docs/#installation))
39
+ - For `.rar` support, one of: `bsdtar`, `unrar`, `7z`, or `unar` (auto-detected)
40
+
41
+ ### Install & Run
42
+
43
+ ```bash
44
+ git clone https://github.com/lpalbou/meshvault.git
45
+ cd meshvault
46
+ poetry install --no-root
47
+ poetry run meshvault
48
+ ```
49
+
50
+ Then open **http://localhost:8420** in your browser.
51
+
52
+ ### Install from PyPI
53
+
54
+ ```bash
55
+ pip install meshvault
56
+ meshvault
57
+ ```
58
+
59
+ ### Install from NPM
60
+
61
+ ```bash
62
+ npx meshvault
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ 1. **Browse**: Navigate folders in the sidebar. Toggle list/grid view. Filter by name.
68
+ 2. **Preview**: Click any 3D asset to load it in the viewer (green=OBJ, orange=FBX, cyan=GLTF, purple=STL/archived).
69
+ 3. **Navigate**: Orbit mode (left-drag orbit, scroll zoom, right-drag pan, right-click pivot) or FPV drone mode (W/Shift forward, S/Ctrl backward, A/D yaw, E/Q altitude). Spacebar resets camera.
70
+ 4. **Scene tools**: Toggle grid, axes (XYZ), wireframe, and lighting from the toolbar. Pick background color from swatches.
71
+ 5. **Transform**: Center model at origin, ground it on Y=0, or auto-orient via PCA. Reset undoes all transforms.
72
+ 6. **Export**: Set name and path in the top bar, click Export. Modified models (centered/oriented/scaled) are exported as OBJ with baked transforms.
73
+
74
+ ## Project Structure
75
+
76
+ ```
77
+ meshvault/
78
+ ├── backend/
79
+ │ ├── app.py # FastAPI server + routes
80
+ │ ├── file_browser.py # Filesystem navigation + asset discovery
81
+ │ ├── archive_inspector.py # ZIP/RAR inspection + multi-tool extraction
82
+ │ ├── export_manager.py # Asset export with renaming
83
+ │ └── fbx_converter.py # FBX 6100 binary parser + OBJ converter
84
+ ├── frontend/
85
+ │ ├── index.html # Main HTML page
86
+ │ ├── css/styles.css # Dark professional theme
87
+ │ └── js/
88
+ │ ├── app.js # Main orchestrator
89
+ │ ├── file_browser.js # File browser + search + grid/list
90
+ │ ├── viewer_3d.js # Three.js 3D viewer
91
+ │ └── export_panel.js # Rename/export controls
92
+ ├── tests/
93
+ │ └── test_file_browser.py # Backend unit tests
94
+ ├── docs/ # Full documentation
95
+ ├── pyproject.toml # Poetry / PyPI configuration
96
+ ├── package.json # NPM configuration
97
+ └── poetry.lock
98
+ ```
99
+
100
+ ## Documentation
101
+
102
+ - [Getting Started](docs/getting_started.md) — Installation, first run, basic usage
103
+ - [Architecture](docs/architecture.md) — System design, components, design decisions
104
+ - [API Reference](docs/api.md) — REST API endpoints, request/response schemas
105
+ - [FAQ](docs/faq.md) — Common questions and troubleshooting
106
+
107
+ ## Tests
108
+
109
+ ```bash
110
+ poetry run pytest tests/ -v
111
+ ```
112
+
113
+ ## Contributing
114
+
115
+ Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/lpalbou/meshvault).
116
+
117
+ ## License
118
+
119
+ MIT License — see [LICENSE](LICENSE) for details.
120
+
121
+ © 2026 Laurent-Philippe Albou — contact@abstractcore.ai
File without changes
@@ -0,0 +1,393 @@
1
+ """
2
+ Main FastAPI application - serves the 3D asset browser.
3
+
4
+ This is the entry point that:
5
+ - Serves the frontend static files
6
+ - Provides REST API for file browsing, asset loading, and export
7
+ - Manages the lifecycle of backend services
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import mimetypes
13
+ import urllib.parse
14
+ from pathlib import Path
15
+ from typing import Optional
16
+ from contextlib import asynccontextmanager
17
+
18
+ import uvicorn
19
+ from fastapi import FastAPI, HTTPException, Query
20
+ from fastapi.staticfiles import StaticFiles
21
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
22
+ from pydantic import BaseModel
23
+
24
+ from backend.file_browser import FileBrowser
25
+ from backend.archive_inspector import ArchiveInspector
26
+ from backend.export_manager import ExportManager
27
+ from backend.fbx_converter import get_fbx_version, convert_fbx_to_obj
28
+
29
+
30
+ # --- Configuration ---
31
+
32
+ # Default browse root: user's home directory
33
+ DEFAULT_ROOT = str(Path.home())
34
+
35
+ # Register additional MIME types for 3D files
36
+ mimetypes.add_type("model/obj", ".obj")
37
+ mimetypes.add_type("model/fbx", ".fbx")
38
+ mimetypes.add_type("model/mtl", ".mtl")
39
+ mimetypes.add_type("model/gltf+json", ".gltf")
40
+ mimetypes.add_type("model/gltf-binary", ".glb")
41
+ mimetypes.add_type("model/stl", ".stl")
42
+
43
+
44
+ # --- Pydantic models for API ---
45
+
46
+ class ExportRequest(BaseModel):
47
+ """Request body for exporting an asset."""
48
+ source_path: str
49
+ target_dir: str
50
+ new_name: str
51
+ is_in_archive: bool = False
52
+ archive_path: Optional[str] = None
53
+ inner_path: Optional[str] = None
54
+ related_files: list[str] = []
55
+
56
+
57
+ class ExportModifiedRequest(BaseModel):
58
+ """Request body for exporting a modified model (OBJ text from frontend)."""
59
+ target_dir: str
60
+ new_name: str
61
+ obj_content: str
62
+
63
+
64
+ class BrowseResponse(BaseModel):
65
+ """Response for browse endpoint."""
66
+ current_path: str
67
+ parent_path: Optional[str]
68
+ folders: list[dict]
69
+ assets: list[dict]
70
+
71
+
72
+ # --- App lifecycle ---
73
+
74
+ archive_inspector = ArchiveInspector()
75
+ file_browser = FileBrowser()
76
+ export_manager = ExportManager()
77
+
78
+
79
+ @asynccontextmanager
80
+ async def lifespan(app: FastAPI):
81
+ """Manage application lifecycle — clean up temp files on shutdown."""
82
+ yield
83
+ archive_inspector.cleanup()
84
+
85
+
86
+ # --- FastAPI App ---
87
+
88
+ app = FastAPI(
89
+ title="MeshVault",
90
+ description="Professional 3D asset browser for rapid management",
91
+ version="0.1.0",
92
+ lifespan=lifespan,
93
+ )
94
+
95
+ # Serve frontend static files
96
+ # Works both in development (project root) and when installed via pip
97
+ # (frontend/ is installed alongside backend/ in site-packages parent)
98
+ _project_root = Path(__file__).parent.parent
99
+ frontend_dir = _project_root / "frontend"
100
+ if not frontend_dir.exists():
101
+ # Fallback: check if installed as a package (site-packages layout)
102
+ import importlib.resources
103
+ try:
104
+ frontend_dir = Path(importlib.resources.files("frontend"))
105
+ except Exception:
106
+ pass
107
+ app.mount(
108
+ "/static",
109
+ StaticFiles(directory=str(frontend_dir)),
110
+ name="static",
111
+ )
112
+
113
+
114
+ # --- Routes ---
115
+
116
+ @app.get("/", response_class=HTMLResponse)
117
+ async def root():
118
+ """Serve the main HTML page."""
119
+ index_path = frontend_dir / "index.html"
120
+ return HTMLResponse(content=index_path.read_text(encoding="utf-8"))
121
+
122
+
123
+ @app.get("/api/browse")
124
+ async def browse(path: Optional[str] = Query(default=None)):
125
+ """
126
+ Browse a directory and return its contents.
127
+
128
+ Query params:
129
+ path: Directory path to browse. Defaults to user's home.
130
+ """
131
+ browse_path = path or DEFAULT_ROOT
132
+
133
+ try:
134
+ result = file_browser.browse(browse_path)
135
+ except FileNotFoundError as e:
136
+ raise HTTPException(status_code=404, detail=str(e))
137
+ except ValueError as e:
138
+ raise HTTPException(status_code=403, detail=str(e))
139
+
140
+ return {
141
+ "current_path": result.current_path,
142
+ "parent_path": result.parent_path,
143
+ "folders": [
144
+ {
145
+ "name": f.name,
146
+ "path": f.path,
147
+ "has_children": f.has_children,
148
+ }
149
+ for f in result.folders
150
+ ],
151
+ "assets": [
152
+ {
153
+ "name": a.name,
154
+ "path": a.path,
155
+ "extension": a.extension,
156
+ "size": a.size,
157
+ "is_in_archive": a.is_in_archive,
158
+ "archive_path": a.archive_path,
159
+ "inner_path": a.inner_path,
160
+ "related_files": a.related_files,
161
+ }
162
+ for a in result.assets
163
+ ],
164
+ }
165
+
166
+
167
+ def _maybe_convert_fbx(file_path: Path) -> tuple[Path, str]:
168
+ """
169
+ Check if an FBX file needs conversion (version < 7000) and convert it.
170
+
171
+ Returns (path_to_serve, extension) — if converted, path points to the
172
+ generated OBJ file and extension is ".obj". Otherwise returns the
173
+ original path and extension unchanged.
174
+ """
175
+ ext = file_path.suffix.lower()
176
+ if ext != ".fbx":
177
+ return file_path, ext
178
+
179
+ version = get_fbx_version(str(file_path))
180
+ if version is not None and version < 7000:
181
+ # FBX version too old for Three.js — convert to OBJ
182
+ obj_path = file_path.with_suffix(".converted.obj")
183
+ if not obj_path.exists():
184
+ success = convert_fbx_to_obj(str(file_path), str(obj_path))
185
+ if not success:
186
+ # Conversion failed — let the frontend try anyway
187
+ return file_path, ext
188
+ return obj_path, ".obj"
189
+
190
+ return file_path, ext
191
+
192
+
193
+ @app.get("/api/asset/file")
194
+ async def serve_asset_file(path: str = Query(...)):
195
+ """
196
+ Serve a 3D asset file for the viewer.
197
+
198
+ For regular files, serves directly.
199
+ For FBX files with version < 7000, auto-converts to OBJ.
200
+ """
201
+ file_path = Path(path)
202
+ if file_path.exists() and file_path.is_file():
203
+ # Auto-convert old FBX if needed
204
+ serve_path, _ = _maybe_convert_fbx(file_path)
205
+ content_type = mimetypes.guess_type(str(serve_path))[0] or "application/octet-stream"
206
+ return FileResponse(
207
+ path=str(serve_path),
208
+ media_type=content_type,
209
+ filename=serve_path.name,
210
+ )
211
+
212
+ raise HTTPException(status_code=404, detail=f"File not found: {path}")
213
+
214
+
215
+ @app.get("/api/asset/archive")
216
+ async def serve_archive_asset(
217
+ archive_path: str = Query(...),
218
+ inner_path: str = Query(...),
219
+ ):
220
+ """
221
+ Extract and serve a 3D asset from an archive.
222
+
223
+ Extracts the asset (and related files) to a temp directory,
224
+ then serves the main asset file.
225
+ """
226
+ extracted = archive_inspector.extract_asset(archive_path, inner_path)
227
+ if extracted is None:
228
+ raise HTTPException(
229
+ status_code=500,
230
+ detail=f"Failed to extract {inner_path} from {archive_path}",
231
+ )
232
+
233
+ file_path = Path(extracted)
234
+ if not file_path.exists():
235
+ raise HTTPException(status_code=404, detail="Extracted file not found")
236
+
237
+ content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
238
+ return FileResponse(
239
+ path=str(file_path),
240
+ media_type=content_type,
241
+ filename=file_path.name,
242
+ )
243
+
244
+
245
+ @app.get("/api/asset/prepare_archive")
246
+ async def prepare_archive_asset(
247
+ archive_path: str = Query(...),
248
+ inner_path: str = Query(...),
249
+ ):
250
+ """
251
+ Extract an archived asset and return JSON with resolved temp paths.
252
+
253
+ This endpoint extracts the main asset and its related files to a
254
+ temp directory, then returns the absolute filesystem paths so the
255
+ frontend can use /api/asset/file and /api/asset/related with them.
256
+
257
+ This solves the problem of archive-internal paths not being valid
258
+ filesystem paths for the Three.js loaders.
259
+ """
260
+ extracted = archive_inspector.extract_asset(archive_path, inner_path)
261
+ if extracted is None:
262
+ raise HTTPException(
263
+ status_code=500,
264
+ detail=f"Failed to extract {inner_path} from {archive_path}",
265
+ )
266
+
267
+ file_path = Path(extracted)
268
+ if not file_path.exists():
269
+ raise HTTPException(status_code=404, detail="Extracted file not found")
270
+
271
+ # Auto-convert old FBX if needed
272
+ serve_path, actual_ext = _maybe_convert_fbx(file_path)
273
+
274
+ # Build the file URL for the main asset (points to converted file if applicable)
275
+ file_url = f"/api/asset/file?path={urllib.parse.quote(str(serve_path))}"
276
+
277
+ # Resolve related file paths: map archive-internal -> extracted temp paths
278
+ # First, get all related files from the archive listing
279
+ result = file_browser.browse(str(Path(archive_path).parent))
280
+ archived_asset = None
281
+ for a in result.assets:
282
+ if (a.archive_path == archive_path and a.inner_path == inner_path):
283
+ archived_asset = a
284
+ break
285
+
286
+ related_inner = archived_asset.related_files if archived_asset else []
287
+ related_resolved = archive_inspector.get_extracted_related_paths(
288
+ archive_path, related_inner
289
+ )
290
+
291
+ return {
292
+ "file_url": file_url,
293
+ "file_path": str(serve_path),
294
+ "related_files": related_resolved,
295
+ # Tell frontend the actual format to use (may differ if converted)
296
+ "actual_extension": actual_ext,
297
+ }
298
+
299
+
300
+ @app.get("/api/asset/related")
301
+ async def serve_related_file(path: str = Query(...)):
302
+ """
303
+ Serve a related file (texture, material) for the 3D viewer.
304
+
305
+ This endpoint allows the Three.js loaders to fetch .mtl files,
306
+ textures, etc., that are referenced by the main 3D asset.
307
+ """
308
+ file_path = Path(path)
309
+ if not file_path.exists():
310
+ raise HTTPException(status_code=404, detail=f"File not found: {path}")
311
+
312
+ content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
313
+ return FileResponse(
314
+ path=str(file_path),
315
+ media_type=content_type,
316
+ filename=file_path.name,
317
+ )
318
+
319
+
320
+ @app.post("/api/export")
321
+ async def export_asset(request: ExportRequest):
322
+ """
323
+ Export a 3D asset to a target directory with a new name.
324
+
325
+ Handles both regular files and archived assets.
326
+ """
327
+ result = export_manager.export_asset(
328
+ source_path=request.source_path,
329
+ target_dir=request.target_dir,
330
+ new_name=request.new_name,
331
+ is_in_archive=request.is_in_archive,
332
+ archive_path=request.archive_path,
333
+ inner_path=request.inner_path,
334
+ related_files=request.related_files,
335
+ )
336
+
337
+ if not result.success:
338
+ raise HTTPException(status_code=500, detail=result.message)
339
+
340
+ return {
341
+ "success": result.success,
342
+ "output_path": result.output_path,
343
+ "message": result.message,
344
+ "files_exported": result.files_exported,
345
+ }
346
+
347
+
348
+ @app.post("/api/export_modified")
349
+ async def export_modified(request: ExportModifiedRequest):
350
+ """
351
+ Export a modified model (OBJ text generated by the frontend).
352
+
353
+ This is used when the user has recentered, auto-oriented, or scaled
354
+ the model in the viewer and wants to export the modified version.
355
+ """
356
+ target_dir = Path(request.target_dir)
357
+ target_dir.mkdir(parents=True, exist_ok=True)
358
+
359
+ obj_path = target_dir / f"{request.new_name}.obj"
360
+ try:
361
+ obj_path.write_text(request.obj_content, encoding="utf-8")
362
+ except Exception as e:
363
+ raise HTTPException(status_code=500, detail=f"Failed to write: {e}")
364
+
365
+ return {
366
+ "success": True,
367
+ "output_path": str(target_dir),
368
+ "message": f"Exported modified model as OBJ",
369
+ "files_exported": [str(obj_path)],
370
+ }
371
+
372
+
373
+ @app.get("/api/default_path")
374
+ async def get_default_path():
375
+ """Return the default browse path (user home)."""
376
+ return {"path": DEFAULT_ROOT}
377
+
378
+
379
+ def main():
380
+ """Entry point for running the server."""
381
+ port = int(os.environ.get("PORT", 8420))
382
+ print(f"\n 🎨 MeshVault")
383
+ print(f" → Open http://localhost:{port} in your browser\n")
384
+ uvicorn.run(
385
+ "backend.app:app",
386
+ host="0.0.0.0",
387
+ port=port,
388
+ reload=False,
389
+ )
390
+
391
+
392
+ if __name__ == "__main__":
393
+ main()