diff options
| -rw-r--r-- | core/acquisition.py | 18 | ||||
| -rw-r--r-- | core/profile.py | 6 | ||||
| -rw-r--r-- | ui/main_window.py | 1 | ||||
| -rw-r--r-- | ui/windows/config_window.py | 6 | ||||
| -rw-r--r-- | ui/windows/plot_window.py | 29 |
5 files changed, 34 insertions, 26 deletions
diff --git a/core/acquisition.py b/core/acquisition.py index 79bdb79..f54f265 100644 --- a/core/acquisition.py +++ b/core/acquisition.py @@ -112,6 +112,24 @@ class AcquisitionEngine(QObject): for ch in dev.info.channels: self._last_units[(dev.info.device_id, ch.channel_id)] = ch.unit + def on_device_reconfigured(self, device_id: str): + """Reconcile buffers with a device's current channel list — reconfigure() + can add/remove channels (e.g. Signals count changed), and buffers keyed + by the old channel list would silently drop data for anything new.""" + dev = next((d for d in self._devices if d.info.device_id == device_id), None) + if dev is None: + return + bufs = self._buffers.setdefault(device_id, {}) + valid_ids = {ch.channel_id for ch in dev.info.channels} + for stale_id in [cid for cid in bufs if cid not in valid_ids]: + bufs.pop(stale_id, None) + for ch in dev.info.channels: + if ch.channel_id not in bufs: + bufs[ch.channel_id] = ChannelBuffer() + self._last_units[(device_id, ch.channel_id)] = ch.unit + for key in [k for k in self._last_units if k[0] == device_id and k[1] not in valid_ids]: + self._last_units.pop(key, None) + def remove_device(self, device_id: str): self._devices = [d for d in self._devices if d.info.device_id != device_id] self._buffers.pop(device_id, None) diff --git a/core/profile.py b/core/profile.py index 8c73b4b..1fa667a 100644 --- a/core/profile.py +++ b/core/profile.py @@ -306,6 +306,12 @@ class ProfileManager: if engine is not None: engine.remove_device(dev.info.device_id) + # Clear signal pipelines and derived channels — profile defines the + # complete set; otherwise the previous profile's leftovers stick around. + processor._pipelines.clear() + for dc in list(processor.get_derived()): + processor.remove_derived(dc.channel_id) + for dev_cfg in profile.devices: dev_type = dev_cfg.get("device_type") dev_id = dev_cfg.get("device_id") diff --git a/ui/main_window.py b/ui/main_window.py index 19d4c69..012d6f5 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -731,6 +731,7 @@ class MainWindow(QMainWindow): self._status.setText(f"Device '{dev_id}' removed.") def _on_device_reconfigured(self, dev_id: str): + self.engine.on_device_reconfigured(dev_id) self._chart.refresh() if self._win_config: self._win_config.on_device_reconfigured(dev_id) diff --git a/ui/windows/config_window.py b/ui/windows/config_window.py index 64c669d..52612fc 100644 --- a/ui/windows/config_window.py +++ b/ui/windows/config_window.py @@ -347,6 +347,8 @@ class ConfigWindow(QWidget): row.name_changed.connect(self._on_device_renamed) self._dev_inner.insertWidget(self._dev_inner.count() - 1, row) self._dev_rows[dev.info.device_id] = row + if hasattr(self, "_signals_tab"): + self._signals_tab.refresh() def _refresh_device_status(self): for row in self._dev_rows.values(): @@ -375,7 +377,7 @@ class ConfigWindow(QWidget): dev = self.registry.get_instance(device_id) if dev: DeviceConfigDialog(dev, self).exec() - self._signals_tab.refresh() + self.refresh_devices() # rebuilds device rows — picks up new channel count self._pipeline_tab.on_device_reconfigured(device_id) self.device_reconfigured.emit(device_id) @@ -429,6 +431,8 @@ class ConfigWindow(QWidget): self._pipeline_tab = PipelineTab(self.registry, self.processor) self._pipeline_tab.changed.connect(self.pipeline_changed) self._pipeline_tab.derived_changed.connect(self.derived_changed) + self._pipeline_tab.refresh_derived() # load derived channels already in processor + # (e.g. window built fresh after profile load) inner_splitters = self._pipeline_tab.findChildren(QSplitter) if inner_splitters: inner_splitters[0].widget(0).setVisible(False) diff --git a/ui/windows/plot_window.py b/ui/windows/plot_window.py index 119efab..dc1617b 100644 --- a/ui/windows/plot_window.py +++ b/ui/windows/plot_window.py @@ -1092,33 +1092,12 @@ class PlotWindow(QWidget): QMessageBox.critical(self, "Import failed", str(e)) def refresh_channels(self): - """Sync virtual channel panes then rebuild UI.""" - existing = { - tr.channel_id - for p in self.cfg.panes - for tr in p.traces if tr.device_id == "derived" - } + """Drop panes whose sole trace is a deleted virtual channel, then rebuild UI. + Does NOT auto-add panes for new virtual channels — the operator adds those + explicitly via "+ Add Pane" / "+ Add Channel", so the plot layout only ever + shows what was deliberately built.""" current_ids = {dc.channel_id for dc in self.processor.get_derived()} - # Add panes for new virtual channels - for dc in self.processor.get_derived(): - if dc.channel_id not in existing: - new_idx = len(self.cfg.panes) - spec = PaneSpec(title=dc.name, y_label=dc.unit) - spec.traces.append(TraceSpec( - device_id="derived", channel_id=dc.channel_id, - label=dc.name, color=dc.color, - )) - self.cfg.panes.append(spec) - if self.cfg.tree is None: - self.cfg.tree = {"kind": "leaf", "pane": 0} - else: - self.cfg.tree = _tree_equalize_ratios({ - "kind": "vsplit", "ratio": 0.5, - "first": self.cfg.tree, - "second": {"kind": "leaf", "pane": new_idx}, - }) - # Remove panes whose sole trace is a deleted virtual channel removed = [ i for i, p in enumerate(self.cfg.panes) |
