Feat/model viewer - #70
Merged
Merged
Conversation
olavgg
marked this pull request as draft
September 10, 2026 11:47
olavgg
added this pull request to stack #102
September 15, 2026 10:29
olavgg
force-pushed
the
feat/model-viewer
branch
from
September 15, 2026 10:30
bcdcd44 to
448650a
Compare
olavgg
marked this pull request as ready for review
September 15, 2026 10:34
olavgg
force-pushed
the
feat/model-viewer
branch
from
September 15, 2026 10:47
448650a to
baf03ae
Compare
JosteinGj
approved these changes
Sep 15, 2026
An uploaded STEP or IFC file was an opaque blob you could only download. The file-information dialog now offers "View in 3D" for a file whose extension we recognise as a model, opening a modal you can orbit, pan and zoom in. Formats: 3dm, 3ds, 3mf, amf, bim, brep, dae, fbx, fcstd, gltf, glb, ifc, iges, igs, step, stp, stl, obj, off, ply, wrl. Classification is by extension rather than mime type, because Tika calls most CAD application/octet-stream and STEP is plain text, so it can arrive as text/plain. The renderer is Online3DViewer 0.18.0, MIT, vendored unmodified with its provenance in static/js/model-viewer/README.md. It stays out of assets.gradle for the same reason graph-network does: running Closure over an already-minified megabyte buys nothing. That megabyte is injected on first open rather than loaded with the files page, so browsing files costs nothing until someone opens a model. This is the first lazily loaded library in the console; the graph loads its eleven scripts on every page that hosts it. The bytes come from datahub-api directly, no console proxy (CONSTRAINTS F2), as a blob because the download route is authenticated and the library cannot attach a bearer token. Two details that are easy to get wrong and were both confirmed against the running api: the request needs Accept: */*, since the endpoint declares produces=application/octet-stream and answers Accept: application/json with a 406; and it needs the Api timeout raised from its 10s default, which would otherwise abort a multi-MB model mid-download. The drawing viewer learned the same two. Closing destroys the viewer. Browsers cap how many live WebGL contexts a page may hold, so leaking one per open would eventually stop rendering. Known limitation, written up in the README: six formats (STEP, IGES, BREP, FCStd, IFC, 3DM, and Draco-compressed glTF) need a decoder that this version fetches from a CDN at runtime, with the URLs baked into the minified file and no setting to redirect them. Those formats therefore need outbound internet from the browser and pull third-party code into the console's origin. Everything else parses offline. Vendoring the decoders would cost 17 MB and make this a patched copy rather than an upstream artefact, so it is left as a decision rather than taken quietly. Verified in headless Chromium against a real STL: wheel moves the camera eye, left-drag orbits it, right-drag moves the centre, and the modal path loads the library only on open, sizes the canvas, clears its status on load and removes the overlay on close. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Olav Gjerde <olav@intellistream.ai>
Online3DViewer 0.18.0 hardcodes jsdelivr URLs for the decoders that STEP, IGES, BREP, FCStd, IFC, 3DM and Draco-compressed glTF need, with no setting to redirect them. That made those formats need outbound internet from the browser, and pulled third-party code into the console's origin, neither of which suits a platform deployed on closed networks. The four decoders are now vendored under libs/ and the URLs rewritten by patch-cdn-urls.py, which is kept next to them so the patch is reproducible against a future release rather than being folklore. They live in one directory because each locates its own .wasm relative to its script URL. This adds 17 MB to the repository, most of it occt-import-js.wasm at 7.7 MB and web-ifc at 6.7 MB. Nothing is fetched until a file of that type is opened, so it costs page weight only for the formats that need it. Verified in headless Chromium: opening a .step now fetches occt-import-js-worker.js from our own path and makes no external request at all, where before it went to jsdelivr. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Olav Gjerde <olav@intellistream.ai>
…lative The previous commit rewrote the viewer's CDN urls to "/static/js/model- viewer/libs/...". That works for the three decoders loaded by a script tag, but not for occt, which O3DV runs in a worker created from a blob: URL. A blob URL has an opaque base, so a root-relative path is not a resolvable URL there and importScripts rejects it outright: Failed to execute 'importScripts' on 'WorkerGlobalScope': The URL '/static/js/model-viewer/libs/occt-import-js.js' is invalid. The urls are now built from location.origin at runtime, which is what the jsdelivr urls they replaced were: absolute, with a scheme and host. My earlier check missed this because it asserted only that no request left the origin, which was true while the worker was failing. The server log now shows occt-import-js-worker.js, occt-import-js.js and the 7.7 MB wasm all fetched and the worker running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Olav Gjerde <olav@intellistream.ai>
A converted plant model rendered as barely visible dark shapes. The cause is not the model: the viewer shades anything carrying PBR materials, which every CAD import does, with Physical shading, which sets ambient light to zero and makes scene.environment the only source. With no environment map there is nothing left to light with. Vendors the cube map from the same release zip, six JPEGs at ~460 KB, and passes it with backgroundIsEnvMap false so it lights the model without replacing the dialog background. It loads only when a model is opened. The difference on the real Huldra model, 2185 meshes converted from an AVEVA E3D export, is the whole feature: before, dark outlines barely distinguishable from the background; after, legible structural steel, pipe supports and brackets. The gear-pump example goes from flat grey shapes to readable bronze gears and steel pipework. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Olav Gjerde <olav@intellistream.ai>
The viewer sent the library the one file you clicked, so an obj opened with its geometry and lost its materials, and a gltf with an external buffer did not open at all. Uploading the companions alongside did not help, since they were never sent. The model is now read for its own references, rather than the folder being swept: an obj for its mtllib and map_* lines, an mtl for the textures it names in turn, a gltf for the buffer and image uris that are not inline data. Only those names are then resolved against the model's own folder, through the listing the browser can already fetch, and downloaded. That keeps the cost proportionate. A folder holding a hundred unrelated files costs one listing call, and a model naming nothing costs none at all. Anything referenced but missing is skipped, so a model whose material library was never uploaded still opens exactly as it did before, untextured. Verified against the console's own example: opening cut-cube.obj now lists its folder and fetches cut-cube.mtl, leaving the unrelated file in that folder alone. Loading the pair resolves the material 'steel' where the obj by itself resolves one unnamed fallback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Olav Gjerde <olav@intellistream.ai>
olavgg
force-pushed
the
feat/model-viewer
branch
from
September 15, 2026 18:56
baf03ae to
5231e61
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
How it was verified
Checklist
git commit -s), per CONTRIBUTING.md./gradlew buildpasses(see AGENTS.md for which of the two documentation sites it belongs in)