3D Viewer
Renders an imported GLB model with live tag bindings — the colour, emissive, opacity and visibility of any node in the model can follow a tag. A 3D equivalent of a P&ID mimic.


Reach for it when you need:
- A 3D plant or machine view where components colour by state
Component name: Viewer3D · Category: Display
In the palette: Renders an imported 3D model (GLB) with live tag bindings: color, emissive, opacity and visibility of any node in the model can follow a tag.
Properties
Any property can be bound to a tag, another component's property, or a query.
| Property | Type | Default | Description |
|---|---|---|---|
model | asset | "" | The 3D model (.glb), from the asset Library or the Uploads folder. Bindable — bind it to a tag/expression yielding a path (e.g. Models/Pumps/p1.glb) or an upload:// ref (e.g. a File Manager widget's {{ <name>.selectedItem.ref }}) to swap the model at runtime. |
bindings | array | [] | Tag→model bindings. Each row drives one property (color / emissive / opacity / visible) of one node. Bind each row's value to a tag, or the whole array to a Named Query. |
labels | array | [] | Text overlays pinned to nodes — a live tag readout with a quality badge. Each row binds its own text + quality. |
fitOnLoad | boolean | true | Frame the model automatically when no camera has been set. |
background | color | "" | Canvas background color. Blank = transparent (the view shows through). |
lighting | object | {"preset":"studio","intensity":1,"ambient":1,"direction":[5,… (truncated) | Light rig, how strong it is, and where the key light comes from. Set the direction with the edit-mode 'Set light' tool (aim the view, then Save) or type it. Every field is bindable. |
interactive | boolean | true | Allow orbit/pan/zoom in LiveView (the editor always locks the camera; use Set camera to frame it). |
camera | object | {} | Input: the AUTHORED default camera pose — written by the Set camera tool, or click + to fill the fields and type them. Leave empty to auto-fit the model. (For the LIVE pose an operator orbits to at runtime, read the output.cameraView output instead.) |
orbit | object | {"minDistance":0,"maxDistance":0,"enableZoom":true,"enablePa… (truncated) | Camera interaction limits (three's OrbitControls) — zoom range, pan/zoom toggles, auto-rotate. |
look | object | {"projection":"perspective","toneMapping":"none","exposure":… (truncated) | How the model READS on screen: projection, tone mapping + exposure, shadows/ground/grid/axes, selection highlight, and the device-pixel-ratio cap. |
animation | object | {"clip":"","playing":false,"speed":1,"time":0,"loop":true} | Input: animation playback CONTROL — clip name, play/pause, speed, scrub time and loop. Every field is bindable. (For the list of clip names this model offers, see the output.animationClips output.) |
output | object | {"selectedObject":{},"cameraView":{},"sceneNodes":[],"animat… (truncated) | Read-only runtime outputs the widget publishes — bind a leaf, e.g. {{ <Viewer3D>.output.selectedObject.name }}. Leaves: selectedObject { name, path }, cameraView { position, target, fov }, sceneNodes [{ name, path }], animationClips [string], morphTargets [string]. |
style | style | {} | Custom CSS properties |
Events
Attach event actions to these in the Event Manager.
| Event | Label | Group | Payload |
|---|---|---|---|
click | Click | Mouse | The DOM event. |
dblclick | Double click | Mouse | The DOM event. |
contextmenu | Right click | Mouse | The DOM event. |
mouseenter | Mouse enter | Mouse | The DOM event. |
mouseleave | Mouse leave | Mouse | The DOM event. |
objectClick | Object click | 3D Object | The picked model node, under selectedObject ({ name, path }). |
objectDoubleClick | Object double click | 3D Object | The picked model node, under selectedObject. |
contextmenu fires your configured actions at runtime only — in LiveView and in preview. On the Designer canvas the right-click is captured to open the Event Manager, so testing it there will not run your actions. Test right-click in preview.
Notes
Reading what it publishes
Everything the viewer reports back — the picked object, the current camera, the model's animation clips and morph targets — lives under one output object, so a binding reads:
{{ <Viewer3D>.output.selectedObject.name }}
{{ <Viewer3D>.output.cameraView }}
{{ <Viewer3D>.output.animationClips }}
{{ <Viewer3D>.output.morphTargets }}
They were top-level props once, and three of them mirrored an authored input by name — cameraView beside camera, animationClips beside animation — so the property grid read as though the same thing were declared twice and a binding author could not tell which one to bind from. Grouping them says which side of the widget each belongs to.
Clicking one object rather than the whole viewer
Objects in the model are addressable parts: each can carry its own objectClick and objectDoubleClick, wired in the Event Manager under its node name. A click on a multi-primitive node is walked up to the deepest ancestor you wired, so the name you see in the model tree is the name to use.
→ Events on a part of a widget
Performance
Large models are the most expensive thing you can put on a screen. Check it on the actual panel hardware, not on your laptop.