You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Module
Main responsibility
lichtfeld
Training control, scene shortcuts, tensors, rendering, viewport, transform gizmos, registration, app helpers
lichtfeld.app
Application-level file open helper
lichtfeld.animation
Tracks, clips, and timeline evaluation
lichtfeld.io
Load/save splats, point clouds, datasets, images, and supported format queries
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:
Path
Purpose
docs/plugins/getting-started.md
Plugin authoring guide, common workflows, panel/operator examples, and runtime patterns
docs/plugins/api-reference.md
Practical 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.md
Plugin runtime architecture, manager responsibilities, scaffolding, and packaging overview
docs/plugin-dev-workflow.md
CLI/Python workflow for creating, validating, installing, and iterating on plugins
docs/Python_UI.md
Compatibility redirect for the old Python UI document
docs/Python_API_issues.md
Known 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.md
RmlUI/RCSS styling rules used by retained Python panels
Registration
importlichtfeldaslflf.register_class(cls) # Register a Panel, Operator, or Menu classlf.unregister_class(cls) # Unregister a Panel, Operator, or Menu class
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
importlichtfeldaslf# 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_policy
str
"interval"
Set to "dirty" or "reactive" for retained panels that update from explicit model/store invalidation
update_interval_ms
int
100
Fallback cadence for retained/hybrid on_update() work. Prefer update_policy = "dirty" for data-driven panels
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
Retained 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)
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.
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.
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.
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. A sibling main_panel.theme.rcss file is also loaded for palette-dependent overrides.
Operator
fromlfs_plugins.typesimportOperator, Event
Operator extends PropertyGroup, so it supports typed properties as class attributes.
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 a regular Panel.draw() is a live RmlUILayout,
an immediate widget API reconciled into retained RmlUi elements. Viewport
overlay panels and document-less draw hooks receive the compatibility
UILayout: its viewport drawing methods are live during the overlay frame,
while interactive controls warn once and return inert defaults.
Editable path plus native browse button on RmlUILayout; folder_mode selects folder vs file. A non-empty title is passed to the custom-title dialog path, while an empty title uses the native default. Unsupported on compatibility UILayout.
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
Rows are position-identified by default. If rows can be removed or reordered,
call push_id() with a stable value (a hidden ##key is accepted) after
begin_table() and before table_next_row(), and keep that id active through
the row's cells. The Rml bridge then preserves the matching row, focus, caret,
listeners, and cell state across reconciliation.
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 an opaque Vulkan UI texture id (uint64).
tex=lf.ui.DynamicTexture() # Emptytex=lf.ui.DynamicTexture(tensor) # From tensor
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
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)
On a viewport-overlay UILayout, these primitives enqueue into the active
viewport-scoped ScreenOverlayRenderer. Coordinates are absolute screen
coordinates. Outside an active overlay frame they emit no command.
The legacy background arguments on applicable draw_* calls are accepted
but ignored: there is no separate background draw list. Overlay calls share
one command stream, packed as shapes followed by text/images. Enqueue order is
retained within each batch, but cross-batch call order is not a z-order
guarantee.
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).
Responsive grid with explicit column/row sizing controls
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.
For split, values are clamped to [0, 1]; a 4dp gap is accounted for while
preserving the requested ratio. Only two children are shown. For grid_flow,
positive columns with even_columns=True assigns equal percentage widths;
columns=0 uses a wrapping 100dp basis. even_columns=False uses content
width. even_rows=True grows and stretches cells to the row height;
even_rows=False preserves natural height.
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
Function
Returns
Description
get_node_transform(name)
list[float]
16 column-major floats
set_node_transform(name, matrix)
None
Set 4x4 transform from 16 column-major floats
decompose_transform(matrix)
dict
Decompose 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:
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 newly deleted mask
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.
Visualizer 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.
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.
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_git_commit()
str
Git commit hash
lf.ui.is_key_pressed(key, repeat=False)
bool
SDL-backed rising edge for the current UI frame; UI thread only, no repeat events
lf.ui.is_key_down(key)
bool
Current SDL keyboard level
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.
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.
Callbacks whose layout cannot host interactive widgets warn once per method
and return inert controls. Viewport drawing remains available during an active
overlay frame.
Tensor API
importlichtfeldaslft=lf.Tensor
The tables below list the most-used tensor APIs. For the full bound surface, see src/python/stubs/lichtfeld/__init__.pyi.
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.
API
Returns
Description
lf.ops.invoke(id, **kwargs)
OperatorReturnValue
Invoke a native or Python operator
lf.ops.poll(id)
bool
Check whether the operator can run
lf.ops.get_all()
list[str]
Registered operator IDs
lf.ops.get_descriptor(id)
OperatorDescriptor | None
Label, description, icon, shortcut, flags
lf.ops.has_modal() / cancel_modal()
bool / None
Modal 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")defecho(args):
return {"message": args.get("message", "")}
Function
Returns
Description
register_tool(fn, name='', description='')
None
Register a Python function as an MCP tool
@tool(name='', description='')
decorator
Decorator form
unregister_tool(name)
None
Remove a Python MCP tool
list_tools() / describe_tools()
list
All shared MCP tools/capabilities
list_python_tools()
list[str]
Python tools registered through lf.mcp
list_resources() / read_resource(uri)
list
Shared MCP resource discovery/read
call_tool(name, args=None)
object
Invoke a registered tool/capability
Packages (lf.packages)
Package installation is backed by uv and the LichtFeld-managed Python
environment.
OpenMesh-style topology, geometry, and property APIs
TriMeshDecimater / PolyMeshDecimater
classes
Decimation 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.