diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-05-07 13:02:34 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-06-03 09:50:17 -0600 |
| commit | deb3ad65d25d4c3167a3cbeca2ac19b5c45b0627 (patch) | |
| tree | 131eebcabfab2d0a19e52aec21a21ad0a24775fa /core/profile.py | |
| parent | 2b9943ba0d28449a590acfa8a41555b174b6ba84 (diff) | |
Added plugin/add-on system
Plugins live in plugins/<name>/ with a manifest.json and a LabPlugin
subclass. Enabled/disabled in Settings > Plugins tab; state persists in
plugins/enabled.json and in .labdaq profiles (loading a profile
enables/disables plugins to match). Plugins can contribute toolbar
buttons, BaseDevice instances (auto-wired into acquisition pipeline),
custom signal filter classes, a settings widget, and profile state via
get_save_state/apply_save_state. Load errors now report the exact
failing stage (file not found, class missing, on_load crash).
Includes example plugin skeleton and docs/plugin-development.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'core/profile.py')
| -rw-r--r-- | core/profile.py | 66 |
1 files changed, 48 insertions, 18 deletions
diff --git a/core/profile.py b/core/profile.py index d21bf2f..fdd3a48 100644 --- a/core/profile.py +++ b/core/profile.py @@ -67,15 +67,19 @@ class ProfileDerived: @dataclass class Profile: - name: str = "Untitled Profile" - version: str = "1.0" - devices: List[Dict] = field(default_factory=list) - controls: List[Dict] = field(default_factory=list) - channels: List[Dict] = field(default_factory=list) - pipelines: List[Dict] = field(default_factory=list) - derived: List[Dict] = field(default_factory=list) - plot: Optional[Dict] = None # LayoutConfig.to_json() - settings: Dict[str, Any] = field(default_factory=dict) + name: str = "Untitled Profile" + version: str = "1.0" + devices: List[Dict] = field(default_factory=list) + controls: List[Dict] = field(default_factory=list) + channels: List[Dict] = field(default_factory=list) + pipelines: List[Dict] = field(default_factory=list) + derived: List[Dict] = field(default_factory=list) + plot: Optional[Dict] = None # LayoutConfig.to_json() + settings: Dict[str, Any] = field(default_factory=dict) + plugin_state: Dict[str, Any] = field(default_factory=dict) + # plugin_state = {plugin_id: plugin.get_save_state()} + plugins_enabled: List[str] = field(default_factory=list) + # plugins_enabled = [plugin_id, ...] — which plugins were on when saved def to_json(self) -> str: return json.dumps(asdict(self), indent=2) @@ -84,15 +88,17 @@ class Profile: def from_json(s: str) -> "Profile": d = json.loads(s) return Profile( - name = d.get("name", ""), - version = d.get("version", "1.0"), - devices = d.get("devices", []), - controls = d.get("controls", []), - channels = d.get("channels", []), - pipelines = d.get("pipelines", []), - derived = d.get("derived", []), - plot = d.get("plot"), - settings = d.get("settings", {}), + name = d.get("name", ""), + version = d.get("version", "1.0"), + devices = d.get("devices", []), + controls = d.get("controls", []), + channels = d.get("channels", []), + pipelines = d.get("pipelines", []), + derived = d.get("derived", []), + plot = d.get("plot"), + settings = d.get("settings", {}), + plugin_state = d.get("plugin_state", {}), + plugins_enabled = d.get("plugins_enabled", []), ) def save(self, path: str): @@ -135,6 +141,7 @@ class ProfileManager: control_specs: List, settings: dict, profile_name: str = "Profile", + plugin_manager=None, ) -> Profile: p = Profile(name=profile_name) @@ -193,6 +200,18 @@ class ProfileManager: pass p.settings = dict(settings) + + # Plugin state + enabled list + if plugin_manager is not None: + p.plugins_enabled = plugin_manager.get_enabled_ids() + for plugin in plugin_manager.get_loaded(): + try: + state = plugin.get_save_state() + if state: + p.plugin_state[plugin.plugin_id] = state + except Exception: + pass + return p # ── Apply ───────────────────────────────────────────────────────────────── @@ -205,6 +224,7 @@ class ProfileManager: control_panel, settings_ref: dict, engine=None, + plugin_manager=None, ): """Apply a loaded profile to the live application state.""" from ui.control_editor import ControlSpec @@ -329,4 +349,14 @@ class ProfileManager: except Exception as e: print(f"[Profile] Could not restore plot layout: {e}") + # Plugin state + if plugin_manager is not None and profile.plugin_state: + for plugin_id, state in profile.plugin_state.items(): + plugin = plugin_manager.get_plugin(plugin_id) + if plugin is not None: + try: + plugin.apply_save_state(state) + except Exception as e: + print(f"[Profile] Plugin '{plugin_id}' state restore error: {e}") + return plot_cfg # caller applies this to the chart |
