Building an App: Layout, Auto-UI & App Builder¶
These modules help you assemble a complete, runnable Dash application: arrange a responsive page, optionally auto-generate controls from typed Python, wire up WebSocket communication, and assemble the server. They are convenience helpers – you can mix in plain Dash components, callbacks, and layouts wherever you like.
Tutorial¶
The snippet below shows a typical end-to-end flow; each step links to its detailed section. Only the layout and the app builder are essential – the viewer and the auto-generated UI are optional and shown here to illustrate a full app.
from dataclasses import dataclass
from kaolin.visualize.dash import ViewerBuilder, auto_ui
from kaolin.visualize.dash.layout import StandardLayoutHelper
from kaolin.visualize.dash.builder import WebappBuilder
from kaolin.render.easy_render import default_camera
# 1. (Optional) Build an interactive viewer (server-side rendering over a
# WebSocket). Skip this for a non-3D app.
vb = ViewerBuilder(camera=default_camera(800))
layer = vb.add_layer('canvas')
vb.add_remote_rendering(active_layer_id=layer, connection_id='main-ws')
viewer = vb.build()
# 2. (Optional) Auto-generate UI controls from a typed dataclass. You can
# instead hand-write any plain Dash components and callbacks.
@dataclass
class RenderSettings:
exposure: float = 1.0
wireframe: bool = False
controls, field_specs, store = auto_ui.controls_from_dataclass(
RenderSettings(), id_prefix='render', make_store=True)
# 3. Arrange your content in a responsive page (plain Dash components welcome).
layout = StandardLayoutHelper(title='My Kaolin App')
layout.add_sidebar()
layout.add_sidebar_components(controls + [store])
layout.add_main_content([viewer])
# 4. Assemble the server, optionally registering a WebSocket handler.
builder = WebappBuilder(debug=True)
builder.set_layout_helper(layout)
app = builder.build(ws_handlers=[(EchoHandler, ())])
# app.run_server(...)
The pieces, in order:
(Optional) Add a viewer built with a
ViewerBuilder– see the dedicated Kaolin Viewer: Layers and Behaviors page.(Optional) Auto-generate UI from a dataclass (or function, or behavior) with
auto_ui– or just use plain Dash components and callbacks. See Auto-UI.Lay it out with a
StandardLayoutHelper– see Layout.Assemble the server with
WebappBuilder, optionally registering a WebSocket handler for custom client-server communication – see App builder and WebSocket handlers.
See Customizing your app for going beyond these helpers.
Auto-UI¶
Auto-UI is entirely optional: it is just a shortcut for generating standard
Dash controls. You are free to hand-write any Dash
components (dcc.Slider, dbc.Input, …) and wire them with ordinary Dash
@callback / clientside callbacks instead – mix and match as you like.
When it helps, auto_ui builds Dash controls
automatically from typed sources – a dataclass instance
(controls_from_dataclass()), a callable’s
signature (controls_from_function()), or a
list of OptionSpec objects
(make_controls()). Generated controls get
deterministic ids of the form f"{id_prefix}-{field_name}" so they are easy to
wire into callbacks, and an optional backing dash.dcc.Store can be seeded
from the instance.
from dataclasses import dataclass
from kaolin.visualize.dash import auto_ui
@dataclass
class RenderSettings:
exposure: float = 1.0
wireframe: bool = False
controls, field_specs, store = auto_ui.controls_from_dataclass(
RenderSettings(), id_prefix='render', make_store=True)
Note
To generate controls for a behavior’s options rather than a dataclass,
use add_user_behavior_options(),
which is described under
Built-in behaviors on the viewer page. It
builds on the same auto_ui machinery, additionally
pushing each edit straight to the live behavior in the browser.
Auto-generation of Dash controls from typed sources.
Controls are built from OptionSpec objects,
which can be obtained from several sources:
a dataclass instance via
controls_from_dataclass()(defaults are the instance’s current attribute values);a callable’s signature via
controls_from_function()(defaults are the parameter defaults, orNone);directly, e.g. from a behavior manifest entry, then rendered with
make_control()/make_controls().
Generated controls have a deterministic id of the form
f"{id_prefix}-{field_name}" so they can be wired to a dash.dcc.Store
or to callbacks without manual id juggling.
controls_from_dataclass() can also build a backing dash.dcc.Store
seeded from the instance (pass make_store=True); apply_store_to_dataclass()
restores store data back onto a typed instance.
bind_controls_to_behavior_clientside() pipes control changes to a JS
InteractiveBehavior via kaolin.core.event.requestBehaviorSetOption.
- class kaolin.visualize.dash.auto_ui.FieldSpec(name: str, control_id: str, value_prop: str, json_to_py: ~typing.Callable[[~typing.Any], ~typing.Any] = <function FieldSpec.<lambda>>)¶
Bases:
objectDescribes how a generated control wires into a
dash.dcc.Store(or any other sink).One
FieldSpecis produced alongside every control. TheFieldSpectells a binder (a) which Dash prop carries the value, and (b) how to convert the JSON-friendly value back into the original Python type when reconstructing an underlying instance on the server.- name¶
original field/parameter/option name as written in the source (e.g.
selection_brush_radius). Used as the dict key inside the store. For fields produced by recursing into a nested dataclass,nameis a dotted path such as"outer.inner".- Type
- control_id¶
deterministic Dash component id of the generated control (e.g.
"usr-selection_brush_radius"); use asInput(control_id, value_prop)orgetElementById(control_id).- Type
- value_prop¶
name of the Dash prop that carries the field’s current value. Differs by control type:
"on"fordash_daq.BooleanSwitch,"value"fordcc.Slider,dcc.Dropdown,dcc.Input.- Type
- json_to_py¶
Callable[[Any], Any]that converts the raw JSON-friendly value emitted by the control (str/int/float/bool) back into the field’s Python type. Sourced fromOptionSpec.json_to_py(the type itself for primitives, theEnumsubclass for enums, identity otherwise). Defaults to the identity.- Type
Callable[[Any], Any]
- kaolin.visualize.dash.auto_ui.apply_store_to_dataclass(store_data: dict, instance: Any, field_specs: list[kaolin.visualize.dash.auto_ui.FieldSpec]) None¶
Mutate
instance(and any nested dataclasses it owns) in place from store data.Inverse of
_make_default_store_data(). For every spec, readsstore_data[spec.name], appliesspec.json_to_py, and writes it onto the appropriate nested attribute. Missing keys are skipped silently;Nonevalues are written through without casting.
- kaolin.visualize.dash.auto_ui.bind_controls_to_behavior_clientside(field_specs: list[kaolin.visualize.dash.auto_ui.FieldSpec], behavior_id: str, viewer_id: str, triggers: list[dash.development.base_component.Component]) None¶
Pipe generated controls to a JS
InteractiveBehavioron every change.Requires a dummy trigger component.
- kaolin.visualize.dash.auto_ui.controls_from_dataclass(instance: Any, id_prefix: str, names: Optional[Iterable[str]] = None, annotations: dict[str, Any] | None = None, make_store: bool = False, storage_type: str = 'session') tuple[list[dash.development.base_component.Component], list[kaolin.visualize.dash.auto_ui.FieldSpec], dash.dcc.Store.Store | None]¶
Build Dash controls + field specs (and optionally a backing store) for a dataclass.
Derives an
OptionSpeclist viakaolin.visualize.dash.option.specs_from_dataclass()and renders it withmake_controls(). Whenmake_storeis set, also returns adash.dcc.Storeseeded with the instance’s current values (keyed to match the field specs, with dotted keys for nested-dataclass fields).- Parameters
instance (Any) – a dataclass instance (or a class instantiable with defaults).
id_prefix (str) – prefix for generated component ids and the store id.
names (Optional[Iterable[str]]) – field names to include and order; defaults to all fields in declaration order.
annotations (Optional[dict]) –
{field_name: annotation}overrides.make_store (bool) – if True, build and return a backing
dcc.Store.storage_type (str) –
dcc.Storestorage type whenmake_storeis set.
- Returns
(tuple)
(controls, field_specs, store)wherestoreisNoneunlessmake_storeis True.
- kaolin.visualize.dash.auto_ui.controls_from_function(fnc: Callable, id_prefix: str, names: Optional[Iterable[str]] = None, annotations: dict[str, Any] | None = None) tuple[list[dash.development.base_component.Component], list[kaolin.visualize.dash.auto_ui.FieldSpec]]¶
Build Dash controls + field specs for a callable’s parameters.
Behaves like
controls_from_dataclass()but introspects a function signature viakaolin.visualize.dash.option.specs_from_function()(*args/**kwargsare skipped, parameters without a default map todefault=None). No backing store is produced, as there is no instance to persist.- Parameters
- Returns
(tuple)
(controls, field_specs).
- kaolin.visualize.dash.auto_ui.make_control(spec: OptionSpec, id_prefix: str, persistence_type: str | None = None, name_prefix: str = '') tuple[list[dash.development.base_component.Component], list[kaolin.visualize.dash.auto_ui.FieldSpec]]¶
Render one
OptionSpecinto Dash components plus matching field specs.The spec’s
OptionKindselects the control:BOOL->dash_daq.BooleanSwitch,ENUM->dash.dcc.Dropdown,INT/FLOATwith both bounds ->dash.dcc.Slider(otherwise a numericdcc.Input),STRING-> text input,COLOR-> native color input,GROUP-> a recursively rendered section, andANY(or anything unexpected) -> a text-input fallback. Each generated control gets a deterministic idf"{id_prefix}-{name_prefix}{spec.name}".- Parameters
spec (OptionSpec) – the option to render.
id_prefix (str) – prefix for the generated component id.
persistence_type (Optional[str]) – if set, enables Dash persistence on the control with this
persistence_type(e.g.'session').name_prefix (str) – dotted prefix prepended to the spec name for nested (group) children, so field-spec names read like
"outer.inner".
- Returns
(tuple)
(components, field_specs)– a one-element component list (a wrapped control, or a sectionDivfor a group) and oneFieldSpecper rendered leaf control.
- kaolin.visualize.dash.auto_ui.make_controls(specs: Iterable[OptionSpec], id_prefix: str, persistence_type: str | None = None) tuple[list[dash.development.base_component.Component], list[kaolin.visualize.dash.auto_ui.FieldSpec]]¶
Render a list of
OptionSpecobjects into Dash components + field specs.The single rendering pass shared by the OptionSpec ingestion entry points; each spec is delegated to
make_control().- Parameters
specs (Iterable[OptionSpec]) – the options to render, in order.
id_prefix (str) – prefix applied to every generated component id.
persistence_type (Optional[str]) – if set, enables Dash persistence on each control with this
persistence_type(e.g.'session').
- Returns
(tuple)
(components, field_specs)– two lists; aGROUPspec contributes a single sectionDivbut oneFieldSpecper leaf (with dotted names).
Layout¶
layout provides the
AppLayoutHelper protocol and
StandardLayoutHelper, a responsive
(Bootstrap) scaffold for sidebar + viewport apps. Build a layout by adding a
sidebar, dropping components/sections into it, and placing your viewer(s) in the
main content area:
from kaolin.visualize.dash.layout import StandardLayoutHelper
layout = StandardLayoutHelper(title='My Kaolin App')
layout.add_sidebar()
layout.add_sidebar_components(controls + [store])
layout.add_main_content([viewer])
# or: layout.add_viewer_grid([viewer_a, viewer_b], columns=2)
- class kaolin.visualize.dash.layout.StandardLayoutHelper(title='Kaolin App', max_width: Optional[Literal['sm', 'md', 'lg', 'xl', 'xxl', 'flex']] = 'lg')¶
Bases:
AppLayoutHelper- add_modal(content, title=None)¶
Create a centered overlay modal with a close (X) button.
- Parameters
content – A Dash component (or list of components) to display inside the modal body.
title – Optional title shown at the top of the modal.
- Returns
A dict describing the modal with keys
component,overlay_id,close_idandtrigger_ids(a list of ids whose clicks open the modal; append to it to add triggers).
- add_sidebar(disappearing=False, width=20, min_width_px=300)¶
Adds sidebar component to the layout. :param disappearing:
Returns:
- add_sidebar_section(name, collapsible=True, bootstrap_icon=None, collapsed_on_load=False)¶
Add a titled section to the sidebar.
- Parameters
name – Section title displayed in the header.
collapsible – If True (default), the section header is clickable to collapse/expand its content, with a dropdown caret indicator shown in the header.
bootstrap_icon – Optional Bootstrap icon name (e.g.
"bi-boxes") shown before the section title.collapsed_on_load – If True, a collapsible section starts collapsed on page load (default False, expanded).
- Returns
The section container component.
- add_viewer_grid(viewers, columns=None, labels=None)¶
Lay out a flat list of viewers in a CSS grid in the main content area.
Cells are equal
1frtracks at full height; grid items stretch to fill their cell, which gives each viewer a definite, non-zero size (viewers are width:100%/height:100% and size their canvas from the cell’s pixel dimensions). Wraps the grid in a div and forwards it toadd_main_content().- Parameters
viewers – flat list of components, placed left-to-right, top-to-bottom.
columns – number of columns. Defaults to a single row (all viewers side by side). E.g.
columns=2with four viewers yields a 2x2 grid.labels – optional list of strings, one per viewer. When provided, each viewer is wrapped in a column with a text header above it. Must be the same length as
viewers.
- register_help_trigger(trigger)¶
Register an existing component (or its id) as an extra trigger that opens the help page.
Must be called after
add_help_page()and before the app is built.
WebSocket handlers¶
Rich client-server communication (server-side rendering, streaming geometry,
custom binary messages) flows over WebSockets. On the server you implement a
message handler – a class satisfying
SyncMessageHandlerProtocol (or its async
sibling AsyncMessageHandlerProtocol) – and
register it with the app builder. A handler declares which message tags it
accepts and reacts to them, writing messages back to the client:
from kaolin.visualize.web.sockets import SyncMessageHandlerProtocol
class EchoHandler(SyncMessageHandlerProtocol):
def accepted_message_tags(self):
return ['ping']
def on_connection_open(self, write_message_fn):
write_message_fn({'status': 'connected'}, False) # False -> JSON
def on_message(self, message_tag, message_content, write_message_fn):
if message_tag == 'ping':
write_message_fn({'pong': message_content}, False)
Handlers are registered by passing ws_handlers to
build() as a list of
(handler_class, args) (or (handler_class, args, kwargs)) tuples; a fresh
handler instance is constructed per connection, so per-client state is never
shared:
app = builder.build(ws_handlers=[(EchoHandler, ())])
For large payloads such as images or tensors, use binary messages: encode them on
the server with kaolin.visualize.web.io and decode them in the browser with
the matching utilities in the
JavaScript API. The clientside
counterpart that opens these connections and dispatches messages by tag is
core/sockets.ts, also documented there.
The full handler API – the protocols, ready-made handlers (e.g.
AnyRendererMessageHandler), and the
WebSocketHandlerManager – lives on the
dedicated kaolin.visualize.web.sockets page.
Customizing your app¶
WebappBuilder is deliberately thin; reach
for plain Dash whenever it serves you better, and use the builder hooks below to
extend an app:
Custom client-side behaviors – drop a plain
.jsfile defining and registering your behaviors (see Custom behaviors on the viewer page) into your app’sassets/directory. Dash servesassets/automatically and loads.jsfiles on page load, so thekaolinnamespace and yourBehaviorRegister.register(...)calls are available with no extra wiring. To make Python aware of those behaviors (e.g. foradd_user_behavior_options()), also callBehaviorLibrary.register_user_directory(.Extra scripts / stylesheets –
add_extra_scripts()andadd_extra_stylesheets().ES module imports (e.g. third-party libraries) –
add_importmap_item().Serving your own files / directories –
add_static_file()andadd_static_dir().Raw HTML in the document body –
add_raw_body_html().Persisted, per-tab user settings –
add_user_settings().
For anything beyond these, build the dash.Dash app (and tornado server)
yourself – nothing here is required.
App builder¶
WebappBuilder ties layout, assets, user
settings, and WebSocket handlers into a single Dash + tornado server.
SessionRegistry bridges Dash callbacks
and per-tab WebSocket state.
- class kaolin.visualize.dash.builder.SessionRegistry¶
Bases:
objectProcess-wide registry mapping
tab_uuidto a_SessionEntry.Bridges Dash (
@callbacktriggered by adcc.Storechange withState('kaolin-tab-uuid', 'data')) and the WebSocket transport (WebSocketHandlerManager.tab_uuid). Both sides converge on the sametab_uuidkey so a setting written by a control reaches every WS handler belonging to that browser tab.- NOTE: prototype-quality, app-level. Future improvements:
Promote to a library helper (e.g.
kaolin.visualize.dash.session_registry).GC stale entries when the last WS handler for a tab disconnects (use
GlobalWebSocketConnectionManager.get_handlers_by_tab(tab_uuid)to decide). Acceptable to leak for now – short-lived dev runs.Make the per-entry payload pluggable (factory) instead of hard-coding
ServerSideUserSettings.