| Age | Commit message (Collapse) | Author |
|
|
|
|
|
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>
|
|
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>
|
|
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>
|
|
|
|
motion_capture setting to false
|
|
|
|
communication
|
|
|
|
|
|
|
|
|
|
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>
|
|
- 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>
|
|
- 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>
|
|
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>
|
|
- 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>
|
|
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>
|
|
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>
|
|
Profile integration:
- CameraDevice.get_save_config() serialises camera_index, simulate,
resolution, fps — cameras now persist in .labdaq profiles
- ProfileManager.register/unregister_device_factory() lets plugins
register their device classes for profile restoration without
core → plugin dependency
- MotionCapturePlugin registers CameraDevice factory on load/unload
Camera configuration:
- Add Frame Rate combo to CameraPanel (Default/15/24/30/60/120 fps)
defaults to 30 fps; flows through to CAP_PROP_FPS on connect
- tracker.start() accepts fps param alongside existing resolution
Tracking persistence:
- CameraTracker.is_running() — checks thread alive state
- MotionCaptureWindow initialises _connected from tracker.is_running()
so reopening the window resumes the live feed without restarting
the camera or losing the active tracking state
Window state:
- Plugin saves px_per_mm + camera_idx when window closes
- Reopened window restores saved values and syncs mode/shape UI
from tracker state via restore_ui_state() (signals blocked to
avoid resetting tracker)
- Window type changed to Qt.WindowType.Window for independent
close button on all Linux WMs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Tracking:
- Add "template" mode (default) using snapshot + shape-masked matchTemplate
- Add "csrt" mode as alternative for texture-rich targets
- set_roi_rect(x, y, w, h, frame_rgb) initialises from exact selection rect
- set_roi() delegates to set_roi_rect (centred square, backwards-compat)
- start() accepts resolution=(w, h) to set camera capture resolution
Selection UX:
- Replace QLabel feed with FrameSelector widget (custom QWidget)
- "Click to Track" freezes frame; user scrolls to zoom (anchored to cursor),
right-drags to pan, left-drags to draw rubber-band selection
- Shape preview: rectangle or inscribed ellipse depending on mode
- "Confirm Selection" extracts template from frozen frame and starts tracking
- "Clear" cancels selection or clears active tracking
Configuration:
- CameraPanel adds Resolution combo (Default / 640×480 / 720p / 1080p / 1440p)
- CameraDevice stores and forwards resolution to tracker.start()
- Template Match listed first in Mode combo; shape combo hidden for CSRT
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- 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>
|
|
|
|
|
|
get_toolbar_actions() returns a checkable 🎥 button. On click:
- 0 camera devices → info message, button unchecks
- 1 camera device → opens MotionCaptureWindow directly
- N camera devices → QInputDialog picker, opens selected device's window
Windows tracked per device_id; closing a window removes it from the
map and unchecks the button when none remain. on_unload closes all
open windows before deregistering the Camera panel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Plugin now registers a "Camera" option in the Add Device dialog on load
(removes it on unload) instead of auto-creating a single device.
- camera_panel.py (new): CameraPanel with CameraScanThread that probes
OpenCV indices 0–5, lists found cameras with resolution, shows
"No cameras detected" when none available, simulation checkbox.
build_device() returns CameraDevice for the selected index.
- device.py: rewrite MotionCaptureDevice → CameraDevice. Each instance
owns its own CameraTracker created in connect(). Status is CONNECTED
or SIMULATED based on tracker.simulated. get_config_widget() shows
source/status and an "Open Camera View" button that launches the
existing MotionCaptureWindow for that device's tracker. Backward-compat
alias MotionCaptureDevice = CameraDevice kept for saved profiles.
- plugin.py: simplified to on_load/_PANELS registration + pixel_to_mm
filter. No auto-devices, no toolbar button, no settings widget.
Camera data flows through the standard Device → Signal → Channel → Plot
pipeline like any other device.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
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>
|
|
- 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>
|
|
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>
|
|
|
|
|
|
Replaces layout radio buttons (Stacked/Side-by-Side/Grid) with a
drag-and-drop tiling canvas. Three drop zones per tile edge:
- squeeze (outer 1/6, amber): insert between two tiles
- split (1/6–1/3, blue): bisect tile 50/50
- center (green): swap tile positions
BSP tree stores layout; tree_to_grid() converts to rowspan/colspan for
pyqtgraph. _tree_equalize_ratios ensures all tiles share space equally
after any operation. Backward-compatible with existing .labdaq profiles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
_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>
|
|
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>
|
|
- 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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
- 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>
|
|
- 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>
|
|
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>
|
|
Superseded by ui/windows/signals_window.py; no longer imported anywhere.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- 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>
|
|
- 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>
|