Content conventions for viewer GLBs

What a GLB must look like to get the viewer's built-in behaviors (tooltips, the coming layers toolbar) and to avoid the known build-flag landmines. Aimed at content pipelines (machine-pin bakers, kicad-to-glb, board exporters). Everything here was learned the hard way on adom/bq25792-charger and the machine-pin pages (adom/wiki #35, #47, #48).

Layer names

Name meshes (or the transform nodes grouping them) with these exact names. The viewer maps them to human labels today (hover tooltips) and the layers toolbar will group toggles by them.

Name Label shown Notes
fr4_board FR4 board the board substrate
pad_top / pad_bottom Pads (top/bottom) copper pads
solder_top / solder_bottom Solder (top/bottom) joints/balls; default OFF in the layers toolbar
paste_top / paste_bottom Paste (top/bottom) paste dots
silk or silk_top / silk_bottom Silkscreen
barrel Plated barrel plated through-hole barrels
pin1_marker Pin 1 pin-1 designator geometry

Anything else keeps its own name (underscores become spaces in labels).

Hover tooltips (adom/wiki #35)

  • Put a tooltip string in the glTF extras of the node or mesh: "extras": { "tooltip": "Solder ball\nDia 0.30 mm (300 um)" }. Babylon lands it at mesh.metadata.gltf.extras.tooltip. \n makes a multi-line label. This always wins over the name.
  • No extras: the mesh name is the fallback, run through the layer-label table above. Auto-generated junk names (mesh_*, node_*, *_primitive3, __root__, ...) show nothing, so unnamed models stay label-free.
  • The viewer checks the picked mesh, then up to 5 ancestors, so extras on a group node cover all its children (Babylon splits multi-material primitives into child meshes).

GLB build flags that work

  • --instance false. gltf-transform optimize defaults to EXT_mesh_gpu_instancing, which mis-renders here as one giant mis-scaled instance. The viewer detects it and warns in the console, but the fix is rebuilding without it.
  • Draco (KHR_draco_mesh_compression) works on the wiki's /viewer/3d (self-hosted decoder + wasm-unsafe-eval), but NOT inside a readme sandbox (no wasm, no workers). Content meant to load in readmes should ship uncompressed or quantized.
  • KHR_mesh_quantization is decoder-free and loads everywhere. Framing and bounds verified correct on Babylon 9.5 (gltf-transform v3 output). If you see a mis-framed quantized model, file it with the GLB; one historical report (radius ~2700) could not be reproduced.
  • --flatten false --join true preserves the node structure tooltips and layers rely on while still reducing draw calls.
  • Animations must target TRS on nodes (not skins) for the exploded-stackup style effects; the viewer auto-unfreezes the shadow map when a GLB has animation groups, so animated content gets moving shadows for free.

Shadows

  • The bundle uses PCF (QUALITY_HIGH). PCSS (useContactHardeningShadow) combined with enableSoftTransparentShadow silently renders no shadows at all (pixel-diff proven: PCSS 0.0% change vs PCF 26.7%); the bundle detects the combination every frame and falls back to PCF with a console warning.
  • Shadows only darken direct light. If you re-light a scene, keep the spot dominant (env as fill, ~0.35 intensity) or shadows wash out on PBR surfaces.

Verifying lighting/shadow changes (John's protocol)

Freeze the scene, verify twice, hide the ground, then pixel-diff two captures. Never pixel-diff an animating scene; motion reads as change.

Embedding quick reference

const viewer = Adom3DViewerBabylon9.init(container, { zUp: true, showViewCube: true, showGround: true });
await viewer.loadModel(url);   // frames, registers shadow casters

The wiki's /viewer/3d/<type>/<slug> route accepts ?model=<repo-path>, ?ground=0, ?axes=1, ?animate=0, ?bottomlight=1, and ?camera=alpha,beta[,radiusScale] so a page can embed variants and start states without page scripting.

Sandboxed readme embeds need no storage shim as of 20260717a; the bundle guards its own storage access. The full sandbox contract lives in git-wiki's docs/readme-sandbox.md.