Adom 3D Viewer
Public Made by Adomby adom
The Babylon 9.5 engine behind every component page's 3D tab on wiki.adom.inc. Versioned ESM bundle with GLB loading, view cube, layers toolbar, ground shadows, and Z-up CAD framing.
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
tooltipstring in the glTFextrasof the node or mesh:"extras": { "tooltip": "Solder ball\nDia 0.30 mm (300 um)" }. Babylon lands it atmesh.metadata.gltf.extras.tooltip.\nmakes 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 optimizedefaults 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_quantizationis 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 truepreserves 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 withenableSoftTransparentShadowsilently 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.
# 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
```js
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`.