fix(export,optimize): read WORKSPACE_DIR dynamically so edits work after the workspace moves - #340
Conversation
…ter the workspace moves
export.py and optimize.py bound the workspace path with `from
services.generator_registry import WORKSPACE_DIR`, capturing it at import
time. POST /settings/paths rebinds that module global when the workspace
is moved in Settings, but these routers kept resolving paths against the
old folder. A model generated after the move could not be exported,
decimated, smoothed or transformed ("File not found"), and results for
inputs outside the workspace were written into the old folder's
Workflows/.
Read registry.WORKSPACE_DIR at call time instead, the same way
generation.py and ply_to_splat already do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
00d1325 to
466ff08
Compare
|
Rebased onto current Two test changes came with it:
With the routers reverted, |
|
I do this locally actually. Basically I create a .json file that reads the filepath of the last run and also links ANY image input node paths and mesh input node paths to their folders before concurrent runs so that it overrides data from the same workspace folder. So possibly just a json file with previous run data that is read and integrated natively rather than through extensions as that relies on the extension creators to incorporate that into the generation scripts. Just a helpful thought |
What
After moving the workspace folder in Settings → Storage, the mesh tools stop working on anything generated after the move, until the app is restarted. On the Generate page, Decimate, Smooth, baking a gizmo transform, and Export OBJ/STL/PLY all fail with
File not found: <collection>/<mesh>.glb. The same happens tomodly exportin the CLI (/export/{fmt}) and the assistant'sdecimate_mesh/smooth_meshtools. For an input outside the workspace, the result is written into the old folder'sWorkflows/and its/workspace/...URL 404s.Why it triggers
Both routers bound the path at import time:
POST /settings/paths→GeneratorRegistry.update_paths()rebinds the module global, butapi/routers/export.pyandapi/routers/optimize.pykept their own copy, so every relative path resolved against the previous folder:This is the same stale binding #287 fixed in
generation.py.optimize.ply_to_splatalready reads it dynamically; the rest of that module andexport.pydid not.Fix
Import the module and read
registry.WORKSPACE_DIRat call time inexport.pyandoptimize.py. No logic changes: eachWORKSPACE_DIRreference becomesregistry.WORKSPACE_DIR, including the mesh-ops helpers #329 added (_operation_output_path,_run_operation,_operation_response), which/optimize/meshand/optimize/smoothnow go through.ply_to_splatis left as it was.test_optimize_mesh_ops.pypatchedoptimize.WORKSPACE_DIR, which no longer exists, so it now patchesoptimize.registry.WORKSPACE_DIR(5 lines, same assertions).Rebased onto
devafter #329 landed. #266 touches some of the same lines but doesn't change the import-time binding; it's a mechanical rename, so I'm happy to rebase again if that lands first.Verification
New
api/tests/test_mesh_routers_workspace.py. It relocatesregistry.WORKSPACE_DIRthe wayupdate_pathsdoes, and puts a box mesh atMyColl/mesh.glbin the new workspace. Followingtest_generation_router.py, any stale router copy is pointed at an empty "old" folder in the temp tree.test_export_router_converts_a_mesh_in_the_moved_workspace(/export/stl): fails before, passes after.test_optimize_export_converts_a_mesh_in_the_moved_workspace(/optimize/export): fails before, passes after.test_transform_writes_its_result_into_the_moved_workspace(/optimize/transform): fails before, passes after.test_decimate_and_smooth_read_their_input_from_the_moved_workspace: fails before, passes after. Covers the input helper both routes use.test_decimate_and_smooth_write_their_result_into_the_moved_workspace(/optimize/mesh,/optimize/smooth): fails before, passes after. The meshoptimizer/pymeshlab backends aren't needed: it stubs the mesh-ops registry and checks the workspace handed to the operation, the output folder and the returned URL, so it covers the feat(mesh): add unified mesh operations registry #329 helpers. With only those three helpers reverted to the import-time name, it still fails ('/workspace/Workflows/mesh_opt500.glb' != '/workspace/MyColl/mesh_opt500.glb').test_a_path_leaving_the_workspace_is_still_refused: passes before and after.../outside.glbis still rejected with 400 by both export endpoints, which proves that reading the live workspace doesn't loosen the containment check. The change is not a widening.Fail-before output, with the routers reverted and the test kept. It is verbatim except that the local checkout path prefix is replaced with
<repo>, and the registry's import-time path banner that follows it is omitted:After the fix,
python -m unittest tests.test_mesh_routers_workspacepasses all 6 tests (OK).Whole suite,
python -m unittest discover -s testsinapi/(venv withfastapi+python-multipart+httpx+trimesh; pymeshlab not installed):devRan 112 tests/OK (skipped=3)Ran 118 tests/OK (skipped=3)The 3 skips were already there on
dev.Lint: the repo configures no Python linter, and ESLint ignores
api/**. As a spot check,ruff check --isolatedonexport.py+optimize.py+test_optimize_mesh_ops.pyreports 5 findings ondevand the same 5 on this branch. The new test file has one I001 (import grouping). Ruff flags the same grouping on the existingtest_generation_router.py,test_model_router.pyandtest_workflow_runs_lifecycle.py, because in isolated mode it doesn't knowrouters/servicesare local, so the file follows the repo's style. No TypeScript touched.🤖 Generated with Claude Code