diff options
| author | Christian Kolset <christian.kolset@gmail.com> | 2026-06-09 22:14:20 -0600 |
|---|---|---|
| committer | Christian Kolset <christian.kolset@gmail.com> | 2026-06-09 22:14:20 -0600 |
| commit | 7aec470acd69e9c1810b40aa5eea9b96b9eb73ad (patch) | |
| tree | 9146091077ec64ed1437621ebf2725edf987801f /ui/windows/channels_window.py | |
| parent | b138e72d6f81cf58f27c6c1e4ce65cfebbb58624 (diff) | |
Scripting: named signal vars, shared vars dict, control scripts, code templates
- 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>
Diffstat (limited to 'ui/windows/channels_window.py')
| -rw-r--r-- | ui/windows/channels_window.py | 63 |
1 files changed, 55 insertions, 8 deletions
diff --git a/ui/windows/channels_window.py b/ui/windows/channels_window.py index f0ea34e..ecc7cbc 100644 --- a/ui/windows/channels_window.py +++ b/ui/windows/channels_window.py @@ -606,9 +606,21 @@ _KIND_HELP = { "rms": "Rolling RMS from 1 source (set window below)", "difference": "source[0] − source[1] from 2 sources", "sum": "Σ all source values", - "expression": "x = list of source values, t = elapsed time (s)\nExample: x[0] * 3.14159 / 180", - "function": "def compute(x, t):\n # x = source values list\n return x[0] * 2.0", - "custom_script":"Full Python. Must define: def compute(x, t): ...", + "expression": ( + "Single-line Python expression.\n" + "Available: x[0]… or use signal names directly (e.g. A0, encoder_0)\n" + " t = elapsed time (s), math, vars, state\n" + "Example: A0 * 3.14159 / 180" + ), + "function": ( + "Multi-line function body — auto-wrapped as def compute(x, t):\n" + "Available globals: x, signal names, t, math, vars, state\n" + "Example:\n offset = vars.get('zero', 0)\n return x[0] - offset" + ), + "custom_script": ( + "Full Python. Must define: def compute(x, t): ...\n" + "Available globals: x, signal names, t, math, vars, state" + ), } @@ -728,17 +740,28 @@ class DerivedBlock(QFrame): wrt_lay.addWidget(self._wrt_x_row) bl.addWidget(self._wrt_grp) - # Code editor + # Code editor (shown for all kinds — read-only for built-ins) self._code_grp = QGroupBox("Code") code_lay = QVBoxLayout(self._code_grp); code_lay.setContentsMargins(6, 4, 6, 4) + _mono = QFont("IBM Plex Mono, Consolas, Monospace"); _mono.setStyleHint(QFont.StyleHint.Monospace) self._code = QTextEdit(); self._code.setObjectName("codeEditor") self._code.setMinimumHeight(90); self._code.setMaximumHeight(180) - _mono = QFont("IBM Plex Mono, Consolas, Monospace"); _mono.setStyleHint(QFont.StyleHint.Monospace) self._code.setFont(_mono) self._code.setTabStopDistance(QFontMetrics(_mono).horizontalAdvance(" ") * 4) - txt = self.dc.expression if self.dc.kind == "expression" else self.dc.script + if self.dc.kind in _BUILTIN: + from ui.code_templates import BUILTIN_TEMPLATES + txt = BUILTIN_TEMPLATES.get(self.dc.kind, "") + elif self.dc.kind == "expression": + txt = self.dc.expression + else: + txt = self.dc.script self._code.setPlainText(txt) code_lay.addWidget(self._code) + # "Edit as Custom Script" button — only visible for built-in kinds + self._edit_as_script_btn = QPushButton("✎ Edit as Custom Script") + self._edit_as_script_btn.setObjectName("addTraceBtn") + self._edit_as_script_btn.clicked.connect(self._convert_to_custom_script) + code_lay.addWidget(self._edit_as_script_btn) bl.addWidget(self._code_grp) # Status + apply @@ -783,19 +806,43 @@ class DerivedBlock(QFrame): if it.widget(): it.widget().deleteLater() def _on_kind(self, idx: int): + from ui.code_templates import BUILTIN_TEMPLATES k = _ALL_KINDS[idx] self.dc.kind = k self._help.setText(_KIND_HELP.get(k, "")) if k in ("derivative", "second_derivative") and not self._src_combos: self._add_src() + if k in _BUILTIN: + # Show the template as a read-only preview + self._code.setPlainText(BUILTIN_TEMPLATES.get(k, "")) self._update_visibility() def _on_wrt_changed(self, idx: int): self._wrt_x_row.setVisible(idx == 1) + def _convert_to_custom_script(self): + """Switch kind to custom_script, pre-fill with built-in template, make editable.""" + from ui.code_templates import BUILTIN_TEMPLATES + template = BUILTIN_TEMPLATES.get(self.dc.kind, "") + # Switch kind combo to custom_script + idx = _ALL_KINDS.index("custom_script") + self._kind_cb.blockSignals(True) + self._kind_cb.setCurrentIndex(idx) + self._kind_cb.blockSignals(False) + self.dc.kind = "custom_script" + self._help.setText(_KIND_HELP.get("custom_script", "")) + self.dc.script = template + self._code.setPlainText(template) + self._update_visibility() + def _update_visibility(self): - is_deriv = self.dc.kind in ("derivative", "second_derivative") - self._code_grp.setVisible(self.dc.kind in _SCRIPT) + is_deriv = self.dc.kind in ("derivative", "second_derivative") + is_builtin = self.dc.kind in _BUILTIN + is_script = self.dc.kind in _SCRIPT + # Code group shown for all kinds; read-only for built-ins + self._code_grp.setVisible(True) + self._code.setReadOnly(is_builtin) + self._edit_as_script_btn.setVisible(is_builtin) self._win_widget.setVisible(self.dc.kind == "rms") self._wrt_grp.setVisible(is_deriv) self._add_src_btn.setVisible(not is_deriv) |
