Viewer messaging API¶
The Maverick Excelsior Viewer is a lightweight, embeddable 3D renderer built for the web: it runs in the browser, with nothing to install, on every device from phones to workstations. It loads .webex scenes and renders them in real time with physically-based materials. This page is the reference for driving an embedded viewer from JavaScript.
When to use the SDK¶
You do not need any of this just to publish a model. The simple route is the Excelsior Editor's Create Link button: one click uploads the scene and gives you a shareable, embeddable viewer page (see Sharing a viewer online).
Reach for the Viewer SDK when you want custom behavior: your page driving the viewer, or the viewer driving your page. Build your own configurator, run the viewer directly against your model catalog, update prices as the customer switches materials and stones, restyle the controls to match your brand, or feed captures into your own systems.
As a proof of concept, catalog.maverickexcelsior.com is a mockup webshop built exactly this way: every product page embeds the viewer over a model catalog, with materials, stone types, and prices wired into the page.
Communication model¶
JavaScript and the viewer talk over two one-way string channels:
| Direction | Channel | Description |
|---|---|---|
| Page to viewer | wasm_i(op, d0, d1, d2) | Send a command |
| Viewer to page | CustomEvent('wasm_o') with detail = { op, d0, d1, d2 } | Receive an event |
All four parameters are strings. Trailing empty strings may be omitted.
Warning
If you have older integration code that calls window.webex_in(...), migrate it to the wasm_i interface documented here.
Wiring the channels¶
// Call once after Module.onRuntimeInitialized fires.
// The stock webex-viewer-module.js already defines this for you.
window.wasm_i = (op, d0, d1, d2) => {
Module.ccall('wasm_i', null,
['string', 'string', 'string', 'string'],
[op, d0 || '', d1 || '', d2 || '']);
};
// The viewer dispatches its events itself as CustomEvent('wasm_o')
// on window; just listen for them.
window.addEventListener('wasm_o', (e) => {
const { op, d0, d1, d2 } = e.detail;
// Handle events...
});
Authentication¶
api_key¶
Must be set before loading any scene; it encodes the domain allowlist that open_scene URLs are checked against. Provide it at startup via Module.json.api_key or send it explicitly:
wasm_i('api_key', 'ak_live_xxxxxxxxxxxxxxxx');
Startup values (Module.json)¶
Instead of sending them after startup, any of these can be provided as string fields of Module.json and are applied automatically during boot, in this order: set_quality, set_clear_color, set_infotag, set_autospin, api_key, and open_scene.
var Module = {
canvas: document.getElementById('webex-canvas'),
json: {
api_key: 'ak_live_xxxxxxxxxxxxxxxx',
set_quality: 'high',
open_scene: 'https://assets.example.com/ring.webex'
}
};
Scene lifecycle¶
open_scene¶
wasm_i('open_scene', 'https://assets.example.com/ring.webex');
The URL is checked against the API key's domain allowlist; a viewer showing content that is not covered by its key runs in unlicensed (watermarked) mode. Repeated calls with the same URL are ignored.
Where should the scene live? In the SDK, the scene is a .webex file you export from the Excelsior Editor (Export Interactive Scene) and host yourself. The simplest home is right next to your page, on the same domain: a relative path such as ./scenes/ring.webex just works, with nothing to configure, and it is what all the working examples do. You can also serve scenes from a separate assets host or CDN of yours; in that case the browser's cross-origin rules kick in (see the note below).
One route that is not for the SDK: the share links and shortcodes produced by the editor's Create Link button. Those belong to our hosted viewer and to iframe embeds, and the files behind them are served with a cross-origin policy that only admits our own pages. Point your SDK page at your own hosting instead.
| Event | d0 | Meaning |
|---|---|---|
open_scene_starting | filename | Loading has begun. |
open_scene_progress | percentage | Download progress, 0 to 100. |
open_scene_ready | Scene decoded; warmup begins. | |
open_scene_complete | Scene fully loaded and interactive. |
CORS is the number one show-stopper
open_scene fetches the scene with a normal browser request, so the browser's cross-origin rules apply. Same origin as your page: always fine. A different origin (your assets subdomain, a bucket, a CDN): that server must answer with an Access-Control-Allow-Origin header that covers your page's domain, or the browser will silently refuse the download. S3 and R2 buckets do not send this header until you configure it. If a scene loads when hosted next to your page but not from your CDN, this is almost always why.
close_scene¶
wasm_i('close_scene');
Unloads the current scene and returns the viewer to its empty state.
Scene controls¶
| Command | d0 | Description |
|---|---|---|
set_quality | medium / high / ultra | Quality preset. medium: base geometry, no effects. high: better geometry plus reflections. ultra: best geometry, ambient occlusion, reflections, and glare. |
set_clear_color | black, white, #RRGGBB, or RRGGBB | Background clear color behind the scene. |
set_autospin | 0 / 1 | Continuous automatic rotation. |
set_infotag | 0 / 1 | Information label overlay. |
zoom | integer delta | Camera zoom. Positive zooms in, negative zooms out. |
capture | Asynchronously capture the current frame. The reply arrives as a capture_data event whose d0 is a base64 JPEG data URI (max 512 px, quality 85). Intended for thumbnails. |
Materials¶
apply_mtl¶
wasm_i('apply_mtl', 'Metal 01', 'White Gold 18k');
| Parameter | Description |
|---|---|
| d0 | Layer name, matched case-sensitively. Every layer with that exact alias receives the material. |
| d1 | Material name from the library, matched case-sensitively. See Content library names. |
The reply is an applied event (d0 = material, d1 = layer, d2 = material) on success, or a failed event with the same payload when the layer or material name does not match anything.
Properties¶
set_string and get_string¶
wasm_i('set_string', '::globals', 'globals_ibl_angle', '45.0');
wasm_i('get_string', '::camera', 'camera_hfov');
| Parameter | Description |
|---|---|
| d0 | Node: ::globals, ::camera (alias ::main_camera), ::render_pipeline (alias ::main_pipeline), ::animation, or a layer name (matched at any depth). |
| d1 | Property USTR name, e.g., globals_resolution_w. See Scene properties (USTR). |
| d2 | (set only) New value as a string. |
get_string replies with a get_string event carrying d0 = node, d1 = ustr, d2 = current value. After a set, the render restarts to reflect the change.
The live camera can be driven through the virtual properties on ::camera (tracker_cam_theta, tracker_cam_phi, tracker_cam_dolly, tracker_cam_pan_x, tracker_cam_pan_y, tracker_cam_hfov), which read and write the on-screen camera directly.
Message reference¶
Commands (page to viewer)¶
| Command | d0 | d1 | d2 | Description |
|---|---|---|---|---|
api_key | key | Authenticate; sets the scene domain allowlist. | ||
open_scene | URL | Load a .webex scene. | ||
close_scene | Unload the current scene. | |||
set_quality | preset | medium / high / ultra. | ||
set_clear_color | color | Background color. | ||
set_autospin | 0/1 | Auto rotation. | ||
set_infotag | 0/1 | Info label. | ||
zoom | delta | Camera zoom. | ||
capture | Frame snapshot. | |||
apply_mtl | layer | material | Apply a library material to a layer. | |
set_string | node | ustr | value | Set a property. |
get_string | node | ustr | Query a property. |
Events (viewer to page)¶
| Event | d0 | d1 | d2 | Description |
|---|---|---|---|---|
open_scene_starting | filename | Loading began. | ||
open_scene_progress | percentage | Download progress. | ||
open_scene_ready | Scene decoded, warming up. | |||
open_scene_complete | Scene interactive. | |||
get_string | node | ustr | value | Property query reply. |
applied | material | layer | material | apply_mtl succeeded. |
failed | material | layer | material | apply_mtl matched no layer or material. |
capture_data | data URI | Base64 JPEG of the current frame. |
Working examples¶
A set of small, view-source-friendly pages exercises everything on this page against the live viewer. Each one is dissected, with its source annotated, on its own page:
- Material switcher: the canonical integration, walked through top to bottom.
- CDN embed: the same page, loading the runtime from jsDelivr.
- Multi-part configurator: variants as layers, business rules in your page.
- Iframe embed: the zero-JavaScript route.
Minimal integration example¶
The page below loads its scene from a separate assets host. Remember the main show-stopper: that host must reply with CORS headers that cover your page's domain, or simply host the scene next to the page and use a relative path (see open_scene).
<!DOCTYPE html>
<html>
<head><title>WebEx Viewer</title></head>
<body>
<canvas id="webex-canvas" style="width:100%;height:100vh;"></canvas>
<script>
var Module = {
canvas: document.getElementById('webex-canvas'),
json: { api_key: 'ak_live_xxxxxxxxxxxxxxxx' }
};
Module.onRuntimeInitialized = () => {
window.wasm_i = (op, d0, d1, d2) => {
Module.ccall('wasm_i', null,
['string', 'string', 'string', 'string'],
[op, d0 || '', d1 || '', d2 || '']);
};
window.addEventListener('wasm_o', (e) => {
if (e.detail.op === 'open_scene_complete') {
wasm_i('apply_mtl', 'Metal 01', 'White Gold 18k');
wasm_i('set_quality', 'ultra');
wasm_i('set_clear_color', '#F0F0F0');
wasm_i('set_autospin', '1');
wasm_i('set_string', '::globals', 'globals_ibl_angle', '45.0');
}
});
wasm_i('open_scene', 'https://assets.example.com/ring.webex');
};
</script>
<script src="webex-viewer.js"></script>
</body>
</html>
Found a mistake?
We do our best to keep this reference accurate and in step with the product, but mistakes and omissions can slip through, and they are never intentional. If anything here looks wrong, incomplete, or unclear, please contact us and we will gladly fix it.
