Summary
Vitamins.get(File) (in bowler-script-kernel) loads an STL into a CSG. When the mesh fails the Manifold topological check — surfaced as ManifoldError.NOT_MANIFOLD from the manifold3d-java binding to Google's libmanifold — the load fails terminally and the user has no in-app guidance on what went wrong or what to try next.
Filing this as a UX / recovery-path discussion, not a bug. The manifold check itself is correct.
The relevant code
Reproducer
Self-contained A/B/C study with a runnable Groovy script:
https://github.com/JansenSmith/bowlerstudio-manifold-aliasing
| asset |
provenance |
admesh -fudv report |
result via Vitamins.get |
A bisonCouche_aliased.stl |
HueForge raw, min_detail = 0.3 |
6 disconnected facets, 4 wrongly-oriented normals |
NOT_MANIFOLD |
B bisonCouche_Front_242x175.stl |
HueForge raw, min_detail = 0.2 (same project as A) |
0 disconnected facets |
loads cleanly |
C bisonCouche_admesh_fix.stl |
admesh-repaired version of A |
0 disconnected facets |
loads cleanly |
Open manifold_aliasing_test.groovy in BowlerStudio and press Run. A fails as expected; B and C load with dimensions printed. (The reproducer wraps Vitamins.get through a small user-land download helper, but the failure point is at Vitamins.)
C was produced from A with a single command:
admesh -fudvb bisonCouche_admesh_fix.stl bisonCouche_aliased.stl
-f fill-holes, -u remove-unconnected, -d normal-directions, -v normal-values, -b NAME write-binary-stl. On already-manifold input (the B case), admesh is a no-op — non-destructive.
Two paths, low effort to high
Path 1 — surface admesh in the failure message
When Vitamins.get fails with NOT_MANIFOLD, include in the message:
- a one-line pointer to admesh (https://github.com/admesh/admesh)
- the standard repair command, with the user's failing path interpolated, ready to copy-paste
- a mention of the opt-in flag described in Path 2, if implemented
That alone gives the user a concrete next step. A handful of lines, no new dependencies, no structural change.
Path 2 — optional flag for BS to run admesh
A Vitamins.get(file, recoverWithAdmesh: true) overload (or equivalent). When the flag is set and the mesh fails NOT_MANIFOLD, BS invokes admesh and retries; without the flag, Path 1's behavior applies. Provisioning admesh fits the pattern BS already uses for managed dependencies (~/bin/BowlerStudioInstall/...). CaDoodle could surface this as a "non-manifold input — try repair?" dialog on the GUI side.
Why this matters in practice
I hit this during a real piece bring-up. The HueForge export silently produced 6 orphan facets at min_detail = 0.3; re-exporting at 0.2 produced a clean ~2.13M-triangle mesh. The fix was easy once I knew where to look, but the discovery path — STL fails → suspect HueForge → A/B test settings → confirm with admesh → identify admesh as the rescue — took a couple of hours that a one-line hint would have collapsed to a couple of minutes.
Related (separate sub-note)
DownloadManager (also in bowler-script-kernel) has an on-disk cache at ~/bin/BowlerStudioInstall/0.1.0/<fileName>.stl that's independent of Vitamins.clear(). While debugging non-manifold inputs, every re-run re-tested the previously-downloaded bytes regardless of release-asset updates, until I manually rm'd the cached file. Happy to split this into its own issue if preferred — flagging here because they came up together.
Environment
- BowlerStudio 5.1.0
- HueForge v0.9.2.3 (Linux AppImage)
- admesh 0.98.5
Summary
Vitamins.get(File)(inbowler-script-kernel) loads an STL into a CSG. When the mesh fails the Manifold topological check — surfaced asManifoldError.NOT_MANIFOLDfrom themanifold3d-javabinding to Google's libmanifold — the load fails terminally and the user has no in-app guidance on what went wrong or what to try next.Filing this as a UX / recovery-path discussion, not a bug. The manifold check itself is correct.
The relevant code
Vitamins.get(File)— bowler-script-kernel/src/main/java/.../Vitamins.java#L76 (callsSTL.file(...)to load the CSG; this is where user scripts hit the failure)ManifoldError.NOT_MANIFOLD— manifold3d-java/.../ManifoldBindings.java#L20-L21 (enum value the native libmanifold reports)Reproducer
Self-contained A/B/C study with a runnable Groovy script:
https://github.com/JansenSmith/bowlerstudio-manifold-aliasing
admesh -fudvreportVitamins.getbisonCouche_aliased.stlmin_detail = 0.3bisonCouche_Front_242x175.stlmin_detail = 0.2(same project as A)bisonCouche_admesh_fix.stlOpen
manifold_aliasing_test.groovyin BowlerStudio and press Run. A fails as expected; B and C load with dimensions printed. (The reproducer wrapsVitamins.getthrough a small user-land download helper, but the failure point is at Vitamins.)C was produced from A with a single command:
-ffill-holes,-uremove-unconnected,-dnormal-directions,-vnormal-values,-b NAMEwrite-binary-stl. On already-manifold input (the B case), admesh is a no-op — non-destructive.Two paths, low effort to high
Path 1 — surface admesh in the failure message
When
Vitamins.getfails with NOT_MANIFOLD, include in the message:That alone gives the user a concrete next step. A handful of lines, no new dependencies, no structural change.
Path 2 — optional flag for BS to run admesh
A
Vitamins.get(file, recoverWithAdmesh: true)overload (or equivalent). When the flag is set and the mesh fails NOT_MANIFOLD, BS invokes admesh and retries; without the flag, Path 1's behavior applies. Provisioning admesh fits the pattern BS already uses for managed dependencies (~/bin/BowlerStudioInstall/...). CaDoodle could surface this as a "non-manifold input — try repair?" dialog on the GUI side.Why this matters in practice
I hit this during a real piece bring-up. The HueForge export silently produced 6 orphan facets at
min_detail = 0.3; re-exporting at0.2produced a clean ~2.13M-triangle mesh. The fix was easy once I knew where to look, but the discovery path — STL fails → suspect HueForge → A/B test settings → confirm withadmesh→ identify admesh as the rescue — took a couple of hours that a one-line hint would have collapsed to a couple of minutes.Related (separate sub-note)
DownloadManager(also inbowler-script-kernel) has an on-disk cache at~/bin/BowlerStudioInstall/0.1.0/<fileName>.stlthat's independent ofVitamins.clear(). While debugging non-manifold inputs, every re-run re-tested the previously-downloaded bytes regardless of release-asset updates, until I manuallyrm'd the cached file. Happy to split this into its own issue if preferred — flagging here because they came up together.Environment