Plugin API Reference

July 8, 2026 ยท View on GitHub

Complete API reference for LichtFeld Studio plugins.

This page is aligned against the committed Python stubs in src/python/stubs/lichtfeld/ and the plugin framework in src/python/lfs_plugins/. When in doubt, the stubs are the quickest way to verify exact signatures.


Python API Surface Map

The public Python surface is split between the native lichtfeld module and the pure-Python plugin helpers in lfs_plugins.

ModuleMain responsibility
lichtfeldTraining control, scene shortcuts, tensors, rendering, viewport, transform gizmos, registration, app helpers
lichtfeld.appApplication-level file open helper
lichtfeld.animationTracks, clips, and timeline evaluation
lichtfeld.ioLoad/save splats, point clouds, datasets, images, and supported format queries
lichtfeld.keymapInput binding profiles, action lookup, capture, import/export
lichtfeld.logNative log sink
lichtfeld.mcpRegister Python MCP tools and call/read shared MCP capabilities/resources
lichtfeld.meshOpenMesh-style mesh data, handles, iterators, readers/writers, decimaters
lichtfeld.opsNative operator invocation, descriptors, built-in operator/tool enums
lichtfeld.packagesuv-backed package install/list/uninstall and stub path helpers
lichtfeld.pipelineChainable selection/edit/transform operation stages
lichtfeld.pluginsPlugin discovery, lifecycle, registry install/update, capabilities, settings, scaffolding
lichtfeld.sceneScene graph, nodes, splat data, point clouds, cameras, selection groups
lichtfeld.scriptsScript panel state and batch execution
lichtfeld.selectionGaussian stroke/preview/brush/lasso/depth/crop selection primitives
lichtfeld.uiPanels, menus, immediate UI, dialogs, theme, tools, app UI state, RML bridge
lichtfeld.undoUndo/redo stack, transactions, memory accounting, subscriptions
lfs_plugins.*Plugin base types, properties, runtime state bindings, tool definitions, capabilities, templates, managers

The sections below focus on the APIs plugin authors most often call directly. Large low-level surfaces such as lichtfeld.mesh.TriMesh and every tensor operator are intentionally summarized; inspect the .pyi files for exhaustive method lists.

Documentation Map

Python and plugin API documentation currently lives in these places:

PathPurpose
docs/plugins/getting-started.mdPlugin authoring guide, common workflows, panel/operator examples, and runtime patterns
docs/plugins/api-reference.mdPractical Python/plugin API reference aligned with the committed stubs
docs/plugins/examples/README.md and docs/plugins/examples/Runnable example plugins and focused API examples
docs/plugin-system.mdPlugin runtime architecture, manager responsibilities, scaffolding, and packaging overview
docs/plugin-dev-workflow.mdCLI/Python workflow for creating, validating, installing, and iterating on plugins
docs/Python_UI.mdCompatibility redirect for the old Python UI document
docs/Python_API_issues.mdKnown Python API issues, binding gaps, stale-doc corrections, and follow-up recommendations
docs/docs/development/mcp/MCP automation guide, resources, tools, and workflow recipes
docs/docs/development/rmlui-styling.mdRmlUI/RCSS styling rules used by retained Python panels

Registration

import lichtfeld as lf

lf.register_class(cls)           # Register a Panel, Operator, or Menu class
lf.unregister_class(cls)         # Unregister a Panel, Operator, or Menu class

Scrub Controls

from lfs_plugins import ScrubFieldController, ScrubFieldSpec

Retained panels can use these helpers to turn a range slider row (input.setting-slider) into a scrub field:

  • Drag horizontally on the scrub field to scrub values.
  • Click the numeric text area to type a value directly.
  • The controller keeps the displayed value in sync and applies clamping, snapping, and fill width updates.
SCRUB_FIELD_SPECS = {
    "quality": ScrubFieldSpec(min_value=0.0, max_value=1.0, step=0.01, fmt="%.2f"),
}

class MyPanel(lf.ui.Panel):
    # ...
    def on_bind_model(self, ctx):
        model = ctx.create_data_model("my_panel")
        if model is None:
            return
        model.bind("quality", lambda: f"{self._quality:.2f}", self._set_quality)

    def __init__(self):
        self._scrub_fields = ScrubFieldController(
            SCRUB_FIELD_SPECS,
            self._get_scrub_value,
            self._set_scrub_value,
        )

    def on_mount(self, doc):
        self._scrub_fields.mount(doc)

    def on_unmount(self, doc):
        self._scrub_fields.unmount()

    def on_update(self, doc):
        return self._scrub_fields.sync_all()

Each scrubbed data-value still needs a normal model.bind(...) entry. The controller upgrades the range input UI, but it does not create data-model variables for you.

ScrubFieldSpec fields are min_value, max_value, step, fmt, data_type (default float), and pixels_per_step (unused in the current controller implementation).

Panel

import lichtfeld as lf
# lf.ui.Panel is the base class for all panels
AttributeTypeDefaultDescription
idstrmodule.qualnameUnique panel identifier
labelstr""Display name (id fallback when empty)
spacelf.ui.PanelSpacelf.ui.PanelSpace.MAIN_PANEL_TABPanel space (see below)
parentstr""Parent panel id. Embeds as a collapsible section; embedded panels must not override space
orderint100Sort order (lower = higher)
optionsset[lf.ui.PanelOption]set()DEFAULT_CLOSED, HIDE_HEADER
poll_dependenciesset[lf.ui.PollDependency]{SCENE, SELECTION, TRAINING}Which state changes trigger poll()
sizetuple[float, float] | NoneNoneInitial width/height hint, mainly for floating panels
templatestr | os.PathLike[str]""Retained RML template. Use an absolute path for plugin-local files
stylestr""Inline RCSS appended to the retained document
height_modelf.ui.PanelHeightModelf.ui.PanelHeightMode.FILLFILL or CONTENT for retained panels
update_policystr"interval"Set to "dirty" or "reactive" for retained panels that update from explicit model/store invalidation
update_interval_msint100Fallback cadence for retained/hybrid on_update() work. Prefer update_policy = "dirty" for data-driven panels
MethodReturnsDescription
poll(cls, context)boolClassmethod. Show/hide condition
draw(self, ui)NoneImmediate-mode content
on_bind_model(self, ctx)NoneBind retained data models before document load
on_mount(self, doc)NoneCalled once after the retained document mounts
on_unmount(self, doc)NoneCalled before the retained document is destroyed
on_update(self, doc)None | boolRetained update hook. With update_policy = "interval" it runs on the interval; with "dirty" it runs only after explicit invalidation, scene changes, or update requests. Return True to mark content dirty
on_scene_changed(self, doc)NoneCalled when the active scene generation changes

Registering a panel with the same id as an existing panel replaces it (see Panel replacement).

lf.ui.Panel is unified: a panel can start as draw(ui) only and later add template, style, height_mode, or retained hooks without switching base classes or rewriting the panel body.

Panel definitions are validated during lf.register_class(). Invalid enum values, removed legacy field names, unsupported retained features on VIEWPORT_OVERLAY, or conflicting embedded-panel fields raise ValueError, TypeError, or AttributeError.

The panel API is strict in v1: use the enum values above, not string literals.

Reactive retained panels

For retained RML panels, prefer dirty-policy updates over timer polling. A dirty-policy panel runs on_update() only when scene state changes, document/model state is marked dirty, or an explicit update is requested.

import lichtfeld as lf
from lfs_plugins.ui import RuntimeState, PanelStateBinding


class MyPanel(lf.ui.Panel):
    id = "my_plugin.panel"
    label = "My Panel"
    template = "/absolute/path/to/main_panel.rml"
    update_policy = "dirty"

    def __init__(self):
        self._handle = None
        self._store_binding = PanelStateBinding()
        self._title = "No scene"

    def on_bind_model(self, ctx):
        model = ctx.create_data_model("my_plugin_panel")
        if model is None:
            return
        model.bind_func("title", lambda: self._title)
        self._handle = model.get_handle()

    def on_mount(self, doc):
        self._store_binding.set_handle(self._handle).watch(
            RuntimeState.scene_generation,
            RuntimeState.selection_generation,
            refresh=self._refresh_title,
            dirty="title",
            immediate=True,
        )

    def on_unmount(self, doc):
        self._store_binding.close()
        doc.remove_data_model("my_plugin_panel")
        self._handle = None

    def _refresh_title(self):
        scene = lf.get_scene()
        self._title = getattr(scene, "name", "Scene") if scene else "No scene"

Use PanelStateBinding for normal panel subscriptions. It keeps subscription lifetime and RML invalidation together:

APIPurpose
RuntimeState.<field>.valueRead or publish a current app value
RuntimeState.<field>.subscribe(callback)Low-level subscription, mostly for non-panel code
PanelStateBinding(handle).watch(...)Preferred retained-panel subscription helper
dirty=NoneRequest on_update() without dirtying every bound variable
dirty="field"Dirty one data-model variable
dirty=("a", "b")Dirty several data-model variables
dirty="*"Dirty the full data model
batch_updates()Publish several store fields atomically

Store fields currently exposed to plugins:

RuntimeState.iteration
RuntimeState.total_iterations
RuntimeState.loss
RuntimeState.num_gaussians
RuntimeState.max_gaussians
RuntimeState.training_running
RuntimeState.training_state
RuntimeState.trainer_loaded
RuntimeState.eval_psnr
RuntimeState.eval_ssim
RuntimeState.scene_generation
RuntimeState.selection_generation
RuntimeState.fps
RuntimeState.mode_text
RuntimeState.active_tool
RuntimeState.active_submode
RuntimeState.transform_space
RuntimeState.pivot_mode
RuntimeState.import_overlay_state
RuntimeState.video_export_overlay_state
RuntimeState.export_progress_state
RuntimeState.mesh2splat_state
RuntimeState.splat_simplify_state
RuntimeState.scripts_generation
RuntimeState.language_generation

AppState, AppStore, and NativeAppStore remain as compatibility aliases for older plugins. New plugin code should import RuntimeState from lfs_plugins.ui.

Old Python UI hooks still compile, but hook registration is deprecated for external plugins. Use retained RML data models plus RuntimeState subscriptions for new UI.

Panel spaces

MAIN_PANEL_TAB, SIDE_PANEL, VIEWPORT_OVERLAY, SCENE_HEADER, FLOATING, STATUS_BAR

Retained shell behavior

If a panel uses retained features and template is empty, LichtFeld selects a shell automatically:

  • FLOATING -> rmlui/floating_window.rml
  • STATUS_BAR -> rmlui/status_bar_panel.rml
  • Other retained panel spaces -> rmlui/docked_panel.rml

Built-in template aliases:

  • builtin:docked-panel
  • builtin:floating-window
  • builtin:status-bar

Panel styling guide

GoalUseNotes
Minimal paneldraw(self, ui)No extra files needed
Light retained stylingstyleInline RCSS text, not a path
Full custom retained UItemplateUse an absolute path for plugin-local .rml
Hybrid paneltemplate plus draw(ui)Render immediate content into <div id="im-root"></div>

When a plugin-local template file such as main_panel.rml is present, LichtFeld automatically loads a sibling main_panel.rcss stylesheet if it exists. A sibling main_panel.theme.rcss file is also loaded for palette-dependent overrides.


Operator

from lfs_plugins.types import Operator, Event

Operator extends PropertyGroup, so it supports typed properties as class attributes.

AttributeTypeDescription
labelstrDisplay name
descriptionstrTooltip text
optionsSet[str]{'UNDO', 'BLOCKING'}
MethodReturnsDescription
poll(cls, context)boolClassmethod. Can the op run?
invoke(self, context, event)setCalled on trigger, can start modal
execute(self, context)setSynchronous execution
modal(self, context, event)setHandle events in modal mode
cancel(self, context)NoneCalled on cancellation

Return sets

{"FINISHED"}, {"CANCELLED"}, {"RUNNING_MODAL"}, {"PASS_THROUGH"}

Or dict form: {"status": "FINISHED", "key": value, ...}


Event

from lfs_plugins.types import Event
AttributeTypeDescription
typestr'MOUSEMOVE', 'LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE', 'KEY_A'-'KEY_Z', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'ESC', 'RET', 'SPACE'
valuestr'PRESS', 'RELEASE', 'NOTHING'
mouse_xfloatMouse X (viewport coords)
mouse_yfloatMouse Y (viewport coords)
mouse_region_xfloatMouse X relative to region
mouse_region_yfloatMouse Y relative to region
delta_xfloatMouse delta X
delta_yfloatMouse delta Y
scroll_xfloatScroll X offset
scroll_yfloatScroll Y offset
shiftboolShift modifier
ctrlboolCtrl modifier
altboolAlt modifier
pressurefloatTablet pressure (1.0 for mouse)
over_guiboolMouse is over GUI element
key_codeintKey code (see key_codes.hpp)

Properties

from lfs_plugins.props import (
    Property, FloatProperty, IntProperty, BoolProperty,
    StringProperty, EnumProperty, FloatVectorProperty,
    IntVectorProperty, TensorProperty, CollectionProperty,
    PointerProperty, PropertyGroup, PropSubtype,
)

FloatProperty

FloatProperty(
    default: float = 0.0,
    min: float = -inf,
    max: float = inf,
    step: float = 0.1,
    precision: int = 3,
    subtype: str = "",       # FACTOR, PERCENTAGE, ANGLE, TIME, DISTANCE, POWER
    name: str = "",
    description: str = "",
    update: Callable = None,
)

IntProperty

IntProperty(
    default: int = 0,
    min: int = -2**31,
    max: int = 2**31 - 1,
    step: int = 1,
    name: str = "",
    description: str = "",
    update: Callable = None,
)

BoolProperty

BoolProperty(
    default: bool = False,
    name: str = "",
    description: str = "",
    update: Callable = None,
)

StringProperty

StringProperty(
    default: str = "",
    maxlen: int = 0,         # 0 = unlimited
    subtype: str = "",       # FILE_PATH, DIR_PATH, FILE_NAME
    name: str = "",
    description: str = "",
    update: Callable = None,
)

EnumProperty

EnumProperty(
    items: list[tuple[str, str, str]] = [],  # (identifier, label, description)
    default: str = None,     # First item if None
    name: str = "",
    description: str = "",
    update: Callable = None,
)

FloatVectorProperty

FloatVectorProperty(
    default: tuple = (0.0, 0.0, 0.0),
    size: int = 3,
    min: float = -inf,
    max: float = inf,
    subtype: str = "",       # COLOR, COLOR_GAMMA, TRANSLATION, DIRECTION,
                             # VELOCITY, ACCELERATION, XYZ, EULER, QUATERNION
    name: str = "",
    description: str = "",
    update: Callable = None,
)

IntVectorProperty

IntVectorProperty(
    default: tuple = (0, 0, 0),
    size: int = 3,
    min: int = -2**31,
    max: int = 2**31 - 1,
    name: str = "",
    description: str = "",
    update: Callable = None,
)

TensorProperty

TensorProperty(
    shape: tuple = (),       # Use -1 for variable dims, e.g. (-1, 3)
    dtype: str = "float32",
    device: str = "cuda",
    name: str = "",
    description: str = "",
    update: Callable = None,
)

CollectionProperty

CollectionProperty(
    type: Type[PropertyGroup],  # Item type
    name: str = "",
    description: str = "",
)
MethodReturnsDescription
add()PropertyGroupAdd new item
remove(index)NoneRemove by index
clear()NoneRemove all items
move(from, to)NoneReorder items
__len__()intItem count
__getitem__(index)PropertyGroupAccess by index
__iter__()IteratorIterate items

PointerProperty

PointerProperty(
    type: Type[PropertyGroup],  # Referenced type
    name: str = "",
    description: str = "",
)
MethodReturnsDescription
get_instance()PropertyGroupGet or create referenced object

PropertyGroup

from lfs_plugins.props import PropertyGroup
MethodReturnsDescription
get_instance()clsClassmethod. Singleton access
add_property(name, prop)NoneAdd property at runtime
remove_property(name)NoneRemove runtime property
get_all_properties()dict[str, Property]All properties (class + runtime)
get(prop_id)AnyGet property value by name
set(prop_id, value)NoneSet property value by name

PropSubtype constants

from lfs_plugins.props import PropSubtype

PropSubtype.NONE             # ""
PropSubtype.FILE_PATH        # "FILE_PATH"
PropSubtype.DIR_PATH         # "DIR_PATH"
PropSubtype.FILE_NAME        # "FILE_NAME"
PropSubtype.COLOR            # "COLOR"
PropSubtype.COLOR_GAMMA      # "COLOR_GAMMA"
PropSubtype.TRANSLATION      # "TRANSLATION"
PropSubtype.DIRECTION        # "DIRECTION"
PropSubtype.VELOCITY         # "VELOCITY"
PropSubtype.ACCELERATION     # "ACCELERATION"
PropSubtype.XYZ              # "XYZ"
PropSubtype.EULER            # "EULER"
PropSubtype.QUATERNION       # "QUATERNION"
PropSubtype.AXISANGLE        # "AXISANGLE"
PropSubtype.ANGLE            # "ANGLE"
PropSubtype.FACTOR           # "FACTOR"
PropSubtype.PERCENTAGE       # "PERCENTAGE"
PropSubtype.TIME             # "TIME"
PropSubtype.DISTANCE         # "DISTANCE"
PropSubtype.POWER            # "POWER"
PropSubtype.TEMPERATURE      # "TEMPERATURE"
PropSubtype.PIXEL            # "PIXEL"
PropSubtype.UNSIGNED         # "UNSIGNED"
PropSubtype.LAYER            # "LAYER"
PropSubtype.LAYER_MEMBER     # "LAYER_MEMBER"

ToolDef / ToolRegistry

ToolDef

from lfs_plugins.tool_defs.definition import ToolDef, SubmodeDef, PivotModeDef
@dataclass(frozen=True)
class ToolDef:
    id: str                                      # Unique tool ID
    label: str                                   # Display label
    icon: str                                    # Icon name
    group: str = "default"                       # "select", "transform", "utility"
    order: int = 100                             # Sort order within group
    description: str = ""                        # Tooltip
    shortcut: str = ""                           # Keyboard shortcut
    gizmo: str = ""                              # "translate", "rotate", "scale", ""
    operator: str = ""                           # Operator to invoke on activation
    submodes: tuple[SubmodeDef, ...] = ()
    pivot_modes: tuple[PivotModeDef, ...] = ()
    poll: Callable[[Any], bool] | None = None    # Availability check
    plugin_name: str = ""                        # For custom icon loading
    plugin_path: str = ""                        # For custom icon loading
MethodReturnsDescription
can_activate(context)boolCheck if tool can be activated
to_dict()dictConvert to dict for C++ interop

SubmodeDef

@dataclass(frozen=True)
class SubmodeDef:
    id: str           # Unique submode ID
    label: str        # Display label
    icon: str         # Icon name
    shortcut: str = ""

PivotModeDef

@dataclass(frozen=True)
class PivotModeDef:
    id: str           # Unique pivot mode ID
    label: str        # Display label
    icon: str         # Icon name

ToolRegistry

from lfs_plugins.tools import ToolRegistry
MethodReturnsDescription
register_tool(tool)NoneRegister a custom tool
unregister_tool(tool_id)NoneUnregister by ID
get(tool_id)Optional[ToolDef]Get tool by ID (builtins first)
get_all()list[ToolDef]All tools (builtins + custom, sorted)
set_active(tool_id)boolActivate a tool
get_active()Optional[ToolDef]Get active tool
get_active_id()strGet active tool ID

Native Transform Gizmos

APIReturnsDescription
lf.TransformGizmo(operation="translate", matrix=[], id="")TransformGizmoReusable native TRS gizmo
lf.TranslationGizmo(matrix=[], id="")TransformGizmoTranslate handle
lf.RotationGizmo(matrix=[], id="")TransformGizmoRotate handle
lf.ScaleGizmo(matrix=[], id="")TransformGizmoScale handle
lf.get_transform_gizmo_ids()list[str]Attached transform gizmo IDs
lf.has_transform_gizmos()boolWhether any native TRS gizmos are attached
lf.clear_transform_gizmos()NoneDetach all native TRS gizmos

TransformGizmo properties:

PropertyTypeDescription
idstrStable ID
operationstr"translate", "rotate", or "scale"
spacestr"local" or "world"
matrixlist[float]16 floats, column-major
translationlist[float]Translation component
visible, enabled, input_enabledboolRuntime draw/input controls
active, hovered, changedboolLast-frame interaction state
snapboolEnable snapping
translate_snap, rotate_snap_degrees, scale_snap_ratiofloatPer-operation snap settings
MethodDescription
attach()Draw without an automatic target
attach_to_callbacks(getter, setter)Bind to arbitrary Python transform callbacks
attach_to_node(node_name, visualizer_world=True)Bind to a scene node
detach()Remove from viewport drawing
set_on_begin(callback), set_on_change(callback), set_on_end(callback)Drag lifecycle callbacks

Signals

from lfs_plugins.ui.signals import Signal, ComputedSignal, ThrottledSignal, Batch, batch

Signal[T]

Signal(initial_value: T, name: str = "")
Property/MethodReturnsDescription
.valueTGet/set current value
.peek()TGet without tracking
.subscribe(callback)() -> NoneSubscribe; returns unsubscribe fn
.subscribe_as(owner, callback)() -> NoneOwner-tracked subscription

ComputedSignal[T]

ComputedSignal(compute: Callable[[], T], dependencies: list[Signal])
Property/MethodReturnsDescription
.valueTGet computed value (lazy)
.subscribe(callback)() -> NoneSubscribe to changes
.subscribe_as(owner, callback)() -> NoneOwner-tracked subscription

ThrottledSignal[T]

ThrottledSignal(initial_value: T, max_rate_hz: float = 60.0, name: str = "")
Property/MethodReturnsDescription
.valueTGet/set current value
.flush()NoneForce pending notification
.subscribe(callback)() -> NoneSubscribe to changes
.subscribe_as(owner, callback)() -> NoneOwner-tracked subscription

Batch / batch()

with Batch():       # Class form
    ...

with batch():       # Function form
    ...

Defers all signal notifications until the block exits.

SubscriptionRegistry

from lfs_plugins.ui.subscription_registry import SubscriptionRegistry

registry = SubscriptionRegistry.instance()
unsub = registry.register(owner="my_plugin", unsubscribe_fn=fn)
registry.unregister_all("my_plugin")   # Cleanup on unload

Capabilities

from lfs_plugins.capabilities import CapabilityRegistry, CapabilitySchema, Capability
from lfs_plugins.context import PluginContext, SceneContext, ViewContext, CapabilityBroker

CapabilityRegistry

registry = CapabilityRegistry.instance()
MethodReturnsDescription
register(name, handler, ...)NoneRegister a capability
unregister(name)boolUnregister by name
unregister_all_for_plugin(plugin_name)intUnregister all for plugin
invoke(name, args)dictInvoke capability
get(name)Optional[Capability]Get by name
list_all()list[Capability]List all capabilities
has(name)boolCheck existence

register() parameters

registry.register(
    name: str,                    # Unique name, e.g. "my_plugin.feature"
    handler: Callable,            # fn(args: dict, ctx: PluginContext) -> dict
    description: str = "",
    schema: CapabilitySchema = None,
    plugin_name: str = None,
    requires_gui: bool = True,
)

CapabilitySchema

@dataclass
class CapabilitySchema:
    properties: dict[str, dict[str, Any]]   # JSON Schema-like property defs
    required: list[str]                      # Required property names

Capability

@dataclass
class Capability:
    name: str
    description: str
    handler: Callable
    schema: CapabilitySchema
    plugin_name: Optional[str]
    requires_gui: bool

PluginContext

@dataclass
class PluginContext:
    scene: Optional[SceneContext]
    view: Optional[ViewContext]
    capabilities: CapabilityBroker
MethodReturnsDescription
build(registry, include_view=True)PluginContextClassmethod. Build from state

SceneContext

@dataclass
class SceneContext:
    scene: Any                          # PyScene object
MethodReturnsDescription
set_selection_mask(mask)NoneApply selection mask

ViewContext

@dataclass
class ViewContext:
    image: Any                          # [H, W, 3] tensor
    screen_positions: Optional[Any]     # [N, 2] tensor or None
    width: int
    height: int
    fov: float
    rotation: Any                       # [3, 3] tensor
    translation: Any                    # [3] tensor

CapabilityBroker

class CapabilityBroker:
    def invoke(self, name: str, args: dict = None) -> dict
    def has(self, name: str) -> bool
    def list_all(self) -> list[str]

PluginManager

from lfs_plugins.manager import PluginManager
mgr = PluginManager.instance()
MethodReturnsDescription
plugins_dirPathProperty. ~/.lichtfeld/plugins/
discover()list[PluginInfo]Scan for plugins
load(name, on_progress=None)boolLoad a plugin
unload(name)boolUnload a plugin
reload(name)boolHot-reload a plugin
load_all()dict[str, bool]Load all user-enabled plugins
install(url, on_progress=None, auto_load=True)strInstall from Git URL
uninstall(name)boolRemove a plugin
update(name, on_progress=None)boolUpdate a plugin
search(query, compatible_only=True)list[RegistryPluginInfo]Search registry
check_updates()dict[str, tuple]Check installed plugin updates
get_state(name)Optional[PluginState]Get plugin state
get_error(name)Optional[str]Get error message
get_traceback(name)Optional[str]Get error traceback

PluginInfo

@dataclass
class PluginInfo:
    name: str
    version: str
    path: Path
    description: str = ""
    author: str = ""
    entry_point: str = "__init__"
    dependencies: list[str] = []
    auto_start: bool = False
    hot_reload: bool = True
    plugin_api: str = ""
    lichtfeld_version: str = ""
    required_features: list[str] = []

PluginState

class PluginState(Enum):
    UNLOADED = "unloaded"
    INSTALLING = "installing"
    LOADING = "loading"
    ACTIVE = "active"
    ERROR = "error"
    DISABLED = "disabled"

lichtfeld.plugins convenience API

import lichtfeld as lf
FunctionReturnsDescription
lf.plugins.discover()list[PluginInfo]Discover plugins in ~/.lichtfeld/plugins/
lf.plugins.load(name)boolLoad a plugin
lf.plugins.unload(name)boolUnload a plugin
lf.plugins.reload(name)boolReload a plugin
lf.plugins.load_all()dict[str, bool]Load all user-enabled plugins
lf.plugins.start_watcher()NoneStart the hot-reload watcher
lf.plugins.stop_watcher()NoneStop the hot-reload watcher
lf.plugins.get_state(name)PluginState | NoneRead plugin state
lf.plugins.get_error(name)str | NoneRead the last plugin error
lf.plugins.get_traceback(name)str | NoneRead the full traceback
lf.plugins.create(name)strCreate the v1 source scaffold in ~/.lichtfeld/plugins/<name>

lf.plugins.create() writes the source package, including panels/main_panel.py, panels/main_panel.rml, and panels/main_panel.rcss. If you want a scaffold that also adds .venv, .vscode, and pyrightconfig.json, use the CLI command LichtFeld-Studio plugin create <name>.

Runtime compatibility constants:

ConstantTypeDescription
lf.PLUGIN_API_VERSIONstrHost plugin API version
lf.plugins.API_VERSIONstrSame plugin API version through the plugin namespace
lf.plugins.FEATURESlist[str]Supported optional plugin features on this host

Layout API

The ui object passed to Panel.draw() provides the immediate widget API used by both simple and hybrid panels. Depending on the panel space and shell, it may be rendered through the direct viewport path or the immediate-mode RML bridge, but the Python widget surface stays the same.

Text

MethodReturnsDescription
label(text)NonePlain text
label_centered(text)NoneCentered text
heading(text)NoneLarge heading
text_colored(text, color)NoneColored text (RGBA tuple)
text_colored_centered(text, color)NoneCentered colored text
text_selectable(text, height=0)NoneSelectable text
text_wrapped(text)NoneWord-wrapped text
text_disabled(text)NoneGrayed-out text
bullet_text(text)NoneBulleted text

Buttons

MethodReturnsDescription
button(label, size=(0,0))boolStandard button
button_styled(label, style, size=(0,0))boolStyled: "success", "error", "warning", "primary", "secondary"
button_callback(label, callback=None, size=(0,0))boolButton with callback
small_button(label)boolCompact button
invisible_button(id, size)boolInvisible clickable area

Input

MethodReturnsDescription
checkbox(label, value)(bool, bool)(changed, new_value)
radio_button(label, current, value)(bool, int)Radio button
input_text(label, value)(bool, str)Text input
input_text_with_hint(label, hint, value)(bool, str)Text with placeholder
input_text_enter(label, value)(bool, str)Confirm on Enter
input_float(label, value, step=0, step_fast=0, format='%.3f')(bool, float)Float input
input_int(label, value, step=1, step_fast=100)(bool, int)Integer input
input_int_formatted(label, value, step=0, step_fast=0)(bool, int)Formatted int input

Sliders & Drags

MethodReturnsDescription
slider_float(label, value, min, max)(bool, float)Float slider
slider_int(label, value, min, max)(bool, int)Integer slider
slider_float2(label, value, min, max)(bool, tuple)2-component slider
slider_float3(label, value, min, max)(bool, tuple)3-component slider
drag_float(label, value, speed=1, min=0, max=0)(bool, float)Float drag
drag_int(label, value, speed=1, min=0, max=0)(bool, int)Integer drag

Selection

MethodReturnsDescription
combo(label, current_idx, items)(bool, int)Dropdown selector
listbox(label, current_idx, items, height_items=-1)(bool, int)List selector
selectable(label, selected=False, height=0)boolSelectable item
prop_search(data, prop_id, search_data, search_prop, text='')(bool, int)Searchable dropdown

Color

MethodReturnsDescription
color_edit3(label, color)(bool, tuple)RGB color picker
color_edit4(label, color)(bool, tuple)RGBA color picker
color_button(label, color, size=(0,0))boolColor swatch button

File/Path

MethodReturnsDescription
path_input(label, value, folder_mode=True, dialog_title='')(bool, str)File/folder picker. dialog_title is accepted for compatibility and currently ignored.

Property Binding

MethodReturnsDescription
prop(data, prop_id, text=None)(bool, Any)Auto-widget based on property type

Layout Structure

MethodReturnsDescription
separator()NoneHorizontal line
spacing()NoneVertical space
same_line(offset=0, spacing=-1)NoneNext widget on same line
new_line()NoneForce new line
indent(width=0)NoneIncrease indent
unindent(width=0)NoneDecrease indent
begin_group() / end_group()NoneLogical widget group
set_next_item_width(width)NoneWidth for next widget
dummy(size)NoneEmpty space placeholder

Collapsible / Tree

MethodReturnsDescription
collapsing_header(label, default_open=False)boolCollapsible section
tree_node(label)boolTree node (call tree_pop())
tree_node_ex(label, flags='')boolExtended tree node
tree_pop()NoneClose tree node

Tables

MethodReturnsDescription
begin_table(id, columns)boolStart table
table_setup_column(label, width=0)NoneDefine column
table_headers_row()NoneDraw header row
table_next_row()NoneNext row
table_next_column()NoneNext column
table_set_column_index(column)boolJump to column
table_set_bg_color(target, color)NoneSet row/cell background
end_table()NoneEnd table

Images

MethodReturnsDescription
image(texture_id, size, tint=(1,1,1,1))NoneDisplay image
image_uv(texture_id, size, uv0, uv1, tint=(1,1,1,1))NoneImage with UV coords
image_button(id, texture_id, size, tint=(1,1,1,1))boolClickable image
toolbar_button(id, tex, size, selected=F, disabled=F, tooltip='')boolToolbar icon button
image_tensor(label, tensor, size, tint=None)NoneDisplay a tensor as an image (cached by label)
image_texture(texture, size, tint=None)NoneDisplay a DynamicTexture

image_tensor is the simplest way to display a GPU tensor โ€” it internally manages a DynamicTexture cached by label. The tensor must be [H, W, 3] or [H, W, 4] (RGB/RGBA). CPU tensors and non-float32 dtypes are converted automatically.

ui.image_tensor("preview", my_tensor, (256, 256))

For full control (e.g. reusing one texture across multiple draw calls), use DynamicTexture directly:

tex = lf.ui.DynamicTexture(tensor)   # or DynamicTexture() + tex.update(tensor)
ui.image_texture(tex, (256, 256))

DynamicTexture

GPU tensor to UI texture bridge. In the Vulkan viewer this uses the backend's opaque ImGui texture handle.

tex = lf.ui.DynamicTexture()          # Empty
tex = lf.ui.DynamicTexture(tensor)    # From tensor
Method / PropertyReturnsDescription
update(tensor)NoneUpload [H, W, 3|4] tensor (auto-converts CPUโ†’CUDA, uint8โ†’float32)
destroy()NoneRelease UI texture resources
idintOpaque ImGui backend texture handle
widthintCurrent width in pixels
heightintCurrent height in pixels
validboolTrue if texture is initialized
uv1tuple[float, float]UV scale factors for power-of-2 padding

Calling update() with a different resolution automatically recreates the backend texture. Textures are freed on plugin unload via lf.ui.free_plugin_textures(name).

Drag & Drop

MethodReturnsDescription
begin_drag_drop_source()boolStart drag source
set_drag_drop_payload(type, data)NoneSet drag payload
end_drag_drop_source()NoneEnd drag source
begin_drag_drop_target()boolStart drag target
accept_drag_drop_payload(type)str or NoneAccept payload
end_drag_drop_target()NoneEnd drag target

Popups & Menus

MethodReturnsDescription
begin_popup(id)boolStart popup
begin_context_menu(id='')boolStyled context menu
begin_popup_modal(title)boolModal popup
open_popup(id)NoneTrigger popup open
end_popup() / end_popup_modal()NoneEnd popup/modal
end_context_menu()NoneEnd context menu
close_current_popup()NoneClose current popup
begin_menu(label)boolStart menu
end_menu()NoneEnd menu
begin_menu_bar() / end_menu_bar()boolMenu bar
menu_item(label, enabled=True)boolMenu item
menu_item_toggle(label, shortcut, selected)boolToggle menu item
menu_item_shortcut(label, shortcut, enabled=True)boolMenu item with shortcut
menu(menu_id, text='', icon='')NoneInline menu reference
popover(panel_id, text='', icon='')NonePanel popover

Windows & Children

MethodReturnsDescription
begin_window(title, flags=0)boolStart window
begin_window_closable(title, flags=0)(bool, bool)Closable window
end_window()NoneEnd window
begin_child(id, size=(0,0), border=False)boolStart child region
end_child()NoneEnd child region
set_next_window_pos(pos, first_use=False)NoneSet window position
set_next_window_size(size, first_use=False)NoneSet window size
set_next_window_pos_center()NoneCenter window
set_next_window_pos_centered(first_use=False)NoneCenter next window (main viewport)
set_next_window_pos_viewport_center()NoneViewport center
set_next_window_focus()NoneFocus next window
set_next_window_bg_alpha(alpha)NoneSet next window BG alpha
push_window_style() / pop_window_style()NoneWindow style stack
push_modal_style() / pop_modal_style()NoneModal style stack

Drawing (Viewport)

MethodReturnsDescription
draw_line(x0, y0, x1, y1, color, thickness=1)NoneLine
draw_rect(x0, y0, x1, y1, color, thickness=1)NoneRectangle outline
draw_rect_filled(x0, y0, x1, y1, color, bg=False)NoneFilled rectangle
draw_rect_rounded(x0, y0, x1, y1, color, r, thick=1, bg=F)NoneRounded rect outline
draw_rect_rounded_filled(x0, y0, x1, y1, color, r, bg=F)NoneFilled rounded rect
draw_circle(x, y, radius, color, segments=32, thickness=1)NoneCircle outline
draw_circle_filled(x, y, radius, color, segments=32)NoneFilled circle
draw_triangle_filled(x0, y0, x1, y1, x2, y2, color, bg=F)NoneFilled triangle
draw_text(x, y, text, color, bg=False)NoneText at position
draw_polyline(points, color, closed=False, thickness=1)NonePolyline
draw_poly_filled(points, color)NoneFilled polygon
plot_lines(label, values, scale_min, scale_max, size)NoneLine plot

Drawing (Window)

MethodReturnsDescription
draw_window_rect_filled(x0, y0, x1, y1, color)NoneFilled rect to window
draw_window_rect(x0, y0, x1, y1, color, thickness=1)NoneRect outline to window
draw_window_line(x0, y0, x1, y1, color, thickness=1)NoneLine to window
draw_window_text(x, y, text, color)NoneText to window
draw_window_triangle_filled(x0, y0, x1, y1, x2, y2, color)NoneTriangle to window

Progress & Status

MethodReturnsDescription
progress_bar(fraction, overlay='', width=0)NoneProgress bar
set_tooltip(text)NoneTooltip for last item

State Queries

MethodReturnsDescription
is_item_hovered()boolLast item hovered
is_item_clicked(button=0)boolLast item clicked
is_item_active()boolLast item active
is_window_focused()boolWindow has focus
is_window_hovered()boolWindow is hovered
is_mouse_double_clicked(button=0)boolDouble click detected
is_mouse_dragging(button=0)boolMouse dragging
get_mouse_wheel()floatScroll wheel delta
get_mouse_delta()tupleMouse delta (dx, dy)

Position / Size

MethodReturnsDescription
get_cursor_pos()tupleCursor position
get_cursor_screen_pos()tupleCursor screen position
get_window_pos()tupleWindow position
get_window_width()floatWindow width
get_text_line_height()floatText line height
get_content_region_avail()tupleAvailable content area
get_viewport_pos()tupleViewport position
get_viewport_size()tupleViewport size
get_dpi_scale()floatDPI scale factor
calc_text_size(text)tupleText dimensions

Styling

MethodReturnsDescription
push_style_var(var, value)NonePush float style var
push_style_var_vec2(var, value)NonePush vec2 style var
pop_style_var(count=1)NonePop style vars
push_style_color(col, color)NonePush color override
pop_style_color(count=1)NonePop color overrides
push_item_width(width) / pop_item_width()NoneItem width stack
begin_disabled(disabled=True) / end_disabled()NoneDisable widget region. For composable disabled regions, prefer SubLayout.enabled (see Layout Composition below).

Keyboard / Mouse Capture

MethodReturnsDescription
set_keyboard_focus_here()NoneFocus next widget
capture_keyboard_from_app(capture=True)NoneCapture keyboard input
capture_mouse_from_app(capture=True)NoneCapture mouse input
set_mouse_cursor_hand()NoneSet hand cursor

Cursor Control

MethodReturnsDescription
set_cursor_pos(pos)NoneSet cursor position
set_cursor_pos_x(x)NoneSet cursor X
set_scroll_here_y(ratio=0.5)NoneScroll to current Y

Specialized Widgets

MethodReturnsDescription
crf_curve_preview(label, gamma, toe, shoulder, gamma_r=0, gamma_g=0, gamma_b=0)NoneTone curve preview
chromaticity_diagram(label, rx, ry, gx, gy, bx, by, nx, ny, range=0.5)(bool, list)Chromaticity diagram
template_list(list_type_id, list_id, data, prop_id, active_data, active_prop, rows=5)(int, int)Custom list template

Layout Composition

Create composable sub-layouts with automatic widget positioning and state cascading.

MethodReturnsDescription
row()SubLayoutHorizontal layout
column()SubLayoutVertical layout
split(factor=0.5)SubLayoutTwo-column split
box()SubLayoutBordered container
grid_flow(columns=0, even_columns=True, even_rows=True)SubLayoutResponsive grid
prop_enum(data, prop_id, value, text='')boolEnum toggle button

SubLayout is a context manager. Use with ui.row() as row: to enter the layout, then call widget methods on row instead of ui. Sub-layouts nest arbitrarily.

SubLayout state properties

PropertyTypeDescription
enabledboolDisabled state (cascades to children)
activeboolActive state (cascades to children)
alertboolOne-shot alert styling (red text/bg)

Example

def draw(self, ui):
    with ui.row() as row:
        row.prop_enum(self, "mode", "fast", "Fast")
        row.prop_enum(self, "mode", "quality", "Quality")

    with ui.box() as box:
        box.heading("Settings")
        box.prop(self, "opacity")

    with ui.column() as col:
        col.enabled = self.is_active
        col.prop(self, "value")
        with col.row() as row:
            row.button("Apply")
            row.button("Cancel")

    with ui.grid_flow(columns=3) as grid:
        for item in items:
            with grid.box() as cell:
                cell.label(item.name)
                cell.button("Select")

Scene API (lf module)

import lichtfeld as lf

Scene Management

FunctionReturnsDescription
get_scene()Scene or NoneGet scene object
get_render_scene()Scene or NoneGet render scene (PyScene)
has_scene()boolWhether scene is loaded
clear_scene()NoneClear all scene content
load_file(path, is_dataset=False)NoneLoad PLY or dataset
load_config_file(path)NoneLoad JSON config
get_scene_generation()intScene generation counter
list_scene()NonePrint scene tree

Node Operations (on Scene object)

MethodReturnsDescription
add_group(name, parent=-1)intAdd group node
add_splat(name, means, sh0, shN, scaling, rotation, opacity, ...)intAdd splat node
add_point_cloud(name, points, colors, parent=-1)intAdd point cloud
add_camera(name, parent, R, T, fx, fy, w, h, ...)intAdd camera node
remove_node(name, keep_children=False)NoneRemove node
rename_node(old, new)boolRename node
reparent(node_id, new_parent_id)NoneChange parent
duplicate_node(name)strDuplicate, returns new node name
merge_group(group_name)strMerge group children, returns merged node name
get_node(name)SceneNodeGet node by name
get_node_by_id(id)SceneNodeGet node by ID
get_nodes()list[SceneNode]All nodes
get_visible_nodes()list[SceneNode]Visible nodes only
root_nodes()list[int]Root node IDs
is_node_effectively_visible(id)boolConsiders parent visibility
total_gaussian_countintProperty. Total gaussians
invalidate_cache()NoneClear internal cache (no redraw)
notify_changed()NoneInvalidate cache + trigger viewport redraw

SceneNode Properties

Property/MethodReturnsDescription
idintNode ID
namestrNode name
typeNodeTypeNode type enum (SPLAT, POINTCLOUD, GROUP, etc.)
parent_idintParent node ID (-1 for root)
childrenlist[int]Child node IDs
visibleboolVisibility flag
lockedboolLock flag
gaussian_countintNumber of gaussians (splat nodes)
centroidtuple[float, float, float]Node centroid
world_transformtupleWorld-space 4x4 transform, row-major
splat_data()SplatData or NoneSplat data for this node (None if not a splat)
point_cloud()PointCloud or NonePoint cloud data (None if not a point cloud)
cropbox()CropBox or NoneCrop box data
ellipsoid()Ellipsoid or NoneEllipsoid data

Selection

FunctionReturnsDescription
select_node(name)NoneSelect node by name
deselect_all()NoneClear selection
has_selection()boolAny selection active
get_selected_node_name()strFirst selected node name
get_selected_node_names()list[str]All selected node names
can_transform_selection()boolSelection is transformable
get_selected_node_transform()list[float]16 column-major floats
set_selected_node_transform(matrix)NoneSet transform from 16 column-major floats
get_selection_center()list[float]Local space center
get_selection_visualizer_world_center()list[float]Visualizer world-space center
get_selection_world_center()list[float]Deprecated legacy data-world center; use get_selection_visualizer_world_center()
capture_selection_transforms()dictSnapshot for undo

Scene Shortcuts

Module-level shortcuts for common scene operations (equivalent to Scene object methods):

FunctionReturnsDescription
set_node_visibility(name, visible)NoneToggle node visibility
remove_node(name, keep_children=False)NoneRemove node
reparent_node(name, new_parent)NoneReparent node
rename_node(old_name, new_name)NoneRename node
add_group(name, parent="")NoneAdd group node
get_num_gaussians()intTotal gaussian count

Gaussian-Level Selection (on Scene object)

MethodReturnsDescription
set_selection_mask(mask)NoneApply bool tensor mask
preview_selection_mask(mask)NonePreview transient selection without an undo step
commit_selection_preview()NoneCommit the transient preview as one undo step
cancel_selection_preview()NoneRestore the selection before preview
clear_selection()NoneClear gaussian selection
has_selection()boolAny gaussians selected
selection_maskTensorProperty. Current mask
set_selection(indices)NoneSelect by index list
add_selection_group(name, color)intCreate a named group
selection_groups()list[SelectionGroup]Current groups
active_selection_groupintProperty. Active group id
set_selection_group_locked(id, locked)NoneLock/unlock group edits

Selection Primitives (lf.selection)

lichtfeld.selection is the lower-level selection-tool API. It operates on selection strokes, previews, and viewport-space hit data.

FunctionReturnsDescription
begin_stroke() / commit_stroke(mode) / cancel_stroke()None / bool / NoneManage one undoable selection stroke
get_stroke_selection()Tensor | NoneCurrent stroke mask [N] uint8
set_preview(add_mode=True) / clear_preview()NoneShow/clear add/remove preview overlay
draw_brush_circle(x, y, radius, add_mode=True)NoneBrush cursor overlay
draw_rect_preview(x0, y0, x1, y1, add_mode=True)NoneRectangle preview overlay
draw_polygon_preview(points, closed=False, add_mode=True)NonePolygon preview overlay
draw_lasso_preview(points, add_mode=True)NoneLasso preview overlay
has_screen_positions() / get_screen_positions()bool / Tensor | NoneViewport-projected positions [N, 2]
set_depth_filter_range(enabled, depth_near=0, depth_far=100, frustum_half_width=50)NoneCamera-space selection depth filter
get_depth_filter_range()tuple[bool, float, float, float](enabled, near, far, half_width)
set_crop_filter(enabled) / apply_crop_filter()NoneCrop-box constrained selection
screen_to_render(x, y)tuple[float, float]Convert screen to render coordinates
pick_at_screen(x, y)PickResult | NoneDepth/world hit at screen point
ring_select(index, add=True)NoneSelect/deselect one gaussian
grow(radius, iterations=1) / shrink(radius, iterations=1)NoneSpatial grow/shrink selection
by_opacity(min_opacity=0, max_opacity=1)NoneSelect by activated opacity range
by_scale(max_scale)NoneSelect by activated scale threshold
by_color(gaussian_index, threshold=0.2)NoneSelect by SH DC color similarity

PickResult.index is the gaussian under the current cursor, not necessarily the queried screen coordinate. Use depth and world_position for the queried coordinate data.

Transforms

FunctionReturnsDescription
get_node_transform(name)list[float]16 column-major floats
set_node_transform(name, matrix)NoneSet 4x4 transform from 16 column-major floats
decompose_transform(matrix)dictDecompose 16 column-major floats; see keys below
compose_transform(translation, euler_deg, scale)list[float]Build 16 column-major floats from components (Euler in degrees)

decompose_transform returns a dict with these keys:

KeyTypeDescription
translation[x, y, z]Position
rotation_quat[x, y, z, w]Quaternion
rotation_euler[rx, ry, rz]Euler angles (radians)
rotation_euler_deg[rx, ry, rz]Euler angles (degrees)
scale[sx, sy, sz]Scale

Splat Data (combined_model() / node.splat_data())

Accessible via scene.combined_model() (all nodes merged) or node.splat_data() (per-node).

Property/MethodReturnsDescription
means_rawTensor[N, 3] positions (view)
sh0_rawTensor[N, 1, 3] base SH (view)
shN_rawTensor[N, K, 3] higher SH (view)
scaling_rawTensor[N, 3] log-space (view)
rotation_rawTensor[N, 4] quaternions (view)
opacity_rawTensor[N, 1] logit-space (view)
get_means()TensorPositions
get_opacity()Tensor[N] sigmoid applied
get_scaling()TensorExp applied
get_rotation()TensorNormalized quaternions
get_shs()TensorSH0 + SHN concatenated
num_pointsintGaussian count
active_sh_degreeintCurrent SH degree
max_sh_degreeintMaximum SH degree
scene_scalefloatScene scale factor
soft_delete(mask)TensorMark for deletion, returns newly deleted mask
undelete(mask)NoneRestore deleted gaussians
apply_deleted()intPermanently remove, returns count
clear_deleted()NoneClear deletion mask
deletedTensorProperty. [N] bool deletion mask
has_deleted_mask()boolWhether deletion mask exists
visible_count()intNumber of non-deleted gaussians

After calling soft_delete(), undelete(), or clear_deleted(), call scene.notify_changed() to update the viewport.

Point Clouds, Cameras, and Dataset Nodes

APIReturnsDescription
Scene.add_point_cloud(name, points, colors, parent=-1)intAdd [N,3] position/color tensors
SceneNode.point_cloud()PointCloud | NonePoint-cloud payload for point-cloud nodes
PointCloud.means / PointCloud.colorsTensorPosition and color tensors
PointCloud.normals, sh0, shN, opacity, scaling, rotationTensor | NoneOptional gaussian-like attributes
PointCloud.normalize_colors()NoneNormalize colors to [0,1]
PointCloud.filter(mask) / filter_indices(indices)intKeep matching points, return removed count
PointCloud.set_means(points) / set_colors(colors)NoneUpdate positions/colors without replacing both
Scene.add_camera_group(name, parent, camera_count)intAdd camera group
Scene.add_camera(name, parent, R, T, focal_x, focal_y, width, height, image_path='', uid=-1, mask=None)intAdd camera node
SceneNode.load_mask(...) / load_depth(...)Tensor | NoneLoad camera mask/depth from node metadata
Scene.get_active_cameras()list[SceneNode]Cameras enabled for training
CameraDataset.cameras()list[Camera]Dataset cameras as Python objects
Camera.load_image(resize_factor=1, max_width=0, output_uint8=False)Tensor[C,H,W] CUDA image
Camera.rotation / Camera.translationTensorVisualizer camera pose, directly usable with render_view()

Deprecated raw dataset-camera properties are still available on Camera: R, T, world_view_transform, and cam_position. Prefer rotation, translation, K, and view_matrix for new code.

Training Control

FunctionReturnsDescription
start_training()NoneStart training
pause_training()NonePause
resume_training()NoneResume
stop_training()NoneStop
reset_training()NoneReset to iteration 0
save_checkpoint()NoneSave checkpoint
switch_to_edit_mode()NoneEnter edit mode
has_trainer()boolTrainer loaded
trainer_state()strState string
finish_reason()str or NoneWhy training ended
trainer_error()str or NoneError message
context()ContextTraining context snapshot
optimization_params()OptimizationParamsTraining parameters
dataset_params()DatasetParamsDataset parameters
loss_buffer()list[float]Loss history
load_checkpoint_for_training(checkpoint_path, dataset_path, output_path)NoneLoad checkpoint for training

Training Status

FunctionReturnsDescription
trainer_elapsed_seconds()floatElapsed training time
trainer_eta_seconds()floatEstimated remaining time (-1 if unavailable)
trainer_strategy_type()strStrategy type (mcmc, default, etc.)
trainer_is_gut_enabled()boolGUT enabled
trainer_max_gaussians()intMax gaussians
trainer_num_splats()intCurrent splat count
trainer_current_iteration()intCurrent iteration
trainer_total_iterations()intTotal iterations
trainer_current_loss()floatCurrent loss

Training Hooks

DecoratorDescription
@lf.on_training_startCalled when training starts
@lf.on_iteration_startCalled at start of each iteration
@lf.on_pre_optimizer_stepCalled before optimizer step
@lf.on_post_stepCalled after each step
@lf.on_training_endCalled when training ends

Callbacks registered with lf.on_* receive one positional hook payload. If you do not need it, declare the parameter as _hook.

Rendering

FunctionReturnsDescription
get_current_view()ViewInfoCurrent camera view
get_viewport_render()ViewportRenderCurrent viewport image
capture_viewport()ViewportRenderCapture for async use
export_viewport_image(path, format='', width=0, height=0, transparent=False, jpeg_quality=95)dictExport active viewport to PNG/JPEG
look_at(eye, target, up=(0,1,0))(Tensor, Tensor)Compute (rotation, translation) for rendering
render_view(rotation, translation, width, height, fov=60, bg_color=None, with_depth=False, depth_mode='median')Tensor | tuple | NoneRender active scene from camera
render_view_u8(rotation, translation, width, height, fov=60, bg_color=None, orthographic=None, ortho_scale=None)Tensor | NoneRender active scene as CPU uint8 RGB
render_at(eye, target, width, height, fov=60, up=(0,1,0), bg_color=None)Tensor | NoneConvenience look-at render
render_asset_preview(path, width=512, height=224, focal_length_mm=35)Tensor | NoneOffscreen asset thumbnail without mutating live scene
render_asset_preview_from_camera(path, eye, target, ...)Tensor | NoneOffscreen thumbnail from custom camera
compute_screen_positions(rotation, translation, width, height, fov=60)Tensor[N, 2] screen positions
get_render_settings()RenderSettingsCurrent render settings
get_render_mode() / set_render_mode(mode)RenderModeRender mode

Viewport Control

FunctionReturnsDescription
reset_camera()NoneReset camera
toggle_fullscreen()NoneToggle fullscreen
is_fullscreen()boolFullscreen state
toggle_ui()NoneToggle UI visibility
set_orthographic(ortho)NoneSet projection mode
is_orthographic()boolOrthographic state

Export

lf.export_scene(
    format: int,             # 0=PLY, 1=SOG, 2=SPZ, 3=HTML, 4=USD,
                             # 5=USDZ NuRec, 6=RAD, 7=COLMAP
    path: str,
    node_names: list[str],
    sh_degree: int,
    rad_flip_y: bool = False,
    rad_streamable: bool = True,
)
lf.save_config_file(path: str)

File I/O (lf.io)

Use lf.io when you need data objects directly instead of loading into the live application scene.

FunctionReturnsDescription
lf.io.load(path, format=None, resize_factor=None, max_width=None, images_folder=None, progress=None, min_track_length=None)LoadResultLoad splat file, point cloud, mesh-derived data, or dataset
lf.io.load_point_cloud(path)(Tensor, Tensor)Load PLY point cloud as positions/colors
lf.io.save_ply(data, path, binary=True, progress=None, extra_attributes=None)NoneSave SplatData as PLY
lf.io.save_point_cloud_ply(point_cloud, path, extra_attributes=None)NoneSave PointCloud as PLY
lf.io.save_sog(data, path, kmeans_iterations=10, use_gpu=True, progress=None)NoneSave SOG-compressed splats
lf.io.save_spz(data, path)NoneSave SPZ splats
lf.io.save_usd(data, path)NoneSave OpenUSD gaussian file
lf.io.save_nurec_usdz(data, path)NoneSave NuRec-compatible USDZ
lf.io.export_html(data, path, kmeans_iterations=10, progress=None)NoneSelf-contained HTML viewer
lf.io.save_image(path, image)NoneSave PNG/JPG/TIFF/EXR from [H,W,C] or [C,H,W] tensor
lf.io.is_dataset_path(path)boolDataset directory detection
lf.io.is_gaussian_splat_ply(path)boolPLY schema check for gaussian splat attributes
lf.io.get_supported_formats() / get_supported_extensions()list[str]Loader/exporter support

LoadResult exposes splat_data, point_cloud, cameras, scene_center, loader_used, load_time_ms, warnings, and is_dataset. PLY extra attributes must avoid reserved gaussian names and must match the visible/raw point count expected by the exporter.

Pipeline Operations (lf.pipeline)

Pipelines compose built-in operations and execute them as one chain.

pipe = (
    lf.pipeline.Pipeline("grow-and-move")
    | lf.pipeline.select.grow(radius=0.05)
    | lf.pipeline.transform.translate(offset=(0.0, 0.1, 0.0))
)
if pipe.poll():
    result = pipe.execute()
APIDescription
lf.pipeline.Pipeline(name='')Create an empty chain
Pipeline.add(stage) / Pipeline | stageAppend a stage
Pipeline.poll() / execute()Check and execute all stages
Stage.execute()Execute a single stage immediately
lf.pipeline.select.all/none/invert/grow/shrink(**kwargs)Selection stages
lf.pipeline.transform.translate/rotate/scale/set(**kwargs)Transform stages
lf.pipeline.edit.duplicate(**kwargs)Duplicate stage

Logging

FunctionDescription
lf.log.info(msg)Info level
lf.log.warn(msg)Warning level
lf.log.error(msg)Error level
lf.log.debug(msg)Debug level

Undo

lf.undo.push(name: str, undo: Callable, redo: Callable, validate: Callable | None = None)
lf.undo.transaction(name: str = "Grouped Changes") -> Transaction
lf.undo.stack() -> dict
  • lf.undo.transaction(...) groups multiple undoable mutations into one history step.
  • lf.undo.stack() returns structured undo/redo items with id, label, source, scope, and estimated_bytes.

UI Functions

FunctionReturnsDescription
lf.ui.tr(key)strTranslate string
lf.ui.theme()ThemeCurrent theme
lf.ui.context()AppContextApp context
lf.ui.request_redraw()NoneRequest UI redraw
lf.ui.set_language(lang_code)NoneSet UI language
lf.ui.get_current_language()strActive language code
lf.ui.get_languages()list[tuple[str, str]]Available languages
lf.ui.set_theme(name)NoneTheme switch by stable theme id
lf.ui.get_theme()strActive stable theme id
lf.ui.themes()list[dict]Available theme presets
lf.ui.set_panel_enabled(panel_id, enabled)NoneToggle panel by id
lf.ui.is_panel_enabled(panel_id)boolPanel enabled state
lf.ui.get_panel_names(space=lf.ui.PanelSpace.FLOATING)list[str]Panel ids for a space
lf.ui.get_panel(panel_id)lf.ui.PanelInfo | NoneTyped panel info
lf.ui.get_main_panel_tabs()list[lf.ui.PanelSummary]Typed summaries for main-panel tabs
lf.ui.set_panel_label(panel_id, label)boolChange panel display name
lf.ui.set_panel_order(panel_id, order)boolChange panel sort order
lf.ui.set_panel_space(panel_id, space)boolMove panel to a different space (lf.ui.PanelSpace)
lf.ui.set_panel_parent(panel_id, parent)boolEmbed panel inside a tab as collapsible section
lf.ui.ops.invoke(op_id, **kwargs)OperatorReturnValueInvoke operator
lf.ui.ops.poll(op_id)boolOperator poll
lf.ui.ops.cancel_modal()NoneCancel modal operator
lf.ui.get_active_tool()strActive tool ID
lf.ui.get_active_submode()strActive submode
lf.ui.set_selection_mode(mode)NoneSet selection submode
lf.ui.get_transform_space()intTransform space enum index
lf.ui.set_transform_space(space)NoneSet transform space index
lf.ui.get_pivot_mode() / set_pivot_mode(mode)intPivot mode enum index
lf.ui.get_fps()floatCurrent FPS
lf.ui.get_git_commit()strGit commit hash

For a coarse CUDA memory number in Python plugin code, use lfs_plugins.get_gpu_memory() from the helper package. There is no lf.ui.get_gpu_memory() binding in the current stubs.

File Dialogs

FunctionReturns
lf.ui.open_image_dialog(start_dir='')str
lf.ui.open_folder_dialog(title='Select Folder', start_dir='')str
lf.ui.open_dataset_folder_dialog()str
lf.ui.open_ply_file_dialog(start_dir='')str
lf.ui.open_mesh_file_dialog(start_dir='')str
lf.ui.open_checkpoint_file_dialog()str
lf.ui.open_ppisp_file_dialog(start_dir='')str
lf.ui.open_json_file_dialog()str
lf.ui.open_csv_file_dialog()str
lf.ui.open_xml_file_dialog()str
lf.ui.open_las_file_dialog()str
lf.ui.open_video_file_dialog()str
lf.ui.select_colmap_sparse_folder_dialog(default_path='')str
lf.ui.open_environment_map_dialog(start_dir='')str
lf.ui.save_las_file_dialog(default_name='export')str
lf.ui.save_laz_file_dialog(default_name='export')str
lf.ui.save_json_file_dialog(default_name='config.json')str
lf.ui.save_png_file_dialog(default_name='export.png')str
lf.ui.save_jpg_file_dialog(default_name='export.jpg')str
lf.ui.save_ply_file_dialog(default_name='export')str
lf.ui.save_sog_file_dialog(default_name='export')str
lf.ui.save_spz_file_dialog(default_name='export')str
lf.ui.save_usd_file_dialog(default_name='export')str
lf.ui.save_usdz_file_dialog(default_name='export')str
lf.ui.save_html_file_dialog(default_name='viewer')str
lf.ui.save_rad_file_dialog(default_name='export')str

lf.ui.open_folder_dialog() accepts title for compatibility with older scripts. The current native dialog backend ignores it.

UI Hooks

Inject UI into existing panels at predefined hook points. Callbacks receive a layout object.

FunctionDescription
lf.ui.add_hook(panel, section, callback, position="append")Register a hook. position: "prepend" or "append"
lf.ui.remove_hook(panel, section, callback)Remove a specific hook callback
lf.ui.clear_hooks(panel, section="")Clear hooks for panel/section (or all sections if empty)
lf.ui.clear_all_hooks()Clear all registered hooks
lf.ui.get_hook_points()List all registered hook point keys
lf.ui.invoke_hooks(panel, section, prepend=False)Invoke hooks (prepend=True for prepend, False for append)
@lf.ui.hook(panel, section, position="append")Decorator form of add_hook

Hook points are runtime-defined. Query them with lf.ui.get_hook_points() instead of hard-coding.

Tensor API

import lichtfeld as lf
t = lf.Tensor

The tables below list the most-used tensor APIs. For the full bound surface, see src/python/stubs/lichtfeld/__init__.pyi.

Creation:

FunctionReturnsDescription
t.zeros(shape, device='cuda', dtype='float32')TensorZero-filled tensor
t.ones(shape, device, dtype)TensorOnes tensor
t.full(shape, value, device, dtype)TensorConstant-filled tensor
t.eye(n, device, dtype)TensorIdentity matrix
t.arange(start, end, step, device, dtype)TensorRange tensor
t.linspace(start, end, steps, device, dtype)TensorLinear space
t.rand(shape, device, dtype)TensorUniform random [0, 1)
t.randn(shape, device, dtype)TensorNormal random
t.empty(shape, device, dtype)TensorUninitialized tensor
t.randint(low, high, shape, device)TensorRandom integers
t.from_numpy(arr, copy=True)TensorFrom NumPy array
t.cat(tensors, dim=0)TensorConcatenate
t.stack(tensors, dim=0)TensorStack
t.where(condition, x, y)TensorConditional select

Properties:

PropertyTypeDescription
.shapetupleTensor dimensions
.ndimintNumber of dimensions
.numelintTotal elements
.devicestr'cpu' or 'cuda'
.dtypestrData type string
.is_contiguousboolMemory contiguous
.is_cudaboolOn GPU

Methods:

MethodReturnsDescription
.clone()TensorDeep copy
.cpu() / .cuda()TensorMove device
.contiguous()TensorMake contiguous
.sync()NoneCUDA synchronize
.numpy(copy=True)ndarrayConvert to NumPy
.to(dtype)TensorConvert dtype
.size(dim)intSize at dimension
.item()scalarExtract scalar
.sum(dim=None, keepdim=False)TensorReduce sum
.mean(dim=None, keepdim=False)TensorReduce mean
.max(dim=None, keepdim=False)TensorReduce max
.min(dim=None, keepdim=False)TensorReduce min
.reshape(shape)TensorReshape
.view(shape)TensorView reshape
.squeeze(dim=None)TensorRemove size-1 dims
.unsqueeze(dim)TensorAdd size-1 dim
.transpose(dim0, dim1)TensorSwap dimensions
.permute(dims)TensorReorder dimensions
.flatten(start=0, end=-1)TensorFlatten range
.expand(sizes)TensorBroadcast view
.repeat(repeats)TensorTile tensor
.prod(), .std(), .var()TensorAdditional reductions
.argmax(), .argmin()TensorIndex reductions
.all(), .any()TensorLogical reductions
.matmul(), .mm(), .bmm()TensorMatrix products
.masked_select(), .masked_fill()TensorMasked operations
.zeros_like(), .ones_like() etc.TensorLike-constructors
.from_dlpack() / .__dlpack__()TensorDLPack interop

Operators: +, -, *, /, **, ==, !=, <, >, <=, >=, [] (indexing/slicing)

Application

FunctionDescription
lf.request_exit()Exit with confirmation
lf.force_exit()Immediate exit
lf.run(path)Execute Python script
lf.on_frame(cb)Per-frame callback
lf.stop_animation()Clear frame callback
lf.mat4(rows)Create 4x4 matrix
lf.help()Show help

Native Operators (lf.ops / lf.ui.ops)

Use lf.ops for the native operator registry and descriptor metadata. The lf.ui.ops namespace exposes the same common invoke/poll/modal controls for UI code.

APIReturnsDescription
lf.ops.invoke(id, **kwargs)OperatorReturnValueInvoke a native or Python operator
lf.ops.poll(id)boolCheck whether the operator can run
lf.ops.get_all()list[str]Registered operator IDs
lf.ops.get_descriptor(id)OperatorDescriptor | NoneLabel, description, icon, shortcut, flags
lf.ops.has_modal() / cancel_modal()bool / NoneModal operator state/control

OperatorReturnValue has boolean helpers: finished, cancelled, running_modal, pass_through, and bool(result) for successful completion. Extra return data can be accessed by attribute.

MCP Tools (lf.mcp)

Python plugins can register MCP tools into the same local MCP surface used by automation clients.

@lf.mcp.tool(name="my_plugin.echo", description="Echo a message")
def echo(args):
    return {"message": args.get("message", "")}
FunctionReturnsDescription
register_tool(fn, name='', description='')NoneRegister a Python function as an MCP tool
@tool(name='', description='')decoratorDecorator form
unregister_tool(name)NoneRemove a Python MCP tool
list_tools() / describe_tools()listAll shared MCP tools/capabilities
list_python_tools()list[str]Python tools registered through lf.mcp
list_resources() / read_resource(uri)listShared MCP resource discovery/read
call_tool(name, args=None)objectInvoke a registered tool/capability

Packages (lf.packages)

Package installation is backed by uv and the LichtFeld-managed Python environment.

FunctionReturnsDescription
init()strInitialize ~/.lichtfeld/venv
install(package) / uninstall(package)strSynchronous package change
list()list[PackageInfo]Installed packages
is_installed(package)boolPackage presence check
install_async(package)boolStart non-blocking install
install_torch(cuda='auto', version='')strInstall PyTorch with CUDA detection
install_torch_async(cuda='auto', version='')boolNon-blocking PyTorch install
is_busy()boolAsync operation running
is_uv_available() / uv_path()bool / struv discovery
embedded_python_path() / site_packages_dir() / typings_dir()strRuntime paths

There is no uninstall_async() API in the current stubs; use synchronous uninstall().

Scripts (lf.scripts)

These functions back the Scripts panel and are useful for plugin-managed script batches.

FunctionReturnsDescription
get_scripts()listLoaded script records
set_script_enabled(index, enabled)NoneToggle one script
set_script_error(index, error)NoneSet or clear one script error
clear_errors() / clear()NoneClear errors or all scripts
run(paths)dictRun scripts, returns success/error data
get_enabled_paths()list[str]Enabled script paths
count()intScript count

Keymaps (lf.keymap)

lf.keymap exposes input bindings for tools and global actions.

FunctionDescription
get_action_for_key(mode, key, modifiers=0) / get_action_for_scroll(mode, modifiers=0, held_keys=[])Resolve input to action
get_key_for_action(action, mode=GLOBAL) / get_trigger(action, mode=GLOBAL)Read current binding
set_binding(mode, action, key, modifiers=0) / set_trigger_binding(mode, action, trigger)Change binding
clear_binding(mode, action) / reset_to_default()Remove or reset bindings
find_conflict_for_action(mode, action)Detect binding conflicts
get_available_profiles() / get_current_profile()Profile discovery
load_profile(name) / save_profile(name) / export_profile(path) / import_profile(path)Profile persistence
start_capture(mode, action) / capture_scroll(...) / cancel_capture()Interactive capture
is_capturing() / get_captured_trigger() / bindings_revision()Capture and change state

The enum surfaces include Action, ToolMode, Modifier, MouseButton, KeyTrigger, and MouseButtonTrigger.

Animation (lf.animation)

ClassPurpose
AnimationTrackKeyframes for one target property path; supports add_keyframe(), remove_keyframe(), evaluate(), and keyframes()
AnimationClipMulti-track clip with add_track(value_type, target_path), remove_track(), get_track(), and evaluate(time)
TimelineCamera keyframe timeline plus optional animation clip; exposes animation_clip() and evaluate_clip(time)

Track value types are "bool", "int", "float", "vec2", "vec3", "vec4", "quat", and "mat4".

Mesh (lf.mesh)

lf.mesh is a broad OpenMesh binding surface. The most common plugin entry points are:

APIReturnsDescription
MeshDataclassTensor-backed mesh payload used by scene/rendering
read_trimesh(filename, **options) / read_polymesh(filename, **options)TriMesh / PolyMeshLoad OpenMesh meshes
write_mesh(filename, mesh, **options)NoneSave TriMesh or PolyMesh
write_mesh(mesh_data, path)NoneSave tensor-backed mesh data
TriMesh / PolyMeshclassesOpenMesh-style topology, geometry, and property APIs
TriMeshDecimater / PolyMeshDecimaterclassesDecimation module management

For exact method coverage, use src/python/stubs/lichtfeld/mesh.pyi; that file is intentionally the canonical exhaustive reference for the mesh binding.


pyproject.toml Schema

[project]
name = ""                    # string, required - Unique plugin identifier
version = ""                 # string, required - Semantic version
description = ""             # string, required
authors = []                 # list[{name, email}], optional - PEP 621 authors
dependencies = []            # list[string], optional - Python packages (PEP 508)

[tool.lichtfeld]
hot_reload = true            # bool, required
entry_point = "__init__"     # string, optional - Module to load (default: __init__)
plugin_api = ">=1,<2"        # string, required - Supported plugin API range (PEP 440)
lichtfeld_version = ">=0.4.2"  # string, required - Supported host app/runtime range (PEP 440)
required_features = []       # list[string], required - Optional host features this plugin needs
author = ""                  # string, optional - Author fallback (if no [project].authors)

v1 is strict. Legacy min_lichtfeld_version / max_lichtfeld_version fields are removed and rejected.


Icon System

from lfs_plugins.icon_manager import get_icon, get_ui_icon, get_scene_icon, get_plugin_icon
FunctionReturnsDescription
get_icon(name)intLoad assets/icon/{name}.png
get_ui_icon(name)intLoad assets/icon/{name} (include ext)
get_scene_icon(name)intLoad assets/icon/scene/{name}.png
get_plugin_icon(name, plugin_path, plugin_name)intLoad {plugin_path}/icons/{name}.png with fallback

All return opaque UI texture handles (0 on failure). Icons are cached by C++.

Direct loading:

import lichtfeld as lf
texture_id = lf.load_icon(name)
lf.free_icon(texture_id)

Errors

Plugin errors are captured and accessible via the plugin manager:

import lichtfeld as lf

state = lf.plugins.get_state("my_plugin")   # PluginState enum
error = lf.plugins.get_error("my_plugin")   # Error message string
tb = lf.plugins.get_traceback("my_plugin")  # Full traceback string

PluginState values

StateDescription
UNLOADEDPlugin is not loaded
INSTALLINGPlugin is being installed
LOADINGPlugin is loading
ACTIVEPlugin is running
ERRORPlugin failed to load/run
DISABLEDPlugin is manually disabled