Skip to content

GUI

Qt widgets for building interactive cellier-based applications.

Canvas and dims

cellier.gui.qt.QtCanvasWidget

Wraps a render canvas above a QtDimsControl panel.

Composes the two elements in a QVBoxLayout so that the canvas expands to fill available space while the dims control sits below it at a fixed height. The dims control includes the 2D/3D toggle button (when the scene has 3+ axes), so no separate toggle widget is needed.

Prefer constructing via :meth:from_scene_and_canvas rather than calling __init__ directly.

Parameters:

Name Type Description Default
canvas_view

A CanvasView instance; its .widget property provides the render surface to embed.

required
dims_control QtDimsControl

An already-constructed QtDimsControl instance.

required
parent QWidget | None

Optional Qt parent widget.

None

widget property

widget: QWidget

The outer QWidget to insert into a layout.

dims_control property

dims_control: QtDimsControl

The QtDimsControl panel embedded below the canvas.

from_scene_and_canvas classmethod

from_scene_and_canvas(scene, canvas_view, axis_values: Mapping[int, AxisValues], *, parent: QWidget | None = None) -> QtCanvasWidget

Construct from live scene and canvas objects.

Derives axis labels and the initial dims state from scene so that callers only need to supply axis_values (which requires data-store knowledge not available on the dims model itself). The 2D/3D toggle is included automatically when the scene has 3 or more axes: 3D displays the last three axis labels, 2D the last two.

Call controller.connect_widget on the returned widget's dims_control after construction to wire subscriptions.

Parameters:

Name Type Description Default
scene

The live Scene object whose dims this panel controls.

required
canvas_view

The CanvasView whose .widget is the render surface.

required
axis_values Mapping[int, AxisValues]

Mapping of axis index to that axis's slider values, e.g. {0: ContinuousAxisValues(min=0, max=99)}.

required
parent QWidget | None

Optional Qt parent widget.

None

compose

compose(host) -> object

Hand this widget to host as a center leaf.

Qt composes canvas-over-dims internally, so unlike AnywidgetCanvasView.compose there is nothing to arrange here.

close

close() -> None

Unsubscribe the dims control from the bus.

cellier.gui.qt.QtDimsControl

Bidirectional dims slider panel + 2D/3D toggle wired to the cellier v2 bus.

Composes a QWidget container (with a QFormLayout) holding one slider per axis, plus (when axes_2d/axes_3d are given) a toggle button that switches the scene between its 2D and 3D axis sets. An axis shows a slider when it is one of the scene's slider_axes and is not displayed.

Follows the v2 widget pattern:

  • One UUID (self._id) shared by the sliders and the toggle.
  • Subscribed to DimsChangedEvent via controller.connect_widget.
  • Echo-filters its own changes using source_id.
  • Suppresses re-entrant slider signals with blockSignals when applying model-driven updates.
  • The toggle never relabels itself optimistically on click -- it waits for the echoed DimsChangedEvent, same as the sliders, so there is a single source of truth for "what is currently displayed."

Wire to the controller after construction::

control = QtDimsControl(scene_id, axis_values=..., axis_labels=...)
controller.connect_widget(
    control, subscription_specs=control.subscription_specs()
)

Parameters:

Name Type Description Default
scene_id

UUID of the scene whose slice indices this widget controls.

required
axis_values Mapping[int, AxisValues]

Mapping of axis index to that axis's slider values, in world units. A ContinuousAxisValues axis gets a double-valued slider over [min, max] -- a slice position is a world position, not a voxel index (D3). A DiscreteAxisValues axis gets an integer slider over the positions of its values, with a readout showing the value or its label; it emits the world value, never the position.

required
axis_labels dict[int, str]

Mapping of axis index to display label, e.g. {0: "z", 1: "y", 2: "x"}.

required
initial_slice_indices dict[int, float] | None

Starting slider values; typically scene.dims.selection.slice_indices.

None
initial_displayed_axes tuple[int, ...]

Axes to hide initially; typically scene.dims.selection.displayed_axes.

()
slider_axes tuple[int, ...] | None

The world axes that get a slider when not displayed; typically scene.slider_axes. None (the default) gives every axis in axis_values one. Kept current by SliderAxesChangedEvent.

None
thickness_axes Collection[int] | None

The axes whose slider row gets a half-thickness box ("±", world units, minimum 0), bound to the scene's per-axis thickness. None (the default) gives every axis in axis_values one; a scene passes its axes that are not channel axes.

None
initial_thickness Mapping[int, float] | None

Starting half-thicknesses; typically scene.dims.selection.thickness. An axis absent from it is 0.

None
axes_2d tuple[int, ...] | None

Axis indices to display when toggling to 2D, or None to omit the toggle button entirely (e.g. a scene with fewer than 3 axes).

None
axes_3d tuple[int, ...] | None

Axis indices to display when toggling to 3D, or None to omit the toggle button entirely.

None
parent QWidget | None

Optional Qt parent widget for the internal container.

None

has_toggle property

has_toggle: bool

Whether this control offers a 2D/3D toggle.

Named to match AnywidgetDimsPanel.has_toggle so a caller can ask either front end the same question.

widget property

widget: QWidget

The Qt widget to insert into a layout.

Qt seam 1: replace with the backend element for other toolkits.

slider_axes property

slider_axes: tuple[int, ...] | None

The world axes that get a slider when not displayed.

None means every axis this control was built with.

current_index

current_index() -> dict[int, float]

Return the current world value of every slider regardless of visibility.

A discrete axis reports the world value at its slider position, not the position itself.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

A scope still open (a control closed mid-drag) is ended first.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

Return the inbound subscription this widget requires.

Pass the result to CellierController.connect_widget.

Visual controls

cellier.gui.qt.visuals.QtImageControls

Bases: VisualIdGroup

Bidirectional image appearance control on the cellier bus.

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual, or an OrthoViewer's panel siblings, driven together.

required
values dict[str, Any]

The seed from :func:cellier.gui._image_controls.image_control_values.

required
title str | None

The group frame's title. Defaults to :data:DEFAULT_TITLE.

None
n_displayed_dimensions int

How many dimensions the driven visuals' scenes display now, 2 or 3. Seeds the rows that are shown only in 3D. Default 3, which shows them.

3
scene_ids Sequence[UUID]

The scenes to follow: after construction the control takes :attr:n_displayed_dimensions from their DimsChangedEvent. The visuals are assumed to share one display dimensionality; if they do not, the last change wins.

()
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

widget property

widget

The titled group to insert into a layout.

composite property

composite: bool

Whether the control shows the composite page.

n_displayed_dimensions property writable

n_displayed_dimensions: int

How many dimensions the scene displays, as the rows assume (2 or 3).

Follows the scenes the control was given; setting it re-applies the rows until the next change on one of them.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list

One subscription per inbound event type per driven visual.

Plus one DimsChangedEvent subscription per followed scene.

cellier.gui.qt.visuals.QtVisibleToggle

Bases: QtToggle

Show or hide the visual.

visible exists on every appearance model, so this control applies to every visual type.

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

UUID of the visual, or a sequence of UUIDs to drive as one group.

required
initial_value Any

Starting value -- typically visual_model.appearance.visible. Defaults to True, matching the model default.

_UNSET
parent

Optional Qt parent widget.

None

widget property

widget

The labelled row to insert into a layout.

This is the whole control, name included -- what a caller wants in every case except reaching for the input itself, which is :attr:control.

control property

control

The bare input inside the row -- the checkbox, slider or combo.

For tests and for callers driving the input directly. Layer 2's _read / _apply use self._control and never go through here.

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

field property

field: str

The appearance field name this widget writes.

value property writable

value: Any

The control's current value.

A property, not a method, so it reads the same on both toolkits: the anywidget twin spells this as a synced trait, and code that had to ask which front end it was holding could not be written against either.

Assigning to it behaves like a user edit -- the control moves and the change reaches the bus -- which is also what assigning to the anywidget trait does. To push a value in without emitting (an inbound bus write), use _apply.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

Return one inbound subscription per visual id (subscribe-to-all).

Subscribing to every id rather than only the first means the widget stays correct when a sibling is written by something other than this widget -- see multichannel_widget_design.md section 11.3.

cellier.gui.qt.visuals.QtOpacitySlider

Bases: QtBoundedSlider

Master opacity multiplier.

Applies to every visual type -- opacity is on BaseAppearance. It is also the only appearance field carrying ge/le, which is why it is the only BoundedSlider: the range is read off the model rather than picked (design section 6.5.1 proposal 3).

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

UUID of the visual, or a sequence of UUIDs to drive as one group.

required
initial_value Any

Starting value -- typically visual.appearance.opacity.

_UNSET

widget property

widget

The labelled row to insert into a layout.

This is the whole control, name included -- what a caller wants in every case except reaching for the input itself, which is :attr:control.

control property

control

The bare input inside the row -- the checkbox, slider or combo.

For tests and for callers driving the input directly. Layer 2's _read / _apply use self._control and never go through here.

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

field property

field: str

The appearance field name this widget writes.

value property writable

value: Any

The control's current value.

A property, not a method, so it reads the same on both toolkits: the anywidget twin spells this as a synced trait, and code that had to ask which front end it was holding could not be written against either.

Assigning to it behaves like a user edit -- the control moves and the change reaches the bus -- which is also what assigning to the anywidget trait does. To push a value in without emitting (an inbound bus write), use _apply.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

Return one inbound subscription per visual id (subscribe-to-all).

Subscribing to every id rather than only the first means the widget stays correct when a sibling is written by something other than this widget -- see multichannel_widget_design.md section 11.3.

cellier.gui.qt.visuals.QtUniformColorPicker

Bases: QtColorPicker

Uniform RGBA colour.

One class for mesh, points and lines: they spell the field color alike, so the control is shared rather than triplicated. Only used when the visual's color_mode is "uniform"; per-vertex colours come from the data store.

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

UUID of the visual, or a sequence of UUIDs to drive as one group.

required
initial_value Any

Starting value -- typically visual.appearance.color.

_UNSET

widget property

widget

The labelled row to insert into a layout.

This is the whole control, name included -- what a caller wants in every case except reaching for the input itself, which is :attr:control.

control property

control

The bare input inside the row -- the checkbox, slider or combo.

For tests and for callers driving the input directly. Layer 2's _read / _apply use self._control and never go through here.

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

field property

field: str

The appearance field name this widget writes.

value property writable

value: Any

The control's current value.

A property, not a method, so it reads the same on both toolkits: the anywidget twin spells this as a synced trait, and code that had to ask which front end it was holding could not be written against either.

Assigning to it behaves like a user edit -- the control moves and the change reaches the bus -- which is also what assigning to the anywidget trait does. To push a value in without emitting (an inbound bus write), use _apply.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

Return one inbound subscription per visual id (subscribe-to-all).

Subscribing to every id rather than only the first means the widget stays correct when a sibling is written by something other than this widget -- see multichannel_widget_design.md section 11.3.

cellier.gui.qt.visuals.QtLevelOfDetailControls

Bases: VisualIdGroup

Settled bias slider and "coarsest while moving" checkbox.

Drives settled_lod_bias, coarsest_while_moving_3d and coarsest_while_moving_2d on a multiscale image or labels appearance. The checkbox shows and edits the setting of the view the control is in: the 3D one while :attr:n_displayed_dimensions is 3, the 2D one otherwise.

Changing settled_lod_bias reslices, so its AppearanceUpdateEvent is emitted when the slider is released, not on every tick.

Wire to the controller after construction::

control = QtLevelOfDetailControls(visual_id, values)
controller.connect_widget(
    control, subscription_specs=control.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual the control drives. A sequence drives every listed visual in lock-step (the OrthoViewer's panel siblings).

required
values Mapping[str, Any] | None

cellier.gui._level_of_detail.level_of_detail_values of the visual's appearance. Missing keys take the model's defaults.

None
n_displayed_dimensions int

What the view displays now, 2 or 3.

3
scene_ids Sequence[UUID]

Scenes whose DimsChangedEvent the control follows to keep :attr:n_displayed_dimensions current.

()
lod_range tuple[float, float]

(min, max) of the slider.

SETTLED_BIAS_RANGE
decimals int

Decimal places shown beside the slider.

2
title str | None

The group's name. Defaults to :data:DEFAULT_TITLE.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

widget property

widget

The titled group to insert into a layout.

control property

control

The bare row container inside the group.

n_displayed_dimensions property writable

n_displayed_dimensions: int

What the view displays, 2 or 3; picks the checkbox's field.

moving_field property

moving_field: str

The appearance field the checkbox edits now.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

One appearance subscription per visual, one dims one per scene.

cellier.gui.qt.visuals.QtLoadingIndicator

Bases: VisualIdGroup

How far a multiscale visual's data has loaded.

A bar of target chunks resident over needed, and a status line: the coarse overview (the backstop) first, then the detail, then "Loaded". Both sit in a group titled :data:DEFAULT_TITLE, like the image and bounding-box controls. Read-only: it listens to ResliceProgressEvent and emits nothing (plans/progressive_loading_design_v3.md 5.13).

Wire to the controller after construction::

indicator = QtLoadingIndicator(
    visual_id, initial={visual_id: controller.loading_progress(visual_id)}
)
controller.connect_widget(
    indicator, subscription_specs=indicator.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual, or a group of them (an OrthoViewer's panel siblings), shown summed.

required
initial Mapping[UUID, LoadingProgress | None] | None

Their progress now, so an indicator built mid-load starts right.

None
levels bool

The visuals are meshes: the status line names levels ("Loading coarse level"), not chunk counts.

False
title str | None

The group's title. Defaults to :data:DEFAULT_TITLE.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

DEFAULT_TITLE class-attribute instance-attribute

DEFAULT_TITLE = LOADING_TITLE

Name shown when no title= is given.

widget property

widget

The titled group to insert into a layout.

state property

state: IndicatorState

What the indicator shows now.

text property

text: str

The status line as drawn.

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

One ResliceProgressEvent subscription per visual.

cellier.gui.qt.visuals.QtLoadingConfigControls

Bases: VisualIdGroup

Every ProgressiveLoadingConfig field of a multiscale visual.

One row per field: the backstop's level ("coarsest" for None), its extent and its slot cap. An edit is sent as LoadingConfigUpdateEvent and applied at once. An invalid value is refused by the controller: the rows show the settings again and the reason below them; nothing is corrected.

Wire to the controller after construction::

controls = QtLoadingConfigControls(visual_id, loading=config.model_dump())
controller.connect_widget(
    controls, subscription_specs=controls.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual, or a group of them (an OrthoViewer's panel siblings), edited together.

required
loading Mapping[str, Any]

The current config, as ProgressiveLoadingConfig.model_dump().

required
n_levels int | None

The visual's level count, the most "Backstop level" offers.

None
title str | None

The group's name. Defaults to :data:DEFAULT_TITLE.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

DEFAULT_TITLE class-attribute instance-attribute

DEFAULT_TITLE = LOADING_CONFIG_TITLE

Name shown when no title= is given.

widget property

widget

The titled group to insert into a layout.

config property

config: dict[str, Any]

The settings as last reported by the model.

error property

error: str

The reason the last edit was refused, or "".

input

input(field: str)

The control for one field (for tests and scripting).

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

One LoadingConfigChangedEvent subscription per visual.

cellier.gui.qt.visuals.QtMeshSectionControls

Bases: VisualIdGroup

How a mesh is drawn in a 2D view: every MeshSectionConfig field.

One row per field: the outline on or off, the fill on or off, the outline's width in screen pixels, and whether the cut is the slice plane (cut) or the scene's slab (slab). An edit is sent as MeshSectionUpdateEvent. Turning a part on or off, or changing the mode, reads the mesh again; the width applies at once.

The settings act only in a 2D view, so the group is hidden while none of the scenes in displayed_dimensions displays two dimensions.

Wire to the controller after construction::

controls = QtMeshSectionControls(visual.id, section=visual.section.model_dump())
controller.connect_widget(
    controls, subscription_specs=controls.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The mesh visual, or a group of them (an OrthoViewer's panel siblings), edited together.

required
section Mapping[str, Any]

The current config, as MeshSectionConfig.model_dump().

required
title str | None

The group's name. Defaults to :data:DEFAULT_TITLE.

None
displayed_dimensions Mapping[UUID, int] | None

Scene id to how many dimensions it displays now (section_display_seed). The control follows these scenes' DimsChangedEvent. Empty (the default) keeps it always shown.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

DEFAULT_TITLE class-attribute instance-attribute

DEFAULT_TITLE = MESH_SECTION_TITLE

Name shown when no title= is given.

widget property

widget

The widget to insert into a layout: it holds the titled group.

shown property

shown: bool

Whether the group is shown: a followed scene displays 2D.

config property

config: dict[str, Any]

The settings as last reported by the model.

error property

error: str

The reason the last edit was refused, or "".

input

input(field: str)

The control for one field (for tests and scripting).

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

One MeshSectionChangedEvent per visual, one dims per scene.

cellier.gui.qt.visuals.QtLodConfigControls

Bases: VisualIdGroup

The level-of-detail settings of a multiscale mesh.

One row per GeometryLodConfig setting: what a dims scrub loads, what it draws for a mesh it does not change, and what a moving camera draws in a 3D view. Each is coarse or full. An edit is sent as LodConfigUpdateEvent and applies in the next frame; nothing is read.

Wire to the controller after construction::

controls = QtLodConfigControls(visual.id, lod=visual.lod.model_dump())
controller.connect_widget(
    controls, subscription_specs=controls.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual, or a group of them (an OrthoViewer's panel siblings), edited together.

required
lod Mapping[str, Any]

The current config, as GeometryLodConfig.model_dump().

required
title str | None

The group's name. Defaults to :data:DEFAULT_TITLE.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

DEFAULT_TITLE class-attribute instance-attribute

DEFAULT_TITLE = LOD_CONFIG_TITLE

Name shown when no title= is given.

widget property

widget

The titled group to insert into a layout.

config property

config: dict[str, Any]

The settings as last reported by the model.

error property

error: str

The reason the last edit was refused, or "".

input

input(field: str)

The control for one field (for tests and scripting).

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

One LodConfigChangedEvent subscription per visual.

cellier.gui.qt.visuals.QtClippingPlanesControls

Bases: VisualIdGroup

A visual's clipping planes: one row per plane, and an add button.

Each row has an enabled checkbox, a flip button, a remove button, the normal and a position slider along the normal. The normal has one column per data axis: two buttons named after the axis as the store's coordinate system gives it (+z and -z) that face the plane along the axis, and under them the normal's entry on it. Values are in the visual's data coordinates. Given gizmo_target, a row also has a "Gizmo" toggle that puts a gizmo on its plane in the 3D canvas; one plane of a canvas has it at a time. An edit is sent as ClippingPlanesUpdateEvent with the whole new tuple. A moved plane updates its row in place, so a slider is not destroyed while it is dragged.

Wire to the controller after construction::

data = get_clipping_planes_data_from_visual(visual, store)
controls = QtClippingPlanesControls(visual.id, **data)
controller.connect_widget(
    controls, subscription_specs=controls.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual, or a group of them (an OrthoViewer's panel siblings), edited together.

required
coordinate_system UUID | str

The level-0 data coordinate system of the store they read (its id).

required
axis_names Sequence[str]

The data axis names, in order.

required
bounds Sequence[Sequence[float]]

(low, high) of the data on each axis.

required
planes Sequence[Mapping[str, Any]]

The current rows (rows_from_planes(visual.clipping_planes)).

()
data_store_id UUID | str | None

The store the visuals read.

None
bounds_source Callable[[], Sequence[Sequence[float]]] | None

Reads the store's current (low, high) per axis. Given with data_store_id, the position ranges follow the store's extent; without it they stay at bounds.

None
gizmo_target ClippingPlaneGizmoTarget | None

Where a plane's gizmo is drawn (a ClippingPlaneGizmoTarget), the plane that has it now, and why a plane cannot have one: get_clipping_plane_gizmo_data(controller, visual_id, canvas_id). The target's visual must be one of visual_id. Without gizmo_target the rows have no gizmo toggle.

None
gizmo_plane ClippingPlaneGizmoTarget | None

Where a plane's gizmo is drawn (a ClippingPlaneGizmoTarget), the plane that has it now, and why a plane cannot have one: get_clipping_plane_gizmo_data(controller, visual_id, canvas_id). The target's visual must be one of visual_id. Without gizmo_target the rows have no gizmo toggle.

None
gizmo_blocked ClippingPlaneGizmoTarget | None

Where a plane's gizmo is drawn (a ClippingPlaneGizmoTarget), the plane that has it now, and why a plane cannot have one: get_clipping_plane_gizmo_data(controller, visual_id, canvas_id). The target's visual must be one of visual_id. Without gizmo_target the rows have no gizmo toggle.

None
title str | None

The group's name. Defaults to :data:DEFAULT_TITLE.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

DEFAULT_TITLE class-attribute instance-attribute

DEFAULT_TITLE = CLIPPING_PLANES_TITLE

Name shown when no title= is given.

widget property

widget

The widget to insert into a layout: the titled group.

planes property

planes: list[dict[str, Any]]

The rows as last reported by the model.

editor property

editor: ClippingPlanesEditor

The toolkit-neutral half (for tests and scripting).

error property

error: str

The reason the last edit was refused, or "".

row

row(index: int) -> _PlaneRow

The widgets of plane index (for tests and scripting).

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

One ClippingPlanesChangedEvent per visual, and the store's extent.

Clipping planes helpers

Toolkit-neutral: what a clipping planes control of either toolkit is built from. See Clip a visual with planes.

cellier.gui.get_clipping_planes_data_from_visual

get_clipping_planes_data_from_visual(visual: Any, store: Any) -> dict[str, Any]

What a clipping planes control is built with, read off the models.

Parameters:

Name Type Description Default
visual BaseVisual

The visual. For a group of visuals edited together, the first: they read one store, and the visuals of a link group carry the same planes.

required
store BaseDataStore

The store the visual reads.

required

Returns:

Type Description
dict[str, Any]

coordinate_system (the store's level-0 system id, as a string), data_store_id (as a string), axis_names, bounds ([low, high] per data axis) and planes (the rows).

cellier.gui.get_axis_bounds_from_store

get_axis_bounds_from_store(controller: Any, data: Mapping[str, Any]) -> Callable[[], list[list[float]]] | None

A reader of a store's current bounds; not the bounds themselves.

The returned function takes no arguments, looks the store up by id and returns its current [low, high] per axis. A clipping planes control calls it when the store's extent changes (DataStoreMetadataChangedEvent) to move its slider ranges. It exists because a control does not hold the controller.

Parameters:

Name Type Description Default
controller CellierController or None

Looks the store up by id each time, so the reader holds no store.

required
data Mapping[str, Any]

From :func:get_clipping_planes_data_from_visual: its data_store_id and axis_names are used.

required

Returns:

Type Description
Callable or None

The bounds_source of a clipping planes control; None without a controller.

cellier.gui.ClippingPlaneGizmoTarget

Bases: NamedTuple

Where the gizmo of a clipping planes control is drawn.

Parameters:

Name Type Description Default
visual_id UUID

The visual whose plane the gizmo edits.

required
canvas_id UUID

The 3D canvas the gizmo is drawn in.

required
scene_id UUID

That canvas's scene, whose DimsChangedEvent the control follows.

required

cellier.gui.get_clipping_plane_gizmo_data

get_clipping_plane_gizmo_data(controller: Any, visual_id: UUID, canvas_id: UUID) -> dict[str, Any]

The gizmo arguments of a clipping planes control, for one visual and canvas.

Selects nothing: the caller names the visual whose plane the gizmo edits and the canvas it is drawn in, as for CellierController.add_clipping_plane_gizmo. The canvas must exist: build the canvas before the control.

Parameters:

Name Type Description Default
controller CellierController

The controller the control is wired to.

required
visual_id UUID

The visual whose plane the gizmo edits. One of the control's visuals.

required
canvas_id UUID

A canvas of that visual's scene.

required

Returns:

Type Description
dict[str, Any]

gizmo_target (a :class:ClippingPlaneGizmoTarget), gizmo_plane (the id of the plane that has the canvas's gizmo now, as a string, if it is one of this visual's; otherwise None) and gizmo_blocked (a reader of why a plane cannot have one, over controller.clipping_plane_gizmo_blocked).

Raises:

Type Description
KeyError

If the visual or the canvas is not registered.

ValueError

If the canvas does not show the visual's scene.

Render planes control

The planes a visual in the "plane" render mode draws its data on: the clipping planes rows with the extents added, in world units.

cellier.gui.qt.visuals.QtRenderPlanesControls

Bases: VisualIdGroup

A visual's render planes: one row per plane, and an add button.

The planes a visual in the "plane" render mode draws its data on. Each row is a "Clipping planes" row with the extents added: an enabled checkbox, a flip button, a remove button, the normal (two buttons and an entry per axis of the plane), a position slider along the normal, and for each of the plane's two in-plane axes a minimum and a maximum, each with an "inf" toggle that makes it unbounded. Values are in world units. Given gizmo_target, a row also has a "Gizmo" toggle that puts the canvas's gizmo on its plane; one plane of a canvas has it at a time, clipping planes included.

An edit is sent as RenderPlanesUpdateEvent with the whole new tuple. A moved plane updates its row in place, so a slider is not destroyed while it is dragged. A visual draws at most four planes: the add button is disabled at four. While the planes are not drawn (the visual is not in plane mode, or the view is 2D) the control is disabled and says why.

Wire to the controller after construction::

data = get_render_planes_data_from_visual(controller, visual.id)
controls = QtRenderPlanesControls(visual.id, **data)
controller.connect_widget(
    controls, subscription_specs=controls.subscription_specs()
)

Parameters:

Name Type Description Default
visual_id UUID | Sequence[UUID]

The visual, or a group of them, edited together.

required
coordinate_system UUID | str

The scene's world coordinate system (its id).

required
axis_ids Sequence[str]

The world axes, in order: their ids and names.

required
axis_names Sequence[str]

The world axes, in order: their ids and names.

required
planes Sequence[Any]

The visual's current render_planes.

()
displayed_axes Sequence[str] | None

What the scene and the visual's data are now, and a reader of it: get_render_planes_data_from_visual(controller, visual_id).

None
bounds Sequence[str] | None

What the scene and the visual's data are now, and a reader of it: get_render_planes_data_from_visual(controller, visual_id).

None
state_source Sequence[str] | None

What the scene and the visual's data are now, and a reader of it: get_render_planes_data_from_visual(controller, visual_id).

None
scene_id Sequence[str] | None

What the scene and the visual's data are now, and a reader of it: get_render_planes_data_from_visual(controller, visual_id).

None
data_store_id Sequence[str] | None

What the scene and the visual's data are now, and a reader of it: get_render_planes_data_from_visual(controller, visual_id).

None
gizmo_target RenderPlaneGizmoTarget | None

Where a plane's gizmo is drawn (a RenderPlaneGizmoTarget), the plane that has it now, and why a plane cannot have one: get_render_plane_gizmo_data(controller, visual_id, canvas_id). Without gizmo_target the rows have no gizmo toggle.

None
gizmo_plane RenderPlaneGizmoTarget | None

Where a plane's gizmo is drawn (a RenderPlaneGizmoTarget), the plane that has it now, and why a plane cannot have one: get_render_plane_gizmo_data(controller, visual_id, canvas_id). Without gizmo_target the rows have no gizmo toggle.

None
gizmo_blocked RenderPlaneGizmoTarget | None

Where a plane's gizmo is drawn (a RenderPlaneGizmoTarget), the plane that has it now, and why a plane cannot have one: get_render_plane_gizmo_data(controller, visual_id, canvas_id). Without gizmo_target the rows have no gizmo toggle.

None
title str | None

The group's name. Defaults to :data:DEFAULT_TITLE.

None
parent

Optional Qt parent widget.

None

visual_ids property

visual_ids: tuple[UUID, ...]

The visual ids this widget drives, always as a tuple.

DEFAULT_TITLE class-attribute instance-attribute

DEFAULT_TITLE = RENDER_PLANES_TITLE

Name shown when no title= is given.

widget property

widget

The widget to insert into a layout: the titled group.

planes property

planes: list[dict[str, Any]]

The rows as last reported by the model.

editor property

editor: RenderPlanesEditor

The toolkit-neutral half (for tests and scripting).

error property

error: str

The reason the last edit was refused, or "".

blocked property

blocked: str

Why the control is disabled now, or "".

row

row(index: int) -> _PlaneRow

The widgets of plane index (for tests and scripting).

refresh

refresh() -> None

Read the scene's state again (whether the control is disabled).

close

close() -> None

Emit closed to trigger bus unsubscription via the controller.

subscription_specs

subscription_specs() -> list[SubscriptionSpec]

What the control follows on the bus (see the editor).

cellier.gui.get_render_planes_data_from_visual

get_render_planes_data_from_visual(controller: Any, visual_id: UUID, *, blocked: Callable[[], str] | None = None) -> dict[str, Any]

What a render planes control is built with, read through the controller.

Unlike a clipping planes control, a render planes control reads the scene as well as the visual: its planes are in world coordinates, a new one is put on the axes the 3D view displays, and the control is disabled while the planes are not drawn.

Parameters:

Name Type Description Default
controller CellierController

The controller the control is wired to.

required
visual_id UUID

The visual. For a group of visuals edited together, the first.

required
blocked Callable[[], str] or None

Why the control is disabled now, or "". None uses controller.render_planes_blocked(visual_id): the visual is not in the "plane" render mode, or the view is 2D.

None

Returns:

Type Description
dict[str, Any]

Keyword arguments of the control: coordinate_system (the world system's id, as a string), axis_ids and axis_names (the world axes, in order), planes (the visual's render_planes), scene_id, data_store_id and state_source, a reader of {"blocked", "displayed_axes", "bounds"} as they are now.

Raises:

Type Description
KeyError

If the visual is not registered.

cellier.gui.RenderPlaneGizmoTarget

Bases: NamedTuple

Where the gizmo of a render planes control is drawn.

Parameters:

Name Type Description Default
visual_id UUID

The visual whose plane the gizmo edits.

required
canvas_id UUID

The 3D canvas the gizmo is drawn in.

required
scene_id UUID

That canvas's scene.

required

cellier.gui.get_render_plane_gizmo_data

get_render_plane_gizmo_data(controller: Any, visual_id: UUID, canvas_id: UUID) -> dict[str, Any]

The gizmo arguments of a render planes control, for one visual and canvas.

Selects nothing: the caller names the visual whose plane the gizmo edits and the canvas it is drawn in, as for CellierController.add_render_plane_gizmo. The canvas must exist: build the canvas before the control.

Returns:

Type Description
dict[str, Any]

gizmo_target (a :class:RenderPlaneGizmoTarget), gizmo_plane (the id of the render plane that has the canvas's gizmo now, as a string, if it is one of this visual's; otherwise None) and gizmo_blocked (a reader of why a plane cannot have one, over controller.render_plane_gizmo_blocked).

Raises:

Type Description
KeyError

If the visual or the canvas is not registered.

ValueError

If the canvas does not show the visual's scene.