summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorChristian Kolset <christian.kolset@gmail.com>2026-06-09 23:18:43 -0600
committerChristian Kolset <christian.kolset@gmail.com>2026-06-09 23:18:43 -0600
commit91b0b613f0b58498315ef91f6a541dd1089b88de (patch)
tree65b378d6ad2706ac1e29a7e7888c78770d4c945a /core
parentd396692de571445537e617ac31627d7bd7ab84aa (diff)
Derived channels: signal display names as vars, auto-update template code, 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>
Diffstat (limited to 'core')
-rw-r--r--core/profile.py14
-rw-r--r--core/signal_processor.py31
2 files changed, 28 insertions, 17 deletions
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}", "<expr>", "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