Skip to content

fix(export,optimize): read WORKSPACE_DIR dynamically so edits work after the workspace moves - #340

Open
kevin9327 wants to merge 1 commit into
lightningpixel:devfrom
kevin9327:fix/export-optimize-dynamic-workspace-dir
Open

fix(export,optimize): read WORKSPACE_DIR dynamically so edits work after the workspace moves#340
kevin9327 wants to merge 1 commit into
lightningpixel:devfrom
kevin9327:fix/export-optimize-dynamic-workspace-dir

Conversation

@kevin9327

@kevin9327 kevin9327 commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

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 to modly export in the CLI (/export/{fmt}) and the assistant's decimate_mesh / smooth_mesh tools. For an input outside the workspace, the result is written into the old folder's Workflows/ and its /workspace/... URL 404s.

Why it triggers

Both routers bound the path at import time:

from services.generator_registry import WORKSPACE_DIR

POST /settings/pathsGeneratorRegistry.update_paths() rebinds the module global, but api/routers/export.py and api/routers/optimize.py kept their own copy, so every relative path resolved against the previous folder:

resolved = (WORKSPACE_DIR / raw_path).resolve()   # old workspace → 404

This is the same stale binding #287 fixed in generation.py. optimize.ply_to_splat already reads it dynamically; the rest of that module and export.py did not.

Fix

Import the module and read registry.WORKSPACE_DIR at call time in export.py and optimize.py. No logic changes: each WORKSPACE_DIR reference becomes registry.WORKSPACE_DIR, including the mesh-ops helpers #329 added (_operation_output_path, _run_operation, _operation_response), which /optimize/mesh and /optimize/smooth now go through. ply_to_splat is left as it was. test_optimize_mesh_ops.py patched optimize.WORKSPACE_DIR, which no longer exists, so it now patches optimize.registry.WORKSPACE_DIR (5 lines, same assertions).

Rebased onto dev after #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 relocates registry.WORKSPACE_DIR the way update_paths does, and puts a box mesh at MyColl/mesh.glb in the new workspace. Following test_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.glb is 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:

test_a_path_leaving_the_workspace_is_still_refused (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_a_path_leaving_the_workspace_is_still_refused) ... ok
test_decimate_and_smooth_read_their_input_from_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_decimate_and_smooth_read_their_input_from_the_moved_workspace) ... ERROR
test_decimate_and_smooth_write_their_result_into_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_decimate_and_smooth_write_their_result_into_the_moved_workspace) ... ERROR
test_export_router_converts_a_mesh_in_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_export_router_converts_a_mesh_in_the_moved_workspace) ... ERROR
test_optimize_export_converts_a_mesh_in_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_optimize_export_converts_a_mesh_in_the_moved_workspace) ... ERROR
test_transform_writes_its_result_into_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_transform_writes_its_result_into_the_moved_workspace) ... ERROR

======================================================================
ERROR: test_decimate_and_smooth_read_their_input_from_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_decimate_and_smooth_read_their_input_from_the_moved_workspace)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<repo>\api\tests\test_mesh_routers_workspace.py", line 74, in test_decimate_and_smooth_read_their_input_from_the_moved_workspace
    resolved = optimize_router._resolve_input_path("MyColl/mesh.glb")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\optimize.py", line 59, in _resolve_input_path
    raise HTTPException(404, f"File not found: {raw_path}")
fastapi.exceptions.HTTPException: 404: File not found: MyColl/mesh.glb

======================================================================
ERROR: test_decimate_and_smooth_write_their_result_into_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_decimate_and_smooth_write_their_result_into_the_moved_workspace)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<repo>\api\tests\test_mesh_routers_workspace.py", line 92, in test_decimate_and_smooth_write_their_result_into_the_moved_workspace
    decimated = optimize_router.optimize_mesh(
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\optimize.py", line 131, in optimize_mesh
    input_path = _resolve_input_path(body.path)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\optimize.py", line 59, in _resolve_input_path
    raise HTTPException(404, f"File not found: {raw_path}")
fastapi.exceptions.HTTPException: 404: File not found: MyColl/mesh.glb

======================================================================
ERROR: test_export_router_converts_a_mesh_in_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_export_router_converts_a_mesh_in_the_moved_workspace)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<repo>\api\tests\test_mesh_routers_workspace.py", line 55, in test_export_router_converts_a_mesh_in_the_moved_workspace
    response = export_router.export_mesh("stl", "MyColl/mesh.glb")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\export.py", line 23, in export_mesh
    raise HTTPException(404, f"File not found: {path}")
fastapi.exceptions.HTTPException: 404: File not found: MyColl/mesh.glb

======================================================================
ERROR: test_optimize_export_converts_a_mesh_in_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_optimize_export_converts_a_mesh_in_the_moved_workspace)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<repo>\api\tests\test_mesh_routers_workspace.py", line 60, in test_optimize_export_converts_a_mesh_in_the_moved_workspace
    response = optimize_router.export_mesh(path="MyColl/mesh.glb", format="obj")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\optimize.py", line 409, in export_mesh
    raise HTTPException(404, f"File not found: {path}")
fastapi.exceptions.HTTPException: 404: File not found: MyColl/mesh.glb

======================================================================
ERROR: test_transform_writes_its_result_into_the_moved_workspace (tests.test_mesh_routers_workspace.MeshRoutersAfterWorkspaceMoveTests.test_transform_writes_its_result_into_the_moved_workspace)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<repo>\api\tests\test_mesh_routers_workspace.py", line 65, in test_transform_writes_its_result_into_the_moved_workspace
    result = optimize_router.transform_mesh(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\optimize.py", line 174, in transform_mesh
    input_path = _resolve_input_path(body.path)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<repo>\api\routers\optimize.py", line 59, in _resolve_input_path
    raise HTTPException(404, f"File not found: {raw_path}")
fastapi.exceptions.HTTPException: 404: File not found: MyColl/mesh.glb

----------------------------------------------------------------------
Ran 6 tests in 0.019s

FAILED (errors=5)

After the fix, python -m unittest tests.test_mesh_routers_workspace passes all 6 tests (OK).

Whole suite, python -m unittest discover -s tests in api/ (venv with fastapi + python-multipart + httpx + trimesh; pymeshlab not installed):

dev this branch
Whole suite Ran 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 --isolated on export.py + optimize.py + test_optimize_mesh_ops.py reports 5 findings on dev and the same 5 on this branch. The new test file has one I001 (import grouping). Ruff flags the same grouping on the existing test_generation_router.py, test_model_router.py and test_workflow_runs_lifecycle.py, because in isolated mode it doesn't know routers/services are local, so the file follows the repo's style. No TypeScript touched.

🤖 Generated with Claude Code

…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>
@kevin9327
kevin9327 force-pushed the fix/export-optimize-dynamic-workspace-dir branch from 00d1325 to 466ff08 Compare September 13, 2026 21:44
@kevin9327

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev to resolve the conflict with #329. The fix is the same rename, now also applied to the code #329 added to optimize.py: _operation_output_path, _run_operation (the workspace_dir handed to mesh ops) and _operation_response read registry.WORKSPACE_DIR too. ply_to_splat is unchanged.

Two test changes came with it:

  • test_optimize_mesh_ops.py patched optimize.WORKSPACE_DIR, which no longer exists as a module name. It now patches optimize.registry.WORKSPACE_DIR. That's 5 lines, with the same assertions.
  • /optimize/mesh and /optimize/smooth no longer share the transform route's output code, so I added test_decimate_and_smooth_write_their_result_into_the_moved_workspace. It stubs the mesh-ops registry and checks the workspace, output folder and URL. With only those three helpers reverted to the import-time name, it fails ('/workspace/Workflows/mesh_opt500.glb' != '/workspace/MyColl/mesh_opt500.glb').

With the routers reverted, tests.test_mesh_routers_workspace fails 5 of 6 tests; with the fix, it passes 6/6. Whole suite (python -m unittest discover -s tests in api/): dev gives Ran 112 tests / OK (skipped=3), and this branch gives Ran 118 tests / OK (skipped=3).

@iammojogo-sudo

Copy link
Copy Markdown
Contributor

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

@iammojogo-sudo

Copy link
Copy Markdown
Contributor

Also #338 and #339 can be combined into this feature as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants