.. _kaolin.visualize.dash.viewer_group:
Kaolin Viewer: Layers and Behaviors
===================================
This page covers the interactive viewer itself, the catalog of built-in behaviors
it understands, how to write your own behaviors, and the typed option schemas that
tie Python and the browser together.
Overview & design
-----------------
The :class:`~kaolin.visualize.dash.viewer.KaolinViewer` is a React component,
exposed to Python, that provides an interactive 2D/3D surface inside a
`Plotly Dash `_ app. Its design has two core concepts:
* **Layers** -- the viewer is composed of stacked, pixel-aligned layers
(``canvas``, ``svg``, ...). Layers share the same coordinate space and are
composited with opacity, so you can, for example, draw a server-rendered image
on one canvas and let the user annotate on another above it.
* **Behaviors** -- named units of client-side logic that map user interaction
(pointer / touch / keyboard) and incoming messages to actions. A behavior may
paint on a layer, drive the camera, or stream data to/from the server.
Everything -- layers and behaviors alike -- **runs client-side** in the browser.
Behaviors are what make rich **client-server** workflows possible: a behavior can
serialize the camera or a custom binary message and send it over a WebSocket, and
another behavior can receive server frames and draw them. Heavy work (rendering,
inference) stays on the server while interaction stays fluid in the browser. The
browser-side helpers used to build behaviors live in the
:doc:`JavaScript API `.
.. important::
**Never construct** :class:`~kaolin.visualize.dash.viewer.KaolinViewer`
**directly.** Always assemble a viewer with
:class:`~kaolin.visualize.dash.viewer.ViewerBuilder` and call
:meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.build`. The builder
allocates globally-unique layer/behavior ids, validates behavior options
against their schemas, and wires the clientside callbacks that a hand-built
component would be missing.
Getting started
---------------
A typical flow is: create a :class:`~kaolin.visualize.dash.viewer.ViewerBuilder`,
add one or more layers, attach behaviors by name, optionally generate UI controls
for those behaviors, then :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.build`
the Dash component:
.. code-block:: python
from kaolin.visualize.dash import ViewerBuilder
from kaolin.render.easy_render import default_camera
builder = ViewerBuilder(camera=default_camera(500))
# 1. Stack a layer (canvas/svg) inside the viewer.
draw_layer = builder.add_layer('canvas')
# 2. Attach a behavior by its registered name (see Built-in Behaviors).
draw_id = builder.add_behavior('drawing', active_layer_id=draw_layer,
options={'color': '#ffcc00'})
# 3. Auto-generate UI controls for that behavior's options. Returns Dash
# components you insert into your app layout.
option_controls = builder.add_user_behavior_options(draw_id)
# 4. Build the Dash component. NEVER instantiate KaolinViewer yourself.
viewer = builder.build()
From here, dive into the detailed sections:
* :ref:`dash-configuring-viewer` -- layers, cameras, remote rendering, and the
full :class:`~kaolin.visualize.dash.viewer.ViewerBuilder` API.
* :ref:`dash-builtin-behaviors` -- discover ready-made behaviors with
:class:`~kaolin.visualize.dash.builtins.BehaviorLibrary` and expose their
options as UI.
* :ref:`dash-custom-behaviors` -- register your own behavior in plain JavaScript.
* :ref:`dash-behavior-schemas` -- how
:class:`~kaolin.visualize.dash.option.OptionSpec` schemas drive validation and
auto-UI.
.. _dash-configuring-viewer:
Configuring the viewer
----------------------
:class:`~kaolin.visualize.dash.viewer.ViewerBuilder` is the single entry point for
assembling a viewer. The most common methods are:
* :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_layer` -- add a
``canvas`` / ``svg`` layer; returns a builder-level layer id.
* :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_behavior` -- attach a
behavior by registered name, optionally bound to a layer and configured with
``options``.
* :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_user_behavior_options`
-- auto-generate UI controls for a behavior's options (see
:ref:`dash-builtin-behaviors`).
* :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_remote_rendering` --
one-call setup for server-side rendering: streams the camera to the server and
draws the frames it pushes back.
* :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.build` -- produce the
:class:`~kaolin.visualize.dash.viewer.KaolinViewer` Dash component.
Server-side rendering in a few lines:
.. code-block:: python
builder = ViewerBuilder(camera=default_camera(800))
render_layer = builder.add_layer('canvas')
# Stream the camera to the server and draw the frames it returns.
builder.add_remote_rendering(active_layer_id=render_layer,
connection_id='main-ws')
viewer = builder.build()
.. automodule:: kaolin.visualize.dash.viewer
:members:
:show-inheritance:
.. _dash-builtin-behaviors:
Built-in behaviors
------------------
Kaolin ships a library of ready-made behaviors (drawing, SVG annotation, camera
sending, remote-image drawing, ...). Use them by passing their name to
:meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_behavior`.
:class:`~kaolin.visualize.dash.builtins.BehaviorLibrary` is the single Python
entry point to discover what is available -- it merges the build-time library
manifest with any behaviors found in your own asset directories:
.. code-block:: python
from kaolin.visualize.dash import BehaviorLibrary
print(BehaviorLibrary) # human-readable summary
names = BehaviorLibrary.names() # available behavior names
meta = BehaviorLibrary.meta('drawing') # BehaviorMeta (incl. option schema)
A behavior's option schema is also what powers **auto-generated UI**. Call
:meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_user_behavior_options`
with a behavior id to get Dash controls (plus a hidden store for per-tab
persistence); each control edit is pushed to the live behavior in the browser
without a server roundtrip:
.. code-block:: python
draw_id = builder.add_behavior('drawing', active_layer_id=draw_layer)
# All uiBound options, or a subset by name / overriding OptionSpec.
controls = builder.add_user_behavior_options(draw_id)
controls = builder.add_user_behavior_options(draw_id, options=['thickness'])
# ...insert `controls` into your Dash layout (e.g. a sidebar).
.. automodule:: kaolin.visualize.dash.builtins
:members:
:show-inheritance:
.. _dash-custom-behaviors:
Custom behaviors
----------------
Behaviors are resolved by name in the browser, so a custom behavior is just a
class registered under a name -- **plain JavaScript is enough, no TypeScript or
build step required**. Drop a script (e.g. ``custom.js``) into your app's served
assets; the ``kaolin`` namespace is loaded automatically (see the
:doc:`JavaScript API `).
.. code-block:: javascript
// custom.js -- loaded in the browser; `kaolin` is available globally.
const { CanvasBehavior, BehaviorRegister, OptionKind } = kaolin.core.behavior;
class DotBehavior extends CanvasBehavior {
// Schema drives Python-side validation and auto-UI (see Behavior Schemas).
static schema = { radius: { kind: OptionKind.INT, default: 6, min: 1, max: 40 } };
// Re-render whenever an option is edited from Python.
updateForOptions() { this.redraw(); }
// CanvasBehavior provides `this.element` / `this.ctx` and pointer handlers.
onPointerDown(event) { this.x = event.offsetX; this.y = event.offsetY; this.redraw(); }
redraw() {
if (!this.ctx || this.x === undefined) return;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.options.radius, 0, 2 * Math.PI);
this.ctx.fill();
}
}
// 'dot' is the name you pass to add_behavior(...) from Python.
BehaviorRegister.register('dot', DotBehavior, 'Draws a dot where you click.');
Then use it from Python exactly like a built-in:
.. code-block:: python
dot_layer = builder.add_layer('canvas')
dot_id = builder.add_behavior('dot', active_layer_id=dot_layer,
options={'radius': 10})
See the :doc:`JavaScript API ` for the base
classes (``Behavior``, ``InteractiveBehavior``, ``CanvasBehavior``,
``CameraControllerBase``, ``MessageHandlerBase``), the event interfaces, and the
binary message I/O utilities used for custom client-server communication.
.. _dash-behavior-schemas:
Behavior schemas
----------------
Each behavior declares an **option schema**: the set of configurable parameters,
their types, bounds, and defaults. On the browser side this is a `Zod
`_ schema (or, in plain JS, the ``static schema`` object shown
above); at build time it is projected to JSON and read back on the Python side as
:class:`~kaolin.visualize.dash.option.OptionSpec` objects.
A single :class:`~kaolin.visualize.dash.option.OptionSpec` describes one option
and is the unit that flows through the whole system:
* it round-trips through JSON (Python ⇄ TypeScript) via ``as_dict`` / ``from_dict``;
* :class:`~kaolin.visualize.dash.builtins.BehaviorLibrary` exposes the specs of
every known behavior;
* :meth:`~kaolin.visualize.dash.viewer.ViewerBuilder.add_user_behavior_options`
turns specs into Dash controls and validates option values against them.
You rarely construct one by hand, but you can -- for example to override a bound
or define an option for a custom behavior that has no manifest:
.. code-block:: python
from kaolin.visualize.dash.option import OptionSpec, OptionKind
thickness = OptionSpec(
name='thickness', kind=OptionKind.INT, default=4, minimum=1, maximum=20)
# Expose just this option, with our overridden bounds, as UI for a behavior.
controls = builder.add_user_behavior_options(draw_id, options=[thickness])
.. automodule:: kaolin.visualize.dash.option
:members:
:show-inheritance: