From 17b2f07545b89487c2d1bb936dc52a612d38d470 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Thu, 4 Jun 2026 13:24:54 -0600 Subject: docs: update CLAUDE.md — add plugin system, BSP tree layout, opencv dep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) (limited to 'CLAUDE.md') diff --git a/CLAUDE.md b/CLAUDE.md index 0a10c22..0fa549d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md -Guidance for Claude Code (claude.ai/code) working in this repo. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Running the app @@ -14,7 +14,8 @@ No build step. Simulation mode default (no hardware needed). ```bash pip install PyQt6 pyqtgraph numpy pyserial -pip install nidaqmx # optional — only for real NI hardware +pip install nidaqmx # optional — only for real NI hardware +pip install opencv-python # optional — only for motion capture plugin ``` ## Architecture @@ -61,11 +62,16 @@ Control widgets (left panel) go other direction: UI → `ControlWidget._write()` | `core/acquisition.py` | `AcquisitionEngine` + `ChannelBuffer` | | `core/signal_processor.py` | Filter chain + derived/virtual channels | | `core/profile.py` | `.labdaq` profile save/load | -| `ui/main_window.py` | Top-level window, toolbar, demo device init | +| `ui/main_window.py` | Top-level window, toolbar, demo device init, plugin lifecycle | | `ui/control_panel.py` | Left panel output widgets (`OnOffSwitch`, `MotorControl`, etc.) | | `ui/strip_chart.py` | Live pyqtgraph chart, config-driven by `LayoutConfig` | +| `ui/add_device_dialog.py` | Add Device dialog; `_PANELS` dict extended by plugins | +| `ui/windows/plot_window.py` | Plot Builder: BSP tree, `LayoutCanvas` drag-drop, `LayoutConfig` | | `ui/windows/` | Floating tool windows (Devices, Signals, Plot, Settings) | | `ui/style_dark.qss` / `style_light.qss` | Full app theme | +| `plugins/base_plugin.py` | `LabPlugin` ABC, `PluginAction`, `PluginContext` | +| `plugins/plugin_manager.py` | Discovery, load/unload, `enabled.json` persistence | +| `plugins/motion_capture/` | Camera device plugin — adds Camera type to Add Device dialog | ### Arduino firmware @@ -73,6 +79,41 @@ Firmware embedded as `ARDUINO_FIRMWARE` in `api_layers/arduino_layer.py`. When e Serial protocol: `A0:3.14,D2:1\n` stream from Arduino; `W:D7:1\n` / `P:D9:128\n` commands from PC. +### Plot layout — BSP tree + +`ui/windows/plot_window.py` stores the subplot arrangement as a binary space-partition tree of plain dicts: + +``` +{"kind": "leaf", "pane": } +{"kind": "hsplit", "ratio": , "first": , "second": } # left/right +{"kind": "vsplit", "ratio": , "first": , "second": } # top/bottom +``` + +Key tree functions (all in `plot_window.py`): `_tree_insert`, `_tree_remove`, `_tree_swap`, `_tree_reindex`, `_tree_equalize_ratios`. `tree_to_grid()` converts the tree to pyqtgraph `addItem(row, col, rowspan, colspan)` coordinates. `_tree_grid_size()` returns the LCM of all split denominators — the minimum grid size that expresses all ratios as integers. + +`LayoutCanvas` (also in `plot_window.py`) is the drag-and-drop tile editor. Drop zones detected in `_zone_at()`: outer 1/3 of tile = split (bisect), gap between tiles = squeeze (insert-between), center = swap. `_do_drop()` executes the tree mutation. + +### Plugin system + +Plugins live in `plugins//` with a `manifest.json` and entry point module. The plugin directory is added to `sys.path` on load, so intra-plugin imports use bare names (`from tracker import CameraTracker`). Cross-plugin imports use the full path (`from ui.add_device_dialog import _PANELS`). + +`PluginContext` passed to `on_load(context)`: +```python +context.registry # DeviceRegistry +context.engine # AcquisitionEngine +context.processor # SignalProcessor +context.main_window # MainWindow +``` + +Optional hooks a plugin can implement: +- `get_devices() → list[BaseDevice]` — auto-registered into acquisition engine +- `get_filter_classes() → dict` — added to `SignalProcessor` filter registry +- `get_toolbar_actions() → list[PluginAction]` — buttons inserted in main toolbar +- `get_settings_widget() → QWidget` — shown in Settings → Plugins panel +- `get_save_state() / apply_save_state(dict)` — persisted in `.labdaq` profiles + +**Extending Add Device dialog from a plugin**: `ui/add_device_dialog._PANELS` is a module-level dict `{type_name: (PanelClass, id_prefix)}`. Plugins add/remove entries in `on_load`/`on_unload`. Each panel class needs a `build_device(device_id) → BaseDevice` method. + ### Adding a new device type 1. Subclass `BaseDevice` in new file under `devices/` -- cgit v1.2.3