From 0fa8f9652f6deedb792966ec2b1632cb143c54f6 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Wed, 3 Jun 2026 12:09:08 -0600 Subject: Improve Channels window: split layout + derivative w.r.t. selector - Split PipelineTab into Physical/Virtual sections via QSplitter - Each section has independent scroll area, header, and add controls - Add "With Respect To" control for derivative/second_derivative virtual channels: Time (dy/dt) or Channel (dy/dx) - When Channel selected, x variable picker appears for any physical or derived channel - signal_processor computes dy/dx using channel inputs when deriv_wrt=channel Co-Authored-By: Claude Sonnet 4.6 --- core/signal_processor.py | 53 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 18 deletions(-) (limited to 'core/signal_processor.py') diff --git a/core/signal_processor.py b/core/signal_processor.py index 723de66..df80dbb 100644 --- a/core/signal_processor.py +++ b/core/signal_processor.py @@ -442,28 +442,45 @@ class SignalProcessor(QObject): return dc._fn(inputs, t) elif dc.kind == "derivative": - # derivative of source[0] - src = dc.sources[0] if dc.sources else None - if src is None: return None + if not dc.sources: return None state = dc.params.setdefault("_state", {}) - prev_t = state.get("t"); prev_v = state.get("v") - state["t"] = t; state["v"] = inputs[0] - if prev_t is None or t == prev_t: return 0.0 - return (inputs[0] - prev_v) / (t - prev_t) + prev_v = state.get("v") + state["v"] = inputs[0] + if dc.params.get("deriv_wrt") == "channel": + if len(inputs) < 2: return None + prev_x = state.get("x") + state["x"] = inputs[1] + if prev_x is None or inputs[1] == prev_x: return 0.0 + return (inputs[0] - prev_v) / (inputs[1] - prev_x) + else: + prev_t = state.get("t") + state["t"] = t + if prev_t is None or t == prev_t: return 0.0 + return (inputs[0] - prev_v) / (t - prev_t) elif dc.kind == "second_derivative": - # second derivative — derivative of derivative - src = dc.sources[0] if dc.sources else None - if src is None: return None + if not dc.sources: return None state = dc.params.setdefault("_state", {}) - prev_t = state.get("t"); prev_v = state.get("v") - cur_vel = 0.0 - if prev_t is not None and t != prev_t: - cur_vel = (inputs[0] - prev_v) / (t - prev_t) - prev_vel = state.get("vel", 0.0) - state["t"] = t; state["v"] = inputs[0]; state["vel"] = cur_vel - if prev_t is None or t == prev_t: return 0.0 - return (cur_vel - prev_vel) / (t - prev_t) + prev_v = state.get("v") + if dc.params.get("deriv_wrt") == "channel": + if len(inputs) < 2: return None + prev_x = state.get("x") + cur_vel = 0.0 + if prev_x is not None and inputs[1] != prev_x: + cur_vel = (inputs[0] - prev_v) / (inputs[1] - prev_x) + prev_vel = state.get("vel", 0.0) + state["x"] = inputs[1]; state["v"] = inputs[0]; state["vel"] = cur_vel + if prev_x is None or inputs[1] == prev_x: return 0.0 + return (cur_vel - prev_vel) / (inputs[1] - prev_x) + else: + prev_t = state.get("t") + cur_vel = 0.0 + if prev_t is not None and t != prev_t: + cur_vel = (inputs[0] - prev_v) / (t - prev_t) + prev_vel = state.get("vel", 0.0) + state["t"] = t; state["v"] = inputs[0]; state["vel"] = cur_vel + if prev_t is None or t == prev_t: return 0.0 + return (cur_vel - prev_vel) / (t - prev_t) elif dc.kind == "power": # V * I -- cgit v1.2.3