Add the GLB and glTF 3D model FileDef families - #5782
Conversation
Introduce a shared ModelDef base (extends FileDef) with two leaves: StlDef (.stl) and ThreeMfDef (.3mf). Each extracts format metadata in extractAttributes (pure-JS parsers ported from the handoff realm) and renders a deterministic inline SVG silhouette across atom/fitted/ embedded/isolated — no WebGL, prerender-safe. - ModelDef: shared Model3DInfoField scene facts, silhouette preview, and a thumbnailUrl seam for the shaded-PNG follow-up (CS-12401). - StlDef: ASCII/binary detection, facet/normal/degenerate counts, color-data flag, bounding-box extents. - ThreeMfDef: OPC ZIP unzip (fflate), model-part + slicer-config parse (objects, materials, plates, print parts, extruders, bounds). - Register .stl/.3mf in file-def-code-ref; add fflate dependency. Shaded raster PNG previews are intentionally deferred to CS-12401; the SVG silhouette is the preview until then. CS-12053, CS-12054 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Register `fflate` as an async module shim in host externals.ts and add it to host deps. Bare imports in realm-served base modules resolve only via the shim registry, not node_modules, so the extractor's `import 'fflate'` would 404 at runtime without this. - Render 3MF materialNames via a joined getter instead of interpolating a string[] directly (glint: string[] is not a ContentValue). - prettier formatting. CS-12053, CS-12054
isolated + embedded now mount an interactive three.js orbit viewer (ModelViewer + renderModel modifier), matching the handoff prototype. three.js + OrbitControls + STL/3MFLoader load lazily from a CDN (esm.sh) at client render time only — the sanctioned Boxel pattern for card libraries — so the engine never runs during extraction/indexing. - fitted keeps the pure SVG silhouette (collection-tile budget). - The silhouette is the loading placeholder and the fallback whenever WebGL/the CDN engine is unavailable (e.g. prerender), so the static prerendered HTML degrades gracefully. - Full GPU teardown on unmount (dispose + forceContextLoss). CS-12053, CS-12054
The WebGL viewer's `fetch(url, { credentials: 'include' })` was blocked
cross-origin (host 4200 → realm 4201): a credentialed CORS request is
illegal against the realm's wildcard Access-Control-Allow-Origin, so the
STL/3MF bytes never loaded and every model fell back to the silhouette.
Drop `credentials: 'include'` — realm auth is carried by an Authorization
header injected by the host auth service worker on the GET (the same path
that lets <img src> load realm images), not by cookies. Verified in the
browser: complex STL and moderate 3MF now render in the live orbit viewer.
CS-12053, CS-12054
Six sample model files (stl + three-mf, simple/moderate/complex) under experiments/model-samples/ so StlDef/ThreeMfDef can be exercised manually in the app — each is indexed directly as its FileDef subtype. CS-12053, CS-12054
Rework the isolated view to mirror the handoff prototype: a header bar with a mono extension pill, a bordered live-viewer stage, and a two-column (stage + property inspector) body that collapses to one column on narrow containers. Metadata reads as grouped "3D model" / "STL mesh" / "3MF package" sections with uppercase mono labels; the viewer hint is a small mono chip bottom-right. Also aligns tokens with the design conventions: semantic role tokens (--foreground/--muted-foreground/--card/--muted/--border) instead of numbered palette values, rem units, and no `font` shorthand. CS-12053, CS-12054
Match the handoff realm's inspector: wrap the property groups in a bordered, rounded --card panel and separate rows with hairline top borders (first row none), with mono uppercase group headings. Applied to the shared "3D model" group and the leaf STL/3MF groups. CS-12053, CS-12054
Fitted now mounts the WebGL viewer instead of only the silhouette, but: - lazy via IntersectionObserver — the engine boots only when the tile is on-screen, so a grid of many models doesn't exhaust the browser's WebGL context budget; off-screen tiles keep the silhouette. - non-interactive — no orbit controls, no gesture trapping, no hint, so fitted tiles render a static model and the enclosing grid scrolls normally. During prerender/indexing the engine never loads (no CDN/WebGL), so fitted still falls back to the silhouette there. Verified in the browser across badge/strip tile sizes. CS-12053, CS-12054
Extract the STL/3MF metadata parsers into pure `.ts` modules (stl-meta-extractor / three-mf-meta-extractor, mirroring png-meta-extractor) so they're unit-testable without the card-api harness, and harden them per review: - STL parsing is now a single streaming pass over the DataView with no per-vertex array, so index-time memory stays flat regardless of model size; degeneracy is counted per facet as read, fixing the misaligned count when non-finite vertices were dropped. - parseThreeMf returns undefined for any unparseable input (non-ZIP, no model part, malformed XML) so the leaf falls back cleanly to base FileDef; removed a redundant modelPart regex clause. - Both leaves take an optional fileSizeLimitBytes, defaulting to the realm's standard DEFAULT_FILE_SIZE_LIMIT_BYTES. Over the cap they skip the parse and return base attributes only (keeping the 3D type + live viewer), rather than throwing and demoting to a plain file. - Share Model3dData and getExtension; replace the ~40-line inspector style block duplicated across the three isolated templates with a data-driven ModelInspectorSection component. - Document that model3d.vertices counts vertex records as the format stores them (not comparable across formats). - Add unit tests for parseStl, parseThreeMf, and silhouettePath. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives the real render/file-extract route (the indexer's path) over an ASCII STL and a real 3MF ZIP served from a test realm, asserting the file-meta search doc carries the parsed model3d / stlMetadata / threeMfMetadata, plus the mismatch-fallback for a .stl whose bytes aren't STL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Build test web assets" (rolldown) job failed with MISSING_EXPORT cascading from card-api.gts: the unit test value-imported silhouettePath from model-file-def.gts, which pulled model-file-def -> card-api into the host test bundle, where card-api's long-standing loose type imports (CodeRef/ToolContext/LocalPath/SingleCardDocument imported without the `type` keyword) become hard rolldown errors. Move silhouettePath into a pure model-silhouette.ts (no card-api dependency) and import the test from there; model-file-def re-exports it for back-compat. No host test now value-imports a card-api-chained .gts, so card-api is no longer bundled into the test assets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ent-side size Rework STL/ThreeMf FileDefs per review feedback: - Rename ModelDef -> ThreeDModelDef (and model-file-def.gts -> three-d-model-def.gts) so the base class no longer reads like a shared base type. - Fitted view now renders a static cube icon + filename (mirroring the audio FileDef) instead of a live per-tile WebGL viewer, so a grid of tiles can no longer exhaust the browser's WebGL context budget. The shaded-thumbnail seam (`thumbnailUrl`) is kept for CS-12401. - Drop the deterministic SVG silhouette (model-silhouette.ts) and the index-time bounding box (sizeX/Y/Z). Physical dimensions now come from the live client-side viewer, which reads the true, transform-correct bounds off the loaded geometry — so the 3MF-transform approximation disappears too. - STL extraction is header-only: it reads the binary header (facet count, COLOR= flag) or the ASCII prologue (solid name) and never scans the facet body. - 3MF extraction is a bounded, DOM-free prologue read: fflate's filter decompresses only the `.model` + `model_settings.config` entries (skipping embedded thumbnails/textures and refusing oversized entries as a ZIP-bomb backstop), and metadata is regex-read from the model part's prologue rather than DOM-parsing the geometry. Also validates the 3MF `<model>` core root so unrelated `.model` XML is rejected. - Thread the realm's configured fileSizeLimitBytes through FileDefAttributesExtractor -> the leaf extractAttributes, so the size cap tracks the write-path limit instead of a hard-coded default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…creen Reframe the WebGL/static split around render context rather than format, and fix the context-budget leak the review flagged: - Prerender gate: `ModelViewer`'s modifier no-ops when `globalThis.__boxelRenderContext` is set (server-side indexing/prerender), so WebGL and the CDN engine import are never started there — the static thumbnail is the deterministic prerender representation, instead of relying on a doomed CDN import failing. - Viewport gate: the viewer now lazy-boots when the element scrolls on-screen and disposes its WebGL context (renderer.dispose + forceContextLoss) when it scrolls off, re-booting from cached bytes on re-entry. So an embedded strip of models holds a context only for the visible ones and never exhausts the browser's ~16-context budget — the actual fix to the "contexts grow monotonically" finding. Fitted stays on the static thumbnail (a grid can show more tiles than the context cap at once; pooled-renderer live fitted is tracked in CS-12401). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the 3D model family (StlDef/ThreeMfDef, CS-12053/CS-12054) with the two glTF forms, so `.glb` and `.gltf` files get the same interactive viewer and inspector as the other model formats. GlbDef and GltfDef are thin ThreeDModelDef leaves — they inherit the shared Three.js orbit viewer, whose GLTFLoader default path already renders both the binary and JSON forms, plus the fitted thumbnail and the isolated body. Each adds a `gltfMetadata` field and an extension-guarded, size-capped `extractAttributes`, mirroring StlDef. The metadata is read from the glTF structure itself rather than a geometry scan: a glTF describes its accessors, so `gltf-meta-extractor.ts` reads the vertex and triangle counts, mesh/material/node/animation/texture counts, and the bounding-box dimensions straight from the header — the JSON of a `.gltf`, or the JSON chunk of a `.glb` (its BIN chunk is never touched). Returns undefined for non-glTF bytes, which the leaf turns into a FileContentMismatchError so a mislabeled file falls back to the plain FileDef. Routes `.glb` → GlbDef and `.gltf` → GltfDef, and adds unit tests for the extractor (both container forms, indexed/non-indexed/strip triangle counts, bounds, and the non-glTF fallback) plus code-ref routing assertions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 36m 39s ⏱️ + 30m 30s Results for commit cd4adf3. ± Comparison against earlier commit 2d96c8c. Realm Server Test Results 1 files ± 0 1 suites ±0 17m 51s ⏱️ + 3m 47s Results for commit cd4adf3. ± Comparison against earlier commit 2d96c8c. |
b97f29e to
b30b584
Compare
…subclasses' into cs-12243-3d-model-formats-glb-gltf-stl-3mf # Conflicts: # packages/base/stl-model-def.gts # packages/base/three-d-model-def.gts # packages/base/three-mf-def.gts # packages/host/tests/acceptance/model-file-extract-test.gts # packages/host/tests/unit/file-def-code-ref-test.ts # packages/host/tests/unit/model-meta-extractor-test.ts # packages/runtime-common/file-def-code-ref.ts
|
[Claude Code 🤖] The merge from the base branch was more than marker cleanup, because the base's squashed commit rewrote the 3D family onto the shared file-format shells while this branch's GLB/glTF leaves were written against the earlier hand-rolled architecture. The merge commit (cd4adf3) ports them:
Typechecks pass across runtime-common and host; template-lint is clean on the touched |
…mats-glb-gltf-stl-3mf
Part of CS-12243 (3D model formats). Stacked on #5658 (StlDef/ThreeMfDef, CS-12053/CS-12054) — base this PR against that branch so the diff shows only the glTF additions. Per that decision, GLB/glTF are added in #5658's existing style (Three.js
ThreeDModelDefleaves), not a separate shared-shell rebuild.What
Adds the two glTF forms so
.glband.gltfget the same interactive orbit viewer and inspector as STL/3MF.GlbDef/GltfDef— thinThreeDModelDefleaves. They inherit the shared Three.js viewer (whoseGLTFLoaderdefault path already renders both the binary and JSON forms), the fitted thumbnail, and the isolated body. Each adds agltfMetadatafield and an extension-guarded, size-cappedextractAttributes, mirroringStlDef.gltf-meta-extractor.ts— reads metadata from the glTF structure itself rather than a geometry scan: a glTF describes its accessors, so vertex/triangle counts, mesh/material/node/animation/texture counts, and bounding-box dimensions come straight from the header — the JSON of a.gltf, or the JSON chunk of a.glb(the BIN chunk is never touched). This directly delivers the ticket's "vertex/triangle counts, bounding box." Non-glTF bytes →undefined→FileContentMismatchError→ graceful fallback..glb→GlbDef,.gltf→GltfDef.Tests
model-meta-extractor-test.ts): both container forms, indexed / non-indexed / triangle-strip counts, bounding box, and the non-glTF fallback — all fixtures built in-code, no binaries.GlbDef/GltfDef.Validation
ember-tsc --noEmit(host): 0 errors; base template-lint clean.Notes / limitations
.gltfthat references external.bin/texture resources renders its metadata fine but the live viewer (parsing with an empty resource path) will fall back to the thumbnail for the 3D scene; self-contained.gltf(embedded base64) and.glbrender fully.dimensionsis presented unitless (matching STL's honesty), anddisplayUnitstays inherited-empty.🤖 Generated with Claude Code