summaryrefslogtreecommitdiff
path: root/ui
AgeCommit message (Collapse)Author
2026-08-02Merge origin/main: reconcile ConfigWindow consolidation with Debug window + ↵Christian Kolset
protocol updates origin/main (23 commits) added a Debug window/log system on top of the old separate Devices/Channels/Plot windows, plus protocol fixes (cml, mark10, modbus_rtu, scpi) and device/profile changes. Local main (3 commits) replaced the separate windows with a unified ConfigWindow + dock panel. Kept local's ConfigWindow/dock architecture and ported the Debug window onto it: new toolbar button + _open_debug(), gated by developer_mode same as origin's version. Deduped the two independent "developer mode" toggles that had collided in SettingsWindow (origin's General-tab checkbox gating Debug window + device sim-mode visibility, local's Advanced-tab checkbox setting verbose logging) into one General-tab checkbox that does both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02Fix Settings button calling nonexistent _toggle_win methodChristian Kolset
Consolidation of Devices/Channels/Plot into ConfigWindow left the Settings button wired to a method that no longer exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02Consolidate Devices/Channels/Plot into unified ConfigWindowChristian Kolset
Replaces three separate floating windows with single tabbed ConfigWindow: - Tab 0: Devices (device cards, add/remove/configure) - Tab 1: Channels (signal table with Dir/IN/OUT badge + pipeline ⚙ per row; derived channels pane) - Tab 2: Plot (layout canvas + pane config) Adds is_output field to ChannelConfig for output channel direction badges. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-01Major control panel improvements.Christian Kolset
- Added dedicated firmware file for arduino - Added View button to consolidate windows - Control Panel changes: - Changed name fron "Controls" to "Control Panel" - Blue side bar when Panel is hidden. - Supports "popping" out contorl panel - Settings > Advanced tab added with developer settings
2026-07-29Merge branch 'fix/control-channel-direction-filter'Christian Kolset
# Conflicts: # ui/control_editor.py
2026-07-29Merge branch 'fix/run-stop-log-master-switch'Christian Kolset
2026-07-29Merge branch 'feat/plugin-pip-install-prompt'Christian Kolset
2026-07-29Merge branch 'feat/device-naming'Christian Kolset
2026-07-29Merge branch 'feat/dev-debug-window'Christian Kolset
2026-07-29Add Developer Mode setting, Debug window, and gate simulate behind itChristian Kolset
New Settings > General > "Developer mode" checkbox (default on, so this repo's simulate-by-default workflow is unaffected out of the box) persisted through the existing core/app_settings.py load/save functions. Exposed to code that can't easily receive the settings dict via core.app_settings.is_developer_mode()/set_developer_mode(), a small in-memory cache MainWindow keeps in sync whenever settings are loaded or applied. Debug window (ui/windows/debug_window.py): minimal first pass per the plan — a live console. core/debug_log.py tees stdout/stderr into a ring buffer + Qt signal (installed once in main.py, before anything prints), so the window shows everything printed since app start, including from background poll threads, not just what's printed while it happens to be open. Its toolbar button is hidden unless developer mode is on. Simulate gating: every device's Simulation Mode checkbox/dropdown is now hidden when developer mode is off — ui/add_device_dialog.py (also covers the motion_capture camera panel, which shares this same checkbox rather than having its own), and the per-device config widgets in analog_input.py, arduino_device.py, digital_io.py, nidaqmx_device.py, serial_device.py. Hiding rather than force-clearing an already-simulating device's state — an existing simulated device keeps working if developer mode is turned off later; the option is just not offered again until it's back on. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Offer to pip install missing plugin dependencies instead of just warningChristian Kolset
Builds on the previous branch's dependency check: the missing-deps QMessageBox is now a Yes/No prompt. Yes runs a blocking `sys.executable -m pip install <reqs>` (subprocess.run, output captured), shows a result dialog, and on success proceeds straight to enabling the plugin — no need to click Enable a second time. Blocking is a deliberate simplification, not an oversight: this codebase has no worker-thread/progress-dialog pattern for slow operations anywhere else, so a threaded installer would be inconsistent with everything else here. pip install is a one-time, infrequent action, unlike e.g. a serial connect that runs constantly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Check plugin dependencies before loading, warn instead of silent console errorChristian Kolset
manifest.json already had an unused "requires" field (motion_capture's manifest lists opencv-python>=4.8.0) — PluginManifest never parsed it, so a missing dependency just crashed the import inside _load_plugin(), caught by the broad except and only ever printed to the console. Adds PluginManifest.requires, PluginManager.missing_requirements()/ get_missing_dependencies(), and an early check in _load_plugin() that skips the risky import entirely when a requirement is missing. main_window.plugin_enable() now checks this before calling PluginManager.enable() and shows a QMessageBox with the missing packages and a pip install command instead of failing silently. Checks by distribution name via importlib.metadata (what pip installed it as), not import name — those differ for packages like opencv-python (imports as cv2) or pyserial (imports as serial), so importlib.util. find_spec() would give false negatives. Also fixes a button-state bug this surfaces: SettingsWindow's plugin toggle optimistically flipped to "Disable" the instant Enable was clicked, before knowing whether enabling actually succeeds — pre-existing, but now trivially reproducible (any plugin missing a dependency). Enable no longer flips the button immediately; main_window calls the new sync_plugin_button() once the real outcome is known. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Make Run the master stop switch; add Log button disabled/recording stylingChristian Kolset
Run/Stop previously only paused acquisition — control outputs (motor speed, switches, PWM duty) kept whatever value was last written, so stopping the run loop didn't stop a running motor. Adds ControlWidget.safe_stop() (per-widget override, default zeros the output) and ControlPanel.safe_stop_all(), called from _toggle_run's Stop branch before engine.stop(). Per-widget behavior is deliberately not uniform: - OnOffSwitch/MotorControl/PwmControl have a latched running/enabled state, so safe_stop() drives their own toggle handler (consistent UI + write in one path) and, for Motor/PWM, zeroes the slider too. - SetpointControl/AnalogOutputControl only write on an explicit user action and have no universally safe forced value (e.g. 0 isn't necessarily "off" for an arbitrary process setpoint or analog output) — Stop leaves them untouched rather than guessing. Log button: added a :disabled QSS rule so "can't log yet" reads as clearly inert rather than a duller version of the enabled look, and a 600ms blink (toggling a "recording" dynamic property the QSS keys off) while a recording is active, so it reads as live/recording rather than a static pressed button. Master Stop now calls _toggle_log(False) explicitly when forcing the button off, since QPushButton.setChecked() doesn't emit clicked — without this the blink would keep running after a master Stop even though logging itself already halted via engine.stop()'s internal stop_logging() call. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Rework channel labeling and add source picker to virtual-channel creationChristian Kolset
Relabel every channel picker/header to "NAME (DEVICE/SIGNAL)" instead of the previous "DEVICE/SIGNAL (NAME)" ordering, consistently across channels_window.py (_channel_combo, ChannelPickerDialog, ChannelPipelineBlock header), plot_builder.py, plot_config.py, control_editor.py, and plot_window.py. While touching each of those pickers, added the missing `.enabled` filter that _make_picker()/_x_cb/_build_channel_picker lacked (the default-layout builders already filtered disabled channels; these manual "add channel to pane" pickers didn't). Devices window Signals tab: removed the separate "Signal ID" column (folded into the existing non-editable Device column instead of the editable Name column, to avoid corrupting the in-place channel rename feature that column already supports). Channels window Virtual Channels pane: added a "Source" dropdown next to "+ Add Channel" — picking a source pre-seeds the new derived channel with it via DerivedBlock._add_src(); leaving it on the default "none" entry keeps today's behavior of creating an empty channel. Note: docs/ToDo.md's "Channels window UI" items referred to the *live* UI (this window's PipelineTab + the Devices window's Signals tab), not SignalsListTab in this same file, which its own module docstring flags as dead/unused code kept only for old-profile compatibility — verified by grepping for any instantiation of it (none exist). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Add editable device display name, persist it across profile save/loadChristian Kolset
DeviceInfo.name existed but was hardcoded per device type and never operator-editable. Adds a Display Name field to Add Device and makes the Info-tab Name field in the device config dialog editable, and threads the name through get_save_config()/ProfileManager.apply() so a custom name survives a .labdaq save/reload instead of reverting to the type default. Name is applied post-construction rather than as a constructor kwarg, since no device factory declares a "name" param. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29Hide non-writable channels from Controls picker, gate writes on enabled+writableChristian Kolset
Adds ChannelConfig.writable so the Controls editor can only bind to channels with a real write mapping (VS/MA/ME/MD/ST for CML, do*/DO for digital I/O, write_cmd-configured SCPI channels, holding-register Modbus channels) instead of read-only telemetry — this is exactly the class of mistake hit earlier (binding a motor-speed control to M1_TV instead of M1_VS). Also carries forward the enabled-channel gating for controls (hide disabled channels from the picker, block writes to them at _write() time) done alongside this investigation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27Fix .gitignore: remove duplicate entry for plugins/enabled.jsonChristian Kolset
2026-06-09Derived channels: signal display names as vars, auto-update template code, ↵Christian Kolset
None op - Variable names derived from signal display name (e.g. "Acceleration X" → acceleration_x) instead of raw channel ID; all auto-names lowercase with underscores - Template code auto-updates x[0]/x[1] refs to named vars as sources are added/changed; stops updating once user edits the code manually - Derivative wrt-channel mode uses separate template with x[1] substituted to channel var - Added "None" passthrough operation (result = x[0]) - source_names persisted in .labdaq profiles Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Fix custom_script execution + add templates for expression/function kindsChristian Kolset
- custom_script now uses exec-style (assign to `result`) matching the built-in templates — previously auto-wrapped in def compute() which had no return statement, causing state and channel vars to silently fail - function kind unchanged: auto-wraps body as def compute(x,t); use return - Add SCRIPT_TEMPLATES for expression, function, custom_script kinds; shown automatically when user switches to a script kind (replacing stale code from previous kind) - New channels default to template when no saved code exists Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Channels: inject variable names as comment in code editor, fix derivative wrtChristian Kolset
- Variable names now appear as a live '# Variables: A0 = x[0] · ...' comment at the top of the code editor (function, custom_script, and built-in read-only templates); updates on every source add/remove/change - Derivative 'With Respect To' channel combo now included in variable list as x[1] when channel mode is selected - _apply() strips the auto-generated comment before saving the script so it doesn't pollute the stored expression Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Channels: live variable-name hint in code editor updates with sourcesChristian Kolset
When sources are added, removed, or changed in the derived channel editor, the code group now shows a live "Variables: A0 = x[0] · encoder = x[1]" hint above the code editor so users know the exact names to use in scripts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Scripting: named signal vars, shared vars dict, control scripts, code templatesChristian Kolset
- Named variables in expressions: source channel IDs injected as Python identifiers alongside x[] (e.g. write `A0 * 2` instead of `x[0] * 2`) - Shared `vars` dict on SignalProcessor accessible from all expressions, functions, and control scripts; persisted in .labdaq profiles - Per-derived-channel `state` dict for persistent computation state in custom scripts - Control widgets gain optional on_action_script (Python snippet with value, vars, channels, math in scope); syntax-checked in editor dialog - Built-in derived kinds (derivative, rms, power, etc.) now show their Python implementation as read-only code previews; "Edit as Custom Script" converts to custom_script kind with template pre-filled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Channels/Signals UX: rename labels, calibration section, splitter, sig# IDsChristian Kolset
Naming conventions: - Devices window: "Channel ID" column → "Signal ID" - Serial device auto-gen IDs: CH# → sig# (generic, SCPI, Modbus fallbacks) - Channels window physical section: "Physical Channels" → "Signals" - Channels window virtual section: "Virtual Channels" → "Channels" - "Add Virtual" button → "Add Channel" - New derived channel default name: "Virtual Channel #" → "Channel #" Signals panel (ChannelPipelineBlock): - Remove Calibrate header button; add inline Calibration section in body - Remove Math section (Derivative/Integral) entirely - scale_offset excluded from filter dropdown; routed to Calibration section - Existing pipelines: scale_offset loads into Calibration, math filters dropped - _FILTER_KEYS excludes derivative, integral, scale_offset Splitter default position: - Left (Signals) pane initialised to minimum content width via QTimer.singleShot - setStretchFactor(0,0) / (1,1): left stays narrow, right grows on resize Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09UI: streamline Add Device dialog and serial config widgetChristian Kolset
Add Device dialog: - Replace Available Devices QGroupBox with plain devWindowTitle label to match Configuration section style — no box/border - Auto-scan on open via QTimer.singleShot(0) - Rename Scan All → Refresh Serial config widget: - Fix gap between protocol panel and Available Serial Ports: introduce _AdaptiveStack (QStackedWidget subclass) that reports only the current panel's sizeHint; set Fixed vertical size policy so the stack never expands beyond its content regardless of window size - Stretch added between Available Serial Ports and Apply & Reconnect so the button stays pinned to the bottom - Rename Channels → Signals in _GenericPanel and _SCPIPanel (Signal Queries) Config dialog (device info tab): - Rename Channels → Signals Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09Redesign Add Device dialog with streamlined two-section layoutChristian Kolset
- Unified device scanner at top (serial ports, NI devices, cameras) replaces per-panel scan groups; clicking pre-fills port/type - Port, baud rate, NI Device, and simulation toggle moved into Available Devices section; shown/hidden by device type - Device type, ID, and Protocol/Format grouped under Configuration - Panel order: Serial/UART → Arduino → NI-DAQ (Serial first) - Add button always enabled; validation on press shows error popup with focus jump to the offending field - CameraPanel loses sim_chk (sim state set by dialog via set_simulate) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04Merge plot-builder-layout-feature: layout canvas drag-drop fixesChristian Kolset
2026-06-04Fix split zone detection and canvas-edge squeeze behaviorChristian Kolset
Split zone was unreachable: squeezed between outer squeeze (1/6) and center (1/3), leaving a 1/6-wide band impossible to target reliably. Users hitting the tile edge always triggered squeeze instead of split. - Remove squeeze from inside-tile detection entirely. Split now owns the outer 1/3 of each tile edge — large, easy to target. Center keeps the inner 1/3. Squeeze remains gap-detection only (second pass). - Fix canvas-edge squeeze (no adjacent neighbor): instead of bisecting the edge tile, wrap the entire remaining tree in a new directional split with the dragged tile placed at the outer position, then equalize — all tiles end up equal size. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04Fix layout canvas drag-drop: zone logic, pop-out bug, canvas heightChristian Kolset
- Fix squeeze zone pop-out: _adjacent_tile used self._drag_tile (already None at drop time) to exclude the dragged tile from neighbor candidates. Pass dragged_idx explicitly via exclude_idx param instead. - Swap split/squeeze detection areas: outer 1/6 now triggers squeeze (insert-between), inner 1/6→1/3 band triggers split (bisect target). - Extend squeeze detection to gaps between tiles and canvas edges via a second pass in _zone_at that expands tile rects by _LC_GAP+2 and finds the closest outside edge. Canvas-edge squeeze falls through to bisect. - Dynamic canvas height: sync_panes sets height to max(160, n*(min_tile+gap)+2*gap) so 4+ panes never get crushed below minimum tile size. - Remove _tree_grid_size cap of 12: LCM was truncated prematurely, causing unequal rowspans in the strip chart for 5+ panes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Fix virtual channels not appearing in Plot BuilderChristian Kolset
Root cause: PlotWindow deepcopied _chart._cfg on init, so refresh_channels() operated on a stale copy. Also, when _win_plot was None, _on_derived_changed did nothing to prepare the config for when it opened. Fix: - refresh_channels() now owns the derived-pane sync: adds panes for new virtual channels (updating BSP tree), removes panes for deleted ones - _open_plot always passes current _chart._cfg reference then calls refresh_channels() so derived panes sync on every open - _on_derived_changed syncs cfg reference before calling refresh_channels() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Remove __pycache__ from trackingChristian Kolset
2026-06-03Changed dialog text window from channels to signals for clarityChristian Kolset
2026-06-03Fix horizontal zone detection for wide/short tilesChristian Kolset
_zone_at used absolute pixel distance to pick dominant axis, which always favoured the short (vertical) axis for landscape tiles. Replaced with normalized fraction (dist/dimension), so left/right zones are accessible on wide tiles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Three-zone drop system: squeeze, split, centerChristian Kolset
Zone layout (outer to inner per edge): 0–1/6 squeeze (amber): insert dropped tile between two existing tiles 1/6–1/3 split (blue): bisect the target tile 50/50 1/3–2/3 center (green): swap/move Squeeze behaviour: find the tile adjacent to the target in the drop direction; insert dragged tile to the opposite side of that neighbor so it lands visually between them. Falls back to split when no neighbor exists (edge tile). - _zone_at: returns "squeeze_*"/"split_*"/"center" with dominant-axis selection (nearest edge wins) - _adjacent_tile: finds geometrically adjacent tile by pixel proximity - _do_drop: routes squeeze→insert-at-neighbor, split→bisect, center→swap - paintEvent: amber/blue/green per zone type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Add center-zone move (swap); restore center drop zoneChristian Kolset
- Edge zones (left/right/top/bottom): split target tile equally - Center zone: swap dragged and target tile positions in BSP tree (tree structure preserved, only leaf pane values exchanged) - Center overlay: green highlight (vs blue for split zones) - _tree_swap: immutable swap of two leaf pane values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Equalize tile sizes after every BSP tree operationChristian Kolset
After each drag-drop, add, or remove, walk the tree and reset each split's ratio to left_leaves/(left_leaves+right_leaves). This distributes space equally per pane regardless of the prior tree structure. - _tree_equalize_ratios: recomputes all ratios bottom-up by leaf count - _tree_leaf_count: counts leaves in a subtree - _tree_grid_size: uses LCM of split denominators (capped at 12) instead of 2^depth, giving exact integer rowspans for equal distributions - _tree_default: balanced binary tree + equalize (not uneven chain) - Applied in _do_drop, _add_pane, _rm Result: 3 side-by-side panes → equal widths; any mixed layout → equal area. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Replace grid-snap canvas with BSP-tree drag-to-split tilingChristian Kolset
Dragging a tile to the edge (left/right/top/bottom) of another tile splits that tile's space and inserts the dragged tile on that side. Drop zones occupy the outer third of each tile edge; center has no zone. Ghost overlay shows the split target during drag. Data model: LayoutConfig.tree (BSP tree as nested dict). PaneSpec.row/col removed. strip_chart uses tree_to_grid() → rowspan/colspan for pyqtgraph. Backward compat: tree=None falls back to layout_mode stacking path. Example: 3 stacked → drag Tile 3 to left of Tile 1 → Tile 3|Tile 1 on top (side-by-side), Tile 2 spans full bottom. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Add drag-and-snap tiling layout canvas to Plot BuilderChristian Kolset
Replace layout radio buttons (Stacked/Side-by-Side/Grid) with a visual LayoutCanvas widget. Pane tiles snap to grid cells on drag-drop; dropping on an occupied cell swaps positions. Ghost highlight shows snap target during drag. - PaneSpec gains row/col fields (None = unassigned; backward compat preserved) - LayoutConfig gains "free" mode; strip_chart reads explicit positions - PaneBlock loses ▲▼ move buttons; reordering is canvas-only - QSS entries added for layoutTile, layoutTileDragging, layoutCanvasBar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Make Devices, Channels, Plot windows maximizableChristian Kolset
Remove Qt.WindowType.Tool flag — it suppresses the maximize button. Window now has standard chrome with minimize/maximize/close. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Redesign Channels window: side-by-side layout, picker dialog, collapsible ↵Christian Kolset
virtual blocks Physical channels panel: horizontal splitter (left/right), channel picker dialog replaces dropdown, Filters and Math sections per block, Calibrate button, pipeline-enabled checkbox removed. Virtual channels: collapsible blocks, enable checkbox removed, kind dropdown removed (defaults to expression). Window resized to 1400×820. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Apply saved settings (theme, poll rate) on startupChristian Kolset
Loaded settings were never consumed — theme defaulted to dark and poll interval stayed at 100 ms regardless of what was saved to disk. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Persist settings per-computer; add Reset to Defaults in General tabChristian Kolset
- New core/app_settings.py: load/save settings.json to platform config dir (Windows: %APPDATA%, Linux: ~/.config, macOS: ~/Library/Application Support) - MainWindow loads saved settings on startup, saves on every Apply & Close - Settings survive profile switches and app restarts independently of profiles - Settings > General: Reset to Defaults button with QMessageBox confirmation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Fix derivative sources visibility; code editor monospace + 4-space tabsChristian Kolset
- Always insert one source row for derivative/second_derivative on build - Guard in _on_kind: add row when switching to derivative with no sources - Code editor: monospace font (IBM Plex Mono/Consolas fallback) - Code editor: tab stop = 4 character widths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Rename signals_window.py → channels_window.py; update docstringChristian Kolset
Reflects current design: split Physical/Virtual Channels layout, no tabs, no legacy Signals list as primary UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Remove dead signal_builder.pyChristian Kolset
Superseded by ui/windows/signals_window.py; no longer imported anywhere. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Rename virtual channel defaults and Kind labelChristian Kolset
- Default ID: sig_N → CH_VN - Default name: Signal N → Virtual Channel N - Rename "Kind" label to "Operation" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Improve Channels window: split layout + derivative w.r.t. selectorChristian Kolset
- Split PipelineTab into Physical/Virtual sections via QSplitter - Each section has independent scroll area, header, and add controls - Add "With Respect To" control for derivative/second_derivative virtual channels: Time (dy/dt) or Channel (dy/dx) - When Channel selected, x variable picker appears for any physical or derived channel - signal_processor computes dy/dx using channel inputs when deriv_wrt=channel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Sync Channels window with device/channel changesChristian Kolset
- Remove redundant Signals tab from Channels window; PipelineTab is now direct content - PipelineTab removes stale blocks when devices are removed or reconfigured - Devices>Signals tab refreshes automatically on device add/remove/configure - Channel name and unit edits in Devices>Signals propagate live to Channels pipeline block headers; missing blocks auto-added if channel is enabled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Fix settings/plugin window bugs; remove example pluginChristian Kolset
- Add missing QGroupBox import to settings_window (caused silent crash on open) - Remove Controls tab and _controls_tab() method from settings - Clamp child window positions to screen bounds so they don't go off-screen on Windows when main window is maximized - Apply same screen-clamp fix to Motion Capture plugin window - Delete example plugin; disable it in enabled.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Added Motion Capture pluginChristian Kolset
Tracks a user-selected point via webcam (OpenCV CSRT tracker) and streams x_pos / y_pos pixel coordinates as live channels into the acquisition pipeline. Falls back to a Lissajous figure simulation when opencv-python is not installed, so the plugin works immediately without hardware. - tracker.py: background thread, real camera + sim modes, thread-safe API - device.py: BaseDevice reading x/y from tracker, wires into engine - filter.py: custom pixel_to_mm filter (registered in FILTER_CLASSES) - window.py: floating tool window, live feed, click-to-track ROI selection - plugin.py: LabPlugin wiring all pieces together, toolbar button, settings widget, profile save/restore Also: PluginAction.button_ref_callback lets plugins store a ref to their toolbar button so they can uncheck it when their window closes. opencv-python install note added to requirements.txt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03Added plugin/add-on systemChristian Kolset
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>