From 91b0b613f0b58498315ef91f6a541dd1089b88de Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Tue, 9 Jun 2026 23:18:43 -0600 Subject: Derived channels: signal display names as vars, auto-update template code, None op MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- core/profile.py | 14 ++++++++------ core/signal_processor.py | 31 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) (limited to 'core') diff --git a/core/profile.py b/core/profile.py index 16c923f..d3c7e3a 100644 --- a/core/profile.py +++ b/core/profile.py @@ -197,12 +197,13 @@ class ProfileManager: "unit": dc.unit, "color": dc.color, "kind": dc.kind, - "sources": [list(s) for s in dc.sources], - "expression": dc.expression, - "script": dc.script, - "params": {k: v for k, v in dc.params.items() - if not k.startswith("_")}, - "enabled": dc.enabled, + "sources": [list(s) for s in dc.sources], + "source_names": list(dc.source_names), + "expression": dc.expression, + "script": dc.script, + "params": {k: v for k, v in dc.params.items() + if not k.startswith("_")}, + "enabled": dc.enabled, }) # Plot layout @@ -345,6 +346,7 @@ class ProfileManager: color=dd.get("color", "#f72585"), kind=dd.get("kind", "expression"), sources=sources, + source_names=dd.get("source_names", []), expression=dd.get("expression", ""), script=dd.get("script", ""), params=dd.get("params", {}), diff --git a/core/signal_processor.py b/core/signal_processor.py index e2752cd..bfb7ba3 100644 --- a/core/signal_processor.py +++ b/core/signal_processor.py @@ -200,24 +200,28 @@ def unregister_filter_class(name: str) -> None: # Helpers # ══════════════════════════════════════════════════════════════════════════════ -def _make_src_names(sources: List[Tuple[str, str]]) -> List[str]: +def _make_src_names(sources: List[Tuple[str, str]], + source_names: Optional[List[str]] = None) -> List[str]: """ - Build a list of Python-safe variable names from (device_id, channel_id) pairs. - Uses channel_id alone when unambiguous; prefixes device_id when two sources - share the same channel_id from different devices. + Build Python-safe variable names for each source. + Priority: user-defined name > channel_id (with device prefix if ambiguous). + User names are lowercased and spaces replaced with underscores. """ - seen: Dict[str, str] = {} # ch_id -> dev_id of first occurrence + seen: Dict[str, str] = {} names = [] - for dev_id, ch_id in sources: - if ch_id in seen and seen[ch_id] != dev_id: + for i, (dev_id, ch_id) in enumerate(sources): + user = source_names[i].strip() if (source_names and i < len(source_names)) else "" + if user: + raw = user.lower().replace(" ", "_") + elif ch_id in seen and seen[ch_id] != dev_id: raw = f"{dev_id}_{ch_id}" else: - seen[ch_id] = dev_id raw = ch_id - safe = re.sub(r"\W", "_", raw) + seen[ch_id] = dev_id + safe = re.sub(r"\W", "_", raw.lower()) if safe and safe[0].isdigit(): safe = "_" + safe - names.append(safe or "_ch") + names.append(safe or f"_ch{i}") return names @@ -252,6 +256,8 @@ class DerivedChannel: expression: str = "" # single-line: "x[0] * 2" script: str = "" # multi-line function body enabled: bool = True + # User-defined names for each source (e.g. ["force", "moment"]) + source_names: List[str] = field(default_factory=list) # Runtime: compiled callable and per-channel script state (not serialised) _fn: Optional[Callable] = field(default=None, repr=False, compare=False) _exec_state: dict = field(default_factory=dict, repr=False, compare=False) @@ -270,7 +276,7 @@ class DerivedChannel: state — per-channel persistent dict (survives between evaluations) """ try: - src_names = _make_src_names(self.sources) + src_names = _make_src_names(self.sources, self.source_names) if self.kind == "expression": code = compile(f"__result__ = {self.expression}", "", "exec") @@ -585,4 +591,7 @@ class SignalProcessor(QObject): elif dc.kind == "sum": return sum(inputs) + elif dc.kind == "none": + return inputs[0] if inputs else None + return None -- cgit v1.2.3