From cf31fd7908c794c7842c5cca3268ed380d5645f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Mon, 11 Jul 2022 21:37:06 +0200 Subject: [PATCH] Basic working support for math operations between traces --- .../Calibration/calibration.cpp | 4 +- .../PC_Application/Traces/Marker/marker.cpp | 2 +- Software/PC_Application/Traces/trace.cpp | 399 ++++++++++++++++-- Software/PC_Application/Traces/trace.h | 75 +++- .../PC_Application/Traces/traceeditdialog.cpp | 100 ++++- .../PC_Application/Traces/traceeditdialog.ui | 44 +- Software/PC_Application/Traces/tracemodel.cpp | 17 +- .../PC_Application/Traces/tracewidget.cpp | 2 + 8 files changed, 579 insertions(+), 64 deletions(-) diff --git a/Software/PC_Application/Calibration/calibration.cpp b/Software/PC_Application/Calibration/calibration.cpp index 96d5b95..1645447 100644 --- a/Software/PC_Application/Calibration/calibration.cpp +++ b/Software/PC_Application/Calibration/calibration.cpp @@ -672,7 +672,7 @@ std::vector Calibration::getErrorTermTraces() constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false}; for(int i=0;i<12;i++) { auto t = new Trace(traceNames[i], Qt::red); - t->setCalibration(true); + t->setCalibration(); t->setReflection(reflection[i]); traces.push_back(t); } @@ -728,7 +728,7 @@ std::vector Calibration::getMeasurementTraces() } for(auto prefix : usedPrefixes) { auto t = new Trace(prefix + " " + info.name); - t->setCalibration(true); + t->setCalibration(); t->setReflection(prefix == "S11" || prefix == "S22"); for(auto p : m.second.datapoints) { Trace::Data d; diff --git a/Software/PC_Application/Traces/Marker/marker.cpp b/Software/PC_Application/Traces/Marker/marker.cpp index 7e8e970..055960a 100644 --- a/Software/PC_Application/Traces/Marker/marker.cpp +++ b/Software/PC_Application/Traces/Marker/marker.cpp @@ -870,7 +870,7 @@ std::set Marker::getSupportedTypes() supported.insert(Type::Highpass); supported.insert(Type::Bandpass); } - if(parentTrace->isLive()) { + if(parentTrace->getSource() == Trace::Source::Live) { switch(parentTrace->liveParameter()) { case Trace::LiveParameter::S11: case Trace::LiveParameter::S12: diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index dbb38b7..3515876 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -4,6 +4,8 @@ #include "Util/util.h" #include "Marker/marker.h" #include "traceaxis.h" +#include "tracemodel.h" +#include "Math/parser/mpParser.h" #include #include @@ -12,19 +14,23 @@ #include using namespace std; +using namespace mup; Trace::Trace(QString name, QColor color, LiveParameter live) - : _name(name), + : model(nullptr), + _name(name), _color(color), + source(Source::Live), + hash(0), + hashSet(false), + JSONskipHash(false), _liveType(LivedataType::Overwrite), _liveParam(live), vFactor(0.66), reflection(true), - reference_impedance(50.0), visible(true), paused(false), - createdFromFile(false), - calibration(false), + reference_impedance(50.0), domain(DataType::Frequency), lastMath(nullptr) { @@ -32,6 +38,10 @@ Trace::Trace(QString name, QColor color, LiveParameter live) mathOps.push_back(self); updateLastMath(mathOps.rbegin()); + lastMathUpdate = QTime::currentTime(); + mathCalcTimer.setSingleShot(true); + connect(&mathCalcTimer, &QTimer::timeout, this, &Trace::calculateMath); + self.enabled = false; dataType = DataType::Frequency; connect(this, &Trace::typeChanged, [=](){ @@ -52,8 +62,8 @@ Trace::~Trace() emit deleted(this); } -void Trace::clear() { - if(paused) { +void Trace::clear(bool force) { + if(paused && !force) { return; } data.clear(); @@ -166,7 +176,8 @@ void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter) break; } } - createdFromFile = true; + clearMathSources(); + source = Source::File; reference_impedance = t.getReferenceImpedance(); emit typeChanged(this); emit outputSamplesChanged(0, data.size()); @@ -241,7 +252,8 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter) addData(d, domain); } reflection = false; - createdFromFile = true; + clearMathSources(); + source = Source::File; emit typeChanged(this); emit outputSamplesChanged(0, data.size()); return lastTraceName; @@ -269,7 +281,8 @@ void Trace::fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, c void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param) { - createdFromFile = false; + clearMathSources(); + source = Source::Live; _liveType = type; _liveParam = param; if(param == LiveParameter::S11 || param == LiveParameter::S22) { @@ -280,6 +293,15 @@ void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param) emit typeChanged(this); } +void Trace::fromMath() +{ + source = Source::Math; + clear(); + updateMathTracePoints(); + scheduleMathCalculation(0, data.size()); + emit typeChanged(this); +} + void Trace::setColor(QColor color) { if(_color != color) { _color = color; @@ -309,6 +331,281 @@ void Trace::markerVisibilityChanged(Marker *m) emit visibilityChanged(this); } +const QString &Trace::getMathFormula() const +{ + return mathFormula; +} + +void Trace::setMathFormula(const QString &newMathFormula) +{ + mathFormula = newMathFormula; +} + +bool Trace::mathFormularValid() const +{ + if(mathFormula.isEmpty()) { + return false; + } + try { + ParserX parser(pckCOMMON | pckUNIT | pckCOMPLEX); + parser.SetExpr(mathFormula.toStdString()); + auto vars = parser.GetExprVar(); + for(auto var : vars) { + auto varName = QString::fromStdString(var.first); + // try to find variable name + bool found = false; + for(auto ms : mathSourceTraces) { + if(ms.second == varName) { + found = true; + break; + } + } + if(!found) { + return false; + } + } + } catch (const ParserError &e) { + // parser error occurred + return false; + } + // all variables used in the expression are set as math sources + return true; +} + +bool Trace::resolveMathSourceHashes() +{ + bool success = true; + for(auto unresolved : mathSourceUnresolvedHashes) { + if(!addMathSource(unresolved.first, unresolved.second)) { + success = false; + } + } + if(success) { + mathSourceUnresolvedHashes.clear(); + } + return success; +} + +bool Trace::updateMathTracePoints() +{ + if(!mathSourceTraces.size()) { + return false; + } + double startX = std::numeric_limits::lowest(); + double stopX = std::numeric_limits::max(); + double stepSize = std::numeric_limits::max(); + for(auto t : mathSourceTraces) { + if(t.first->minX() > startX) { + startX = t.first->minX(); + } + if(t.first->maxX() < stopX) { + stopX = t.first->maxX(); + } + double traceStepSize = std::numeric_limits::max(); + if(t.first->numSamples() > 1) { + traceStepSize = (t.first->maxX() - t.first->minX()) / (t.first->numSamples() - 1); + } + if(traceStepSize < stepSize) { + stepSize = traceStepSize; + } + } + unsigned int samples = round((stopX - startX) / stepSize + 1); + bool fullUpdate = false; + if(samples != data.size()) { + data.resize(samples); + fullUpdate = true; + } + if(samples > 0 && (startX != data.front().x || stopX != data.back().x)) { + fullUpdate = true; + } + if(fullUpdate) { + // steps changed, complete update required + mathUpdateBegin = 0; + mathUpdateEnd = samples; + for(unsigned int i=0;i>::quiet_NaN(); + } + return true; + } else { + return false; + } +} + +void Trace::mathSourceTraceDeleted(Trace *t) +{ + if (mathSourceTraces.count(t)) { + removeMathSource(t); + } +} + +void Trace::scheduleMathCalculation(unsigned int begin, unsigned int end) +{ + if(source != Source::Math) { + return; + } + if(begin < mathUpdateBegin) { + mathUpdateBegin = begin; + } + if(end > mathUpdateEnd) { + mathUpdateEnd = end; + } + auto now = QTime::currentTime(); + if (lastMathUpdate.msecsTo(now) >= MinMathUpdateInterval) { + calculateMath(); + } else { + mathCalcTimer.start(MinMathUpdateInterval); + } +} + +void Trace::calculateMath() +{ + lastMathUpdate = QTime::currentTime(); + if(mathUpdateBegin >= data.size() || mathUpdateEnd >= data.size() + 1) { + return; + } + if(mathFormula.isEmpty()) { + error("Expression is empty"); + return; + } + if(!isPaused()) { + try { + ParserX parser(pckCOMMON | pckUNIT | pckCOMPLEX); + parser.SetExpr(mathFormula.toStdString()); + map values; + Value x; + parser.DefineVar("x", Variable(&x)); + for(const auto &ts : mathSourceTraces) { + values[ts.first] = Value(); + parser.DefineVar(ts.second.toStdString(), Variable(&values[ts.first])); + } + for(unsigned int i=mathUpdateBegin;iinterpolatedSample(data[i].x).y; + } + Value res = parser.Eval(); + data[i].y = res.GetComplex(); + } + } catch (const ParserError &e) { + error(QString::fromStdString(e.GetMsg())); + // parser error occurred + for(unsigned int i=mathUpdateBegin;i>::quiet_NaN(); + } + } + success(); + emit outputSamplesChanged(mathUpdateBegin, mathUpdateEnd + 1); + } + mathUpdateBegin = data.size(); + mathUpdateEnd = 0; +} + +void Trace::clearMathSources() +{ + while(mathSourceTraces.size() > 0) { + removeMathSource(mathSourceTraces.begin()->first); + } +} + +bool Trace::addMathSource(unsigned int hash, QString variableName) +{ + if(!model) { + return false; + } + for(auto t : model->getTraces()) { + if(t->toHash() == hash) { + return addMathSource(t, variableName); + } + } + return false; +} + +bool Trace::mathDependsOn(Trace *t, bool onlyDirectDependency) +{ + if(mathSourceTraces.count(t)) { + return true; + } + if(onlyDirectDependency) { + return false; + } else { + // also check math source traces recursively + for(auto m : mathSourceTraces) { + if(m.first->mathDependsOn(t)) { + return true; + } + } + return false; + } +} + +bool Trace::canAddAsMathSource(Trace *t) +{ + if(t == this) { + // can't add itself + return false; + } + // check if we would create a loop of math traces depending on each other + if(t->mathDependsOn(this)) { + return false; + } + if(mathSourceTraces.size() == 0) { + // no traces used as source yet, can add anything + return true; + } else { + // can only add traces of the same domain + if(mathSourceTraces.begin()->first->outputType() == t->outputType()) { + return true; + } else { + return false; + } + } +} + +bool Trace::addMathSource(Trace *t, QString variableName) +{ +// qDebug() << "Adding trace" << t << "as a math source to" << this << "as variable" << variableName; + if(!canAddAsMathSource(t)) { + return false; + } + mathSourceTraces[t] = variableName; + connect(t, &Trace::deleted, this, &Trace::mathSourceTraceDeleted, Qt::UniqueConnection); + connect(t, &Trace::dataChanged, this, [=](unsigned int begin, unsigned int end){ + updateMathTracePoints(); + auto startX = t->sample(begin).x; + auto stopX = t->sample(end).x; + scheduleMathCalculation(index(startX), index(stopX)); + }); + return true; +} + +void Trace::removeMathSource(Trace *t) +{ +// qDebug() << "Removing trace" << t << "as a math source from" << this; + mathSourceTraces.erase(t); + disconnect(t, &Trace::deleted, this, &Trace::mathSourceTraceDeleted); + disconnect(t, &Trace::dataChanged, this, nullptr); +} + +QString Trace::getSourceVariableName(Trace *t) +{ + if(mathSourceTraces.count(t)) { + return mathSourceTraces[t]; + } else { + return QString(); + } +} + +TraceModel *Trace::getModel() const +{ + return model; +} + +void Trace::setModel(TraceModel *model) +{ + this->model = model; +} + double Trace::getReferenceImpedance() const { return reference_impedance; @@ -347,22 +644,41 @@ double Trace::distanceToTime(double distance) nlohmann::json Trace::toJSON() { nlohmann::json j; - if(isCalibration()) { + if(!JSONskipHash) { + j["hash"] = toHash(true); + } + if(source == Source::Calibration) { // calibration traces can't be saved return j; } j["name"] = _name.toStdString(); j["color"] = _color.name().toStdString(); j["visible"] = visible; - if(isLive()) { + switch(source) { + case Source::Live: j["type"] = "Live"; j["parameter"] = _liveParam; j["livetype"] = _liveType; j["paused"] = paused; - } else if(isFromFile()) { + break; + case Source::File: j["type"] = "File"; j["filename"] = filename.toStdString(); j["parameter"] = fileParameter; + break; + case Source::Math: { + j["type"] = "Math"; + j["expression"] = mathFormula.toStdString(); + nlohmann::json jsources; + for(auto ms : mathSourceTraces) { + nlohmann::json jsource; + jsource["trace"] = ms.first->toHash(); + jsource["variable"] = ms.second.toStdString(); + jsources.push_back(jsource); + } + j["sources"] = jsources; + } + break; } j["velocityFactor"] = vFactor; j["reflection"] = reflection; @@ -388,8 +704,13 @@ nlohmann::json Trace::toJSON() void Trace::fromJSON(nlohmann::json j) { - createdFromFile = false; - calibration = false; + source = Source::Live; + if(j.contains("hash")) { + hash = j["hash"]; + hashSet = true; + } else { + hashSet = false; + } _name = QString::fromStdString(j.value("name", "Missing name")); _color = QColor(QString::fromStdString(j.value("color", "yellow"))); visible = j.value("visible", true); @@ -414,6 +735,19 @@ void Trace::fromJSON(nlohmann::json j) std::string what = e.what(); throw runtime_error("Failed to create from file:" + what); } + } else if(type == "Math") { + mathFormula = QString::fromStdString(j.value("expression", "")); + if(j.contains("sources")) { + for(auto js : j["sources"]) { + auto hash = js.value("trace", 0); + QString varName = QString::fromStdString(js.value("variable", "")); + if(!addMathSource(hash, varName)) { + qWarning() << "Unable to find requested math source trace ( hash:"<{}(json_string); + if(!hashSet || forceUpdate) { + // taking the easy way: create the json string and hash it (already contains all necessary information) + // This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem + JSONskipHash = true; + std::string json_string = toJSON().dump(); + JSONskipHash = false; + hash = std::hash{}(json_string); + hashSet = true; + } + return hash; } std::vector Trace::createFromTouchstone(Touchstone &t) @@ -687,9 +1027,9 @@ QString Trace::description() return name() + ": measured data"; } -void Trace::setCalibration(bool value) +void Trace::setCalibration() { - calibration = value; + source = Source::Calibration; } std::set Trace::getMarkers() const @@ -731,21 +1071,6 @@ bool Trace::isPaused() return paused; } -bool Trace::isFromFile() -{ - return createdFromFile; -} - -bool Trace::isCalibration() -{ - return calibration; -} - -bool Trace::isLive() -{ - return !isCalibration() && !isFromFile(); -} - bool Trace::isReflection() { return reflection; @@ -997,7 +1322,7 @@ unsigned int Trace::getFileParameter() const double Trace::getNoise(double frequency) { - if(!isLive() || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) { + if(source != Trace::Source::Live || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) { // data not suitable for noise calculation return std::numeric_limits::quiet_NaN(); } diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 17802f1..d4cb415 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -6,7 +6,6 @@ #include "Device/device.h" #include "Math/tracemath.h" #include "Tools/parameters.h" -#include "tracemodel.h" #include "VNA/vnadata.h" #include @@ -14,8 +13,10 @@ #include #include #include +#include class Marker; +class TraceModel; class Trace : public TraceMath { @@ -24,6 +25,14 @@ public: using Data = TraceMath::Data; + enum class Source { + Live, + File, + Math, + Calibration, + Last, + }; + enum class LiveParameter { S11, S12, @@ -44,7 +53,7 @@ public: Invalid, }; - void clear(); + void clear(bool force = false); void addData(const Data& d, DataType domain, double reference_impedance = 50.0, int index = -1); void addData(const Data& d, const Protocol::SpectrumAnalyzerSettings& s, int index = -1); void setName(QString name); @@ -53,15 +62,14 @@ public: QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data) static void fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector &data); void fromLivedata(LivedataType type, LiveParameter param); + void fromMath(); QString name() { return _name; } QColor color() { return _color; } bool isVisible(); void pause(); void resume(); bool isPaused(); - bool isFromFile(); - bool isCalibration(); - bool isLive(); + Source getSource() {return source;} bool isReflection(); LiveParameter liveParameter() { return _liveParam; } LivedataType liveType() { return _liveType; } @@ -92,7 +100,7 @@ public: double getNoise(double frequency); int index(double x); std::set getMarkers() const; - void setCalibration(bool value); + void setCalibration(); void setReflection(bool value); DataType outputType(DataType inputType) override; @@ -129,7 +137,7 @@ public: // When saving the current graph configuration, the pointer is not useful. Instead a trace // hash is saved to identify the correct trace. The hash should be influenced by every setting // the trace can have (and its math function). It should not depend on the acquired trace samples - unsigned int toHash(); + unsigned int toHash(bool forceUpdate = false); static std::vector createFromTouchstone(Touchstone &t); static std::vector createFromCSV(CSV &csv); @@ -148,12 +156,28 @@ public: double getReferenceImpedance() const; + void setModel(TraceModel *newModel); + TraceModel *getModel() const; + + const QString &getMathFormula() const; + void setMathFormula(const QString &newMathFormula); + bool mathFormularValid() const; + + bool resolveMathSourceHashes(); + public slots: void setVisible(bool visible); void setColor(QColor color); void addMarker(Marker *m); void removeMarker(Marker *m); + // functions for handling source == Source::Math + bool mathDependsOn(Trace *t, bool onlyDirectDependency = false); + bool canAddAsMathSource(Trace *t); + bool addMathSource(Trace *t, QString variableName); + void removeMathSource(Trace *t); + QString getSourceVariableName(Trace *t); + signals: void cleared(Trace *t); void typeChanged(Trace *t); @@ -170,21 +194,50 @@ signals: private slots: void markerVisibilityChanged(Marker *m); + // functions for handling source == Source::Math + bool updateMathTracePoints(); + void mathSourceTraceDeleted(Trace *t); + void scheduleMathCalculation(unsigned int begin, unsigned int end); + void calculateMath(); + void clearMathSources(); + + bool addMathSource(unsigned int hash, QString variableName); + private: + TraceModel *model; // model which this trace will be part of QString _name; QColor _color; + Source source; + + unsigned int hash; + bool hashSet; + bool JSONskipHash; + + // Members for when source == Source::Live LivedataType _liveType; LiveParameter _liveParam; + + // Members for when source == Source::File + QString filename; + unsigned int fileParameter; + + // Members for when source == Source::Math + std::map mathSourceTraces; + std::map mathSourceUnresolvedHashes; + QString mathFormula; + static constexpr int MinMathUpdateInterval = 100; + QTime lastMathUpdate; + QTimer mathCalcTimer; + unsigned int mathUpdateBegin; + unsigned int mathUpdateEnd; + double vFactor; bool reflection; bool visible; bool paused; - bool createdFromFile; - bool calibration; double reference_impedance; DataType domain; - QString filename; - unsigned int fileParameter; + std::set markers; struct { union { diff --git a/Software/PC_Application/Traces/traceeditdialog.cpp b/Software/PC_Application/Traces/traceeditdialog.cpp index c0e209f..853f8c7 100644 --- a/Software/PC_Application/Traces/traceeditdialog.cpp +++ b/Software/PC_Application/Traces/traceeditdialog.cpp @@ -26,6 +26,11 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : ui->impedance->setPrecision(3); ui->impedance->setValue(t.getReferenceImpedance()); + if(!t.getModel()) { + // without information about the other traces in the model, math is not available as a source + ui->bMath->setEnabled(false); + } + connect(ui->bLive, &QPushButton::clicked, [=](bool live) { if(live) { ui->stack->setCurrentIndex(0); @@ -45,6 +50,12 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : } } }); + connect(ui->bMath, &QPushButton::clicked, [&](bool math){ + if(math) { + ui->stack->setCurrentIndex(3); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid()); + } + }); connect(ui->color, &ColorPickerButton::colorChanged, [=](const QColor& color){ trace.setColor(color); @@ -52,8 +63,9 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : ui->GSource->setId(ui->bLive, 0); ui->GSource->setId(ui->bFile, 1); + ui->GSource->setId(ui->bFile, 2); - if(t.isCalibration()) { + if(t.getSource() == Trace::Source::Calibration) { // prevent editing imported calibration traces (and csv files for now) ui->bLive->setEnabled(false); ui->bFile->setEnabled(false); @@ -136,8 +148,83 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateTouchstoneFileStatus); connect(ui->csvImport, &CSVImport::filenameChanged, updateCSVFileStatus); - if(t.isFromFile()) { - ui->bFile->click(); + // Math source configuration + if(t.getModel()) { + ui->lMathFormula->setText(t.getMathFormula()); + connect(ui->lMathFormula, &QLineEdit::editingFinished, [&](){ + t.setMathFormula(ui->lMathFormula->text()); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid()); + }); + + ui->mathTraceTable->setColumnCount(2); + ui->mathTraceTable->setHorizontalHeaderItem(0, new QTableWidgetItem("Trace Name")); + ui->mathTraceTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Variable Name")); + auto traces = t.getModel()->getTraces(); + ui->mathTraceTable->setRowCount(traces.size()); + for(unsigned int i=0;iname()); + auto flags = traceItem->flags() | Qt::ItemIsUserCheckable; + flags &= ~(Qt::ItemIsEditable | Qt::ItemIsEnabled); + if(t.canAddAsMathSource(ts)) { + flags |= Qt::ItemIsEnabled; + } + traceItem->setFlags(flags); + auto variableItem = new QTableWidgetItem(t.getSourceVariableName(ts)); + variableItem->setFlags(variableItem->flags() & ~Qt::ItemIsEditable); + if(t.mathDependsOn(ts, true)) { + traceItem->setCheckState(Qt::Checked); + variableItem->setFlags(variableItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsEditable); + } else { + traceItem->setCheckState(Qt::Unchecked); + } + ui->mathTraceTable->setItem(i, 0, traceItem); + ui->mathTraceTable->setItem(i, 1, variableItem); + } + connect(ui->mathTraceTable, &QTableWidget::itemChanged, [&](QTableWidgetItem *item){ + auto row = ui->mathTraceTable->row(item); + auto column = ui->mathTraceTable->column(item); + qDebug() << "Item changed at row"<mathTraceTable->blockSignals(true); + auto trace = t.getModel()->trace(row); + if(column == 0) { + auto variableItem = ui->mathTraceTable->item(row, 1); + // checked state changed + if(item->checkState() == Qt::Checked) { + // add this trace to the math sources, enable editing of variable name + t.addMathSource(trace, trace->name()); + variableItem->setText(trace->name()); + variableItem->setFlags(variableItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsEditable); + } else { + // trace disabled, remove from math sources + t.removeMathSource(trace); + variableItem->setText(""); + variableItem->setFlags(variableItem->flags() & ~(Qt::ItemIsEnabled | Qt::ItemIsEditable)); + } + // available trace selections may have changed, disable/enable other rows + for(unsigned int i=0;igetTraces().size();i++) { + auto traceItem = ui->mathTraceTable->item(i, 0); + auto flags = traceItem->flags(); + if(t.canAddAsMathSource(t.getModel()->trace(i))) { + traceItem->setFlags(flags | Qt::ItemIsEnabled); + } else { + traceItem->setFlags(flags & ~Qt::ItemIsEnabled); + } + } + } else { + // changed the variable name text + t.addMathSource(trace, item->text()); + } + ui->mathTraceTable->blockSignals(false); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid()); + }); + } + + switch(t.getSource()) { + case Trace::Source::Live: ui->bLive->click(); break; + case Trace::Source::File: ui->bFile->click(); break; + case Trace::Source::Math: ui->bMath->click(); break; + default: break; } // setup math part of the GUI @@ -254,7 +341,7 @@ void TraceEditDialog::on_buttonBox_accepted() { trace.setName(ui->name->text()); trace.setVelocityFactor(ui->vFactor->value()); - if(!trace.isCalibration()) { + if(trace.getSource() != Trace::Source::Calibration) { // only apply changes if it is not a calibration trace if (ui->bFile->isChecked()) { if(ui->stack->currentIndex() == 1) { @@ -265,7 +352,7 @@ void TraceEditDialog::on_buttonBox_accepted() // CSV page active ui->csvImport->fillTrace(trace); } - } else { + } else if(ui->bLive->isChecked()) { Trace::LivedataType type = Trace::LivedataType::Overwrite; Trace::LiveParameter param = Trace::LiveParameter::S11; switch(ui->CLiveType->currentIndex()) { @@ -287,6 +374,9 @@ void TraceEditDialog::on_buttonBox_accepted() } } trace.fromLivedata(type, param); + } else { + // math operation trace + trace.fromMath(); } } delete this; diff --git a/Software/PC_Application/Traces/traceeditdialog.ui b/Software/PC_Application/Traces/traceeditdialog.ui index 35e283c..24bae6c 100644 --- a/Software/PC_Application/Traces/traceeditdialog.ui +++ b/Software/PC_Application/Traces/traceeditdialog.ui @@ -9,8 +9,8 @@ 0 0 - 931 - 392 + 979 + 487 @@ -110,6 +110,16 @@ + + + + From Math + + + GSource + + + @@ -133,7 +143,7 @@ - 0 + 3 @@ -203,6 +213,34 @@ + + + + + + true + + + false + + + + + + + + + Formula: + + + + + + + + + + diff --git a/Software/PC_Application/Traces/tracemodel.cpp b/Software/PC_Application/Traces/tracemodel.cpp index d3915a7..e28a243 100644 --- a/Software/PC_Application/Traces/tracemodel.cpp +++ b/Software/PC_Application/Traces/tracemodel.cpp @@ -32,6 +32,7 @@ void TraceModel::addTrace(Trace *t) }); traces.push_back(t); endInsertRows(); + t->setModel(this); emit traceAdded(t); } @@ -117,7 +118,7 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const } break; case ColIndexPlayPause: - if (role == Qt::DecorationRole && trace->isLive()) { + if (role == Qt::DecorationRole && trace->getSource() == Trace::Source::Live) { // TODO trace needs function to check if it may change due to live data if (trace->isPaused()) { return QIcon(":/icons/pause.svg"); } else { @@ -157,7 +158,7 @@ std::vector TraceModel::getTraces() const bool TraceModel::PortExcitationRequired(int port) { for(auto t : traces) { - if(t->isLive() && !t->isPaused()) { + if(t->getSource() == Trace::Source::Live && !t->isPaused()) { // this trace needs measurements from VNA, check if port has to be excited for its measurement auto param = t->liveParameter(); if(port == 1 && (param == Trace::LiveParameter::S11 || param == Trace::LiveParameter::S21)) { @@ -188,6 +189,7 @@ void TraceModel::fromJSON(nlohmann::json j) } for(auto jt : j) { auto trace = new Trace(); + trace->setModel(this); try { trace->fromJSON(jt); addTrace(trace); @@ -195,12 +197,17 @@ void TraceModel::fromJSON(nlohmann::json j) qWarning() << "Failed to create trace:" << e.what(); } } + for(auto t : traces) { + if(!t->resolveMathSourceHashes()) { + qWarning() << "Failed to resolve all math source hashes for"<isLive()) { + if (t->getSource() == Trace::Source::Live) { // this trace is fed from live data t->clear(); } @@ -211,7 +218,7 @@ void TraceModel::addVNAData(const VNAData& d, TraceMath::DataType datatype) { source = DataSource::VNA; for(auto t : traces) { - if (t->isLive() && !t->isPaused()) { + if (t->getSource() == Trace::Source::Live && !t->isPaused()) { int index = -1; Trace::Data td; switch(datatype) { @@ -247,7 +254,7 @@ void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Prot { source = DataSource::SA; for(auto t : traces) { - if (t->isLive() && !t->isPaused()) { + if (t->getSource() == Trace::Source::Live && !t->isPaused()) { int index = -1; Trace::Data td; if(settings.f_start == settings.f_stop) { diff --git a/Software/PC_Application/Traces/tracewidget.cpp b/Software/PC_Application/Traces/tracewidget.cpp index a7504c4..082944b 100644 --- a/Software/PC_Application/Traces/tracewidget.cpp +++ b/Software/PC_Application/Traces/tracewidget.cpp @@ -426,6 +426,8 @@ void TraceWidget::contextMenuEvent(QContextMenuEvent *event) auto duplicate = new Trace(); duplicate->fromJSON(json); duplicate->setName(duplicate->name() + " - Duplicate"); + // force update of hash + duplicate->toHash(true); model.addTrace(duplicate); }); ctxmenu->addAction(action_duplicate);