Skip to content

Latest commit

 

History

History
1577 lines (1276 loc) · 78.3 KB

File metadata and controls

1577 lines (1276 loc) · 78.3 KB

Plugin API Reference

Complete API reference for LichtFeld Studio plugins.


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
Attribute Type Default Description
id str module.qualname Unique panel identifier
label str "" Display name (id fallback when empty)
space lf.ui.PanelSpace lf.ui.PanelSpace.MAIN_PANEL_TAB Panel space (see below)
parent str "" Parent panel id. Embeds as a collapsible section; embedded panels must not override space
order int 100 Sort order (lower = higher)
options set[lf.ui.PanelOption] set() DEFAULT_CLOSED, HIDE_HEADER
poll_dependencies set[lf.ui.PollDependency] {SCENE, SELECTION, TRAINING} Which state changes trigger poll()
size tuple[float, float] | None None Initial width/height hint, mainly for floating panels
template str | os.PathLike[str] "" Retained RML template. Use an absolute path for plugin-local files
style str "" Inline RCSS appended to the retained document
height_mode lf.ui.PanelHeightMode lf.ui.PanelHeightMode.FILL FILL or CONTENT for retained panels
update_interval_ms int 100 Cadence for retained/hybrid on_update() work
Method Returns Description
poll(cls, context) bool Classmethod. Show/hide condition
draw(self, ui) None Immediate-mode content
on_bind_model(self, ctx) None Bind retained data models before document load
on_mount(self, doc) None Called once after the retained document mounts
on_unmount(self, doc) None Called before the retained document is destroyed
on_update(self, doc) None | bool Periodic retained update. Return True to mark content dirty
on_scene_changed(self, doc) None Called 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.

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

Goal Use Notes
Minimal panel draw(self, ui) No extra files needed
Light retained styling style Inline RCSS text, not a path
Full custom retained UI template Use an absolute path for plugin-local .rml
Hybrid panel template 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.


Operator

from lfs_plugins.types import Operator, Event

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

Attribute Type Description
label str Display name
description str Tooltip text
options Set[str] {'UNDO', 'BLOCKING'}
Method Returns Description
poll(cls, context) bool Classmethod. Can the op run?
invoke(self, context, event) set Called on trigger, can start modal
execute(self, context) set Synchronous execution
modal(self, context, event) set Handle events in modal mode
cancel(self, context) None Called on cancellation

Return sets

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

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


Event

from lfs_plugins.types import Event
Attribute Type Description
type str 'MOUSEMOVE', 'LEFTMOUSE', 'RIGHTMOUSE', 'MIDDLEMOUSE', 'KEY_A'-'KEY_Z', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE', 'ESC', 'RET', 'SPACE'
value str 'PRESS', 'RELEASE', 'NOTHING'
mouse_x float Mouse X (viewport coords)
mouse_y float Mouse Y (viewport coords)
mouse_region_x float Mouse X relative to region
mouse_region_y float Mouse Y relative to region
delta_x float Mouse delta X
delta_y float Mouse delta Y
scroll_x float Scroll X offset
scroll_y float Scroll Y offset
shift bool Shift modifier
ctrl bool Ctrl modifier
alt bool Alt modifier
pressure float Tablet pressure (1.0 for mouse)
over_gui bool Mouse is over GUI element
key_code int Key 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 = "",
)
Method Returns Description
add() PropertyGroup Add new item
remove(index) None Remove by index
clear() None Remove all items
move(from, to) None Reorder items
__len__() int Item count
__getitem__(index) PropertyGroup Access by index
__iter__() Iterator Iterate items

PointerProperty

PointerProperty(
    type: Type[PropertyGroup],  # Referenced type
    name: str = "",
    description: str = "",
)
Method Returns Description
get_instance() PropertyGroup Get or create referenced object

PropertyGroup

from lfs_plugins.props import PropertyGroup
Method Returns Description
get_instance() cls Classmethod. Singleton access
add_property(name, prop) None Add property at runtime
remove_property(name) None Remove runtime property
get_all_properties() dict[str, Property] All properties (class + runtime)
get(prop_id) Any Get property value by name
set(prop_id, value) None Set 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", "paint", "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
Method Returns Description
can_activate(context) bool Check if tool can be activated
to_dict() dict Convert 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
Method Returns Description
register_tool(tool) None Register a custom tool
unregister_tool(tool_id) None Unregister 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) bool Activate a tool
get_active() Optional[ToolDef] Get active tool
get_active_id() str Get active tool ID

Signals

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

Signal[T]

Signal(initial_value: T, name: str = "")
Property/Method Returns Description
.value T Get/set current value
.peek() T Get without tracking
.subscribe(callback) () -> None Subscribe; returns unsubscribe fn
.subscribe_as(owner, callback) () -> None Owner-tracked subscription

ComputedSignal[T]

ComputedSignal(compute: Callable[[], T], dependencies: list[Signal])
Property/Method Returns Description
.value T Get computed value (lazy)
.subscribe(callback) () -> None Subscribe to changes
.subscribe_as(owner, callback) () -> None Owner-tracked subscription

ThrottledSignal[T]

ThrottledSignal(initial_value: T, max_rate_hz: float = 60.0, name: str = "")
Property/Method Returns Description
.value T Get/set current value
.flush() None Force pending notification
.subscribe(callback) () -> None Subscribe to changes
.subscribe_as(owner, callback) () -> None Owner-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()
Method Returns Description
register(name, handler, ...) None Register a capability
unregister(name) bool Unregister by name
unregister_all_for_plugin(plugin_name) int Unregister all for plugin
invoke(name, args) dict Invoke capability
get(name) Optional[Capability] Get by name
list_all() list[Capability] List all capabilities
has(name) bool Check 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
Method Returns Description
build(registry, include_view=True) PluginContext Classmethod. Build from state

SceneContext

@dataclass
class SceneContext:
    scene: Any                          # PyScene object
Method Returns Description
set_selection_mask(mask) None Apply 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()
Method Returns Description
plugins_dir Path Property. ~/.lichtfeld/plugins/
discover() list[PluginInfo] Scan for plugins
load(name, on_progress=None) bool Load a plugin
unload(name) bool Unload a plugin
reload(name) bool Hot-reload a plugin
load_all() dict[str, bool] Load all user-enabled plugins
install(url, on_progress=None, auto_load=True) str Install from Git URL
uninstall(name) bool Remove a plugin
update(name, on_progress=None) bool Update 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
Function Returns Description
lf.plugins.discover() list[PluginInfo] Discover plugins in ~/.lichtfeld/plugins/
lf.plugins.load(name) bool Load a plugin
lf.plugins.unload(name) bool Unload a plugin
lf.plugins.reload(name) bool Reload a plugin
lf.plugins.load_all() dict[str, bool] Load all user-enabled plugins
lf.plugins.start_watcher() None Start the hot-reload watcher
lf.plugins.stop_watcher() None Stop the hot-reload watcher
lf.plugins.get_state(name) PluginState | None Read plugin state
lf.plugins.get_error(name) str | None Read the last plugin error
lf.plugins.get_traceback(name) str | None Read the full traceback
lf.plugins.create(name) str Create 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:

Constant Type Description
lf.PLUGIN_API_VERSION str Host plugin API version
lf.plugins.API_VERSION str Same plugin API version through the plugin namespace
lf.plugins.FEATURES list[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

Method Returns Description
label(text) None Plain text
label_centered(text) None Centered text
heading(text) None Large heading
text_colored(text, color) None Colored text (RGBA tuple)
text_colored_centered(text, color) None Centered colored text
text_selectable(text, height=0) None Selectable text
text_wrapped(text) None Word-wrapped text
text_disabled(text) None Grayed-out text
bullet_text(text) None Bulleted text

Buttons

Method Returns Description
button(label, size=(0,0)) bool Standard button
button_styled(label, style, size=(0,0)) bool Styled: "success", "error", "warning", "primary", "secondary"
button_callback(label, callback=None, size=(0,0)) bool Button with callback
small_button(label) bool Compact button
invisible_button(id, size) bool Invisible clickable area

Input

Method Returns Description
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

Method Returns Description
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

Method Returns Description
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) bool Selectable item
prop_search(data, prop_id, search_data, search_prop, text='') (bool, int) Searchable dropdown

Color

Method Returns Description
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)) bool Color swatch button

File/Path

Method Returns Description
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

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

Layout Structure

Method Returns Description
separator() None Horizontal line
spacing() None Vertical space
same_line(offset=0, spacing=-1) None Next widget on same line
new_line() None Force new line
indent(width=0) None Increase indent
unindent(width=0) None Decrease indent
begin_group() / end_group() None Logical widget group
set_next_item_width(width) None Width for next widget
dummy(size) None Empty space placeholder

Collapsible / Tree

Method Returns Description
collapsing_header(label, default_open=False) bool Collapsible section
tree_node(label) bool Tree node (call tree_pop())
tree_node_ex(label, flags='') bool Extended tree node
tree_pop() None Close tree node

Tables

Method Returns Description
begin_table(id, columns) bool Start table
table_setup_column(label, width=0) None Define column
table_headers_row() None Draw header row
table_next_row() None Next row
table_next_column() None Next column
table_set_column_index(column) bool Jump to column
table_set_bg_color(target, color) None Set row/cell background
end_table() None End table

Images

Method Returns Description
image(texture_id, size, tint=(1,1,1,1)) None Display image
image_uv(texture_id, size, uv0, uv1, tint=(1,1,1,1)) None Image with UV coords
image_button(id, texture_id, size, tint=(1,1,1,1)) bool Clickable image
toolbar_button(id, tex, size, selected=F, disabled=F, tooltip='') bool Toolbar icon button
image_tensor(label, tensor, size, tint=None) None Display a tensor as an image (cached by label)
image_texture(texture, size, tint=None) None Display 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 OpenGL texture bridge via CUDA-GL interop.

tex = lf.ui.DynamicTexture()          # Empty
tex = lf.ui.DynamicTexture(tensor)    # From tensor
Method / Property Returns Description
update(tensor) None Upload [H, W, 3|4] tensor (auto-converts CPU→CUDA, uint8→float32)
destroy() None Release GL resources
id int OpenGL texture ID
width int Current width in pixels
height int Current height in pixels
valid bool True if texture is initialized
uv1 tuple[float, float] UV scale factors for power-of-2 padding

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

Drag & Drop

Method Returns Description
begin_drag_drop_source() bool Start drag source
set_drag_drop_payload(type, data) None Set drag payload
end_drag_drop_source() None End drag source
begin_drag_drop_target() bool Start drag target
accept_drag_drop_payload(type) str or None Accept payload
end_drag_drop_target() None End drag target

Popups & Menus

Method Returns Description
begin_popup(id) bool Start popup
begin_context_menu(id='') bool Styled context menu
begin_popup_modal(title) bool Modal popup
open_popup(id) None Trigger popup open
end_popup() / end_popup_modal() None End popup/modal
end_context_menu() None End context menu
close_current_popup() None Close current popup
begin_menu(label) bool Start menu
end_menu() None End menu
begin_menu_bar() / end_menu_bar() bool Menu bar
menu_item(label, enabled=True) bool Menu item
menu_item_toggle(label, shortcut, selected) bool Toggle menu item
menu_item_shortcut(label, shortcut, enabled=True) bool Menu item with shortcut
menu(menu_id, text='', icon='') None Inline menu reference
popover(panel_id, text='', icon='') None Panel popover

Windows & Children

Method Returns Description
begin_window(title, flags=0) bool Start window
begin_window_closable(title, flags=0) (bool, bool) Closable window
end_window() None End window
begin_child(id, size=(0,0), border=False) bool Start child region
end_child() None End child region
set_next_window_pos(pos, first_use=False) None Set window position
set_next_window_size(size, first_use=False) None Set window size
set_next_window_pos_center() None Center window
set_next_window_pos_centered(first_use=False) None Center next window (main viewport)
set_next_window_pos_viewport_center() None Viewport center
set_next_window_focus() None Focus next window
set_next_window_bg_alpha(alpha) None Set next window BG alpha
push_window_style() / pop_window_style() None Window style stack
push_modal_style() / pop_modal_style() None Modal style stack

Drawing (Viewport)

Method Returns Description
draw_line(x0, y0, x1, y1, color, thickness=1) None Line
draw_rect(x0, y0, x1, y1, color, thickness=1) None Rectangle outline
draw_rect_filled(x0, y0, x1, y1, color, bg=False) None Filled rectangle
draw_rect_rounded(x0, y0, x1, y1, color, r, thick=1, bg=F) None Rounded rect outline
draw_rect_rounded_filled(x0, y0, x1, y1, color, r, bg=F) None Filled rounded rect
draw_circle(x, y, radius, color, segments=32, thickness=1) None Circle outline
draw_circle_filled(x, y, radius, color, segments=32) None Filled circle
draw_triangle_filled(x0, y0, x1, y1, x2, y2, color, bg=F) None Filled triangle
draw_text(x, y, text, color, bg=False) None Text at position
draw_polyline(points, color, closed=False, thickness=1) None Polyline
draw_poly_filled(points, color) None Filled polygon
plot_lines(label, values, scale_min, scale_max, size) None Line plot

Drawing (Window)

Method Returns Description
draw_window_rect_filled(x0, y0, x1, y1, color) None Filled rect to window
draw_window_rect(x0, y0, x1, y1, color, thickness=1) None Rect outline to window
draw_window_line(x0, y0, x1, y1, color, thickness=1) None Line to window
draw_window_text(x, y, text, color) None Text to window
draw_window_triangle_filled(x0, y0, x1, y1, x2, y2, color) None Triangle to window

Progress & Status

Method Returns Description
progress_bar(fraction, overlay='', width=0) None Progress bar
set_tooltip(text) None Tooltip for last item

State Queries

Method Returns Description
is_item_hovered() bool Last item hovered
is_item_clicked(button=0) bool Last item clicked
is_item_active() bool Last item active
is_window_focused() bool Window has focus
is_window_hovered() bool Window is hovered
is_mouse_double_clicked(button=0) bool Double click detected
is_mouse_dragging(button=0) bool Mouse dragging
get_mouse_wheel() float Scroll wheel delta
get_mouse_delta() tuple Mouse delta (dx, dy)

Position / Size

Method Returns Description
get_cursor_pos() tuple Cursor position
get_cursor_screen_pos() tuple Cursor screen position
get_window_pos() tuple Window position
get_window_width() float Window width
get_text_line_height() float Text line height
get_content_region_avail() tuple Available content area
get_viewport_pos() tuple Viewport position
get_viewport_size() tuple Viewport size
get_dpi_scale() float DPI scale factor
calc_text_size(text) tuple Text dimensions

Styling

Method Returns Description
push_style_var(var, value) None Push float style var
push_style_var_vec2(var, value) None Push vec2 style var
pop_style_var(count=1) None Pop style vars
push_style_color(col, color) None Push color override
pop_style_color(count=1) None Pop color overrides
push_item_width(width) / pop_item_width() None Item width stack
begin_disabled(disabled=True) / end_disabled() None Disable widget region. For composable disabled regions, prefer SubLayout.enabled (see Layout Composition below).

Keyboard / Mouse Capture

Method Returns Description
set_keyboard_focus_here() None Focus next widget
capture_keyboard_from_app(capture=True) None Capture keyboard input
capture_mouse_from_app(capture=True) None Capture mouse input
set_mouse_cursor_hand() None Set hand cursor

Cursor Control

Method Returns Description
set_cursor_pos(pos) None Set cursor position
set_cursor_pos_x(x) None Set cursor X
set_scroll_here_y(ratio=0.5) None Scroll to current Y

Specialized Widgets

Method Returns Description
crf_curve_preview(label, gamma, toe, shoulder, gamma_r=0, gamma_g=0, gamma_b=0) None Tone 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.

Method Returns Description
row() SubLayout Horizontal layout
column() SubLayout Vertical layout
split(factor=0.5) SubLayout Two-column split
box() SubLayout Bordered container
grid_flow(columns=0, even_columns=True, even_rows=True) SubLayout Responsive grid
prop_enum(data, prop_id, value, text='') bool Enum 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

Property Type Description
enabled bool Disabled state (cascades to children)
active bool Active state (cascades to children)
alert bool One-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

Function Returns Description
get_scene() Scene or None Get scene object
get_render_scene() Scene or None Get render scene (PyScene)
has_scene() bool Whether scene is loaded
clear_scene() None Clear all scene content
load_file(path, is_dataset=False) None Load PLY or dataset
load_config_file(path) None Load JSON config
get_scene_generation() int Scene generation counter
list_scene() None Print scene tree

Node Operations (on Scene object)

Method Returns Description
add_group(name, parent=-1) int Add group node
add_splat(name, means, sh0, shN, scaling, rotation, opacity, ...) int Add splat node
add_point_cloud(name, points, colors, parent=-1) int Add point cloud
add_camera(name, parent, R, T, fx, fy, w, h, ...) int Add camera node
remove_node(name, keep_children=False) None Remove node
rename_node(old, new) bool Rename node
reparent(node_id, new_parent_id) None Change parent
duplicate_node(name) str Duplicate, returns new name
merge_group(group_name) str Merge group children
get_node(name) SceneNode Get node by name
get_node_by_id(id) SceneNode Get 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) bool Considers parent visibility
total_gaussian_count int Property. Total gaussians
invalidate_cache() None Clear internal cache (no redraw)
notify_changed() None Invalidate cache + trigger viewport redraw

SceneNode Properties

Property/Method Returns Description
id int Node ID
name str Node name
type NodeType Node type enum (SPLAT, POINTCLOUD, GROUP, etc.)
parent_id int Parent node ID (-1 for root)
children list[int] Child node IDs
visible bool Visibility flag
locked bool Lock flag
gaussian_count int Number of gaussians (splat nodes)
centroid tuple[float, float, float] Node centroid
world_transform tuple World-space 4x4 transform
splat_data() SplatData or None Splat data for this node (None if not a splat)
point_cloud() PointCloud or None Point cloud data (None if not a point cloud)
cropbox() CropBox or None Crop box data
ellipsoid() Ellipsoid or None Ellipsoid data

Selection

Function Returns Description
select_node(name) None Select node by name
deselect_all() None Clear selection
has_selection() bool Any selection active
get_selected_node_name() str First selected node name
get_selected_node_names() list[str] All selected node names
can_transform_selection() bool Selection is transformable
get_selected_node_transform() list[float] 16 floats, column-major
set_selected_node_transform(matrix) None Set transform
get_selection_center() list[float] Local space center
get_selection_world_center() list[float] World space center
capture_selection_transforms() dict Snapshot for undo

Scene Shortcuts

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

Function Returns Description
set_node_visibility(name, visible) None Toggle node visibility
remove_node(name, keep_children=False) None Remove node
reparent_node(name, new_parent) None Reparent node
rename_node(old_name, new_name) None Rename node
add_group(name, parent="") None Add group node
get_num_gaussians() int Total gaussian count

Gaussian-Level Selection (on Scene object)

Method Returns Description
set_selection_mask(mask) None Apply bool tensor mask
clear_selection() None Clear gaussian selection
has_selection() bool Any gaussians selected
selection_mask Tensor Property. Current mask
set_selection(indices) None Select by index list

Transforms

Function Returns Description
get_node_transform(name) list[float] 16 floats, column-major
set_node_transform(name, matrix) None Set 4x4 transform
decompose_transform(matrix) dict See keys below
compose_transform(translation, euler_deg, scale) list[float] Build 4x4 from components (Euler in degrees)

decompose_transform returns a dict with these keys:

Key Type Description
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/Method Returns Description
means_raw Tensor [N, 3] positions (view)
sh0_raw Tensor [N, 1, 3] base SH (view)
shN_raw Tensor [N, K, 3] higher SH (view)
scaling_raw Tensor [N, 3] log-space (view)
rotation_raw Tensor [N, 4] quaternions (view)
opacity_raw Tensor [N, 1] logit-space (view)
get_means() Tensor Positions
get_opacity() Tensor [N] sigmoid applied
get_scaling() Tensor Exp applied
get_rotation() Tensor Normalized quaternions
get_shs() Tensor SH0 + SHN concatenated
num_points int Gaussian count
active_sh_degree int Current SH degree
max_sh_degree int Maximum SH degree
scene_scale float Scene scale factor
soft_delete(mask) Tensor Mark for deletion, returns prev state
undelete(mask) None Restore deleted gaussians
apply_deleted() int Permanently remove, returns count
clear_deleted() None Clear deletion mask
deleted Tensor Property. [N] bool deletion mask
has_deleted_mask() bool Whether deletion mask exists
visible_count() int Number of non-deleted gaussians

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

Training Control

Function Returns Description
start_training() None Start training
pause_training() None Pause
resume_training() None Resume
stop_training() None Stop
reset_training() None Reset to iteration 0
save_checkpoint() None Save checkpoint
switch_to_edit_mode() None Enter edit mode
has_trainer() bool Trainer loaded
trainer_state() str State string
finish_reason() str or None Why training ended
trainer_error() str or None Error message
context() Context Training context snapshot
optimization_params() OptimizationParams Training parameters
dataset_params() DatasetParams Dataset parameters
loss_buffer() list[float] Loss history
load_checkpoint_for_training(checkpoint_path, dataset_path, output_path) None Load checkpoint for training

Training Status

Function Returns Description
trainer_elapsed_seconds() float Elapsed training time
trainer_eta_seconds() float Estimated remaining time (-1 if unavailable)
trainer_strategy_type() str Strategy type (mcmc, default, etc.)
trainer_is_gut_enabled() bool GUT enabled
trainer_max_gaussians() int Max gaussians
trainer_num_splats() int Current splat count
trainer_current_iteration() int Current iteration
trainer_total_iterations() int Total iterations
trainer_current_loss() float Current loss

Training Hooks

Decorator Description
@lf.on_training_start Called when training starts
@lf.on_iteration_start Called at start of each iteration
@lf.on_pre_optimizer_step Called before optimizer step
@lf.on_post_step Called after each step
@lf.on_training_end Called when training ends

Rendering

Function Returns Description
get_current_view() ViewInfo Current camera view
get_viewport_render() ViewportRender Current viewport image
capture_viewport() ViewportRender Capture for async use
render_view(rotation, translation, w, h, fov=60, bg=None) Tensor Render from camera
compute_screen_positions(rotation, translation, w, h, fov=60) Tensor [N, 2] screen positions
get_render_settings() RenderSettings Current render settings
get_render_mode() / set_render_mode(mode) RenderMode Render mode

Viewport Control

Function Returns Description
reset_camera() None Reset camera
toggle_fullscreen() None Toggle fullscreen
is_fullscreen() bool Fullscreen state
toggle_ui() None Toggle UI visibility
set_orthographic(ortho) None Set projection mode
is_orthographic() bool Orthographic state

Export

lf.export_scene(
    format: int,             # 0=PLY, 1=SOG, 2=SPZ, 3=HTML
    path: str,
    node_names: list[str],
    sh_degree: int,
)
lf.save_config_file(path: str)

Logging

Function Description
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

Function Returns Description
lf.ui.tr(key) str Translate string
lf.ui.theme() Theme Current theme
lf.ui.context() AppContext App context
lf.ui.request_redraw() None Request UI redraw
lf.ui.set_language(lang_code) None Set UI language
lf.ui.get_current_language() str Active language code
lf.ui.get_languages() list[tuple[str, str]] Available languages
lf.ui.set_theme(name) None Theme switch (dark/light)
lf.ui.get_theme() str Active theme name
lf.ui.set_panel_enabled(panel_id, enabled) None Toggle panel by id
lf.ui.is_panel_enabled(panel_id) bool Panel 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 | None Typed 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) bool Change panel display name
lf.ui.set_panel_order(panel_id, order) bool Change panel sort order
lf.ui.set_panel_space(panel_id, space) bool Move panel to a different space (lf.ui.PanelSpace)
lf.ui.set_panel_parent(panel_id, parent) bool Embed panel inside a tab as collapsible section
lf.ui.ops.invoke(op_id, **kwargs) OperatorReturnValue Invoke operator
lf.ui.ops.poll(op_id) bool Operator poll
lf.ui.ops.cancel_modal() None Cancel modal operator
lf.ui.get_active_tool() str Active tool ID
lf.ui.get_active_submode() str Active submode
lf.ui.set_selection_mode(mode) None Set selection submode
lf.ui.get_transform_space() int Transform space enum index
lf.ui.set_transform_space(space) None Set transform space index
lf.ui.get_pivot_mode() / set_pivot_mode(mode) int Pivot mode enum index
lf.ui.get_fps() float Current FPS
lf.ui.get_gpu_memory() (int, int, int) (process_used, total_used, total) bytes
lf.ui.get_git_commit() str Git commit hash

File Dialogs

Function Returns
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_json_file_dialog() str
lf.ui.open_video_file_dialog() str
lf.ui.save_json_file_dialog(default_name='config.json') str
lf.ui.save_ply_file_dialog(default_name='export.ply') str
lf.ui.save_sog_file_dialog(default_name='export.sog') str
lf.ui.save_spz_file_dialog(default_name='export.spz') str
lf.ui.save_html_file_dialog(default_name='viewer.html') 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.

Function Description
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:

Function Returns Description
t.zeros(shape, device='cuda', dtype='float32') Tensor Zero-filled tensor
t.ones(shape, device, dtype) Tensor Ones tensor
t.full(shape, value, device, dtype) Tensor Constant-filled tensor
t.eye(n, device, dtype) Tensor Identity matrix
t.arange(start, end, step, device, dtype) Tensor Range tensor
t.linspace(start, end, steps, device, dtype) Tensor Linear space
t.rand(shape, device, dtype) Tensor Uniform random [0, 1)
t.randn(shape, device, dtype) Tensor Normal random
t.empty(shape, device, dtype) Tensor Uninitialized tensor
t.randint(low, high, shape, device) Tensor Random integers
t.from_numpy(arr, copy=True) Tensor From NumPy array
t.cat(tensors, dim=0) Tensor Concatenate
t.stack(tensors, dim=0) Tensor Stack
t.where(condition, x, y) Tensor Conditional select

Properties:

Property Type Description
.shape tuple Tensor dimensions
.ndim int Number of dimensions
.numel int Total elements
.device str 'cpu' or 'cuda'
.dtype str Data type string
.is_contiguous bool Memory contiguous
.is_cuda bool On GPU

Methods:

Method Returns Description
.clone() Tensor Deep copy
.cpu() / .cuda() Tensor Move device
.contiguous() Tensor Make contiguous
.sync() None CUDA synchronize
.numpy(copy=True) ndarray Convert to NumPy
.to(dtype) Tensor Convert dtype
.size(dim) int Size at dimension
.item() scalar Extract scalar
.sum(dim=None, keepdim=False) Tensor Reduce sum
.mean(dim=None, keepdim=False) Tensor Reduce mean
.max(dim=None, keepdim=False) Tensor Reduce max
.min(dim=None, keepdim=False) Tensor Reduce min
.reshape(shape) Tensor Reshape
.view(shape) Tensor View reshape
.squeeze(dim=None) Tensor Remove size-1 dims
.unsqueeze(dim) Tensor Add size-1 dim
.transpose(dim0, dim1) Tensor Swap dimensions
.permute(dims) Tensor Reorder dimensions
.flatten(start=0, end=-1) Tensor Flatten range
.expand(sizes) Tensor Broadcast view
.repeat(repeats) Tensor Tile tensor
.prod(), .std(), .var() Tensor Additional reductions
.argmax(), .argmin() Tensor Index reductions
.all(), .any() Tensor Logical reductions
.matmul(), .mm(), .bmm() Tensor Matrix products
.masked_select(), .masked_fill() Tensor Masked operations
.zeros_like(), .ones_like() etc. Tensor Like-constructors
.from_dlpack() / .__dlpack__() Tensor DLPack interop

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

Application

Function Description
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

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
Function Returns Description
get_icon(name) int Load assets/icon/{name}.png
get_ui_icon(name) int Load assets/icon/{name} (include ext)
get_scene_icon(name) int Load assets/icon/scene/{name}.png
get_plugin_icon(name, plugin_path, plugin_name) int Load {plugin_path}/icons/{name}.png with fallback

All return OpenGL texture ID (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

State Description
UNLOADED Plugin is not loaded
INSTALLING Plugin is being installed
LOADING Plugin is loading
ACTIVE Plugin is running
ERROR Plugin failed to load/run
DISABLED Plugin is manually disabled