@liveroom-tech/react-immersive 5.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +582 -41
- package/dist/{ModelViewer-DOexHkJC.d.ts → ModelViewer-Bj-KjiDL.d.mts} +91 -42
- package/dist/{ModelViewer-CbbJT85n.d.mts → ModelViewer-BkQ73y6N.d.ts} +91 -42
- package/dist/binding-builder.css +1 -1
- package/dist/binding-builder.js +13 -12
- package/dist/binding-builder.mjs +1 -1
- package/dist/chunk-32WCLPTZ.mjs +1 -0
- package/dist/chunk-CSGNL4XU.mjs +3 -0
- package/dist/chunk-F2ILTIPG.mjs +28 -0
- package/dist/chunk-V6GALFL5.mjs +1 -0
- package/dist/chunk-WN6GMMHF.mjs +1 -0
- package/dist/hosted.css +1 -1
- package/dist/hosted.d.mts +3 -3
- package/dist/hosted.d.ts +3 -3
- package/dist/hosted.js +12 -11
- package/dist/hosted.mjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.mts +54 -16
- package/dist/index.d.ts +54 -16
- package/dist/index.js +11 -10
- package/dist/index.mjs +1 -1
- package/dist/{modelInfo-CEmmvVCH.d.mts → modelInfo-DnkmTGMd.d.mts} +6 -0
- package/dist/{modelInfo-CEmmvVCH.d.ts → modelInfo-DnkmTGMd.d.ts} +6 -0
- package/dist/{objectCatalog-5W8n5xNQ.d.ts → objectCatalog-BIeXURoq.d.mts} +276 -5
- package/dist/{objectCatalog-5W8n5xNQ.d.mts → objectCatalog-BIeXURoq.d.ts} +276 -5
- package/dist/simple-model-viewer.d.mts +2 -2
- package/dist/simple-model-viewer.d.ts +2 -2
- package/dist/simple-model-viewer.js +3 -3
- package/dist/simple-model-viewer.mjs +1 -1
- package/dist/utils.d.mts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +1 -1
- package/dist/utils.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-3GX7DOGS.mjs +0 -3
- package/dist/chunk-AH5NAZWX.mjs +0 -27
- package/dist/chunk-BGYK4Y2Y.mjs +0 -1
- package/dist/chunk-WFEYRUZC.mjs +0 -1
- package/dist/chunk-XTLSAYAQ.mjs +0 -1
package/README.md
CHANGED
|
@@ -59,13 +59,18 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
59
59
|
- optional hiding of either built-in panel through `showObjectBindingDataPanel` and `showSceneObjectsPanel`
|
|
60
60
|
- optional model export button through `showDownloadButton` and `downloadFilename`, opt-in PNG capture through `preserveDrawingBuffer`, plus an independent `showResetButton` toggle for the rest of that action bar
|
|
61
61
|
- an optional UV Checker toolbar button through `showUvCheckerButton`, for inspecting UV scale, stretching, seams, orientation, and missing UV0 coordinates directly in `ModelViewer`
|
|
62
|
-
-
|
|
62
|
+
- glTF material variants (`KHR_materials_variants`) with a built-in picker through `showMaterialVariants`, selected by `sceneConfig.model.materialVariant`
|
|
63
|
+
- distance, angle, polygon area, and point-to-plane measurements with vertex/edge snapping and bounding dimensions through `showMeasureTools` and `measurementUnit`
|
|
63
64
|
- an exploded-view slider that slides each bound part outward from the model center to reveal interior/assembly structure through `showExplodeControls`
|
|
65
|
+
- isolation that ghosts or hides everything but the objects you focus on, through `isolation` / `onIsolationChange` and `showIsolateControls`
|
|
66
|
+
- a section cut that opens the model along an axis, with a draggable plane, through `showSectionTools` and `sceneConfig.section`
|
|
64
67
|
- a cinematic auto-camera that glides the camera along authored waypoints (or a zero-config showcase orbit), like a film, through `cinematic`, with a play/pause control that yields the moment you touch the camera
|
|
68
|
+
- video export of a cinematic pass or turntable revolution, through the download menu or `captureVideo`, using browser-supported WebM/MP4 encoding
|
|
65
69
|
- a guided-tour control cluster (Previous/Stop/Next) for stepping through annotations, toggleable via `showAnnotationNavigation`
|
|
66
70
|
- optional annotation detail popups on marker hover via `showAnnotationOnHover`
|
|
67
71
|
- PBR, Matcap, and UV Checker renderer modes, with the checker exposing UV stretching, seams, rotation, and missing UV0 data directly on the model
|
|
68
72
|
- a `sceneConfig` prop accepting the same scene-wide config (lighting, environment, background, post-processing, animations, annotations) authored by `BindingBuilder`'s Scene tab
|
|
73
|
+
- WebGL context-loss recovery with a reload button and automatic rebuild when the context returns
|
|
69
74
|
- render-loop and perf tuning through `renderMode`, `maxDpr`, `performanceProfile`, and compressed-asset decoder options (`dracoDecoderPath`, `ktx2TranscoderPath`, `meshopt`)
|
|
70
75
|
- WebXR "View in your space" (AR) and "Enter VR" through `enableXR`, with tap-to-place, pinch-to-resize, and twist-to-rotate AR placement gestures
|
|
71
76
|
- mesh selection by object name
|
|
@@ -75,6 +80,8 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
75
80
|
- built-in texture upload that commits a URL into `objectBindings.style.material.texture.path` (blob URL by default, or a durable URL when `onTextureUpload` is provided)
|
|
76
81
|
- per-object `MeshPhysicalMaterial` overrides for metalness, roughness, emissive, normal/bump, AO, displacement, clearcoat, sheen, anisotropy, specular, transmission, thickness, reflectivity, sidedness, and limited blending modes
|
|
77
82
|
- per-object visibility toggling through `objectBindings.visible`
|
|
83
|
+
- decals: images and text printed on an object's surface through `objectBindings.decals`, placed from the surface point pointer events report
|
|
84
|
+
- data-driven looks through `sceneConfig.styleRules`: colour scales from metrics, material overrides by status or metadata, and pulsing alarms
|
|
78
85
|
- hover and selected-state highlighting
|
|
79
86
|
- external selection state control through `selectedObject` and `onObjectSelect`
|
|
80
87
|
- hover callbacks through `onObjectHover`
|
|
@@ -84,6 +91,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
84
91
|
- camera change callbacks through `onCameraChange`
|
|
85
92
|
- viewer-ready callbacks through `onViewerReady`
|
|
86
93
|
- action event callbacks through `onAction`
|
|
94
|
+
- opt-in engagement analytics through `onEngagement`, with `debugEngagement` console logging
|
|
87
95
|
- animation playback for GLB/GLTF, FBX, and USDZ models with supported embedded animations through `onAnimationsReady`
|
|
88
96
|
- annotation marker callbacks through `onAnnotationsChange` and controlled/uncontrolled `activeAnnotation` / `onActiveAnnotationChange`
|
|
89
97
|
|
|
@@ -100,7 +108,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
100
108
|
- import a previously exported `objectBindings.json` or `sceneConfig.json` and merge it back onto the current model/config
|
|
101
109
|
- live preview using `ModelViewer`
|
|
102
110
|
- export as JSON or TypeScript, bundled into a `.zip` with a `materials/` folder automatically when any texture field was filled by file upload, otherwise a plain file
|
|
103
|
-
- license-tier gating for some editor features (texture maps, environment lighting/backgrounds, wireframe preview, animation configuration, annotations, post-processing)
|
|
111
|
+
- license-tier gating for some editor features (texture maps, environment lighting/backgrounds, wireframe preview, animation configuration, annotations, style rules, post-processing)
|
|
104
112
|
|
|
105
113
|
`SimpleModelViewer` provides:
|
|
106
114
|
|
|
@@ -267,7 +275,7 @@ export default function Configurator() {
|
|
|
267
275
|
|
|
268
276
|
`viewer.bindings`, `viewer.selection`, `viewer.hover`, `viewer.camera`,
|
|
269
277
|
`viewer.model`, `viewer.animations`, `viewer.actions`, `viewer.scene`,
|
|
270
|
-
`viewer.effects`, and `viewer.connection` are the results of the hooks they're
|
|
278
|
+
`viewer.isolation`, `viewer.effects`, and `viewer.connection` are the results of the hooks they're
|
|
271
279
|
named after (`useObjectBindings`, `useViewerSelection`, and so on), so
|
|
272
280
|
everything below applies to them. Without a `bindings` option, `useViewer`
|
|
273
281
|
generates bindings from the model like `objectBindings="auto"`; pass
|
|
@@ -462,6 +470,9 @@ const {
|
|
|
462
470
|
setTransform,
|
|
463
471
|
resetTransform,
|
|
464
472
|
updateMetadata,
|
|
473
|
+
addDecal,
|
|
474
|
+
updateDecal,
|
|
475
|
+
removeDecal,
|
|
465
476
|
hiddenObjects,
|
|
466
477
|
hiddenObjectIds,
|
|
467
478
|
hideObject,
|
|
@@ -524,6 +535,7 @@ The hook returns:
|
|
|
524
535
|
- `setMaterial`, `setBaseColor`, `setTexture`, `clearTexture`, `copyMaterial`, `resetMaterial`, and `getMaterial`
|
|
525
536
|
- `setTransform` and `resetTransform` for persistent local-space model transforms
|
|
526
537
|
- `updateMetadata` for recursively merged application data
|
|
538
|
+
- `addDecal`, `updateDecal`, and `removeDecal` for images and text printed on objects (see [Decals](#decals))
|
|
527
539
|
- `hiddenObjects`: a derived map of hidden binding keys
|
|
528
540
|
- `hiddenObjectIds`: the currently hidden binding IDs
|
|
529
541
|
- `hideObject`, `showObject`, `toggleObjectVisibility`
|
|
@@ -661,6 +673,7 @@ const {
|
|
|
661
673
|
error,
|
|
662
674
|
bounds,
|
|
663
675
|
objectCount,
|
|
676
|
+
materialVariants,
|
|
664
677
|
objects,
|
|
665
678
|
handleModelLoaded,
|
|
666
679
|
handleObjectCatalog,
|
|
@@ -684,6 +697,8 @@ const {
|
|
|
684
697
|
- `error`: the most recent load/render error, or `null`
|
|
685
698
|
- `bounds`: the loaded scene bounds as `min`, `max`, `center`, and `size`
|
|
686
699
|
- `objectCount`: the number of mesh objects in the loaded scene
|
|
700
|
+
- `materialVariants`: the names of the model's glTF material variants, or an
|
|
701
|
+
empty array (see [`showMaterialVariants`](#showmaterialvariants))
|
|
687
702
|
- `objects`: every bindable object in the model as a `ViewerObjectInfo`, once
|
|
688
703
|
`handleObjectCatalog` is passed to `onObjectCatalog`
|
|
689
704
|
- `handleModelLoaded`: a callback you can pass directly to `onModelLoaded`
|
|
@@ -751,6 +766,7 @@ Current behavior, Object tab:
|
|
|
751
766
|
- upload a `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, or `.usdz` file, or a `.zip` bundle (a `.gltf` with its `.bin` and textures, an `.obj` with its MTL and textures, or an `.fbx` with external textures)
|
|
752
767
|
- traverse renderable mesh nodes and generate starter bindings automatically
|
|
753
768
|
- edit binding identity, label, type, status, booleans, style, actions, metrics, metadata, and saved camera state
|
|
769
|
+
- add decals to the selected object from the **Decals** panel: upload an image or add text to place it automatically at the centre of the camera-facing surface, then set its size, rotation, and opacity; **Move** lets you drag the decal across the object's surface. Release to save, or press Escape to cancel. Uploaded images are exported under `materials/` with the bindings
|
|
754
770
|
- move and rotate the selected object from the **Move & Rotate** panel using a geometry-centered pivot, World/Local axis alignment, or numeric position and degree fields; restore its authored transform with one click
|
|
755
771
|
- apply a one-click material preset (wood, metal, chrome, glass, plastic, fabric, ceramic, concrete, etc.) onto the selected object's material
|
|
756
772
|
- undo/redo the current bindings (toolbar buttons or `Cmd/Ctrl+Z` / `Cmd/Ctrl+Shift+Z`); rapid edits coalesce into a single undo step
|
|
@@ -761,6 +777,11 @@ Current behavior, Scene tab:
|
|
|
761
777
|
|
|
762
778
|
- configure the same `SceneConfig` shape `ModelViewer`'s `sceneConfig` prop accepts (lighting, environment, background, ground shadows, wireframe, post-processing, animations, annotations)
|
|
763
779
|
- configure auto-rotation and its speed, exported as `sceneConfig.model.autoRotate` and `sceneConfig.model.autoRotateSpeed`
|
|
780
|
+
- preview animation clips, several at once and forwards or backwards, and set each clip's loop mode (loop, play once, or ping-pong), speed, and display name
|
|
781
|
+
- pick which of the model's glTF material variants the scene starts with, exported as `sceneConfig.model.materialVariant`
|
|
782
|
+
- isolate a node from the Scene Nodes list to see it on its own in the preview while you edit it (not exported)
|
|
783
|
+
- set up a section cut (**General → Section Cut**: axis, position, and side to keep), exported as `sceneConfig.section`; the preview has the section toolbar and draggable plane too
|
|
784
|
+
- build style rules (**Rules** sub-tab) that restyle objects from their metrics, status, and metadata, with suggestions from the model's bindings, a live match count, and a live preview; exported as `sceneConfig.styleRules`
|
|
764
785
|
- position and rotate directional, point, and spot lights with transform gizmos; spot lights also expose angle and penumbra controls
|
|
765
786
|
- undo/redo the scene config independently of the Object tab's bindings history
|
|
766
787
|
- import a previously exported `sceneConfig.json`, merged by top-level section
|
|
@@ -785,7 +806,7 @@ Some editor features are gated by the license's plan tier (the server-returned `
|
|
|
785
806
|
| Tier | Unlocks |
|
|
786
807
|
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
787
808
|
| `free` | All 3 components are available. `SimpleModelViewer` is fully available, while `ModelViewer` and `BindingBuilder` are limited to local development with base color, metalness/roughness sliders, and basic scene settings only |
|
|
788
|
-
| `starter` | + Texture maps, the specular workflow, reflectivity, environment lighting, environment backgrounds, wireframe preview, animation configuration, annotations
|
|
809
|
+
| `starter` | + Texture maps, the specular workflow, reflectivity, environment lighting, environment backgrounds, wireframe preview, animation configuration, annotations, style rules, decals |
|
|
789
810
|
| `growth` | + Post-processing effects |
|
|
790
811
|
|
|
791
812
|
A locked feature renders in place (not hidden) with a message naming the required plan, so it stays discoverable while editing.
|
|
@@ -906,16 +927,17 @@ const {
|
|
|
906
927
|
|
|
907
928
|
- `clips`: an array of animation clip names found in the GLB/GLTF file
|
|
908
929
|
- `clipDetails`: clip metadata including `sourceName` and `duration` in seconds
|
|
909
|
-
- `currentClip`: the
|
|
910
|
-
- `isPlaying`: `true` while
|
|
911
|
-
- `speed`: the
|
|
912
|
-
- `time`: the live playback position of
|
|
913
|
-
- `duration`: the length of
|
|
914
|
-
- `
|
|
915
|
-
- `
|
|
916
|
-
- `
|
|
917
|
-
- `
|
|
918
|
-
- `
|
|
930
|
+
- `currentClip`: the clip played most recently, while it's active, or `null`
|
|
931
|
+
- `isPlaying`: `true` while any clip is playing (not paused, finished, or stopped)
|
|
932
|
+
- `speed`: the playback speed of `currentClip` (default `1`)
|
|
933
|
+
- `time`: the live playback position of `currentClip`, in seconds (updates while playing)
|
|
934
|
+
- `duration`: the length of `currentClip`, in seconds
|
|
935
|
+
- `active`: every active clip (playing, paused, or finished) with its `status`, `time`, `duration`, `speed`, and `reverse`, most recently played last
|
|
936
|
+
- `play(clip, options?)`: starts a clip by name, replacing the clips already playing unless `options.alongside` is set; `options.fade` blends over that many seconds, and `options.reverse` plays it backwards. A paused clip resumes instead of restarting
|
|
937
|
+
- `pause(clip?)`: pauses one clip, or every active clip, at its current time
|
|
938
|
+
- `stop(clip?)`: stops one clip, or every active clip, and resets the pose
|
|
939
|
+
- `setSpeed(speed, clip?)`: changes the playback speed (e.g. `0.5` for half speed, `2` for double) of one clip, or of every active clip and those played later without their own speed
|
|
940
|
+
- `seek(time, clip?)`: scrubs one clip, or every active clip, to an absolute time in seconds (works while playing or paused), clamped to the clip length
|
|
919
941
|
- `handleAnimationsReady`: a callback you can pass directly to `onAnimationsReady`
|
|
920
942
|
|
|
921
943
|
Example with playback controls:
|
|
@@ -930,7 +952,7 @@ const { clips, currentClip, isPlaying, play, pause, stop, setSpeed } =
|
|
|
930
952
|
</button>
|
|
931
953
|
|
|
932
954
|
// Stop
|
|
933
|
-
<button onClick={stop}>Stop</button>
|
|
955
|
+
<button onClick={() => stop()}>Stop</button>
|
|
934
956
|
|
|
935
957
|
// Speed slider
|
|
936
958
|
<input
|
|
@@ -950,16 +972,44 @@ const { clips, currentClip, isPlaying, play, pause, stop, setSpeed } =
|
|
|
950
972
|
))}
|
|
951
973
|
```
|
|
952
974
|
|
|
975
|
+
Several clips can play at once. `play` replaces the clips already playing
|
|
976
|
+
unless you pass `alongside: true`, and the other controls act on one clip when
|
|
977
|
+
you name it, or on every active clip when you don't:
|
|
978
|
+
|
|
979
|
+
```tsx
|
|
980
|
+
const animations = useViewerAnimations();
|
|
981
|
+
|
|
982
|
+
// Open both doors together, then close the left one.
|
|
983
|
+
animations.play("DoorLeftOpen", { alongside: true });
|
|
984
|
+
animations.play("DoorRightOpen", { alongside: true });
|
|
985
|
+
animations.play("DoorLeftOpen", { alongside: true, reverse: true });
|
|
986
|
+
|
|
987
|
+
// Blend from walking to running over half a second.
|
|
988
|
+
animations.play("Run", { fade: 0.5 });
|
|
989
|
+
|
|
990
|
+
animations.pause("Wheels"); // one clip
|
|
991
|
+
animations.stop(); // every clip
|
|
992
|
+
|
|
993
|
+
// What's active, and how far along each clip is
|
|
994
|
+
animations.active.map(({ clip, status, time }) => `${clip}: ${status} at ${time}s`);
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
A clip whose `loopMode` is `"once"` holds its last pose when it finishes
|
|
998
|
+
(`status: "finished"`) until it's stopped or played again, so an opened door
|
|
999
|
+
stays open. `"ping-pong"` plays forwards then backwards, repeatedly. Playing a
|
|
1000
|
+
clip that's already playing in the same direction leaves it running; playing
|
|
1001
|
+
it the other way turns it around where it is.
|
|
1002
|
+
|
|
953
1003
|
## Presets and grouped props
|
|
954
1004
|
|
|
955
1005
|
`preset` sets `ModelViewer` up for a common kind of page:
|
|
956
1006
|
|
|
957
1007
|
| Preset | For | What it sets |
|
|
958
1008
|
| --- | --- | --- |
|
|
959
|
-
| `minimal` | Just the model | Hides both panels, the download and reset buttons,
|
|
1009
|
+
| `minimal` | Just the model | Hides both panels, the download and reset buttons, tour controls, and the material variant buttons |
|
|
960
1010
|
| `showcase` | A product on display | Everything `minimal` does, plus auto-rotation |
|
|
961
|
-
| `configurator` | Customers changing parts | Shows the selected object's panel and the
|
|
962
|
-
| `inspector` | Checking a model | Every panel, the view gizmo, the measure, UV checker,
|
|
1011
|
+
| `configurator` | Customers changing parts | Shows the selected object's panel, the reset button, and the material variant buttons; hides the scene objects panel and download |
|
|
1012
|
+
| `inspector` | Checking a model | Every panel, the view gizmo, the measure, UV checker, exploded-view, section-cut, and isolate tools, and keyboard navigation |
|
|
963
1013
|
|
|
964
1014
|
`MODEL_VIEWER_PRESETS` lists exactly which props each preset sets.
|
|
965
1015
|
|
|
@@ -981,8 +1031,8 @@ readable:
|
|
|
981
1031
|
|
|
982
1032
|
| Group | Fields, and the flat prop each one sets |
|
|
983
1033
|
| --- | --- |
|
|
984
|
-
| `ui` | `objectPanel` (`showObjectBindingDataPanel`), `sceneObjects` (`showSceneObjectsPanel`), `download` (`showDownloadButton`; a string also sets `downloadFilename`), `reset` (`showResetButton`), `loadingOverlay` (`showLoadingOverlay`), `viewGizmo` (`showViewGizmo`), `mouseController` (`showMouseController`; `{ position, opacity }` also sets `mouseControllerPosition` and `mouseControllerOpacity`), `annotationNavigation` (`showAnnotationNavigation`), `annotationOnHover` (`showAnnotationOnHover`), `highlightOnHover` |
|
|
985
|
-
| `tools` | `measure` (`showMeasureTools`; `{ unit }` also sets `measurementUnit`), `uvChecker` (`showUvCheckerButton`), `explode` (`showExplodeControls`), `texturePositioning` (`texturePositioning`) |
|
|
1034
|
+
| `ui` | `objectPanel` (`showObjectBindingDataPanel`), `sceneObjects` (`showSceneObjectsPanel`), `download` (`showDownloadButton`; a string also sets `downloadFilename`), `reset` (`showResetButton`), `fullscreen` (`showFullscreenButton`), `loadingOverlay` (`showLoadingOverlay`), `viewGizmo` (`showViewGizmo`), `mouseController` (`showMouseController`; `{ position, opacity }` also sets `mouseControllerPosition` and `mouseControllerOpacity`), `annotationNavigation` (`showAnnotationNavigation`), `annotationOnHover` (`showAnnotationOnHover`), `highlightOnHover`, `materialVariants` (`showMaterialVariants`) |
|
|
1035
|
+
| `tools` | `measure` (`showMeasureTools`; `{ unit }` also sets `measurementUnit`), `uvChecker` (`showUvCheckerButton`), `explode` (`showExplodeControls`), `section` (`showSectionTools`), `isolate` (`showIsolateControls`), `texturePositioning` (`texturePositioning`) |
|
|
986
1036
|
| `controls` | `enabled` (`enableCameraControls`), `keyboard` (`enableKeyboardNavigation`), `zoom` (the opposite of `disableZoom`), `zoomOnSelect` (`zoomOnSelected`), `autoRotate` (a number also sets `autoRotateSpeed`), `refitOnResize`, `limits` (`cameraLimits`), `moveSensitivity`, `zoomSensitivity` |
|
|
987
1037
|
| `perf` | `renderMode`, `maxDpr`, `profile` (`performanceProfile`), `preserveDrawingBuffer` |
|
|
988
1038
|
| `tiles` | `url` (`tilesetUrl`), `errorTarget` (`tilesErrorTarget`), `cacheSize` (`tilesCacheSize`) |
|
|
@@ -1029,6 +1079,8 @@ type Props = {
|
|
|
1029
1079
|
controller: "orbit" | "pointerLock",
|
|
1030
1080
|
) => void;
|
|
1031
1081
|
onAction?: (event: ObjectActionEvent) => void;
|
|
1082
|
+
onEngagement?: (event: ViewerEngagementEvent) => void | Promise<void>;
|
|
1083
|
+
debugEngagement?: boolean;
|
|
1032
1084
|
onHiddenObjectsChange?: (next: Record<string, boolean>) => void;
|
|
1033
1085
|
onCameraChange?: (state: ViewerCameraState) => void;
|
|
1034
1086
|
onViewerReady?: (viewer: ViewerReadyState) => void;
|
|
@@ -1053,6 +1105,7 @@ type Props = {
|
|
|
1053
1105
|
showDownloadButton?: boolean;
|
|
1054
1106
|
downloadFilename?: string;
|
|
1055
1107
|
showResetButton?: boolean;
|
|
1108
|
+
showFullscreenButton?: boolean;
|
|
1056
1109
|
showLoadingOverlay?: boolean;
|
|
1057
1110
|
showMouseController?: boolean;
|
|
1058
1111
|
mouseControllerPosition?:
|
|
@@ -1086,6 +1139,11 @@ type Props = {
|
|
|
1086
1139
|
showMeasureTools?: boolean;
|
|
1087
1140
|
showUvCheckerButton?: boolean;
|
|
1088
1141
|
showExplodeControls?: boolean;
|
|
1142
|
+
showSectionTools?: boolean;
|
|
1143
|
+
isolation?: ViewerIsolation | null;
|
|
1144
|
+
onIsolationChange?: (isolation: ViewerIsolation | null) => void;
|
|
1145
|
+
showIsolateControls?: boolean;
|
|
1146
|
+
showMaterialVariants?: boolean;
|
|
1089
1147
|
cinematic?: boolean | CinematicConfig;
|
|
1090
1148
|
measurementUnit?: string;
|
|
1091
1149
|
enableXR?: boolean;
|
|
@@ -1312,6 +1370,7 @@ type ObjectPointerEvent = {
|
|
|
1312
1370
|
clientX: number; // viewport coordinates
|
|
1313
1371
|
clientY: number;
|
|
1314
1372
|
pointerType: string; // "mouse" | "pen" | "touch"
|
|
1373
|
+
surface?: { position: Vec3; normal: Vec3 }; // where on the object, in its own space
|
|
1315
1374
|
};
|
|
1316
1375
|
```
|
|
1317
1376
|
|
|
@@ -1389,9 +1448,8 @@ type ViewerObjectInfo = {
|
|
|
1389
1448
|
`minimum` and `maximum` are the object's bounds in the model's own units, in
|
|
1390
1449
|
Z-up coordinates as in 3D Tiles: a point at glTF `(x, y, z)` is `(x, -z, y)`
|
|
1391
1450
|
here. They're the same for loaded and streamed models, and don't change when the
|
|
1392
|
-
viewer moves or scales the model.
|
|
1393
|
-
|
|
1394
|
-
its `handleObjectCatalog`.
|
|
1451
|
+
viewer moves or scales the model. To keep the list in state, use
|
|
1452
|
+
`useViewerModel` and pass its `handleObjectCatalog`.
|
|
1395
1453
|
|
|
1396
1454
|
### `onModelLoaded`
|
|
1397
1455
|
|
|
@@ -1404,6 +1462,7 @@ count:
|
|
|
1404
1462
|
type ViewerModelInfo = {
|
|
1405
1463
|
bounds: { min: Vec3; max: Vec3; center: Vec3; size: Vec3 }; // world space
|
|
1406
1464
|
objectCount: number; // meshes in the loaded scene
|
|
1465
|
+
materialVariants: string[]; // glTF material variant names, if any
|
|
1407
1466
|
};
|
|
1408
1467
|
```
|
|
1409
1468
|
|
|
@@ -1451,6 +1510,60 @@ type ObjectActionEvent = {
|
|
|
1451
1510
|
};
|
|
1452
1511
|
```
|
|
1453
1512
|
|
|
1513
|
+
### `onEngagement` / `debugEngagement`
|
|
1514
|
+
|
|
1515
|
+
Optional interaction analytics. Set `onEngagement` to receive typed
|
|
1516
|
+
`ViewerEngagementEvent` objects, or set `debugEngagement` to log them in the
|
|
1517
|
+
browser's developer console with the `[react-immersive engagement]` prefix.
|
|
1518
|
+
Both are disabled by default. The viewer does not send telemetry or persist
|
|
1519
|
+
analytics identifiers; your callback decides where events go.
|
|
1520
|
+
|
|
1521
|
+
```tsx
|
|
1522
|
+
import { ModelViewer, type ViewerEngagementEvent } from "@liveroom-tech/react-immersive";
|
|
1523
|
+
|
|
1524
|
+
function handleEngagement(event: ViewerEngagementEvent) {
|
|
1525
|
+
if (event.type === "part-viewed") {
|
|
1526
|
+
console.info("Part viewed:", event.objectKey, event.source);
|
|
1527
|
+
}
|
|
1528
|
+
if (event.type === "ar-ended") {
|
|
1529
|
+
console.info("Time in AR:", event.durationMs);
|
|
1530
|
+
}
|
|
1531
|
+
// Connect this callback to your application's analytics integration.
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
<ModelViewer
|
|
1535
|
+
modelUrl="/car.glb"
|
|
1536
|
+
licenseKey={licenseKey}
|
|
1537
|
+
onEngagement={handleEngagement}
|
|
1538
|
+
debugEngagement
|
|
1539
|
+
/>
|
|
1540
|
+
```
|
|
1541
|
+
|
|
1542
|
+
Every event includes `sessionId` (an ephemeral ID for this viewer/model),
|
|
1543
|
+
`timestamp` (ISO format), `elapsedMs`, `modelUrl`, and `tilesetUrl` (or `null`).
|
|
1544
|
+
Durations use a monotonic clock and are expressed in milliseconds. Session
|
|
1545
|
+
elapsed time includes loading and background time; it is not an attention metric.
|
|
1546
|
+
|
|
1547
|
+
| Event `type` | Details and trigger |
|
|
1548
|
+
| --- | --- |
|
|
1549
|
+
| `part-viewed` | `objectId`, `objectKey`, optional `label`, and `source` (`"canvas"` or `"panel"`). Selecting/focusing a different part; reselecting the current part is ignored. |
|
|
1550
|
+
| `variant-chosen` | `variant` and `previousVariant` (names or `null` for the default). Choosing a different built-in material variant. |
|
|
1551
|
+
| `ar-started` | A confirmed immersive WebXR AR session begins. |
|
|
1552
|
+
| `ar-ended` | `durationMs` and `reason` (`"session-ended"` or `"interrupted"`). A confirmed AR session ends, or tracking stops on model change, unmount, or context loss. |
|
|
1553
|
+
| `ar-launch-requested` | `channel` (`"quick-look"` or `"mobile-handoff"`). Opening external AR or its QR handoff; this does not claim an AR session started. |
|
|
1554
|
+
| `tour-started` | `annotationCount`. Starting the guided tour. |
|
|
1555
|
+
| `tour-completed` | `annotationCount` and `durationMs`. Advancing past its final annotation; emitted once per completed tour. |
|
|
1556
|
+
| `tour-stopped` | `annotationCount`, `durationMs`, and `reason` (`"stopped"`, `"restarted"`, `"annotations-changed"`, or `"interrupted"`). Leaving a tour before completion. |
|
|
1557
|
+
|
|
1558
|
+
Hover, camera motion, renders, and externally supplied selection/variant updates
|
|
1559
|
+
do not create engagement events. WebXR duration measures the session interval;
|
|
1560
|
+
Quick Look runs outside the page and has no reliable duration callback. Turning
|
|
1561
|
+
tracking on during an existing AR session measures from attachment. Callback
|
|
1562
|
+
changes do not restart timing, and synchronous or asynchronous callback failures are logged without
|
|
1563
|
+
interrupting the viewer. A new model or re-enabling tracking creates a new
|
|
1564
|
+
analytics session; WebGL recovery keeps the session ID; context loss ends active intervals
|
|
1565
|
+
as interrupted.
|
|
1566
|
+
|
|
1454
1567
|
### `onViewerReady`
|
|
1455
1568
|
|
|
1456
1569
|
Optional callback fired once camera controls are available and again when the
|
|
@@ -1470,6 +1583,7 @@ type ViewerReadyState = {
|
|
|
1470
1583
|
objectBindings: Record<string, ObjectBinding>;
|
|
1471
1584
|
homeCameraState: { position: Vec3; target: Vec3 } | null;
|
|
1472
1585
|
captureImage: (options?: CaptureImageOptions) => Promise<string>;
|
|
1586
|
+
captureVideo: (options?: CaptureVideoOptions) => Promise<Blob>;
|
|
1473
1587
|
invalidate: () => void;
|
|
1474
1588
|
raw: {
|
|
1475
1589
|
controls: CameraControls; // camera-controls
|
|
@@ -1522,6 +1636,46 @@ const connection = useViewerConnection(camera, effects);
|
|
|
1522
1636
|
const dataUrl = await connection.captureImage({ width: 1920, height: 1080 });
|
|
1523
1637
|
```
|
|
1524
1638
|
|
|
1639
|
+
`captureVideo` records one complete cinematic pass or turntable revolution and
|
|
1640
|
+
resolves with a video `Blob`. It is also available on `useViewerConnection` and
|
|
1641
|
+
`useViewer().connection`. Call it from a button handler after the model has
|
|
1642
|
+
finished loading and framing; ready-state callbacks run repeatedly as the camera
|
|
1643
|
+
moves and should not start recordings automatically.
|
|
1644
|
+
|
|
1645
|
+
```tsx
|
|
1646
|
+
const viewer = useViewer();
|
|
1647
|
+
const abort = new AbortController();
|
|
1648
|
+
|
|
1649
|
+
<ModelViewer {...viewer.props} cinematic showDownloadButton />;
|
|
1650
|
+
|
|
1651
|
+
// Run in an event handler. Upload the Blob or preview it with an object URL.
|
|
1652
|
+
const clip = await viewer.connection.captureVideo({
|
|
1653
|
+
mode: "turntable", // or "cinematic" to use the enabled camera path
|
|
1654
|
+
format: "auto", // prefers WebM; falls back to MP4
|
|
1655
|
+
duration: 12, // seconds; defaults to path timing or a 24-second orbit
|
|
1656
|
+
fps: 30, // 1–60; actual throughput depends on rendering/encoding
|
|
1657
|
+
videoBitsPerSecond: 8_000_000,
|
|
1658
|
+
signal: abort.signal,
|
|
1659
|
+
});
|
|
1660
|
+
// abort.abort() cancels an in-progress export and discards its data.
|
|
1661
|
+
```
|
|
1662
|
+
|
|
1663
|
+
The download menu offers **Record turntable video** and, when cinematic is
|
|
1664
|
+
enabled, **Record cinematic video**, plus Auto/WebM/MP4 format selection. A
|
|
1665
|
+
recording shows progress and a Cancel control. The filename and Blob MIME type
|
|
1666
|
+
match the encoded format; an explicitly requested unsupported format rejects
|
|
1667
|
+
instead of silently changing formats. With `loop: true`, a cinematic export
|
|
1668
|
+
includes the return segment and stops after one cycle.
|
|
1669
|
+
|
|
1670
|
+
Recording runs in real time at the canvas's current pixel resolution, includes
|
|
1671
|
+
post-processing, and hides editor gizmos. It pauses cinematic playback, blocks
|
|
1672
|
+
viewer gestures, then restores the previous camera pose and controls on success,
|
|
1673
|
+
cancellation, or failure. Keep the tab visible until recording completes.
|
|
1674
|
+
`preserveDrawingBuffer` is unnecessary for video. No audio, HTML overlays,
|
|
1675
|
+
GIF encoding, or format transcoding is included. The browser must support
|
|
1676
|
+
`canvas.captureStream` and `MediaRecorder`; exports reject before framing,
|
|
1677
|
+
during XR, during another recording, or for durations outside (0, 300] seconds.
|
|
1678
|
+
|
|
1525
1679
|
### `onTextureUpload`
|
|
1526
1680
|
|
|
1527
1681
|
Optional async callback for handling texture file uploads. When provided, the viewer calls it instead of creating a blob URL, letting consumers upload to their own storage (S3, Cloudinary, a CDN, etc.) and return a durable URL that survives page reloads.
|
|
@@ -1606,22 +1760,39 @@ Current callback shape:
|
|
|
1606
1760
|
Where `AnimationControls` is:
|
|
1607
1761
|
|
|
1608
1762
|
```ts
|
|
1609
|
-
type
|
|
1610
|
-
|
|
1611
|
-
|
|
1763
|
+
type AnimationPlayOptions = {
|
|
1764
|
+
alongside?: boolean; // keep the clips already playing (default false: replace them)
|
|
1765
|
+
fade?: number; // seconds to fade in, and to fade out the clips it replaces
|
|
1766
|
+
reverse?: boolean; // play backwards, from where the clip is or from its end
|
|
1767
|
+
};
|
|
1768
|
+
|
|
1769
|
+
type AnimationClipState = {
|
|
1770
|
+
clip: string;
|
|
1771
|
+
status: "playing" | "paused" | "finished"; // finished: a play-once clip holding its last pose
|
|
1772
|
+
time: number; // seconds
|
|
1773
|
+
duration: number; // seconds
|
|
1612
1774
|
speed: number;
|
|
1613
|
-
|
|
1614
|
-
|
|
1775
|
+
reverse: boolean;
|
|
1776
|
+
};
|
|
1777
|
+
|
|
1778
|
+
type AnimationPlaybackState = {
|
|
1779
|
+
currentClip: string | null; // the clip played most recently, while it's active
|
|
1780
|
+
isPlaying: boolean; // whether any clip is playing
|
|
1781
|
+
speed: number; // speed, position and length of currentClip
|
|
1782
|
+
time: number;
|
|
1783
|
+
duration: number;
|
|
1784
|
+
active: AnimationClipState[]; // every active clip, most recently played last
|
|
1615
1785
|
};
|
|
1616
1786
|
|
|
1787
|
+
// Leave out clipName to act on every active clip.
|
|
1617
1788
|
type AnimationControls = {
|
|
1618
1789
|
clips: string[];
|
|
1619
1790
|
clipDetails?: { sourceName: string; duration: number }[];
|
|
1620
|
-
play: (clipName: string) => void;
|
|
1621
|
-
pause: () => void;
|
|
1622
|
-
stop: () => void;
|
|
1623
|
-
setSpeed: (speed: number) => void;
|
|
1624
|
-
seek?: (time: number) => void; //
|
|
1791
|
+
play: (clipName: string, options?: AnimationPlayOptions) => void;
|
|
1792
|
+
pause: (clipName?: string) => void;
|
|
1793
|
+
stop: (clipName?: string) => void; // stops and resets the pose
|
|
1794
|
+
setSpeed: (speed: number, clipName?: string) => void;
|
|
1795
|
+
seek?: (time: number, clipName?: string) => void; // absolute time, in seconds
|
|
1625
1796
|
getState?: () => AnimationPlaybackState;
|
|
1626
1797
|
subscribe?: (listener: (state: AnimationPlaybackState) => void) => () => void;
|
|
1627
1798
|
};
|
|
@@ -1629,8 +1800,8 @@ type AnimationControls = {
|
|
|
1629
1800
|
|
|
1630
1801
|
Wire this to `useViewerAnimations().handleAnimationsReady` for the simplest integration.
|
|
1631
1802
|
When `sceneConfig.animations.autoplayClip` is set, `ModelViewer` will auto-play
|
|
1632
|
-
that clip on load and respect per-clip `loopMode
|
|
1633
|
-
soft-delete (`hidden`) settings.
|
|
1803
|
+
that clip on load and respect per-clip `loopMode` (`"repeat"`, `"once"`, or
|
|
1804
|
+
`"ping-pong"`), `speed`, `displayName`, and soft-delete (`hidden`) settings.
|
|
1634
1805
|
|
|
1635
1806
|
### Lighting
|
|
1636
1807
|
|
|
@@ -1680,6 +1851,81 @@ An attached light follows the object, offset from the object's origin in its
|
|
|
1680
1851
|
local space (the origin is often at its base, not its center). Lighting set up
|
|
1681
1852
|
in `BindingBuilder`'s Scene tab exports as the same `sceneConfig`.
|
|
1682
1853
|
|
|
1854
|
+
### Style rules
|
|
1855
|
+
|
|
1856
|
+
`sceneConfig.styleRules` restyles objects from their own data while the app
|
|
1857
|
+
runs: a colour for a temperature, grey for a disabled part, a pulsing glow for
|
|
1858
|
+
an alarm. Each rule picks objects (`target`), checks their data (`when`), and
|
|
1859
|
+
sets a look (`material`, `colorScale`, `pulse`):
|
|
1860
|
+
|
|
1861
|
+
```tsx
|
|
1862
|
+
const viewer = useViewer({
|
|
1863
|
+
bindings,
|
|
1864
|
+
sceneConfig: {
|
|
1865
|
+
...DEFAULT_SCENE_CONFIG,
|
|
1866
|
+
styleRules: [
|
|
1867
|
+
{
|
|
1868
|
+
id: "temperature",
|
|
1869
|
+
target: { group: "pumps" },
|
|
1870
|
+
colorScale: {
|
|
1871
|
+
metric: "temperature",
|
|
1872
|
+
stops: [[18, "#3b82f6"], [24, "#22c55e"], [32, "#ef4444"]],
|
|
1873
|
+
},
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
id: "offline",
|
|
1877
|
+
when: { status: "disabled" },
|
|
1878
|
+
material: { baseColor: "#9ca3af", opacity: 0.4 },
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
id: "alarm",
|
|
1882
|
+
when: [{ metric: "temperature", above: 32 }, { metadata: "alarm.acknowledged", equals: false }],
|
|
1883
|
+
pulse: { color: "#ff3b30", speed: 1.5 },
|
|
1884
|
+
},
|
|
1885
|
+
],
|
|
1886
|
+
},
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1889
|
+
<ModelViewer modelUrl="/plant.glb" licenseKey="your-license-key" {...viewer.props} />;
|
|
1890
|
+
|
|
1891
|
+
// Live data: update the bindings and the looks follow.
|
|
1892
|
+
viewer.bindings.updateObjectBindings("pump-3", { metrics: { temperature: 34 } });
|
|
1893
|
+
viewer.bindings.updateObjectBindings("valve-1", { status: "disabled" });
|
|
1894
|
+
```
|
|
1895
|
+
|
|
1896
|
+
- `target` takes the same targets as the binding helpers: an id, a list of
|
|
1897
|
+
ids, `{ group }`, `{ tag }`, or `{ type }`. Left out, the rule considers every
|
|
1898
|
+
bound object.
|
|
1899
|
+
- `when` is a condition, or a list that must all match; left out, the rule
|
|
1900
|
+
always applies. Conditions: `{ metric, above?, below? }` (the value must be
|
|
1901
|
+
greater than `above` and less than `below`; an object without the metric
|
|
1902
|
+
doesn't match), `{ status }` with one status or a list (a binding without
|
|
1903
|
+
one counts as `"normal"`), and `{ metadata, equals }`, where dots in the key
|
|
1904
|
+
reach into nested metadata.
|
|
1905
|
+
- `material` accepts any `style.material` field. `colorScale` sets
|
|
1906
|
+
`baseColor` (or `emissive`, with `channel: "emissive"`) from a metric,
|
|
1907
|
+
blending between hex colour stops and taking the nearest stop outside them.
|
|
1908
|
+
`pulse` makes the glow fade in and out (`true`, or `{ color, speed }` in
|
|
1909
|
+
pulses per second).
|
|
1910
|
+
- Rules apply in order over each object's own `style.material`, so a later
|
|
1911
|
+
rule wins where two set the same field. Hover and selection highlighting
|
|
1912
|
+
still show on top.
|
|
1913
|
+
- Rules only change how objects look. `objectBindings`, `onObjectBindingsChange`,
|
|
1914
|
+
and every callback keep the object's own values, so nothing a rule sets is
|
|
1915
|
+
saved with the bindings.
|
|
1916
|
+
- The pulse isn't drawn on streamed 3D Tiles models (`tilesetUrl`); the other
|
|
1917
|
+
looks are.
|
|
1918
|
+
|
|
1919
|
+
BindingBuilder's Scene tab has a **Rules** sub-tab for building these rules
|
|
1920
|
+
visually. It suggests your groups, tags, types, metric names, and metadata
|
|
1921
|
+
keys, shows how many objects each rule matches right now, and previews the
|
|
1922
|
+
result live. Rules export and import with the rest of `sceneConfig`.
|
|
1923
|
+
|
|
1924
|
+
`applyStyleRules(bindings, rules)` (from the main entry or `/utils`) returns
|
|
1925
|
+
the styled bindings, for example to colour your own list the way the viewer
|
|
1926
|
+
does, and `colorScaleValue(stops, value)` the colour a scale gives a value, for
|
|
1927
|
+
a legend.
|
|
1928
|
+
|
|
1683
1929
|
### `camera`
|
|
1684
1930
|
|
|
1685
1931
|
Optional starting camera: where it is and its lens.
|
|
@@ -1943,7 +2189,7 @@ SVG texture template per material. Meshes without a `uv` attribute are omitted;
|
|
|
1943
2189
|
when the model has no UV coordinates, the viewer reports that no layout is
|
|
1944
2190
|
available.
|
|
1945
2191
|
|
|
1946
|
-
The bottom-right action bar renders when at least one of `showResetButton`, `showDownloadButton`, or (`showAnnotationNavigation` with annotations present) is `true`.
|
|
2192
|
+
The bottom-right action bar renders when at least one of `showResetButton`, `showDownloadButton`, `showFullscreenButton` (when supported), or (`showAnnotationNavigation` with annotations present) is `true`.
|
|
1947
2193
|
|
|
1948
2194
|
### `downloadFilename`
|
|
1949
2195
|
|
|
@@ -1965,6 +2211,26 @@ Default:
|
|
|
1965
2211
|
true;
|
|
1966
2212
|
```
|
|
1967
2213
|
|
|
2214
|
+
### `showFullscreenButton`
|
|
2215
|
+
|
|
2216
|
+
Optional boolean that adds an enter/exit fullscreen toggle to the bottom-right
|
|
2217
|
+
action bar. Its grouped form is `ui.fullscreen`. Default: `false`.
|
|
2218
|
+
|
|
2219
|
+
```tsx
|
|
2220
|
+
<ModelViewer
|
|
2221
|
+
modelUrl="/model.glb"
|
|
2222
|
+
licenseKey={licenseKey}
|
|
2223
|
+
ui={{ fullscreen: true }}
|
|
2224
|
+
/>
|
|
2225
|
+
```
|
|
2226
|
+
|
|
2227
|
+
Fullscreen expands the whole viewer, including its panels and overlays. The
|
|
2228
|
+
button reflects browser exits such as Escape, and is hidden when fullscreen is
|
|
2229
|
+
unavailable. It is temporarily disabled during video recording. Embedded viewers
|
|
2230
|
+
need an iframe with `allow="fullscreen"` (or `allowFullScreen` in React). A rejected
|
|
2231
|
+
request displays an error beside the button; see the
|
|
2232
|
+
[Fullscreen API permissions](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen#security).
|
|
2233
|
+
|
|
1968
2234
|
### `showLoadingOverlay`
|
|
1969
2235
|
|
|
1970
2236
|
Optional boolean controlling whether the built-in loading overlay is shown while the model is loading and the initial camera fit is settling, and the load-error message if the model fails to load (see [`onLoadError`](#onloaderror)).
|
|
@@ -2312,6 +2578,18 @@ Default:
|
|
|
2312
2578
|
"auto";
|
|
2313
2579
|
```
|
|
2314
2580
|
|
|
2581
|
+
If the WebGL context is lost, rendering pauses and the viewer displays a
|
|
2582
|
+
**Reload viewer** button. This message appears even with
|
|
2583
|
+
`ui={{ loadingOverlay: false }}`. When the browser fires `webglcontextrestored`,
|
|
2584
|
+
the viewer automatically rebuilds its canvas and scene; the reload button starts
|
|
2585
|
+
the same rebuild without waiting for browser restoration.
|
|
2586
|
+
|
|
2587
|
+
Recovery uses the current props and cached model assets, cancels video recording,
|
|
2588
|
+
and resets transient viewer state (camera, selection, measurements, and playback).
|
|
2589
|
+
Keep configuration edits in controlled props to retain them across a rebuild.
|
|
2590
|
+
`onSceneLoaded`, `onModelLoaded`, `onAnimationsReady`, and `onViewerReady` run again
|
|
2591
|
+
for the rebuilt scene, so integrations receive fresh controls and object refs.
|
|
2592
|
+
|
|
2315
2593
|
### `dracoDecoderPath` / `ktx2TranscoderPath` / `meshopt`
|
|
2316
2594
|
|
|
2317
2595
|
Optional controls for the compressed-asset decoders used when loading the GLB/GLTF asset.
|
|
@@ -2376,7 +2654,33 @@ false;
|
|
|
2376
2654
|
|
|
2377
2655
|
### `showMeasureTools`
|
|
2378
2656
|
|
|
2379
|
-
Optional boolean that shows
|
|
2657
|
+
Optional boolean that shows measurement tools and a toggleable bounding-box
|
|
2658
|
+
dimensions overlay. Start measuring, then choose a tool:
|
|
2659
|
+
|
|
2660
|
+
- **Distance:** pick points for distances between consecutive picks.
|
|
2661
|
+
- **Angle:** pick an arm, the angle's vertex, and the other arm. The readout is
|
|
2662
|
+
in degrees, with an arc around the middle pick.
|
|
2663
|
+
- **Area:** pick polygon corners in boundary order, then **Finish area**. The
|
|
2664
|
+
closed polygon is shaded and labeled in square units. Concave polygons are
|
|
2665
|
+
supported; nonplanar, crossing, touching, duplicate, or collinear boundaries
|
|
2666
|
+
show an error. Use Undo to correct a pick.
|
|
2667
|
+
- **Point to plane:** pick a reference face, then a point. The tool highlights
|
|
2668
|
+
the picked triangle and shows the shortest distance to its infinite plane,
|
|
2669
|
+
with a perpendicular segment to the projected point.
|
|
2670
|
+
|
|
2671
|
+
**No snapping** keeps picks on the surface. **Snap: vertex**, **Snap: edge**,
|
|
2672
|
+
and **Snap: auto** snap within 12 CSS pixels to vertices or triangle edges on
|
|
2673
|
+
the picked face. Auto prioritizes nearby vertices. Hover markers identify the
|
|
2674
|
+
snap target; snapped picks are yellow. Snapping respects world transforms,
|
|
2675
|
+
instances, and the current pose of skinned/morph geometry. Mesh triangulation
|
|
2676
|
+
edges can also be snap targets.
|
|
2677
|
+
|
|
2678
|
+
Undo removes the last pick; Clear removes the current measurement. Switching
|
|
2679
|
+
tools clears picks. A new pick after a completed angle, area, or point-to-plane
|
|
2680
|
+
measurement starts another measurement. Picks are snapshots in world space;
|
|
2681
|
+
model changes, display rotation, and binding transforms clear them. Pause
|
|
2682
|
+
animated models before measuring a pose. Hidden objects, decals, and editor
|
|
2683
|
+
helpers are excluded from picking.
|
|
2380
2684
|
|
|
2381
2685
|
Default:
|
|
2382
2686
|
|
|
@@ -2386,7 +2690,10 @@ false;
|
|
|
2386
2690
|
|
|
2387
2691
|
### `measurementUnit`
|
|
2388
2692
|
|
|
2389
|
-
Optional unit suffix appended to
|
|
2693
|
+
Optional unit suffix appended to distance and dimension readouts; area uses its
|
|
2694
|
+
squared form (for example `m²`). Angles always use degrees. glTF models are
|
|
2695
|
+
authored in meters by spec, so values are not converted; this only changes the
|
|
2696
|
+
displayed label.
|
|
2390
2697
|
|
|
2391
2698
|
Default:
|
|
2392
2699
|
|
|
@@ -2404,6 +2711,235 @@ Default:
|
|
|
2404
2711
|
false;
|
|
2405
2712
|
```
|
|
2406
2713
|
|
|
2714
|
+
### `showMaterialVariants`
|
|
2715
|
+
|
|
2716
|
+
Shows a row of buttons, centered at the bottom of the viewer, for switching
|
|
2717
|
+
between the model's glTF material variants (the `KHR_materials_variants`
|
|
2718
|
+
extension): the colorways, finishes, or trims a product file ships with. It
|
|
2719
|
+
appears only for a glTF/GLB with more than one variant. Default `true`.
|
|
2720
|
+
|
|
2721
|
+
Picking a variant sets `sceneConfig.model.materialVariant`, which is also how
|
|
2722
|
+
you choose one yourself:
|
|
2723
|
+
|
|
2724
|
+
```tsx
|
|
2725
|
+
const viewer = useViewer();
|
|
2726
|
+
|
|
2727
|
+
<ModelViewer modelUrl="/shoe.glb" licenseKey="your-license-key" {...viewer.props} />;
|
|
2728
|
+
|
|
2729
|
+
// The model's variant names, once it has loaded
|
|
2730
|
+
viewer.model.materialVariants; // ["midnight", "beach", "street"]
|
|
2731
|
+
|
|
2732
|
+
viewer.scene.updateSceneConfig({ model: { materialVariant: "beach" } });
|
|
2733
|
+
viewer.scene.updateSceneConfig({ model: { materialVariant: null } }); // the defaults
|
|
2734
|
+
```
|
|
2735
|
+
|
|
2736
|
+
- A variant sets each object's starting material. `objectBindings` material
|
|
2737
|
+
overrides (`style.material`) still apply on top of it, and clearing an
|
|
2738
|
+
override returns to the variant's material rather than the file's default.
|
|
2739
|
+
- Objects the variant doesn't map keep their default material, bound or not.
|
|
2740
|
+
- A variant's materials and textures load the first time it's chosen; the
|
|
2741
|
+
current look stays until they're ready.
|
|
2742
|
+
- With `onSceneConfigChange`, a pick goes to your scene config (`useViewer`
|
|
2743
|
+
wires this up). Without it, `ModelViewer` keeps the pick itself until
|
|
2744
|
+
`sceneConfig.model.materialVariant` or `modelUrl` changes.
|
|
2745
|
+
- A name the model doesn't have shows the defaults and logs a console warning
|
|
2746
|
+
listing the model's variants.
|
|
2747
|
+
- An action can switch variants with a scene-config effect:
|
|
2748
|
+
`{ sceneConfig: { model: { materialVariant: "beach" } } }`.
|
|
2749
|
+
- `onModelLoaded` reports the names as `materialVariants`, and BindingBuilder's
|
|
2750
|
+
Scene tab (**General → Material variant**) picks the one the exported scene
|
|
2751
|
+
config starts with. OBJ, FBX, USDZ, and streamed 3D Tiles models have no
|
|
2752
|
+
variants.
|
|
2753
|
+
|
|
2754
|
+
### Decals
|
|
2755
|
+
|
|
2756
|
+
`binding.decals` prints images and text onto an object's surface: a logo on a
|
|
2757
|
+
shirt, a name on a mug, a label on a machine. Each decal is projected onto the
|
|
2758
|
+
object and follows it as it moves, and it's saved with the binding like any
|
|
2759
|
+
other edit.
|
|
2760
|
+
|
|
2761
|
+
```tsx
|
|
2762
|
+
const viewer = useViewer();
|
|
2763
|
+
|
|
2764
|
+
<ModelViewer
|
|
2765
|
+
modelUrl="/mug.glb"
|
|
2766
|
+
licenseKey="your-license-key"
|
|
2767
|
+
{...viewer.props}
|
|
2768
|
+
// Put the customer's text where they double-click.
|
|
2769
|
+
onObjectDoubleClick={({ binding, surface }) => {
|
|
2770
|
+
if (!surface) return;
|
|
2771
|
+
viewer.bindings.addDecal(binding.id, {
|
|
2772
|
+
text: customerName,
|
|
2773
|
+
color: "#1e293b",
|
|
2774
|
+
...surface, // position and normal, in the object's own space
|
|
2775
|
+
size: 0.06, // width in model units
|
|
2776
|
+
});
|
|
2777
|
+
return true; // skip the viewer's own double-click behavior
|
|
2778
|
+
}}
|
|
2779
|
+
/>;
|
|
2780
|
+
|
|
2781
|
+
const id = viewer.bindings.addDecal("mug", { image: "/logos/acme.png", position, normal, size: 0.05 });
|
|
2782
|
+
viewer.bindings.updateDecal("mug", id, { size: 0.08, rotation: Math.PI / 12 });
|
|
2783
|
+
viewer.bindings.removeDecal("mug", id);
|
|
2784
|
+
```
|
|
2785
|
+
|
|
2786
|
+
```ts
|
|
2787
|
+
type ObjectBindingDecal = {
|
|
2788
|
+
id: string;
|
|
2789
|
+
image?: string; // a URL or public path
|
|
2790
|
+
text?: string; // shown when there's no image
|
|
2791
|
+
color?: string; // text colour, default "#ffffff"
|
|
2792
|
+
fontFamily?: string; // CSS font family, default "sans-serif"
|
|
2793
|
+
fontWeight?: "normal" | "bold"; // default "bold"
|
|
2794
|
+
position: [number, number, number]; // on the object, in its own space
|
|
2795
|
+
normal: [number, number, number]; // the way the surface faces there
|
|
2796
|
+
size: number; // width in model units; the height follows the image or text
|
|
2797
|
+
rotation?: number; // radians around the normal, default 0
|
|
2798
|
+
opacity?: number; // 0–1, default 1
|
|
2799
|
+
};
|
|
2800
|
+
```
|
|
2801
|
+
|
|
2802
|
+
- `onObjectDoubleClick` and `onObjectContextMenu` report `surface`: the point
|
|
2803
|
+
under the pointer and the way the surface faces, in the object's own space,
|
|
2804
|
+
which is exactly what a decal's `position` and `normal` take.
|
|
2805
|
+
- `addDecal` returns the decal's id (pass your own `id` to choose it), and
|
|
2806
|
+
takes the same targets as the other binding helpers, so
|
|
2807
|
+
`addDecal({ group: "panels" }, …)` prints on each. Decals are plain binding
|
|
2808
|
+
data: you can also set `decals` directly.
|
|
2809
|
+
- A decal wraps onto the surface around its position, reaching about half
|
|
2810
|
+
its size in depth, so it follows gentle curves without printing on the far
|
|
2811
|
+
side of a thin part.
|
|
2812
|
+
- A decal fades with its object when that object is ghosted by isolation or
|
|
2813
|
+
made see-through by a style rule, and is cut by a section cut.
|
|
2814
|
+
- To let the people using your viewer add their own, give a binding the
|
|
2815
|
+
built-in `add-text` and `add-image` actions:
|
|
2816
|
+
|
|
2817
|
+
```ts
|
|
2818
|
+
actions: [
|
|
2819
|
+
{ id: "add-text", label: "Add Text", type: "command" },
|
|
2820
|
+
{ id: "add-image", label: "Add Image", type: "command" },
|
|
2821
|
+
]
|
|
2822
|
+
```
|
|
2823
|
+
|
|
2824
|
+
Each opens a control under its button in the object panel. **Add Text**
|
|
2825
|
+
takes the text and a colour; **Add Image** takes an image file (sent through
|
|
2826
|
+
`onTextureUpload` when you pass it, so the saved URL lasts; otherwise a
|
|
2827
|
+
blob URL for this session). The decal appears automatically at the centre
|
|
2828
|
+
of the camera-facing surface, ready to drag, resize, turn, or remove. **Move** enables dragging across
|
|
2829
|
+
the object's surface; release to save, or press Escape to cancel. The
|
|
2830
|
+
**Position** arrows move a decal in small steps relative to the current
|
|
2831
|
+
view, without needing to grab it. Click for a fine adjustment or hold an
|
|
2832
|
+
arrow for continuous movement. Each step stays on the nearby visible surface.
|
|
2833
|
+
Every change is saved to the
|
|
2834
|
+
binding's `decals` and reported through `onObjectBindingsChange`. While a
|
|
2835
|
+
decal is in Move mode, a hint shows over the viewer and Escape cancels
|
|
2836
|
+
the current drag without removing the centred decal.
|
|
2837
|
+
- On a rigged (skinned) mesh a decal bends with the animation: it takes on
|
|
2838
|
+
the bone weights of the surface under it and is driven by the same skeleton.
|
|
2839
|
+
A click on a posed mesh is stored at the matching point of its rest shape, so
|
|
2840
|
+
a decal lands where you clicked whatever pose the model is in. Over a joint
|
|
2841
|
+
that bends sharply across few polygons, the decal's outer edge can sit a
|
|
2842
|
+
hair off the surface. Decals don't follow morph targets (blend shapes).
|
|
2843
|
+
- Decals need the binding's object to be a mesh, and aren't drawn on streamed
|
|
2844
|
+
3D Tiles models.
|
|
2845
|
+
|
|
2846
|
+
### Isolating objects
|
|
2847
|
+
|
|
2848
|
+
`isolation` focuses the view on some objects: everything else is ghosted
|
|
2849
|
+
(drawn faintly) or hidden, and clicks and hovers pass through to what's
|
|
2850
|
+
isolated. Nothing about the bindings changes, and clearing the isolation puts
|
|
2851
|
+
everything back. It's view state, like the selection, so it isn't part of
|
|
2852
|
+
`sceneConfig`.
|
|
2853
|
+
|
|
2854
|
+
```tsx
|
|
2855
|
+
const viewer = useViewer();
|
|
2856
|
+
|
|
2857
|
+
<ModelViewer
|
|
2858
|
+
modelUrl="/engine.glb"
|
|
2859
|
+
licenseKey="your-license-key"
|
|
2860
|
+
{...viewer.props}
|
|
2861
|
+
tools={{ isolate: true }}
|
|
2862
|
+
/>;
|
|
2863
|
+
|
|
2864
|
+
viewer.isolation.isolate({ group: "gearbox" }); // ghost everything else
|
|
2865
|
+
viewer.isolation.isolate(["piston-1", "piston-2"], { mode: "hide" });
|
|
2866
|
+
viewer.isolation.clearIsolation();
|
|
2867
|
+
```
|
|
2868
|
+
|
|
2869
|
+
```ts
|
|
2870
|
+
type ViewerIsolation = {
|
|
2871
|
+
target: ObjectBindingTarget; // an id, a list of ids, { group }, { tag }, or { type }
|
|
2872
|
+
mode?: "ghost" | "hide"; // default "ghost"
|
|
2873
|
+
ghostOpacity?: number; // 0–1, default 0.12
|
|
2874
|
+
};
|
|
2875
|
+
|
|
2876
|
+
isolation?: ViewerIsolation | null;
|
|
2877
|
+
onIsolationChange?: (isolation: ViewerIsolation | null) => void;
|
|
2878
|
+
showIsolateControls?: boolean; // tools.isolate
|
|
2879
|
+
```
|
|
2880
|
+
|
|
2881
|
+
- Pass `isolation` (including `null`) to control it, and update it from
|
|
2882
|
+
`onIsolationChange`; `useViewer` wires both, as `viewer.isolation`. Leave
|
|
2883
|
+
`isolation` out and `ModelViewer` keeps it itself, clearing it when
|
|
2884
|
+
`modelUrl` changes. `useViewerIsolation` is the same state on its own.
|
|
2885
|
+
- `showIsolateControls` (`tools.isolate`, default `false`) adds an isolate
|
|
2886
|
+
button to each row of the scene objects panel (click again to show all) and
|
|
2887
|
+
a "Show all" pill at the bottom of the viewer while anything is isolated.
|
|
2888
|
+
- An action can isolate with an `isolation` effect:
|
|
2889
|
+
`{ isolation: { target: { group: "engine" } } }`, or `{ isolation: null }`
|
|
2890
|
+
to show everything again. Pass `isolation` to `useViewerActions` for it
|
|
2891
|
+
(`useViewer` does).
|
|
2892
|
+
- A target that matches no object isolates nothing, rather than fading out
|
|
2893
|
+
the whole model. Ghosting never raises an object's own lower opacity, and
|
|
2894
|
+
style rules still apply underneath.
|
|
2895
|
+
- `applyIsolation` (main entry and `/utils`) applies an isolation to bindings
|
|
2896
|
+
the same way, for rendering your own lists.
|
|
2897
|
+
|
|
2898
|
+
### `showSectionTools`
|
|
2899
|
+
|
|
2900
|
+
Shows a section-cut button over the canvas (`tools.section`). It cuts the model
|
|
2901
|
+
open along an axis, hiding everything on one side of a plane, to look inside
|
|
2902
|
+
assemblies, equipment, and buildings. Default `false`.
|
|
2903
|
+
|
|
2904
|
+
With the cut on, the toolbar picks the axis (X, Y, or Z), the position along
|
|
2905
|
+
it, and which side stays, and a plane appears across the model that you can
|
|
2906
|
+
drag. The cut is stored in `sceneConfig.section`, so you can also set it
|
|
2907
|
+
yourself, author it in BindingBuilder, or switch it from an action:
|
|
2908
|
+
|
|
2909
|
+
```tsx
|
|
2910
|
+
viewer.scene.updateSceneConfig({
|
|
2911
|
+
section: { enabled: true, axis: "y", position: 0.4, flip: false }, // a floor-plan cut
|
|
2912
|
+
});
|
|
2913
|
+
```
|
|
2914
|
+
|
|
2915
|
+
```ts
|
|
2916
|
+
type SceneSectionConfig = {
|
|
2917
|
+
enabled: boolean;
|
|
2918
|
+
axis: "x" | "y" | "z"; // world axis the cut runs across
|
|
2919
|
+
position: number; // 0 at the model's minimum along the axis, 1 at its maximum
|
|
2920
|
+
flip: boolean; // keep the higher side instead of the lower one
|
|
2921
|
+
};
|
|
2922
|
+
```
|
|
2923
|
+
|
|
2924
|
+
- The cut applies to the model only; lights, helpers, and gizmos stay whole.
|
|
2925
|
+
It also shows without the toolbar whenever `sceneConfig.section.enabled` is
|
|
2926
|
+
set; the draggable plane appears only with `showSectionTools`.
|
|
2927
|
+
- While the cut is on, the model's materials render both sides of each
|
|
2928
|
+
surface, so a cut part shows its inside surfaces instead of disappearing.
|
|
2929
|
+
There are no solid caps over the cut.
|
|
2930
|
+
- Clicking and hovering ignore whatever is cut away, so the parts you can see
|
|
2931
|
+
behind the cut are the ones you select.
|
|
2932
|
+
- Toolbar changes go to `onSceneConfigChange` when you pass it (`useViewer`
|
|
2933
|
+
does); otherwise `ModelViewer` keeps them until `sceneConfig.section` or
|
|
2934
|
+
`modelUrl` changes.
|
|
2935
|
+
- The measuring tool can still pick points on the hidden side of a cut.
|
|
2936
|
+
|
|
2937
|
+
Default:
|
|
2938
|
+
|
|
2939
|
+
```ts
|
|
2940
|
+
false;
|
|
2941
|
+
```
|
|
2942
|
+
|
|
2407
2943
|
### `cinematic`
|
|
2408
2944
|
|
|
2409
2945
|
Optional cinematic auto-camera. The camera glides along a path on its own, like a film, ideal for showing off a gallery, apartment, or product with no user interaction. Renders a play/pause button over the canvas; grabbing the camera (or selecting a part) pauses it.
|
|
@@ -2763,6 +3299,7 @@ type ObjectBinding = {
|
|
|
2763
3299
|
hoverable?: boolean;
|
|
2764
3300
|
transform?: ObjectBindingTransform;
|
|
2765
3301
|
style?: ObjectBindingStyle;
|
|
3302
|
+
decals?: ObjectBindingDecal[];
|
|
2766
3303
|
actions?: ObjectBindingAction[];
|
|
2767
3304
|
metrics?: Record<string, number>;
|
|
2768
3305
|
metadata?: Record<string, unknown>;
|
|
@@ -2790,9 +3327,9 @@ Notes:
|
|
|
2790
3327
|
|
|
2791
3328
|
## Supported Built-In Actions
|
|
2792
3329
|
|
|
2793
|
-
`ModelViewer` handles
|
|
2794
|
-
`change-material`, `
|
|
2795
|
-
and `set-brightness`. Any other id does something only when you handle it in
|
|
3330
|
+
`ModelViewer` handles eight action ids itself: `change-color`,
|
|
3331
|
+
`change-material`, `add-text`, `add-image`, `toggle-visibility`,
|
|
3332
|
+
`toggle-light`, `change-light-color`, and `set-brightness`. Any other id does something only when you handle it in
|
|
2796
3333
|
`onAction` (pass `useViewerActions().handleAction` there to run an action's
|
|
2797
3334
|
declared `effects`). If a binding has such an action and `ModelViewer` has no
|
|
2798
3335
|
`onAction`, its button does nothing, and the viewer logs a console warning
|
|
@@ -2810,6 +3347,10 @@ findCatalogAction("toggle-light")?.handling; // "viewer"
|
|
|
2810
3347
|
|
|
2811
3348
|
### Implemented behaviors
|
|
2812
3349
|
|
|
3350
|
+
- `add-text`
|
|
3351
|
+
Opens a text box and colour inline beneath the action; the text appears at the centre of the camera-facing surface, ready to drag, resize, turn, or remove. Saved to `binding.decals` (see [Decals](#decals))
|
|
3352
|
+
- `add-image`
|
|
3353
|
+
Opens an image chooser inline beneath the action; the image is placed and edited the same way. Uses `onTextureUpload` for a durable URL when provided, otherwise a session-scoped blob URL
|
|
2813
3354
|
- `toggle-visibility`
|
|
2814
3355
|
Hides or shows the selected mesh by setting `binding.visible` and calling `onObjectBindingsChange`
|
|
2815
3356
|
- `change-color`
|