Creating Plugins¶
This guide provides detailed steps for creating Lightfall plugins of any type.
Plugin Creation Process¶
1. Choose the Right Plugin Type¶
Select the plugin type that matches your goal:
Goal |
Plugin Type |
|---|---|
Add a preferences page |
|
Add a UI panel |
|
Add a Bluesky plan |
|
Add an execution backend |
|
Add a color theme |
|
Add a status indicator |
|
Add device control widgets |
|
Add Claude assistant tools |
|
Add Claude assistant expertise |
|
2. Create the Plugin Class¶
All plugins inherit from a base class in lightfall.plugins:
from lightfall.plugins.<type>_plugin import <Type>Plugin
class MyPlugin(<Type>Plugin):
@property
def name(self) -> str:
"""Required: Unique identifier within this type."""
return "my_plugin"
# ... implement required methods
3. Implement Required Methods¶
Each plugin type has specific required methods. Check the Plugin Type Reference for your type.
Common patterns:
# All plugins need a name property
@property
def name(self) -> str:
return "my_plugin"
# Most plugins have optional display_name
@property
def display_name(self) -> str:
return "My Plugin" # Human-readable name
4. Register the Plugin¶
Option A: Built-in Manifest (Development)¶
For plugins being developed within Lightfall, add to builtin_manifest.py:
PluginEntry(
type_name="settings",
name="my_plugin",
import_path="lightfall.ui.preferences.my_plugin:MyPlugin",
),
Option B: External Package (Distribution)¶
For plugins in separate packages, use entry points. See External Packages.
5. Test the Plugin¶
Start Lightfall
Verify the plugin loads (check logs for errors)
Test the plugin’s functionality
Common Patterns¶
Accessing Services¶
Plugins often need access to application services. Import them lazily to avoid circular imports:
class MyPlugin(SettingsPlugin):
def save_settings(self) -> None:
# Import inside method to avoid circular imports
from lightfall.ui.preferences.manager import PreferencesManager
prefs = PreferencesManager.get_instance()
prefs.set("my_key", self._value)
Storing Plugin State¶
Use __init__ to initialize instance variables:
class MyPlugin(SettingsPlugin):
def __init__(self) -> None:
self._widget: QWidget | None = None
self._some_control: QLineEdit | None = None
Deferred Imports¶
Use TYPE_CHECKING for type hints that would cause circular imports:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from lightfall.ui.panels.base import BasePanel
class MyPanelPlugin(PanelPlugin):
def get_panel_class(self) -> type[BasePanel]:
# Import here to defer loading
from lightfall.ui.panels.my_panel import MyPanel
return MyPanel
Error Handling¶
Plugins should handle errors gracefully:
def create_widget(self, parent: QWidget | None = None) -> QWidget:
try:
# Create widget...
return widget
except Exception as e:
from loguru import logger
logger.error("Failed to create widget: {}", e)
# Return a fallback widget
return QLabel(f"Error: {e}", parent)
Plugin Type Quick Reference¶
SettingsPlugin¶
from lightfall.plugins.settings_plugin import SettingsPlugin
class MySettingsPlugin(SettingsPlugin):
@property
def name(self) -> str:
return "my_settings"
def create_widget(self, parent=None) -> QWidget:
# Return settings widget
...
def load_settings(self) -> None:
# Load values into widget
...
def save_settings(self) -> None:
# Save values from widget
...
PanelPlugin¶
from lightfall.plugins.panel_plugin import PanelPlugin
class MyPanelPlugin(PanelPlugin):
@property
def name(self) -> str:
return "my_panel"
def get_panel_class(self) -> type[BasePanel]:
from my_package.panels import MyPanel
return MyPanel
PlanPlugin¶
from lightfall.plugins.plan_plugin import PlanPlugin
class MyScanPlugin(PlanPlugin):
@property
def name(self) -> str:
return "my_scan"
@property
def category(self) -> str:
return "custom"
def get_plan_function(self):
return self._my_scan
def _my_scan(self, motor, start, stop, num):
"""My custom scan plan."""
import bluesky.plans as bp
yield from bp.scan([], motor, start, stop, num)
ThemePlugin¶
from lightfall.plugins.theme_plugin import ThemePlugin, ThemeDefinition
class MyThemePlugin(ThemePlugin):
@property
def name(self) -> str:
return "my_theme"
@property
def display_name(self) -> str:
return "My Theme"
@property
def is_dark(self) -> bool:
return True
def get_theme_definition(self) -> ThemeDefinition:
return ThemeDefinition(
background="#1a1a1a",
surface="#2a2a2a",
text="#e0e0e0",
primary="#3b82f6",
)
See Plugin Type Reference for complete documentation on each type.