From a3ff221496f58d67ac962bfa6d011cc3e9d6ad3d Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Wed, 3 Jun 2026 17:11:41 -0600 Subject: Fix virtual channels not appearing in Plot Builder 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 --- ui/main_window.py | 8 ++++++-- ui/windows/plot_window.py | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) (limited to 'ui') diff --git a/ui/main_window.py b/ui/main_window.py index 5078a60..ebc7463 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -352,9 +352,10 @@ class MainWindow(QMainWindow): self._chart._cfg, self) self._win_plot.layout_applied.connect(self._chart.apply_layout) self._win_plot.closed.connect(lambda: self._btn_plot.setChecked(False)) + self._win_plot.refresh_channels() # sync any derived channels else: self._win_plot.cfg = self._chart._cfg - self._win_plot._populate() + self._win_plot.refresh_channels() self._show_win(self._win_plot, "below") def _open_settings(self): @@ -507,7 +508,10 @@ class MainWindow(QMainWindow): def _on_derived_changed(self): self._chart.refresh() - if self._win_plot: self._win_plot.refresh_channels() + if self._win_plot: + # Ensure plot window works on current chart cfg, then sync derived panes + self._win_plot.cfg = self._chart._cfg + self._win_plot.refresh_channels() # ── Run / Log ───────────────────────────────────────────────────────── diff --git a/ui/windows/plot_window.py b/ui/windows/plot_window.py index f929261..95b520c 100644 --- a/ui/windows/plot_window.py +++ b/ui/windows/plot_window.py @@ -357,7 +357,6 @@ class PaneBlock(QFrame): self._x_cb.addItem("⏱ Time (elapsed s)", userData="time") for dev in self.registry.all_instances(): for ch in dev.info.channels: - if not ch.enabled: continue self._x_cb.addItem(f"{dev.info.device_id}/{ch.channel_id} ({ch.name})", userData=f"{dev.info.device_id}/{ch.channel_id}") for dc in self.processor.get_derived(): @@ -420,7 +419,6 @@ class PaneBlock(QFrame): cb = QComboBox(); cb.setObjectName("channelPickerCb") for dev in self.registry.all_instances(): for ch in dev.info.channels: - if not ch.enabled: continue cb.addItem(f"{dev.info.device_id} / {ch.channel_id} ({ch.name})", userData=(dev.info.device_id, ch.channel_id, ch.name, ch.color)) for dc in self.processor.get_derived(): @@ -986,6 +984,47 @@ 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" + } + 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) + if len(p.traces) == 1 + and p.traces[0].device_id == "derived" + and p.traces[0].channel_id not in current_ids + ] + for i in reversed(removed): + self.cfg.panes.pop(i) + if self.cfg.tree: + self.cfg.tree = _tree_remove(self.cfg.tree, i) + if self.cfg.tree: + self.cfg.tree = _tree_equalize_ratios(_tree_reindex(self.cfg.tree, i)) + self._populate() def closeEvent(self, e: QCloseEvent): -- cgit v1.2.3