From c00d04f4e220ea24a9d53935f5c1363a24908972 Mon Sep 17 00:00:00 2001 From: failiz Date: Mon, 5 Feb 2024 11:18:27 +0100 Subject: [PATCH 01/45] Update Bug.md Add instructions to be able to submit sketches --- .github/ISSUE_TEMPLATE/Bug.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/Bug.md b/.github/ISSUE_TEMPLATE/Bug.md index f6674609b..ef63e9241 100644 --- a/.github/ISSUE_TEMPLATE/Bug.md +++ b/.github/ISSUE_TEMPLATE/Bug.md @@ -5,7 +5,9 @@ labels: Bug --- ## Current Behaviour Text - + + + **Build:** Date: Wed, 8 Mar 2023 21:18:25 +0100 Subject: [PATCH 02/45] fixes bug with u simbol. The u symbol should be replaced by a u in ngspice (if not the prefix is ignored). There are two u symbols in unicode and only one of them was being replaced. --- src/mainwindow/getspice.cpp | 1 + src/utils/textutils.cpp | 2 ++ src/utils/textutils.h | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/mainwindow/getspice.cpp b/src/mainwindow/getspice.cpp index 3ca869821..5fbbbd01e 100644 --- a/src/mainwindow/getspice.cpp +++ b/src/mainwindow/getspice.cpp @@ -100,6 +100,7 @@ QString GetSpice::getSpice(ItemBase * itemBase, const QList< QList Date: Sun, 12 Mar 2023 13:38:02 +0100 Subject: [PATCH 03/45] split different options of the spice circuit in sepate lines. Added interp option to interpolate results in tran analysis. --- src/mainwindow/mainwindow_export.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mainwindow/mainwindow_export.cpp b/src/mainwindow/mainwindow_export.cpp index 4f7ea7748..2cd028b61 100644 --- a/src/mainwindow/mainwindow_export.cpp +++ b/src/mainwindow/mainwindow_export.cpp @@ -1571,7 +1571,8 @@ QString MainWindow::getSpiceNetlist(QString simulationName, QList< QList Date: Mon, 13 Mar 2023 23:24:56 +0100 Subject: [PATCH 04/45] towards transitoty simulations (still work in progress) --- src/simulation/ngspice_simulator.cpp | 4 +- src/simulation/simulator.cpp | 146 ++++++++++++++++++++++++++- src/simulation/simulator.h | 3 + 3 files changed, 151 insertions(+), 2 deletions(-) diff --git a/src/simulation/ngspice_simulator.cpp b/src/simulation/ngspice_simulator.cpp index 6c991fa5c..cc24863cf 100644 --- a/src/simulation/ngspice_simulator.cpp +++ b/src/simulation/ngspice_simulator.cpp @@ -167,7 +167,9 @@ std::vector NgSpiceSimulator::getVecInfo(const std::string& vecName) { std::vector realValues; if (vecInfo->v_realdata) { - realValues.push_back(vecInfo->v_realdata[0]); + std::cout << "getVecInfo: data" << vecInfo->v_length << std::endl; + for(int i=0; iv_length; i++) + realValues.push_back(vecInfo->v_realdata[i]); return realValues; } diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 255f3c83a..db67b9ae9 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -69,7 +69,7 @@ Simulator::Simulator(MainWindow *mainWindow) : QObject(mainWindow) { QSettings settings; int enabled = settings.value("simulatorEnabled", 0).toInt(); - enable(enabled); + enable(true); m_simulating = false; } @@ -194,6 +194,21 @@ void Simulator::simulate() { QSet itemBases; QString spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); + //Select the type of analysis based on if there is an oscilloscope in the simulation + foreach (ItemBase * item, itemBases) { + if(item->family().toLower().contains("oscilloscope")) { + //TODO: Use TextUtils::convertFromPowerPrefixU function + double time_div = TextUtils::convertFromPowerPrefix(item->getProperty("time/div"), "s"); + std::cout << "time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << std::endl; + double maxSimTime = time_div * 10; + QString tranAnalysis = QString(".TRAN %1 %2").arg(maxSimTime/100).arg(maxSimTime); + spiceNetlist.replace(".OP", tranAnalysis); + //TODO: Handle several oscilloscopes + break; + } + } + + std::cout << "Netlist: " << spiceNetlist.toStdString() << std::endl; //std::cout << "-----------------------------------" <command("bg_halt"); + //The spice simulation has finished, iterate over each part being simulated and update it (if it is necessary). //This loops is in charge of: // * update the multimeters screen @@ -358,6 +375,10 @@ void Simulator::simulate() { updatePotentiometer(part); continue; } + if (family.contains("oscilloscope")) { + updateOscilloscope(part); + continue; + } } @@ -568,6 +589,46 @@ double Simulator::calculateVoltage(ConnectorItem * c0, ConnectorItem * c1) { return volt0-volt1; } +std::vector Simulator::voltageVector(ConnectorItem * c0) { + int net0 = m_connector2netHash.value(c0); + std::cout << "calculateVoltageVector: "; + QString net0str = QString("v(%1)").arg(net0); + + auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); + if (net0 != 0) { + std::cout << "calculateVoltageVector: "; + } + return m_simulator->getVecInfo(net0str.toStdString()); +} + +QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double v_div, double v_offset) { + std::cout << "VOLTAGE VALUES " << nameId.toStdString() << ": "; + QString svg; + if (!nameId.isEmpty()) + svg += QString("getProperty("channels").toLower(); + ConnectorItem * comProbe = nullptr, * v1Probe = nullptr, * v2Probe = nullptr, * v3Probe = nullptr, * v4Probe = nullptr; + QList probes = part->cachedConnectorItems(); + foreach(ConnectorItem * ci, probes) { + if(ci->connectorSharedName().toLower().compare("com probe") == 0) comProbe = ci; + if(ci->connectorSharedName().toLower().compare("v1 probe") == 0) v1Probe = ci; + if(ci->connectorSharedName().toLower().compare("v2 probe") == 0) v2Probe = ci; + if(ci->connectorSharedName().toLower().compare("v3 probe") == 0) v3Probe = ci; + if(ci->connectorSharedName().toLower().compare("v4 probe") == 0) v4Probe = ci; + } + if(!comProbe || !v1Probe || !v2Probe || !v3Probe || !v4Probe) + return; + + if(!v1Probe->connectedToWires() && !v2Probe->connectedToWires() && !v3Probe->connectedToWires() && !v4Probe->connectedToWires()) { + std::cout << "Oscilloscope does not have any wire connected to the probe terminals. " << std::endl; + return; + } + + + if(comProbe->connectedToWires() && v1Probe->connectedToWires()) { + std::cout << "Oscilloscope probe v1 connected. " << std::endl; + auto v1 = voltageVector(v1Probe); + //auto vCom = voltageVector(comProbe); + std::vector vCom(v1.size(), 0.0); + + //TODO: use convertFromPowerPrefixU + double vols_div = TextUtils::convertFromPowerPrefix(part->getProperty("volts/div"), "V"); + double v1_offset = TextUtils::convertFromPowerPrefix(part->getProperty("v1 offset"), "V"); + + QString svg = QString("\n%5" + "\n" + ) + .arg(125) + .arg(100) + .arg(125) + .arg(100) + .arg(TextUtils::CreatedWithFritzingXmlComment); + svg += generateSvgPath(v1, vCom, "v1-path", vols_div, v1_offset); + svg += ""; + + QGraphicsSvgItem * graph = new QGraphicsSvgItem(part); + QSvgRenderer *graphRender = new QSvgRenderer(svg.toUtf8()); + if(graphRender->isValid()) + std::cout << "SVG Graph is VALID " << std::endl; + else + std::cout << "SVG Graph is NOT VALID " << std::endl; + std::cout << "SVG: " << svg.toStdString() << std::endl; + graph->setSharedRenderer(graphRender); + graph->setElementId("graph"); + graph->setZValue(std::numeric_limits::max()); + + //There are issues as the size of the text changes depending on the display settings in windows + //This hack scales the text to match the appropiate value + QRectF schOscilloscopeBoundingBox = part->boundingRect(); + QRectF schBoundingBox = graph->boundingRect(); + + //Set the text to be a 80% percent of the multimeter´s width and 50% in sch view + //graph->setScale((0.5*schOscilloscopeBoundingBox.width())/schBoundingBox.width()); + + //Update the boundiong box after scaling them + //schBoundingBox = graph->mapRectToParent(graph->boundingRect()); + + //Center the text + //graph->setPos(QPointF((schOscilloscopeBoundingBox.width()-schBoundingBox.width())/2 + // ,0.13*schOscilloscopeBoundingBox.height())); + graph->setPos(QPointF(5.0,5.0)); + + part->addSimulationGraphicsItem(graph); + + } + +} diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 0ec5165bb..dcd7adb48 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -66,6 +66,8 @@ public slots: QString getSymbol(ItemBase*, QString); double getVectorValueOrDefault(const std::string & vecName, double defaultValue); double calculateVoltage(ConnectorItem *, ConnectorItem *); + std::vector voltageVector(ConnectorItem *); + QString generateSvgPath(std::vector, std::vector, QString, double, double); double getCurrent(ItemBase*, QString subpartName=""); double getTransistorCurrent(QString spicePartName, TransistorLeg leg); double getPower(ItemBase*, QString subpartName=""); @@ -75,6 +77,7 @@ public slots: void updateDiode(ItemBase *); void updateLED(ItemBase *); void updateMultimeter(ItemBase *); + void updateOscilloscope(ItemBase *); void updateResistor(ItemBase *); void updatePotentiometer(ItemBase *); void updateDcMotor(ItemBase *); From 7de4eb71a4aa68a07c00aa80fbffe9c97d38792a Mon Sep 17 00:00:00 2001 From: afaina Date: Wed, 22 Mar 2023 21:50:51 +0100 Subject: [PATCH 05/45] allow to change properties of the pulse and sinusoidal power supplies --- resources/properties.xml | 118 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 2 deletions(-) diff --git a/resources/properties.xml b/resources/properties.xml index 025bcca4a..70d30bf77 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -144,12 +144,49 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -329,4 +366,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 46bccb8bf45ef1fcaa594bbfaf7b21ab5bf5ddd4 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Wed, 20 Sep 2023 19:32:47 +0200 Subject: [PATCH 06/45] modified resources.xml to adapt to the new moduleIDs of the new power supply parts --- resources/properties.xml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/resources/properties.xml b/resources/properties.xml index 70d30bf77..d6e59bc85 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -139,32 +139,35 @@ + + - + - + - + - + - + - + + @@ -173,17 +176,17 @@ - + - - + + - + - + From fc03cb07e4412275629a463b2ed6397b01a46d5a Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 21 Sep 2023 00:06:45 +0200 Subject: [PATCH 07/45] added number of cycles as a property to change in pulse power supplies --- resources/properties.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/resources/properties.xml b/resources/properties.xml index d6e59bc85..8a56ca21d 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -164,6 +164,9 @@ + + + From be8fac1e1ae6bd99ab34d40b8f7a4bc1a54b025b Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 21 Sep 2023 00:08:14 +0200 Subject: [PATCH 08/45] fixed bug in loop, removed setElementId to show all the svg elements --- src/simulation/simulator.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index db67b9ae9..46bbcd47a 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -613,14 +613,14 @@ QString Simulator::generateSvgPath(std::vector proveVector, std::vector< double y_0 = 50; // the center of the screen - for (int i = 0; std::min( proveVector.size(), comVector.size() ); i++) { + for (int i = 0; i < std::min( proveVector.size(), comVector.size() ); i++) { double voltage = proveVector[i] - comVector[i]; if (i == 0) { svg.append("M 0 " + QString::number( (voltage + v_offset) * vScale + y_0, 'f', 3) + " "); } else { svg.append("L " + QString::number(i, 'f', 3) + " " + QString::number((voltage + v_offset) * vScale + y_0, 'f', 3) + " "); - } - std::cout << voltage << ' '; + } + std::cout <<" ("<< i << "): " << voltage << ' '; } svg += "' fill='red' stroke='black' stroke-width='10'/> \n"; @@ -1307,7 +1307,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { QString svg = QString("\n%5" "\n" ) .arg(125) @@ -1326,7 +1326,6 @@ void Simulator::updateOscilloscope(ItemBase * part) { std::cout << "SVG Graph is NOT VALID " << std::endl; std::cout << "SVG: " << svg.toStdString() << std::endl; graph->setSharedRenderer(graphRender); - graph->setElementId("graph"); graph->setZValue(std::numeric_limits::max()); //There are issues as the size of the text changes depending on the display settings in windows From 93eedccc2c9cb8555e13c11c4b325c3aea610a76 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Fri, 29 Sep 2023 14:21:42 +0200 Subject: [PATCH 09/45] working towards the aligning the oscilloscope signals with the oscilloscope screen --- src/simulation/simulator.cpp | 59 ++++++++++++++++++++++-------------- src/simulation/simulator.h | 2 +- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 46bbcd47a..603e02db7 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -601,28 +601,32 @@ std::vector Simulator::voltageVector(ConnectorItem * c0) { return m_simulator->getVecInfo(net0str.toStdString()); } -QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double v_div, double v_offset) { +QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double verticalScale, double v_offset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { std::cout << "VOLTAGE VALUES " << nameId.toStdString() << ": "; QString svg; + double screenOffset = 132.87378; + svg += QString("\n").arg(screenOffset).arg(screenWidth).arg(screenHeight).arg(strokeWidth); if (!nameId.isEmpty()) svg += QString(" \n"; //fill='red' std::cout << std::endl; return svg; @@ -1304,34 +1308,40 @@ void Simulator::updateOscilloscope(ItemBase * part) { double vols_div = TextUtils::convertFromPowerPrefix(part->getProperty("volts/div"), "V"); double v1_offset = TextUtils::convertFromPowerPrefix(part->getProperty("v1 offset"), "V"); + double screenWidth = 3690.9385, screenHeight = 2952.7507, screenStrokeWidth= 29.5275; + double screenOffset = 132.87378, verticalDivisions = 8, divisionSize = screenHeight/verticalDivisions; QString svg = QString("\n%5" "\n" ) - .arg(125) - .arg(100) - .arg(125) - .arg(100) + .arg(screenWidth/1000) + .arg(screenHeight/1000) + .arg(screenWidth) + .arg(screenHeight) .arg(TextUtils::CreatedWithFritzingXmlComment); - svg += generateSvgPath(v1, vCom, "v1-path", vols_div, v1_offset); + svg += generateSvgPath(v1, vCom, "v1-path", divisionSize/vols_div, v1_offset, screenHeight, screenWidth, "yellow", "30"); svg += ""; - QGraphicsSvgItem * graph = new QGraphicsSvgItem(part); - QSvgRenderer *graphRender = new QSvgRenderer(svg.toUtf8()); - if(graphRender->isValid()) - std::cout << "SVG Graph is VALID " << std::endl; + QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(part); + QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(m_sch2bbItemHash.value(part)); + QSvgRenderer *schGraphRender = new QSvgRenderer(svg.toUtf8()); + QSvgRenderer *bbGraphRender = new QSvgRenderer(svg.toUtf8()); + if(schGraphRender->isValid()) + std::cout << "SCH SVG Graph is VALID " << std::endl; else - std::cout << "SVG Graph is NOT VALID " << std::endl; + std::cout << "SCH SVG Graph is NOT VALID " << std::endl; std::cout << "SVG: " << svg.toStdString() << std::endl; - graph->setSharedRenderer(graphRender); - graph->setZValue(std::numeric_limits::max()); + schGraph->setSharedRenderer(schGraphRender); + schGraph->setZValue(std::numeric_limits::max()); + bbGraph->setSharedRenderer(bbGraphRender); + bbGraph->setZValue(std::numeric_limits::max()); //There are issues as the size of the text changes depending on the display settings in windows //This hack scales the text to match the appropiate value QRectF schOscilloscopeBoundingBox = part->boundingRect(); - QRectF schBoundingBox = graph->boundingRect(); + QRectF schBoundingBox = schGraph->boundingRect(); //Set the text to be a 80% percent of the multimeter´s width and 50% in sch view //graph->setScale((0.5*schOscilloscopeBoundingBox.width())/schBoundingBox.width()); @@ -1342,9 +1352,12 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Center the text //graph->setPos(QPointF((schOscilloscopeBoundingBox.width()-schBoundingBox.width())/2 // ,0.13*schOscilloscopeBoundingBox.height())); - graph->setPos(QPointF(5.0,5.0)); + //float offset_mm = screenOffset/1000*25.4*3; + //schGraph->setPos(QPointF(offset_mm,offset_mm)); + //bbGraph->setPos(QPointF(offset_mm,offset_mm)); - part->addSimulationGraphicsItem(graph); + part->addSimulationGraphicsItem(schGraph); + m_sch2bbItemHash.value(part)->addSimulationGraphicsItem(bbGraph); } diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index dcd7adb48..0474c9c56 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -67,7 +67,7 @@ public slots: double getVectorValueOrDefault(const std::string & vecName, double defaultValue); double calculateVoltage(ConnectorItem *, ConnectorItem *); std::vector voltageVector(ConnectorItem *); - QString generateSvgPath(std::vector, std::vector, QString, double, double); + QString generateSvgPath(std::vector, std::vector, QString, double, double, double, double, QString, QString); double getCurrent(ItemBase*, QString subpartName=""); double getTransistorCurrent(QString spicePartName, TransistorLeg leg); double getPower(ItemBase*, QString subpartName=""); From 8db3f72133ad3ccb9e6c8a58d05bdc679b7a83c1 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 3 Oct 2023 13:32:15 +0200 Subject: [PATCH 10/45] towards multiple oscilloscope in the simulation. Added offset marks to signals and dispay volt/s and time/s in oscilloscope in BB view --- resources/properties.xml | 57 +++++++++++++++++++-- src/simulation/simulator.cpp | 99 ++++++++++++++++++++++-------------- src/simulation/simulator.h | 1 + 3 files changed, 114 insertions(+), 43 deletions(-) diff --git a/resources/properties.xml b/resources/properties.xml index 8a56ca21d..74edbeee1 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -397,7 +397,7 @@ - + @@ -409,7 +409,7 @@ - + @@ -421,7 +421,7 @@ - + @@ -433,7 +433,7 @@ - + @@ -445,8 +445,55 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 603e02db7..10bea4f58 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -195,18 +195,23 @@ void Simulator::simulate() { QString spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); //Select the type of analysis based on if there is an oscilloscope in the simulation + double maxSimTime = -1; foreach (ItemBase * item, itemBases) { if(item->family().toLower().contains("oscilloscope")) { //TODO: Use TextUtils::convertFromPowerPrefixU function double time_div = TextUtils::convertFromPowerPrefix(item->getProperty("time/div"), "s"); - std::cout << "time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << std::endl; - double maxSimTime = time_div * 10; - QString tranAnalysis = QString(".TRAN %1 %2").arg(maxSimTime/100).arg(maxSimTime); - spiceNetlist.replace(".OP", tranAnalysis); - //TODO: Handle several oscilloscopes - break; + std::cout << "Found oscilloscope: time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << std::endl; + double maxSimTimeOsc = time_div * 10; + if (maxSimTimeOsc > maxSimTime) { + maxSimTime = maxSimTimeOsc; + } } } + if (maxSimTime > 0) { + //We have found at least one oscilloscope + QString tranAnalysis = QString(".TRAN %1 %2").arg(maxSimTime/Simulator::SimSteps).arg(maxSimTime); + spiceNetlist.replace(".OP", tranAnalysis); + } std::cout << "Netlist: " << spiceNetlist.toStdString() << std::endl; @@ -605,7 +610,7 @@ QString Simulator::generateSvgPath(std::vector proveVector, std::vector< std::cout << "VOLTAGE VALUES " << nameId.toStdString() << ": "; QString svg; double screenOffset = 132.87378; - svg += QString("\n").arg(screenOffset).arg(screenWidth).arg(screenHeight).arg(strokeWidth); + //svg += QString("\n").arg(screenOffset).arg(screenWidth).arg(screenHeight).arg(strokeWidth); if (!nameId.isEmpty()) svg += QString("connectedToWires() && v1Probe->connectedToWires()) { std::cout << "Oscilloscope probe v1 connected. " << std::endl; @@ -1305,8 +1315,20 @@ void Simulator::updateOscilloscope(ItemBase * part) { std::vector vCom(v1.size(), 0.0); //TODO: use convertFromPowerPrefixU - double vols_div = TextUtils::convertFromPowerPrefix(part->getProperty("volts/div"), "V"); - double v1_offset = TextUtils::convertFromPowerPrefix(part->getProperty("v1 offset"), "V"); + int nChannels = TextUtils::convertFromPowerPrefix(part->getProperty("channels"), ""); + double timeDiv = TextUtils::convertFromPowerPrefix(part->getProperty("time/div"), "s"); + double hPos = TextUtils::convertFromPowerPrefix(part->getProperty("horizontal position"), "s"); + double ch1_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch1 volts/div"), "V"); + double ch1_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch1 offset"), "V"); + double ch2_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch2 volts/div"), "V"); + double ch2_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch2 offset"), "V"); + double ch3_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 volts/div"), "V"); + double ch3_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 offset"), "V"); + double ch4_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 volts/div"), "V"); + double ch4_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 offset"), "V"); + QString lineColor[4] = {"yellow", "lightgreen", "lightblue", "purple"}; + double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; + double offsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; double screenWidth = 3690.9385, screenHeight = 2952.7507, screenStrokeWidth= 29.5275; double screenOffset = 132.87378, verticalDivisions = 8, divisionSize = screenHeight/verticalDivisions; @@ -1315,14 +1337,33 @@ void Simulator::updateOscilloscope(ItemBase * part) { "version='1.2' baseProfile='tiny' " "x='0in' y='0in' width='%1in' height='%2in' " "viewBox='0 0 %3 %4' >\n" - ) - .arg(screenWidth/1000) - .arg(screenHeight/1000) - .arg(screenWidth) - .arg(screenHeight) - .arg(TextUtils::CreatedWithFritzingXmlComment); - svg += generateSvgPath(v1, vCom, "v1-path", divisionSize/vols_div, v1_offset, screenHeight, screenWidth, "yellow", "30"); - svg += ""; + ) + .arg((screenWidth+screenOffset)/1000) + .arg((screenHeight+screenOffset*2)/1000) + .arg(screenWidth+screenOffset) + .arg(screenHeight+screenOffset*2) + .arg(TextUtils::CreatedWithFritzingXmlComment); + svg += generateSvgPath(v1, vCom, "v1-path", divisionSize/ch1_volsDiv, ch1_offset, screenHeight, screenWidth, lineColor[0], "30"); + + // Add labels of voltage/div and arrows to indicate offsets for each channel + for (int channel = 0; channel < nChannels; channel++) { + if (!probesArray[channel]->connectedToWires()) continue; + svg += QString("CH%4: %5V") + .arg(screenOffset+divisionSize*channel).arg(screenHeight+screenOffset*1.7) + .arg(lineColor[channel]).arg(channel+1).arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); + double arrowSize = 50; + double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+screenOffset-arrowSize; + svg += QString("") + .arg(arrowSize).arg(arrowSize*2).arg(lineColor[channel]).arg(arrowPos); + } + + + svg += QString("time/div: %3s pos: %4") + .arg(screenOffset+screenWidth/2).arg(screenOffset*0.7) + .arg(TextUtils::convertToPowerPrefix(timeDiv)) + .arg(TextUtils::convertToPowerPrefix(hPos)); + + svg += ""; QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(part); QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(m_sch2bbItemHash.value(part)); @@ -1338,24 +1379,6 @@ void Simulator::updateOscilloscope(ItemBase * part) { bbGraph->setSharedRenderer(bbGraphRender); bbGraph->setZValue(std::numeric_limits::max()); - //There are issues as the size of the text changes depending on the display settings in windows - //This hack scales the text to match the appropiate value - QRectF schOscilloscopeBoundingBox = part->boundingRect(); - QRectF schBoundingBox = schGraph->boundingRect(); - - //Set the text to be a 80% percent of the multimeter´s width and 50% in sch view - //graph->setScale((0.5*schOscilloscopeBoundingBox.width())/schBoundingBox.width()); - - //Update the boundiong box after scaling them - //schBoundingBox = graph->mapRectToParent(graph->boundingRect()); - - //Center the text - //graph->setPos(QPointF((schOscilloscopeBoundingBox.width()-schBoundingBox.width())/2 - // ,0.13*schOscilloscopeBoundingBox.height())); - //float offset_mm = screenOffset/1000*25.4*3; - //schGraph->setPos(QPointF(offset_mm,offset_mm)); - //bbGraph->setPos(QPointF(offset_mm,offset_mm)); - part->addSimulationGraphicsItem(schGraph); m_sch2bbItemHash.value(part)->addSimulationGraphicsItem(bbGraph); diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 0474c9c56..9c12496f1 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -99,6 +99,7 @@ public slots: QTimer *m_simTimer; static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5; + static constexpr double SimSteps = 100; }; From 5c2924fe2b9420a70878a3ff93cfa87eaddb337d Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 3 Oct 2023 14:32:53 +0200 Subject: [PATCH 11/45] if the negative terminal of a power supply is not connected and there is no ground symbol, the simulator cannot find the ground. This creates problems when requesting voltages at node 0, as ngspice cannot return the voltage at node 0. This commit fixes this by trying to find an isolated negative power supply if it has not found one before. --- src/mainwindow/mainwindow_export.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mainwindow/mainwindow_export.cpp b/src/mainwindow/mainwindow_export.cpp index 2cd028b61..906d326ba 100644 --- a/src/mainwindow/mainwindow_export.cpp +++ b/src/mainwindow/mainwindow_export.cpp @@ -1465,6 +1465,7 @@ QString MainWindow::getSpiceNetlist(QString simulationName, QList< QList Date: Tue, 3 Oct 2023 14:50:06 +0200 Subject: [PATCH 12/45] handles the 4 channels and adds noise to the signals if the com probe is not connected --- src/simulation/simulator.cpp | 158 +++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 70 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 10bea4f58..6dfc5a2b7 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -636,7 +636,7 @@ QString Simulator::generateSvgPath(std::vector proveVector, std::vector< } std::cout <<" ("<< i << "): " << voltage << ' '; } - svg += "' stroke='"+ color + "' stroke-width='"+ strokeWidth + "'/> \n"; //fill='red' + svg += "' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // std::cout << std::endl; return svg; @@ -1289,7 +1289,6 @@ void Simulator::updateMultimeter(ItemBase * part) { */ void Simulator::updateOscilloscope(ItemBase * part) { std::cout << "updateOscilloscope: " << std::endl; - QString nChannels = part->getProperty("channels").toLower(); ConnectorItem * comProbe = nullptr, * v1Probe = nullptr, * v2Probe = nullptr, * v3Probe = nullptr, * v4Probe = nullptr; QList probes = part->cachedConnectorItems(); foreach(ConnectorItem * ci, probes) { @@ -1308,80 +1307,99 @@ void Simulator::updateOscilloscope(ItemBase * part) { } ConnectorItem * probesArray[4] = {v1Probe, v2Probe, v3Probe, v4Probe}; - if(comProbe->connectedToWires() && v1Probe->connectedToWires()) { - std::cout << "Oscilloscope probe v1 connected. " << std::endl; - auto v1 = voltageVector(v1Probe); - //auto vCom = voltageVector(comProbe); - std::vector vCom(v1.size(), 0.0); - - //TODO: use convertFromPowerPrefixU - int nChannels = TextUtils::convertFromPowerPrefix(part->getProperty("channels"), ""); - double timeDiv = TextUtils::convertFromPowerPrefix(part->getProperty("time/div"), "s"); - double hPos = TextUtils::convertFromPowerPrefix(part->getProperty("horizontal position"), "s"); - double ch1_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch1 volts/div"), "V"); - double ch1_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch1 offset"), "V"); - double ch2_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch2 volts/div"), "V"); - double ch2_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch2 offset"), "V"); - double ch3_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 volts/div"), "V"); - double ch3_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 offset"), "V"); - double ch4_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 volts/div"), "V"); - double ch4_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 offset"), "V"); - QString lineColor[4] = {"yellow", "lightgreen", "lightblue", "purple"}; - double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; - double offsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; - - double screenWidth = 3690.9385, screenHeight = 2952.7507, screenStrokeWidth= 29.5275; - double screenOffset = 132.87378, verticalDivisions = 8, divisionSize = screenHeight/verticalDivisions; - QString svg = QString("\n%5" - "\n" - ) - .arg((screenWidth+screenOffset)/1000) - .arg((screenHeight+screenOffset*2)/1000) - .arg(screenWidth+screenOffset) - .arg(screenHeight+screenOffset*2) - .arg(TextUtils::CreatedWithFritzingXmlComment); - svg += generateSvgPath(v1, vCom, "v1-path", divisionSize/ch1_volsDiv, ch1_offset, screenHeight, screenWidth, lineColor[0], "30"); - - // Add labels of voltage/div and arrows to indicate offsets for each channel - for (int channel = 0; channel < nChannels; channel++) { - if (!probesArray[channel]->connectedToWires()) continue; - svg += QString("CH%4: %5V") - .arg(screenOffset+divisionSize*channel).arg(screenHeight+screenOffset*1.7) - .arg(lineColor[channel]).arg(channel+1).arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); - double arrowSize = 50; - double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+screenOffset-arrowSize; - svg += QString("") - .arg(arrowSize).arg(arrowSize*2).arg(lineColor[channel]).arg(arrowPos); + + std::cout << "Oscilloscope probe v1 connected. " << std::endl; + + + //TODO: use convertFromPowerPrefixU + int nChannels = TextUtils::convertFromPowerPrefix(part->getProperty("channels"), ""); + double timeDiv = TextUtils::convertFromPowerPrefix(part->getProperty("time/div"), "s"); + double hPos = TextUtils::convertFromPowerPrefix(part->getProperty("horizontal position"), "s"); + double ch1_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch1 volts/div"), "V"); + double ch1_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch1 offset"), "V"); + double ch2_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch2 volts/div"), "V"); + double ch2_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch2 offset"), "V"); + double ch3_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 volts/div"), "V"); + double ch3_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 offset"), "V"); + double ch4_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 volts/div"), "V"); + double ch4_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 offset"), "V"); + QString lineColor[4] = {"yellow", "lightgreen", "lightblue", "purple"}; + double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; + double offsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; + + double screenWidth = 3690.9385, screenHeight = 2952.7507, screenStrokeWidth= 29.5275; + double screenOffset = 132.87378, verticalDivisions = 8, divisionSize = screenHeight/verticalDivisions; + QString svg = QString("\n%5" + "\n" + ) + .arg((screenWidth+screenOffset)/1000) + .arg((screenHeight+screenOffset*2)/1000) + .arg(screenWidth+screenOffset) + .arg(screenHeight+screenOffset*2) + .arg(TextUtils::CreatedWithFritzingXmlComment); + + // Generate the signal for each channel and the auxiliary marks (offsets, volts/div, etc.) + for (int channel = 0; channel < nChannels; channel++) { + if (!probesArray[channel]->connectedToWires()) continue; + + //Get the signal and com voltages + auto v = voltageVector(probesArray[channel]); + std::vector vCom(v.size(), 0.0); + if (!comProbe->connectedToWires()) { + //There is no com probe connected, we need to generate noise + std::random_device rd; + std::mt19937 gen(rd()); + std::normal_distribution<> dist(0.0, voltsDiv[channel]); + // Generate random doubles and fill the vector + for(auto& val : vCom) { + val = dist(gen); + } } + //Draw the signal + QString pathId = QString("ch%1-path").arg(channel+1); + svg += generateSvgPath(v, vCom, pathId, divisionSize/ch1_volsDiv, ch1_offset, screenHeight, screenWidth, lineColor[channel], "30"); - svg += QString("time/div: %3s pos: %4") - .arg(screenOffset+screenWidth/2).arg(screenOffset*0.7) - .arg(TextUtils::convertToPowerPrefix(timeDiv)) - .arg(TextUtils::convertToPowerPrefix(hPos)); + //Add text label about volts/div for each channel + svg += QString("CH%4: %5V") + .arg(screenOffset+divisionSize*channel).arg(screenHeight+screenOffset*1.7) + .arg(lineColor[channel]).arg(channel+1).arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); - svg += ""; + //Add triangle as a mark for the offset for each channel + double arrowSize = 50; + double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+screenOffset-arrowSize; + svg += QString("") + .arg(arrowSize).arg(arrowSize*2).arg(lineColor[channel]).arg(arrowPos); + } - QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(part); - QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(m_sch2bbItemHash.value(part)); - QSvgRenderer *schGraphRender = new QSvgRenderer(svg.toUtf8()); - QSvgRenderer *bbGraphRender = new QSvgRenderer(svg.toUtf8()); - if(schGraphRender->isValid()) - std::cout << "SCH SVG Graph is VALID " << std::endl; - else - std::cout << "SCH SVG Graph is NOT VALID " << std::endl; - std::cout << "SVG: " << svg.toStdString() << std::endl; - schGraph->setSharedRenderer(schGraphRender); - schGraph->setZValue(std::numeric_limits::max()); - bbGraph->setSharedRenderer(bbGraphRender); - bbGraph->setZValue(std::numeric_limits::max()); - part->addSimulationGraphicsItem(schGraph); - m_sch2bbItemHash.value(part)->addSimulationGraphicsItem(bbGraph); + svg += QString("time/div: %3s pos: %4") + .arg(screenOffset+screenWidth/2).arg(screenOffset*0.7) + .arg(TextUtils::convertToPowerPrefix(timeDiv)) + .arg(TextUtils::convertToPowerPrefix(hPos)); + + svg += ""; + + QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(part); + QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(m_sch2bbItemHash.value(part)); + QSvgRenderer *schGraphRender = new QSvgRenderer(svg.toUtf8()); + QSvgRenderer *bbGraphRender = new QSvgRenderer(svg.toUtf8()); + if(schGraphRender->isValid()) + std::cout << "SCH SVG Graph is VALID " << std::endl; + else + std::cout << "SCH SVG Graph is NOT VALID " << std::endl; + std::cout << "SVG: " << svg.toStdString() << std::endl; + schGraph->setSharedRenderer(schGraphRender); + schGraph->setZValue(std::numeric_limits::max()); + bbGraph->setSharedRenderer(bbGraphRender); + bbGraph->setZValue(std::numeric_limits::max()); + + part->addSimulationGraphicsItem(schGraph); + m_sch2bbItemHash.value(part)->addSimulationGraphicsItem(bbGraph); + - } } From 1a4378a2706a3151ae93d43b632fd2f4063339fe Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 3 Oct 2023 20:04:26 +0200 Subject: [PATCH 13/45] better name for a property --- resources/properties.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/properties.xml b/resources/properties.xml index 74edbeee1..e700200a6 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -164,7 +164,7 @@ - + From af0c89169076736a3e610b87482906d47326d2e2 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Wed, 4 Oct 2023 17:33:04 +0200 Subject: [PATCH 14/45] improved oscilloscope in sch view and added axis labels --- src/simulation/simulator.cpp | 94 +++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 6dfc5a2b7..3b92ed269 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -609,7 +609,7 @@ std::vector Simulator::voltageVector(ConnectorItem * c0) { QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double verticalScale, double v_offset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { std::cout << "VOLTAGE VALUES " << nameId.toStdString() << ": "; QString svg; - double screenOffset = 132.87378; + double screenOffset = 0;//132.87378; //svg += QString("\n").arg(screenOffset).arg(screenWidth).arg(screenHeight).arg(strokeWidth); if (!nameId.isEmpty()) svg += QString(" \n"; // + svg += "' transform='translate(%1,%2)' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // std::cout << std::endl; return svg; @@ -1328,18 +1328,25 @@ void Simulator::updateOscilloscope(ItemBase * part) { double offsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; double screenWidth = 3690.9385, screenHeight = 2952.7507, screenStrokeWidth= 29.5275; - double screenOffset = 132.87378, verticalDivisions = 8, divisionSize = screenHeight/verticalDivisions; - QString svg = QString("\n%5" - "\n" - ) - .arg((screenWidth+screenOffset)/1000) - .arg((screenHeight+screenOffset*2)/1000) - .arg(screenWidth+screenOffset) - .arg(screenHeight+screenOffset*2) + double verDivisions = 8, horDivisions = 10, divisionSize = screenHeight/verDivisions; + double bbScreenOffset = 132.87378, schScreenOffsetX = 607.41187, schScreenOffsetY = 229.091146; + QString svgHeader = "\n%5" + "\n"; + QString bbSvg = QString(svgHeader) + .arg((screenWidth+bbScreenOffset)/1000) + .arg((screenHeight+bbScreenOffset*2)/1000) + .arg(screenWidth+bbScreenOffset) + .arg(screenHeight+bbScreenOffset*2) .arg(TextUtils::CreatedWithFritzingXmlComment); + QString schSvg = QString(svgHeader) + .arg((screenWidth+schScreenOffsetX*2)/1000) + .arg((screenHeight+schScreenOffsetY*2)/1000) + .arg(screenWidth+schScreenOffsetX*2) + .arg(screenHeight+schScreenOffsetY*2) + .arg(TextUtils::CreatedWithFritzingXmlComment); // Generate the signal for each channel and the auxiliary marks (offsets, volts/div, etc.) for (int channel = 0; channel < nChannels; channel++) { @@ -1361,37 +1368,70 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Draw the signal QString pathId = QString("ch%1-path").arg(channel+1); - svg += generateSvgPath(v, vCom, pathId, divisionSize/ch1_volsDiv, ch1_offset, screenHeight, screenWidth, lineColor[channel], "30"); + QString signalPath = generateSvgPath(v, vCom, pathId, divisionSize/ch1_volsDiv, ch1_offset, screenHeight, screenWidth, lineColor[channel], "30"); + bbSvg += signalPath.arg(bbScreenOffset).arg(bbScreenOffset); + schSvg += signalPath.arg(schScreenOffsetX).arg(schScreenOffsetY); //Add text label about volts/div for each channel - svg += QString("CH%4: %5V") - .arg(screenOffset+divisionSize*channel).arg(screenHeight+screenOffset*1.7) + bbSvg += QString("CH%4: %5V") + .arg(bbScreenOffset+divisionSize*channel).arg(screenHeight+bbScreenOffset*1.7) .arg(lineColor[channel]).arg(channel+1).arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); //Add triangle as a mark for the offset for each channel double arrowSize = 50; - double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+screenOffset-arrowSize; - svg += QString("") + double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+bbScreenOffset-arrowSize; + bbSvg += QString("") .arg(arrowSize).arg(arrowSize*2).arg(lineColor[channel]).arg(arrowPos); - } + //Add voltage scale axis in sch + double xOffset; + if (channel < 2) + xOffset = schScreenOffsetX*0.8/(channel+1); + if (channel >= 2) + xOffset = screenWidth + schScreenOffsetX*1.2*(channel-1); + + for (int tick = 0; tick < (verDivisions+1); ++tick) { + double vTick = voltsDiv[channel]*(verDivisions/2-tick)-offsets[channel]; + schSvg += QString("%4V") + .arg(xOffset).arg(schScreenOffsetY+divisionSize*tick+20) + .arg(lineColor[channel]).arg(TextUtils::convertToPowerPrefix(vTick)); + } + + + } - svg += QString("time/div: %3s pos: %4") - .arg(screenOffset+screenWidth/2).arg(screenOffset*0.7) + //Add time scale axis in bb + bbSvg += QString("time/div: %3s pos: %4") + .arg(bbScreenOffset+screenWidth/2).arg(bbScreenOffset*0.7) .arg(TextUtils::convertToPowerPrefix(timeDiv)) .arg(TextUtils::convertToPowerPrefix(hPos)); - svg += ""; + //Add time scale axis in sch + for (int tick = 0; tick < (horDivisions+1); ++tick) { + schSvg += QString("%4s") + .arg(schScreenOffsetX+divisionSize*tick).arg(screenHeight+schScreenOffsetY*1.4) + .arg("black").arg(TextUtils::convertToPowerPrefix(hPos + timeDiv*tick)); + } + + bbSvg += ""; + schSvg += ""; QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(part); QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(m_sch2bbItemHash.value(part)); - QSvgRenderer *schGraphRender = new QSvgRenderer(svg.toUtf8()); - QSvgRenderer *bbGraphRender = new QSvgRenderer(svg.toUtf8()); + QSvgRenderer *schGraphRender = new QSvgRenderer(schSvg.toUtf8()); + QSvgRenderer *bbGraphRender = new QSvgRenderer(bbSvg.toUtf8()); if(schGraphRender->isValid()) - std::cout << "SCH SVG Graph is VALID " << std::endl; + std::cout << "SCH SVG Graph is VALID \n" << std::endl; else - std::cout << "SCH SVG Graph is NOT VALID " << std::endl; - std::cout << "SVG: " << svg.toStdString() << std::endl; + std::cout << "SCH SVG Graph is NOT VALID \n" << std::endl; + std::cout << "SCH SVG: " << schSvg.toStdString() << std::endl; + + if(bbGraphRender->isValid()) + std::cout << "BB SVG Graph is VALID \n" << std::endl; + else + std::cout << "BB SVG Graph is NOT VALID\n" << std::endl; + std::cout << "BB SVG: " << bbSvg.toStdString() << std::endl; + schGraph->setSharedRenderer(schGraphRender); schGraph->setZValue(std::numeric_limits::max()); bbGraph->setSharedRenderer(bbGraphRender); From a36833f8a832bdbbfb4cc69c50ca965426b7db26 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 5 Oct 2023 20:13:32 +0200 Subject: [PATCH 15/45] added axis scale in the oscilloscope for the sch view --- src/simulation/simulator.cpp | 38 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 3b92ed269..79cc01b98 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -1368,33 +1368,49 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Draw the signal QString pathId = QString("ch%1-path").arg(channel+1); - QString signalPath = generateSvgPath(v, vCom, pathId, divisionSize/ch1_volsDiv, ch1_offset, screenHeight, screenWidth, lineColor[channel], "30"); + QString signalPath = generateSvgPath(v, vCom, pathId, divisionSize/voltsDiv[channel], offsets[channel], + screenHeight, screenWidth, lineColor[channel], "20"); bbSvg += signalPath.arg(bbScreenOffset).arg(bbScreenOffset); schSvg += signalPath.arg(schScreenOffsetX).arg(schScreenOffsetY); //Add text label about volts/div for each channel - bbSvg += QString("CH%4: %5V") + bbSvg += QString("CH%4: %5V\n") .arg(bbScreenOffset+divisionSize*channel).arg(screenHeight+bbScreenOffset*1.7) .arg(lineColor[channel]).arg(channel+1).arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); //Add triangle as a mark for the offset for each channel double arrowSize = 50; double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+bbScreenOffset-arrowSize; - bbSvg += QString("") + bbSvg += QString("\n") .arg(arrowSize).arg(arrowSize*2).arg(lineColor[channel]).arg(arrowPos); //Add voltage scale axis in sch - double xOffset; - if (channel < 2) - xOffset = schScreenOffsetX*0.8/(channel+1); - if (channel >= 2) - xOffset = screenWidth + schScreenOffsetX*1.2*(channel-1); + double xOffset[4] = {schScreenOffsetX*0.95, schScreenOffsetX*0.6, + screenWidth + schScreenOffsetX*1.05, screenWidth + schScreenOffsetX*1.5}; + schSvg += QString("\n") + .arg(xOffset[channel]) + .arg(schScreenOffsetY) + .arg(schScreenOffsetY+screenHeight) + .arg(lineColor[channel]); + + double tickSize = 10; + double tickPadding = channel>=(nChannels/2)? -tickSize: tickSize; + double textPadding = channel>=(nChannels/2)? 10 : -10; + QString alignment = channel>=(nChannels/2)? "start": "end"; for (int tick = 0; tick < (verDivisions+1); ++tick) { double vTick = voltsDiv[channel]*(verDivisions/2-tick)-offsets[channel]; - schSvg += QString("%4V") - .arg(xOffset).arg(schScreenOffsetY+divisionSize*tick+20) - .arg(lineColor[channel]).arg(TextUtils::convertToPowerPrefix(vTick)); + QString voltageText = TextUtils::convertToPowerPrefix(vTick); + schSvg += QString("%5V\n") + .arg(xOffset[channel] + textPadding) + .arg(schScreenOffsetY + divisionSize * tick + 20) + .arg(lineColor[channel]).arg(alignment).arg(voltageText); + + schSvg += QString("\n") + .arg(xOffset[channel] - tickSize + tickPadding) + .arg(schScreenOffsetY + divisionSize * tick) + .arg(xOffset[channel] + tickSize + tickPadding) + .arg(lineColor[channel]); } From 105fd7ba816ada08da88cd1f4f79b79ab910d6f8 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 5 Oct 2023 23:07:52 +0200 Subject: [PATCH 16/45] added control of the horizontal simulation to the analysis without loosing horizontal resolution --- src/simulation/simulator.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 79cc01b98..a9461ff2b 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -195,12 +195,16 @@ void Simulator::simulate() { QString spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); //Select the type of analysis based on if there is an oscilloscope in the simulation - double maxSimTime = -1; + double maxSimTime = -1, startTime = std::numeric_limits::max();; foreach (ItemBase * item, itemBases) { if(item->family().toLower().contains("oscilloscope")) { //TODO: Use TextUtils::convertFromPowerPrefixU function double time_div = TextUtils::convertFromPowerPrefix(item->getProperty("time/div"), "s"); - std::cout << "Found oscilloscope: time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << std::endl; + double pos = TextUtils::convertFromPowerPrefix(item->getProperty("horizontal position"), "s"); + std::cout << "Found oscilloscope: time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << item->getProperty("horizontal position").toStdString() << " " << pos << std::endl; + if (pos < startTime) { + startTime = pos; + } double maxSimTimeOsc = time_div * 10; if (maxSimTimeOsc > maxSimTime) { maxSimTime = maxSimTimeOsc; @@ -209,7 +213,7 @@ void Simulator::simulate() { } if (maxSimTime > 0) { //We have found at least one oscilloscope - QString tranAnalysis = QString(".TRAN %1 %2").arg(maxSimTime/Simulator::SimSteps).arg(maxSimTime); + QString tranAnalysis = QString(".TRAN %1 %2 %3").arg((maxSimTime-startTime)/Simulator::SimSteps).arg(maxSimTime).arg(startTime); spiceNetlist.replace(".OP", tranAnalysis); } From af0414f62cdc123b1fd6390c37096ca9560a85ef Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Fri, 6 Oct 2023 21:55:33 +0200 Subject: [PATCH 17/45] added handling of horizontal positions for multiple oscilloscopes --- src/simulation/simulator.cpp | 53 ++++++++++++++++++++++-------------- src/simulation/simulator.h | 5 ++-- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index a9461ff2b..28f418d6c 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -195,25 +195,26 @@ void Simulator::simulate() { QString spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); //Select the type of analysis based on if there is an oscilloscope in the simulation - double maxSimTime = -1, startTime = std::numeric_limits::max();; + m_simEndTime = -1, m_simStartTime = std::numeric_limits::max();; foreach (ItemBase * item, itemBases) { if(item->family().toLower().contains("oscilloscope")) { //TODO: Use TextUtils::convertFromPowerPrefixU function double time_div = TextUtils::convertFromPowerPrefix(item->getProperty("time/div"), "s"); double pos = TextUtils::convertFromPowerPrefix(item->getProperty("horizontal position"), "s"); std::cout << "Found oscilloscope: time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << item->getProperty("horizontal position").toStdString() << " " << pos << std::endl; - if (pos < startTime) { - startTime = pos; + if (pos < m_simStartTime) { + m_simStartTime = pos; } - double maxSimTimeOsc = time_div * 10; - if (maxSimTimeOsc > maxSimTime) { - maxSimTime = maxSimTimeOsc; + double maxSimTimeOsc = pos + time_div * 10; + if (maxSimTimeOsc > m_simEndTime) { + m_simEndTime = maxSimTimeOsc; } } } - if (maxSimTime > 0) { + if (m_simEndTime > 0) { + m_simStepTime = (m_simEndTime-m_simStartTime)/Simulator::SimSteps; //We have found at least one oscilloscope - QString tranAnalysis = QString(".TRAN %1 %2 %3").arg((maxSimTime-startTime)/Simulator::SimSteps).arg(maxSimTime).arg(startTime); + QString tranAnalysis = QString(".TRAN %1 %2 %3").arg(m_simStepTime).arg(m_simEndTime).arg(m_simStartTime); spiceNetlist.replace(".OP", tranAnalysis); } @@ -610,8 +611,9 @@ std::vector Simulator::voltageVector(ConnectorItem * c0) { return m_simulator->getVecInfo(net0str.toStdString()); } -QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double verticalScale, double v_offset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { - std::cout << "VOLTAGE VALUES " << nameId.toStdString() << ": "; +QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double simStartTime, double simTimeStep, double timePos, double timeScale, double verticalScale, double verOffset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { + std::cout << "OSCILLOSCOPE: pos " << timePos << ", timeScale: " << timeScale << std::endl; + std::cout << "OSCILLOSCOPE: VOLTAGE VALUES " << nameId.toStdString() << ": "; QString svg; double screenOffset = 0;//132.87378; //svg += QString("\n").arg(screenOffset).arg(screenWidth).arg(screenHeight).arg(strokeWidth); @@ -625,20 +627,31 @@ QString Simulator::generateSvgPath(std::vector proveVector, std::vector< double y_0 = screenOffset + screenHeight/2; // the center of the screen int points = std::min( proveVector.size(), comVector.size() ); - double horScale = screenWidth/(points-1); - for (int i = 0; i < points; i++) { - double voltage = proveVector[i] - comVector[i]; - double vPos = (voltage + v_offset) * vScale + y_0; + double oscEndTime = timePos + timeScale * 10; + double nSampleInScreen = (oscEndTime - timePos)/simTimeStep + 1; + double horScale = screenWidth/(nSampleInScreen-1); + std::cout << "OSCILLOSCOPE: nSampleInScreen " << nSampleInScreen << std::endl; + int screenPoint = 0; + for (int vPoint = 0; vPoint < points; vPoint++) { + double time = simStartTime + simTimeStep * vPoint; + if (time < timePos) + continue; + if (time > oscEndTime) + break; + + double voltage = proveVector[vPoint] - comVector[vPoint]; + double vPos = (voltage + verOffset) * vScale + y_0; //Do not go out of the screen vPos = (vPos < screenOffset) ? screenOffset : vPos; vPos = (vPos > (screenOffset+screenHeight)) ? screenOffset+screenHeight : vPos; - if (i == 0) { + if (screenPoint == 0) { svg.append("M "+ QString::number(screenOffset, 'f', 3) +" " + QString::number( vPos, 'f', 3) + " "); } else { - svg.append("L " + QString::number(i*horScale + screenOffset, 'f', 3) + " " + QString::number(vPos, 'f', 3) + " "); + svg.append("L " + QString::number(screenPoint*horScale + screenOffset, 'f', 3) + " " + QString::number(vPos, 'f', 3) + " "); } - std::cout <<" ("<< i << "): " << voltage << ' '; + std::cout <<" ("<< time << "): " << voltage << ' '; + screenPoint++; } svg += "' transform='translate(%1,%2)' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // @@ -1372,7 +1385,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Draw the signal QString pathId = QString("ch%1-path").arg(channel+1); - QString signalPath = generateSvgPath(v, vCom, pathId, divisionSize/voltsDiv[channel], offsets[channel], + QString signalPath = generateSvgPath(v, vCom, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], offsets[channel], screenHeight, screenWidth, lineColor[channel], "20"); bbSvg += signalPath.arg(bbScreenOffset).arg(bbScreenOffset); schSvg += signalPath.arg(schScreenOffsetX).arg(schScreenOffsetY); @@ -1444,13 +1457,13 @@ void Simulator::updateOscilloscope(ItemBase * part) { std::cout << "SCH SVG Graph is VALID \n" << std::endl; else std::cout << "SCH SVG Graph is NOT VALID \n" << std::endl; - std::cout << "SCH SVG: " << schSvg.toStdString() << std::endl; + //std::cout << "SCH SVG: " << schSvg.toStdString() << std::endl; if(bbGraphRender->isValid()) std::cout << "BB SVG Graph is VALID \n" << std::endl; else std::cout << "BB SVG Graph is NOT VALID\n" << std::endl; - std::cout << "BB SVG: " << bbSvg.toStdString() << std::endl; + //std::cout << "BB SVG: " << bbSvg.toStdString() << std::endl; schGraph->setSharedRenderer(schGraphRender); schGraph->setZValue(std::numeric_limits::max()); diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 9c12496f1..8d0e91bae 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -67,7 +67,7 @@ public slots: double getVectorValueOrDefault(const std::string & vecName, double defaultValue); double calculateVoltage(ConnectorItem *, ConnectorItem *); std::vector voltageVector(ConnectorItem *); - QString generateSvgPath(std::vector, std::vector, QString, double, double, double, double, QString, QString); + QString generateSvgPath(std::vector, std::vector, QString, double, double, double, double, double, double, double, double, QString, QString); double getCurrent(ItemBase*, QString subpartName=""); double getTransistorCurrent(QString spicePartName, TransistorLeg leg); double getPower(ItemBase*, QString subpartName=""); @@ -89,6 +89,7 @@ public slots: std::shared_ptr m_simulator; QPointer m_breadboardGraphicsView; QPointer m_schematicGraphicsView; + double m_simStartTime, m_simStepTime, m_simEndTime; bool m_enabled = false; @@ -99,7 +100,7 @@ public slots: QTimer *m_simTimer; static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5; - static constexpr double SimSteps = 100; + static constexpr double SimSteps = 400; }; From 433a51a39b1289a1cd8c40b093ba24c5eb132a03 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sat, 7 Oct 2023 13:18:16 +0200 Subject: [PATCH 18/45] allow to introduce negative numbers in the properties of parts. This is necessary to allow setting negative offsets in the oscilloscope part --- src/items/capacitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/capacitor.cpp b/src/items/capacitor.cpp index fe28817bb..2ad1ba91a 100644 --- a/src/items/capacitor.cpp +++ b/src/items/capacitor.cpp @@ -109,7 +109,7 @@ bool Capacitor::collectExtraInfo(QWidget * parent, const QString & family, const validator->setBounds(propertyDef->minValue, propertyDef->maxValue); } // QString pattern = QString("((\\d{0,10})|(\\d{0,10}\\.)|(\\d{0,10}\\.\\d{1,10}))[%1]{0,1}[%2]{0,1}") - QString pattern = QString("((\\d{1,3})|(\\d{1,3}\\.)|(\\d{1,3}\\.\\d{1,2}))[%1]{0,1}[%2]{0,1}").arg( + QString pattern = QString("((-?\\d{1,3})|(-?\\d{1,3}\\.)|(-?\\d{1,3}\\.\\d{1,2}))[%1]{0,1}[%2]{0,1}").arg( // QString pattern = QString("((\\d{0,3})|(\\d{0,3}\\.)|(\\d{0,3}\\.\\d{1,3}))[%1]{0,1}[%2]{0,1}") TextUtils::PowerPrefixesString, propertyDef->symbol From dab2f73d5bbccf55a53eca9a30d1a33cdc844b1e Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sat, 7 Oct 2023 13:30:20 +0200 Subject: [PATCH 19/45] Show the result of the simulation at the end time, not at the starting time. This shows the LED, multimeters and others components at the max simulated time --- src/simulation/simulator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 28f418d6c..b0e7d2a0f 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -565,8 +565,8 @@ double Simulator::getVectorValueOrDefault(const std::string & vecName, double de auto vecInfo = m_simulator->getVecInfo(vecName); if (vecInfo.empty()) { return defaultValue; - } else { - return vecInfo[0]; + } else { + return vecInfo[vecInfo.size()-1]; } } From 6e2c3dbb8470646d40a747aba88b3ed5c157ca15 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 10 Oct 2023 10:22:04 +0200 Subject: [PATCH 20/45] In sch view, added the name of the axes to the oscilloscope. --- src/simulation/simulator.cpp | 59 +++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index b0e7d2a0f..3056ba2a6 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -650,7 +650,7 @@ QString Simulator::generateSvgPath(std::vector proveVector, std::vector< } else { svg.append("L " + QString::number(screenPoint*horScale + screenOffset, 'f', 3) + " " + QString::number(vPos, 'f', 3) + " "); } - std::cout <<" ("<< time << "): " << voltage << ' '; + //std::cout <<" ("<< time << "): " << voltage << ' '; screenPoint++; } svg += "' transform='translate(%1,%2)' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // @@ -1340,7 +1340,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { double ch3_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 offset"), "V"); double ch4_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 volts/div"), "V"); double ch4_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 offset"), "V"); - QString lineColor[4] = {"yellow", "lightgreen", "lightblue", "purple"}; + QString lineColor[4] = {"yellow", "green", "blue", "purple"}; double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; double offsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; @@ -1404,6 +1404,12 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Add voltage scale axis in sch double xOffset[4] = {schScreenOffsetX*0.95, schScreenOffsetX*0.6, screenWidth + schScreenOffsetX*1.05, screenWidth + schScreenOffsetX*1.5}; + if(!probesArray[0]->connectedToWires()) + xOffset[1]=xOffset[0]; + if(!probesArray[2]->connectedToWires()) + xOffset[3]=xOffset[2]; + + //Add line of the scale axis schSvg += QString("\n") .arg(xOffset[channel]) .arg(schScreenOffsetY) @@ -1411,22 +1417,49 @@ void Simulator::updateOscilloscope(ItemBase * part) { .arg(lineColor[channel]); double tickSize = 10; - double tickPadding = channel>=(nChannels/2)? -tickSize: tickSize; - double textPadding = channel>=(nChannels/2)? 10 : -10; - QString alignment = channel>=(nChannels/2)? "start": "end"; + double paddingAlignment = channel>=(nChannels/2)? 1 : -1; + QString textAlignment = channel>=(nChannels/2)? "start": "end"; + + //Add name of the scale axis + QString netName = QString("Channel %1 (V)").arg(channel); + QList connectorItems; + connectorItems.append(probesArray[channel]); + ConnectorItem::collectEqualPotential(connectorItems, false, ViewGeometry::RatsnestFlag); + + Q_FOREACH ( ConnectorItem * cItem, connectorItems) { + std::cout << "ConnectorItem connected to : " << cItem->modelPartShared()->title().toStdString() << std::endl; + SymbolPaletteItem* symbolItem = dynamic_cast(cItem->attachedTo()); + if (symbolItem) + std::cout << "IS SYMBOL LABEL " << std::endl; + if(symbolItem && symbolItem->isOnlyNetLabel() ) { + std::cout << "IS ONLY NET LABEL " << std::endl; + netName = symbolItem->getLabel(); + netName += " (V)"; + break; + } + } + + schSvg += QString("%4\n") + .arg(xOffset[channel] + paddingAlignment * 140 + (1+paddingAlignment)*40) + .arg(schScreenOffsetY + screenHeight/2) + .arg(lineColor[channel]) + .arg(netName); + + for (int tick = 0; tick < (verDivisions+1); ++tick) { double vTick = voltsDiv[channel]*(verDivisions/2-tick)-offsets[channel]; QString voltageText = TextUtils::convertToPowerPrefix(vTick); - schSvg += QString("%5V\n") - .arg(xOffset[channel] + textPadding) + schSvg += QString("%5\n") + .arg(xOffset[channel] + paddingAlignment * 10) .arg(schScreenOffsetY + divisionSize * tick + 20) - .arg(lineColor[channel]).arg(alignment).arg(voltageText); + .arg(lineColor[channel]).arg(textAlignment).arg(voltageText); schSvg += QString("\n") - .arg(xOffset[channel] - tickSize + tickPadding) + .arg(xOffset[channel] - tickSize + paddingAlignment * tickSize * -1) .arg(schScreenOffsetY + divisionSize * tick) - .arg(xOffset[channel] + tickSize + tickPadding) + .arg(xOffset[channel] + tickSize + paddingAlignment * tickSize * -1) .arg(lineColor[channel]); } @@ -1441,10 +1474,14 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Add time scale axis in sch for (int tick = 0; tick < (horDivisions+1); ++tick) { - schSvg += QString("%4s") + schSvg += QString("%4") .arg(schScreenOffsetX+divisionSize*tick).arg(screenHeight+schScreenOffsetY*1.4) .arg("black").arg(TextUtils::convertToPowerPrefix(hPos + timeDiv*tick)); } + schSvg += QString("Time (s)") + .arg(schScreenOffsetX + screenWidth / 2) + .arg(screenHeight + schScreenOffsetY * 1.8) + .arg("black"); bbSvg += ""; schSvg += ""; From 47ac612af43f906574122de9ac47f11112aca67d Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 10 Oct 2023 15:16:30 +0200 Subject: [PATCH 21/45] updated position of the labels and axes in the oscilloscope when simulating as the oscilloscope part was modified (screen is now balck and the screen is a bit smaller to allow us to place labels and axes) --- src/simulation/simulator.cpp | 66 ++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 3056ba2a6..1d9bd6890 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -1340,23 +1340,23 @@ void Simulator::updateOscilloscope(ItemBase * part) { double ch3_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch3 offset"), "V"); double ch4_volsDiv = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 volts/div"), "V"); double ch4_offset = TextUtils::convertFromPowerPrefix(part->getProperty("ch4 offset"), "V"); - QString lineColor[4] = {"yellow", "green", "blue", "purple"}; + QString lineColor[4] = {"#ffff50", "lightgreen", "lightblue", "pink"}; double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; - double offsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; + double chOffsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; - double screenWidth = 3690.9385, screenHeight = 2952.7507, screenStrokeWidth= 29.5275; + double screenWidth = 3376.022, screenHeight = 2700.072, bbScreenStrokeWidth= 20; double verDivisions = 8, horDivisions = 10, divisionSize = screenHeight/verDivisions; - double bbScreenOffset = 132.87378, schScreenOffsetX = 607.41187, schScreenOffsetY = 229.091146; + double bbScreenOffsetX = 290.544, bbScreenOffsetY = 259.061, schScreenOffsetX = 906.07449, schScreenOffsetY = 354.60801; QString svgHeader = "\n%5" "\n"; QString bbSvg = QString(svgHeader) - .arg((screenWidth+bbScreenOffset)/1000) - .arg((screenHeight+bbScreenOffset*2)/1000) - .arg(screenWidth+bbScreenOffset) - .arg(screenHeight+bbScreenOffset*2) + .arg((screenWidth+bbScreenOffsetX)/1000) + .arg((screenHeight+bbScreenOffsetY*2)/1000) + .arg(screenWidth+bbScreenOffsetX) + .arg(screenHeight+bbScreenOffsetY*2) .arg(TextUtils::CreatedWithFritzingXmlComment); QString schSvg = QString(svgHeader) .arg((screenWidth+schScreenOffsetX*2)/1000) @@ -1385,25 +1385,31 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Draw the signal QString pathId = QString("ch%1-path").arg(channel+1); - QString signalPath = generateSvgPath(v, vCom, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], offsets[channel], + QString signalPath = generateSvgPath(v, vCom, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], chOffsets[channel], screenHeight, screenWidth, lineColor[channel], "20"); - bbSvg += signalPath.arg(bbScreenOffset).arg(bbScreenOffset); + bbSvg += signalPath.arg(bbScreenOffsetX).arg(bbScreenOffsetY); schSvg += signalPath.arg(schScreenOffsetX).arg(schScreenOffsetY); //Add text label about volts/div for each channel bbSvg += QString("CH%4: %5V\n") - .arg(bbScreenOffset+divisionSize*channel).arg(screenHeight+bbScreenOffset*1.7) - .arg(lineColor[channel]).arg(channel+1).arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); + .arg(bbScreenOffsetX + divisionSize*channel) + .arg(screenHeight + bbScreenOffsetY * 1.35) + .arg(lineColor[channel]).arg(channel+1) + .arg(TextUtils::convertToPowerPrefix(voltsDiv[channel])); //Add triangle as a mark for the offset for each channel double arrowSize = 50; - double arrowPos = -1*offsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+bbScreenOffset-arrowSize; - bbSvg += QString("\n") - .arg(arrowSize).arg(arrowSize*2).arg(lineColor[channel]).arg(arrowPos); + double arrowPos = -1*chOffsets[channel]/ch1_volsDiv*divisionSize+screenHeight/2+bbScreenOffsetY-arrowSize; + bbSvg += QString("\n") + .arg(arrowSize) + .arg(arrowSize*2) + .arg(lineColor[channel]) + .arg(bbScreenOffsetX - arrowSize - 10) + .arg(arrowPos); //Add voltage scale axis in sch - double xOffset[4] = {schScreenOffsetX*0.95, schScreenOffsetX*0.6, - screenWidth + schScreenOffsetX*1.05, screenWidth + schScreenOffsetX*1.5}; + double xOffset[4] = {schScreenOffsetX*0.95, schScreenOffsetX*0.62, + screenWidth + schScreenOffsetX*1.05, screenWidth + schScreenOffsetX*1.4}; if(!probesArray[0]->connectedToWires()) xOffset[1]=xOffset[0]; if(!probesArray[2]->connectedToWires()) @@ -1427,12 +1433,8 @@ void Simulator::updateOscilloscope(ItemBase * part) { ConnectorItem::collectEqualPotential(connectorItems, false, ViewGeometry::RatsnestFlag); Q_FOREACH ( ConnectorItem * cItem, connectorItems) { - std::cout << "ConnectorItem connected to : " << cItem->modelPartShared()->title().toStdString() << std::endl; SymbolPaletteItem* symbolItem = dynamic_cast(cItem->attachedTo()); - if (symbolItem) - std::cout << "IS SYMBOL LABEL " << std::endl; if(symbolItem && symbolItem->isOnlyNetLabel() ) { - std::cout << "IS ONLY NET LABEL " << std::endl; netName = symbolItem->getLabel(); netName += " (V)"; break; @@ -1441,7 +1443,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { schSvg += QString("%4\n") - .arg(xOffset[channel] + paddingAlignment * 140 + (1+paddingAlignment)*40) + .arg(xOffset[channel] + paddingAlignment * 180 + (1+paddingAlignment)*30) .arg(schScreenOffsetY + screenHeight/2) .arg(lineColor[channel]) .arg(netName); @@ -1449,7 +1451,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { for (int tick = 0; tick < (verDivisions+1); ++tick) { - double vTick = voltsDiv[channel]*(verDivisions/2-tick)-offsets[channel]; + double vTick = voltsDiv[channel]*(verDivisions/2-tick)-chOffsets[channel]; QString voltageText = TextUtils::convertToPowerPrefix(vTick); schSvg += QString("%5\n") .arg(xOffset[channel] + paddingAlignment * 10) @@ -1467,21 +1469,27 @@ void Simulator::updateOscilloscope(ItemBase * part) { } //Add time scale axis in bb - bbSvg += QString("time/div: %3s pos: %4") - .arg(bbScreenOffset+screenWidth/2).arg(bbScreenOffset*0.7) + bbSvg += QString("time/div: %3s ") + .arg(bbScreenOffsetX + screenWidth / 2) + .arg(bbScreenOffsetY * 0.85) .arg(TextUtils::convertToPowerPrefix(timeDiv)) .arg(TextUtils::convertToPowerPrefix(hPos)); + bbSvg += QString(" pos: %4s") + .arg(bbScreenOffsetX + screenWidth/2) + .arg(bbScreenOffsetY * 0.85) + .arg(TextUtils::convertToPowerPrefix(timeDiv)) + .arg(TextUtils::convertToPowerPrefix(hPos)); //Add time scale axis in sch for (int tick = 0; tick < (horDivisions+1); ++tick) { schSvg += QString("%4") - .arg(schScreenOffsetX+divisionSize*tick).arg(screenHeight+schScreenOffsetY*1.4) - .arg("black").arg(TextUtils::convertToPowerPrefix(hPos + timeDiv*tick)); + .arg(schScreenOffsetX+divisionSize*tick).arg(screenHeight+schScreenOffsetY*1.25) + .arg("white").arg(TextUtils::convertToPowerPrefix(hPos + timeDiv*tick)); } schSvg += QString("Time (s)") .arg(schScreenOffsetX + screenWidth / 2) - .arg(screenHeight + schScreenOffsetY * 1.8) - .arg("black"); + .arg(screenHeight + schScreenOffsetY * 1.5) + .arg("white"); bbSvg += ""; schSvg += ""; From af9e5f62f0799ddfd43d09cf4fcd5c4b43cbf1ff Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 10 Oct 2023 22:50:55 +0200 Subject: [PATCH 22/45] added properties for the triangular wavegenerator --- resources/properties.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/resources/properties.xml b/resources/properties.xml index e700200a6..f23af7668 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -141,11 +141,19 @@ + + + + + + + + @@ -160,12 +168,15 @@ + + + @@ -180,10 +191,12 @@ + + From b848df33f2a30c62c59760b3fe47da0bee925518 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 10 Oct 2023 22:56:08 +0200 Subject: [PATCH 23/45] added some examples for the transitory analysis --- .../EN/trans/flipflop_led_flashing_circuit.fzz | Bin 0 -> 9438 bytes .../EN/trans/oscilloscope_pulse_test.fzz | Bin 0 -> 6277 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz create mode 100644 sketches/core/Simulator/EN/trans/oscilloscope_pulse_test.fzz diff --git a/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz b/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz new file mode 100644 index 0000000000000000000000000000000000000000..17ca14a5407d5fd2332904622e4ecd894e736341 GIT binary patch literal 9438 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 LYxI9ECYb*P{ip$G literal 0 HcmV?d00001 diff --git a/sketches/core/Simulator/EN/trans/oscilloscope_pulse_test.fzz b/sketches/core/Simulator/EN/trans/oscilloscope_pulse_test.fzz new file mode 100644 index 0000000000000000000000000000000000000000..51a796489b15484a602038ce5a9f46d589945c9c GIT binary patch literal 6277 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 Ml%Hlc%70h?1Jh&*=l}o! literal 0 HcmV?d00001 From 8e1440a3a5b92578d534a3fd06d2f620e1d3a266 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Mon, 4 Dec 2023 23:00:59 +0100 Subject: [PATCH 24/45] added 555 example --- .../core/Simulator/EN/trans/555_IC_astable.fzz | Bin 0 -> 9472 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 sketches/core/Simulator/EN/trans/555_IC_astable.fzz diff --git a/sketches/core/Simulator/EN/trans/555_IC_astable.fzz b/sketches/core/Simulator/EN/trans/555_IC_astable.fzz new file mode 100644 index 0000000000000000000000000000000000000000..516cebcea0cc11d438b368370828af3f8b56c85c GIT binary patch literal 9472 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 literal 0 HcmV?d00001 From 082736ddb74cbc452fc5b2e67a3afb7f9b2e6800 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 5 Dec 2023 21:18:28 +0100 Subject: [PATCH 25/45] Finish wiring of the BB view of the 555 example --- .../Simulator/EN/trans/555_IC_astable.fzz | Bin 9472 -> 11073 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sketches/core/Simulator/EN/trans/555_IC_astable.fzz b/sketches/core/Simulator/EN/trans/555_IC_astable.fzz index 516cebcea0cc11d438b368370828af3f8b56c85c..427da430c0ab27d352d9e004f3274cea7bfaa09a 100644 GIT binary patch literal 11073 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 T|0WR8RNv6a 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 From 8e7dcd3d2279a352d9d0670a825968dceaf7f692 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Tue, 5 Dec 2023 22:00:52 +0100 Subject: [PATCH 26/45] added two sketches as examples of the transient simulation --- sketches/index.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sketches/index.xml b/sketches/index.xml index 03458606f..badd21d94 100644 --- a/sketches/index.xml +++ b/sketches/index.xml @@ -245,6 +245,12 @@ + + + + + + @@ -505,6 +511,12 @@ + + + + + + From 3ed26cbb33736513796fd960dd026745985cd5c5 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 7 Dec 2023 20:49:21 +0100 Subject: [PATCH 27/45] fixed oscilloscope text string (too many arguments) --- src/simulation/simulator.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 1d9bd6890..74ae8e1bf 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -1471,13 +1471,11 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Add time scale axis in bb bbSvg += QString("time/div: %3s ") .arg(bbScreenOffsetX + screenWidth / 2) - .arg(bbScreenOffsetY * 0.85) - .arg(TextUtils::convertToPowerPrefix(timeDiv)) - .arg(TextUtils::convertToPowerPrefix(hPos)); + .arg(bbScreenOffsetY * 0.85) + .arg(TextUtils::convertToPowerPrefix(timeDiv)); bbSvg += QString(" pos: %4s") .arg(bbScreenOffsetX + screenWidth/2) .arg(bbScreenOffsetY * 0.85) - .arg(TextUtils::convertToPowerPrefix(timeDiv)) .arg(TextUtils::convertToPowerPrefix(hPos)); //Add time scale axis in sch From bfbacda6970be3d45d2ed748311b2c7812f6a7cf Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 7 Dec 2023 21:44:00 +0100 Subject: [PATCH 28/45] fixed bug when properties contain capital letters. If so, the property is not found when generating the spice netlist. --- src/mainwindow/getspice.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mainwindow/getspice.cpp b/src/mainwindow/getspice.cpp index 5fbbbd01e..ecb9662e5 100644 --- a/src/mainwindow/getspice.cpp +++ b/src/mainwindow/getspice.cpp @@ -34,9 +34,9 @@ QString GetSpice::getSpice(ItemBase * itemBase, const QList< QListinstanceTitle(); if (pos > 0 && replacement.at(0).toLower() == spice.at(pos - 1).toLower()) { // if the type letter is repeated @@ -44,7 +44,7 @@ QString GetSpice::getSpice(ItemBase * itemBase, const QList< QListcachedConnectorItems()) { if (ci->connectorSharedID().toLower() == cname) { @@ -79,7 +79,7 @@ QString GetSpice::getSpice(ItemBase * itemBase, const QList< QList knownTokens; knownTokens << "inductance" << "resistance" << "current" << "tolerance" << "power" << "capacitance" << "voltage"; if(replacement.isEmpty()) { - if (prop.contains(token) || knownTokens.contains(token)) { + if (prop.contains(token) || prop.contains(token.toLower()) || knownTokens.contains(token)) { // Property exists but is empty. Or it is one of a few known tokens. Just assume zero. replacement = "0"; } else { From d258cf5bfaabad106d55077517aad4bf5a0b0702 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 7 Dec 2023 21:48:50 +0100 Subject: [PATCH 29/45] imprved handling of properties with capital letters --- src/mainwindow/getspice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mainwindow/getspice.cpp b/src/mainwindow/getspice.cpp index ecb9662e5..bbf205fae 100644 --- a/src/mainwindow/getspice.cpp +++ b/src/mainwindow/getspice.cpp @@ -79,7 +79,7 @@ QString GetSpice::getSpice(ItemBase * itemBase, const QList< QList knownTokens; knownTokens << "inductance" << "resistance" << "current" << "tolerance" << "power" << "capacitance" << "voltage"; if(replacement.isEmpty()) { - if (prop.contains(token) || prop.contains(token.toLower()) || knownTokens.contains(token)) { + if (prop.contains(token) || knownTokens.contains(token.toLower())) { // Property exists but is empty. Or it is one of a few known tokens. Just assume zero. replacement = "0"; } else { From 7684fbd8193726bafffefd0684c3556afe4d36ed Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 7 Dec 2023 23:20:17 +0100 Subject: [PATCH 30/45] improved simulation error dialog. Now, there is a scroll area for the details of the error and the details can be copied to the clipboard. Before the dialog height was bigger than the screen height for long netlists. The Dialog is based on QBoxMessage instead of FBoxMessage. --- src/simulation/simulator.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 74ae8e1bf..9bffc088f 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -238,14 +238,18 @@ void Simulator::simulate() { QString::fromStdString(m_simulator->getLog(true)).toLower().contains("warning")) { // "warning, can't find model" //Ngspice found an error, do not continue std::cout << "Error loading the netlist. Probably some SPICE field is wrong, check them." <getLog(false)) + - QString::fromStdString(m_simulator->getLog(true)) + - "\n\nNetlist:\n" + spiceNetlist); + + QMessageBox messageBox(QMessageBox::Warning, tr("Simulator Error"), tr("The simulator gave an error when loading the netlist. " + "Probably some SPICE field is wrong, please, check them.\n" + "If the parts are from the simulation bin, please, report the bug in GitHub " + "(https://github.com/fritzing/fritzing-app/issues) and copy the details available below.")); + messageBox.setDetailedText("Errors:\n" + + QString::fromStdString(m_simulator->getLog(false)) + + QString::fromStdString(m_simulator->getLog(true)) + + "\n\nNetlist:\n" + spiceNetlist); + messageBox.setMinimumSize(400, 0); + messageBox.exec(); + stopSimulation(); return; } From a10fdb587959ffbfc6a9dc097b9a0784dad86d77 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 7 Dec 2023 23:21:07 +0100 Subject: [PATCH 31/45] show the right channel in the axis of the oscilloscope (SCH view) --- src/simulation/simulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 9bffc088f..c67b69da2 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -1431,7 +1431,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { QString textAlignment = channel>=(nChannels/2)? "start": "end"; //Add name of the scale axis - QString netName = QString("Channel %1 (V)").arg(channel); + QString netName = QString("Channel %1 (V)").arg(channel + 1); QList connectorItems; connectorItems.append(probesArray[channel]); ConnectorItem::collectEqualPotential(connectorItems, false, ViewGeometry::RatsnestFlag); From ef83cec4362ffa14d21c8b1848e60117f5530a20 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Thu, 7 Dec 2023 23:45:08 +0100 Subject: [PATCH 32/45] added properties of the transformer so that users can modifiy its properties and added first example with transformer --- resources/properties.xml | 18 ++++++++++++++++++ .../EN/trans/full_wave_rectifier.fzz | Bin 0 -> 4134 bytes 2 files changed, 18 insertions(+) create mode 100644 sketches/core/Simulator/EN/trans/full_wave_rectifier.fzz diff --git a/resources/properties.xml b/resources/properties.xml index f23af7668..6de5ce130 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -509,4 +509,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/sketches/core/Simulator/EN/trans/full_wave_rectifier.fzz b/sketches/core/Simulator/EN/trans/full_wave_rectifier.fzz new file mode 100644 index 0000000000000000000000000000000000000000..0e89dbe859898cc8cd273e7393ea95badfabd435 GIT binary patch literal 4134 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 dMn(RY{%^L|)xZb-_XYfu^1nOsZw Date: Thu, 7 Dec 2023 23:47:35 +0100 Subject: [PATCH 33/45] fixed error when a property does not have symbol: If there is no symbol, the pattern of the validator becomes invalid --- src/items/capacitor.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/items/capacitor.cpp b/src/items/capacitor.cpp index 2ad1ba91a..1c81ec9bd 100644 --- a/src/items/capacitor.cpp +++ b/src/items/capacitor.cpp @@ -108,12 +108,14 @@ bool Capacitor::collectExtraInfo(QWidget * parent, const QString & family, const if (propertyDef->maxValue > propertyDef->minValue) { validator->setBounds(propertyDef->minValue, propertyDef->maxValue); } - // QString pattern = QString("((\\d{0,10})|(\\d{0,10}\\.)|(\\d{0,10}\\.\\d{1,10}))[%1]{0,1}[%2]{0,1}") - QString pattern = QString("((-?\\d{1,3})|(-?\\d{1,3}\\.)|(-?\\d{1,3}\\.\\d{1,2}))[%1]{0,1}[%2]{0,1}").arg( - // QString pattern = QString("((\\d{0,3})|(\\d{0,3}\\.)|(\\d{0,3}\\.\\d{1,3}))[%1]{0,1}[%2]{0,1}") + QString symbolRegExp = propertyDef->symbol.isEmpty() ? "" : QString("[%1]{0,1}").arg(propertyDef->symbol); + + // QString pattern = QString("((\\d{0,10})|(\\d{0,10}\\.)|(\\d{0,10}\\.\\d{1,10}))[%1]{0,1}%2") + QString pattern = QString("((-?\\d{1,3})|(-?\\d{1,3}\\.)|(-?\\d{1,3}\\.\\d{1,2}))[%1]{0,1}%2").arg( + // QString pattern = QString("((\\d{0,3})|(\\d{0,3}\\.)|(\\d{0,3}\\.\\d{1,3}))[%1]{0,1}%2") TextUtils::PowerPrefixesString, - propertyDef->symbol - ); + symbolRegExp + ); validator->setRegularExpression(QRegularExpression(pattern)); focusOutComboBox->setValidator(validator); connect(focusOutComboBox->validator(), SIGNAL(sendState(QValidator::State)), this, SLOT(textModified(QValidator::State))); From bcf5960428d2b344e3abda9d4aec97a60e11d145 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Fri, 8 Dec 2023 15:03:16 +0100 Subject: [PATCH 34/45] calculate the com voltage at the oscilloscope when there is a wire connected to it. Return a vector with 0s instead of an empty vector (in case that we ask for the voltage of the ground) --- src/simulation/ngspice_simulator.cpp | 1 - src/simulation/simulator.cpp | 12 ++++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/simulation/ngspice_simulator.cpp b/src/simulation/ngspice_simulator.cpp index cc24863cf..8851a72a3 100644 --- a/src/simulation/ngspice_simulator.cpp +++ b/src/simulation/ngspice_simulator.cpp @@ -167,7 +167,6 @@ std::vector NgSpiceSimulator::getVecInfo(const std::string& vecName) { std::vector realValues; if (vecInfo->v_realdata) { - std::cout << "getVecInfo: data" << vecInfo->v_length << std::endl; for(int i=0; iv_length; i++) realValues.push_back(vecInfo->v_realdata[i]); return realValues; diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index c67b69da2..9e7f80a3f 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -605,14 +605,16 @@ double Simulator::calculateVoltage(ConnectorItem * c0, ConnectorItem * c1) { std::vector Simulator::voltageVector(ConnectorItem * c0) { int net0 = m_connector2netHash.value(c0); - std::cout << "calculateVoltageVector: "; QString net0str = QString("v(%1)").arg(net0); - auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); if (net0 != 0) { - std::cout << "calculateVoltageVector: "; + return m_simulator->getVecInfo(net0str.toStdString()); } - return m_simulator->getVecInfo(net0str.toStdString()); + + //This is the ground (node 0), return a vector with 0s, same size as the time vector + auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); + std::vector voltageVector(timeInfo.size(), 0.0); + return voltageVector; } QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double simStartTime, double simTimeStep, double timePos, double timeScale, double verticalScale, double verOffset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { @@ -1385,6 +1387,8 @@ void Simulator::updateOscilloscope(ItemBase * part) { for(auto& val : vCom) { val = dist(gen); } + } else { + vCom = voltageVector(comProbe); } //Draw the signal From cccd30ecb385f730597f50cc34f4d17f06d9d92a Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sat, 9 Dec 2023 23:59:59 +0100 Subject: [PATCH 35/45] First implementation using an animation of the results of the simulation to show transitory behaviours --- src/simulation/simulator.cpp | 162 +++++++++++++++++++++-------------- src/simulation/simulator.h | 13 ++- 2 files changed, 105 insertions(+), 70 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 9e7f80a3f..ca07388e1 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -67,6 +67,11 @@ Simulator::Simulator(MainWindow *mainWindow) : QObject(mainWindow) { m_simTimer->setSingleShot(true); connect(m_simTimer, &QTimer::timeout, this, &Simulator::simulate); + // Configure the timer to show the simulation results + m_showResultsTimer = new QTimer(this); + m_showResultsTimer->setInterval(50); + connect(m_showResultsTimer, &QTimer::timeout, this, &Simulator::showSimulationResults); + QSettings settings; int enabled = settings.value("simulatorEnabled", 0).toInt(); enable(true); @@ -141,7 +146,8 @@ void Simulator::startSimulation() * simulated, the smoke images, and the messages on the multimeter. */ void Simulator::stopSimulation() { - m_simulating = false; + m_showResultsTimer->stop(); + m_simulating = false; removeSimItems(); emit simulationStartedOrStopped(m_simulating); } @@ -191,7 +197,7 @@ void Simulator::simulate() { m_simulator->clearLog(); QList< QList* > netList; - QSet itemBases; + itemBases.clear(); QString spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); //Select the type of analysis based on if there is an oscilloscope in the simulation @@ -338,70 +344,92 @@ void Simulator::simulate() { m_simulator->command("bg_halt"); + //Delete the pointers + foreach (QList * net, netList) { + delete net; + } + netList.clear(); + + //The spice simulation has finished, iterate over each part being simulated and update it (if it is necessary). - //This loops is in charge of: - // * update the multimeters screen - // * add smoke to a part if something is out of its specifications - // * update the brightness of the LEDs - foreach (ItemBase * part, itemBases){ - //Remove the effects, if any - part->setGraphicsEffect(nullptr); - m_sch2bbItemHash.value(part)->setGraphicsEffect(nullptr); - - std::cout << "-----------------------------------" <instanceTitle().toStdString() << std::endl; - - QString family = part->family().toLower(); - - if (family.contains("capacitor")) { - updateCapacitor(part); - continue; - } - if (family.contains("diode")) { - updateDiode(part); - continue; - } - if (family.contains("led")) { - updateLED(part); - continue; - } - if (family.contains("resistor")) { - updateResistor(part); - continue; - } - if (family.contains("multimeter")) { - updateMultimeter(part); - continue; - } - if (family.contains("dc motor")) { - updateDcMotor(part); - continue; - } - if (family.contains("line sensor") || family.contains("distance sensor")) { - updateIRSensor(part); - continue; - } - if (family.contains("battery") || family.contains("voltage source")) { - updateBattery(part); - continue; - } - if (family.contains("potentiometer") || family.contains("sparkfun trimpot")) { - updatePotentiometer(part); - continue; - } - if (family.contains("oscilloscope")) { - updateOscilloscope(part); - continue; - } + currSimStep = 1; + m_showResultsTimer->start(); + removeSimItems(); + updateParts(itemBases, 0); +} - } +void Simulator::showSimulationResults() { + if (currSimStep < Simulator::SimSteps) { + removeSimItems(); + updateParts(itemBases, currSimStep); + currSimStep++; + } else { + m_showResultsTimer->stop(); + } - //Delete the pointers - foreach (QList * net, netList) { - delete net; - } - netList.clear(); +} + +/** + * Update the parts with the vidual effects. E.g: + * * update the multimeters screen + * * add smoke to a part if something is out of its specifications + * * update the brightness of the LEDs + * @param[in] itemBases A set of parts to be updated + * @param[in] time The simulation time to be used for getting the voltages and currents + */ +void Simulator::updateParts(QSet itemBases, int timeStep) { + foreach (ItemBase * part, itemBases){ + //Remove the effects, if any + part->setGraphicsEffect(nullptr); + m_sch2bbItemHash.value(part)->setGraphicsEffect(nullptr); + + std::cout << "-----------------------------------" <instanceTitle().toStdString() << std::endl; + + QString family = part->family().toLower(); + + if (family.contains("capacitor")) { + updateCapacitor(part); + continue; + } + if (family.contains("diode")) { + updateDiode(part); + continue; + } + if (family.contains("led")) { + updateLED(part); + continue; + } + if (family.contains("resistor")) { + updateResistor(part); + continue; + } + if (family.contains("multimeter")) { + updateMultimeter(part); + continue; + } + if (family.contains("dc motor")) { + updateDcMotor(part); + continue; + } + if (family.contains("line sensor") || family.contains("distance sensor")) { + updateIRSensor(part); + continue; + } + if (family.contains("battery") || family.contains("voltage source")) { + updateBattery(part); + continue; + } + if (family.contains("potentiometer") || family.contains("sparkfun trimpot")) { + updatePotentiometer(part); + continue; + } + if (family.contains("oscilloscope")) { + updateOscilloscope(part, timeStep); + continue; + } + } } /** @@ -617,7 +645,7 @@ std::vector Simulator::voltageVector(ConnectorItem * c0) { return voltageVector; } -QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, QString nameId, double simStartTime, double simTimeStep, double timePos, double timeScale, double verticalScale, double verOffset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { +QString Simulator::generateSvgPath(std::vector proveVector, std::vector comVector, int currTimeStep, QString nameId, double simStartTime, double simTimeStep, double timePos, double timeScale, double verticalScale, double verOffset, double screenHeight, double screenWidth, QString color, QString strokeWidth ) { std::cout << "OSCILLOSCOPE: pos " << timePos << ", timeScale: " << timeScale << std::endl; std::cout << "OSCILLOSCOPE: VOLTAGE VALUES " << nameId.toStdString() << ": "; QString svg; @@ -639,6 +667,8 @@ QString Simulator::generateSvgPath(std::vector proveVector, std::vector< std::cout << "OSCILLOSCOPE: nSampleInScreen " << nSampleInScreen << std::endl; int screenPoint = 0; for (int vPoint = 0; vPoint < points; vPoint++) { + if (currTimeStep < vPoint) + break; double time = simStartTime + simTimeStep * vPoint; if (time < timePos) continue; @@ -1310,7 +1340,7 @@ void Simulator::updateMultimeter(ItemBase * part) { * Calculates the parameter to measure and updates the display of the multimeter. * @param[in] part An oscilloscope that is going to be checked and updated. */ -void Simulator::updateOscilloscope(ItemBase * part) { +void Simulator::updateOscilloscope(ItemBase * part, int currTimeStep) { std::cout << "updateOscilloscope: " << std::endl; ConnectorItem * comProbe = nullptr, * v1Probe = nullptr, * v2Probe = nullptr, * v3Probe = nullptr, * v4Probe = nullptr; QList probes = part->cachedConnectorItems(); @@ -1393,7 +1423,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { //Draw the signal QString pathId = QString("ch%1-path").arg(channel+1); - QString signalPath = generateSvgPath(v, vCom, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], chOffsets[channel], + QString signalPath = generateSvgPath(v, vCom, currTimeStep, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], chOffsets[channel], screenHeight, screenWidth, lineColor[channel], "20"); bbSvg += signalPath.arg(bbScreenOffsetX).arg(bbScreenOffsetY); schSvg += signalPath.arg(schScreenOffsetX).arg(schScreenOffsetY); @@ -1474,7 +1504,7 @@ void Simulator::updateOscilloscope(ItemBase * part) { } - } + } //End of for each channel //Add time scale axis in bb bbSvg += QString("time/div: %3s ") diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 8d0e91bae..9fedcf596 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -46,12 +46,15 @@ public slots: void enable(bool); void stopSimulation(); void startSimulation(); + void showSimulationResults(); + signals: void simulationStartedOrStopped(bool running); void simulationEnabled(bool enabled); -protected: +protected: + void updateParts(QSet, int); void drawSmoke(ItemBase* part); void updateMultimeterScreen(ItemBase *, QString); void updateMultimeterScreen(ItemBase *, double); @@ -67,7 +70,7 @@ public slots: double getVectorValueOrDefault(const std::string & vecName, double defaultValue); double calculateVoltage(ConnectorItem *, ConnectorItem *); std::vector voltageVector(ConnectorItem *); - QString generateSvgPath(std::vector, std::vector, QString, double, double, double, double, double, double, double, double, QString, QString); + QString generateSvgPath(std::vector, std::vector, int, QString, double, double, double, double, double, double, double, double, QString, QString); double getCurrent(ItemBase*, QString subpartName=""); double getTransistorCurrent(QString spicePartName, TransistorLeg leg); double getPower(ItemBase*, QString subpartName=""); @@ -77,7 +80,7 @@ public slots: void updateDiode(ItemBase *); void updateLED(ItemBase *); void updateMultimeter(ItemBase *); - void updateOscilloscope(ItemBase *); + void updateOscilloscope(ItemBase *, int); void updateResistor(ItemBase *); void updatePotentiometer(ItemBase *); void updateDcMotor(ItemBase *); @@ -93,11 +96,13 @@ public slots: bool m_enabled = false; + QSet itemBases; QHash m_sch2bbItemHash; QHash m_connector2netHash; QList* m_instanceTitleSim; - QTimer *m_simTimer; + QTimer *m_simTimer, *m_showResultsTimer; + int currSimStep; static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5; static constexpr double SimSteps = 400; From 2701e68d5d6f082758045338128a514f3794bc9b Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sun, 10 Dec 2023 16:33:15 +0100 Subject: [PATCH 36/45] now all parts get voltages and currents depending on simulation time --- src/simulation/simulator.cpp | 104 ++++++++++++++++++----------------- src/simulation/simulator.h | 30 +++++----- 2 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index ca07388e1..3bce595ae 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -390,43 +390,43 @@ void Simulator::updateParts(QSet itemBases, int timeStep) { QString family = part->family().toLower(); if (family.contains("capacitor")) { - updateCapacitor(part); + updateCapacitor(timeStep, part); continue; } if (family.contains("diode")) { - updateDiode(part); + updateDiode(timeStep, part); continue; } if (family.contains("led")) { - updateLED(part); + updateLED(timeStep, part); continue; } if (family.contains("resistor")) { - updateResistor(part); + updateResistor(timeStep, part); continue; } if (family.contains("multimeter")) { - updateMultimeter(part); + updateMultimeter(timeStep, part); continue; } if (family.contains("dc motor")) { - updateDcMotor(part); + updateDcMotor(timeStep, part); continue; } if (family.contains("line sensor") || family.contains("distance sensor")) { - updateIRSensor(part); + updateIRSensor(timeStep, part); continue; } if (family.contains("battery") || family.contains("voltage source")) { - updateBattery(part); + updateBattery(timeStep, part); continue; } if (family.contains("potentiometer") || family.contains("sparkfun trimpot")) { - updatePotentiometer(part); + updatePotentiometer(timeStep, part); continue; } if (family.contains("oscilloscope")) { - updateOscilloscope(part, timeStep); + updateOscilloscope(timeStep, part); continue; } } @@ -593,12 +593,14 @@ void Simulator::removeSimItems(QList items) { * @param[in] defaultValue value to return on empty vector * @returns the first vector element or the given default value */ -double Simulator::getVectorValueOrDefault(const std::string & vecName, double defaultValue) { +double Simulator::getVectorValueOrDefault(unsigned long timeStep, const std::string & vecName, double defaultValue) { auto vecInfo = m_simulator->getVecInfo(vecName); - if (vecInfo.empty()) { + if (vecInfo.empty()) { return defaultValue; } else { - return vecInfo[vecInfo.size()-1]; + if (timeStep < 0 || timeStep >= vecInfo.size()) + return defaultValue; + return vecInfo[timeStep]; } } @@ -608,7 +610,7 @@ double Simulator::getVectorValueOrDefault(const std::string & vecName, double de * @param[in] c1 the second connector * @returns the voltage between the connector c0 and c1 */ -double Simulator::calculateVoltage(ConnectorItem * c0, ConnectorItem * c1) { +double Simulator::calculateVoltage(unsigned long timeStep, ConnectorItem * c0, ConnectorItem * c1) { int net0 = m_connector2netHash.value(c0); int net1 = m_connector2netHash.value(c1); @@ -621,12 +623,12 @@ double Simulator::calculateVoltage(ConnectorItem * c0, ConnectorItem * c1) { if (net0 != 0) { auto vecInfo = m_simulator->getVecInfo(net0str.toStdString()); if (vecInfo.empty()) return 0.0; - volt0 = vecInfo[0]; + volt0 = vecInfo[timeStep]; } if (net1 != 0) { auto vecInfo = m_simulator->getVecInfo(net1str.toStdString()); if (vecInfo.empty()) return 0.0; - volt1 = vecInfo[0]; + volt1 = vecInfo[timeStep]; } return volt0-volt1; } @@ -767,13 +769,13 @@ double Simulator::getMaxPropValue(ItemBase *part, QString property) { * @param[in] subpartName The name of the subpart. Leave it empty if there is only one spice line for the device. Otherwise, give the suffix of the subpart. * @returns the power that a part is consuming/producing. */ -double Simulator::getPower(ItemBase* part, QString subpartName) { +double Simulator::getPower(unsigned long timeStep, ItemBase* part, QString subpartName) { //TODO: Handle devices that do not return the power QString instanceStr = part->instanceTitle().toLower(); instanceStr.append(subpartName.toLower()); instanceStr.prepend("@"); instanceStr.append("[p]"); - return getVectorValueOrDefault(instanceStr.toStdString(), 0.0); + return getVectorValueOrDefault(timeStep, instanceStr.toStdString(), 0.0); } /** @@ -787,7 +789,7 @@ double Simulator::getPower(ItemBase* part, QString subpartName) { * @param[in] subpartName The name of the subpart. Leave it empty if there is only one spice line for the device. Otherwise, give the suffix of the subpart. * @returns the current that a part is consuming/producing. */ -double Simulator::getCurrent(ItemBase* part, QString subpartName) { +double Simulator::getCurrent(unsigned long timeStep, ItemBase* part, QString subpartName) { QString instanceStr = part->instanceTitle().toLower(); instanceStr.append(subpartName.toLower()); @@ -820,7 +822,7 @@ double Simulator::getCurrent(ItemBase* part, QString subpartName) { break; } - return getVectorValueOrDefault(instanceStr.toStdString(), 0.0); + return getVectorValueOrDefault(timeStep, instanceStr.toStdString(), 0.0); } /** @@ -828,7 +830,7 @@ double Simulator::getCurrent(ItemBase* part, QString subpartName) { * @param[in] spicePartName The name of the spice transistor. * @returns the current that the transistor is sinking/sourcing. */ -double Simulator::getTransistorCurrent(QString spicePartName, TransistorLeg leg) { +double Simulator::getTransistorCurrent(unsigned long timeStep, QString spicePartName, TransistorLeg leg) { if(spicePartName.at(0).toLower()!=QChar('q')) { //TODO: Add tr() throw QString("Error getting the current of a transistor. The device is not a transistor, its first letter is not a Q. Name: %1").arg(spicePartName); @@ -848,7 +850,7 @@ double Simulator::getTransistorCurrent(QString spicePartName, TransistorLeg leg) throw QString("Error getting the current of a transistor. The transistor leg or property is not recognized. Leg: %1").arg(leg); } - return getVectorValueOrDefault(spicePartName.toStdString(), 0.0); + return getVectorValueOrDefault(timeStep, spicePartName.toStdString(), 0.0); } /** @@ -1015,9 +1017,9 @@ void Simulator::removeItemsToBeSimulated(QList & parts) { * Updates and checks a diode. Checks that the power is less than the maximum power. * @param[in] diode A part that is going to be checked and updated. */ -void Simulator::updateDiode(ItemBase * diode) { +void Simulator::updateDiode(unsigned long timeStep, ItemBase * diode) { double maxPower = getMaxPropValue(diode, "power"); - double power = getPower(diode); + double power = getPower(timeStep, diode); if (power > maxPower) { drawSmoke(diode); } @@ -1028,7 +1030,7 @@ void Simulator::updateDiode(ItemBase * diode) { * and updates the brightness of the LED in the breadboard view. * @param[in] part An LED that is going to be checked and updated. */ -void Simulator::updateLED(ItemBase * part) { +void Simulator::updateLED(unsigned long timeStep, ItemBase * part) { LED* led = dynamic_cast(part); if (led) { //Check if this an RGB led @@ -1036,7 +1038,7 @@ void Simulator::updateLED(ItemBase * part) { if (rgbString.isEmpty()) { // Just one LED - double curr = getCurrent(part); + double curr = getCurrent(timeStep, part); double maxCurr = getMaxPropValue(part, "current"); std::cout << "LED Current: " <getProperty("family").toLower(); ConnectorItem * negLeg, * posLeg; @@ -1090,7 +1092,7 @@ void Simulator::updateCapacitor(ItemBase * part) { return; double maxV = getMaxPropValue(part, "voltage"); - double v = calculateVoltage(posLeg, negLeg); + double v = calculateVoltage(timeStep, posLeg, negLeg); std::cout << "MaxVoltage of the capacitor: " << maxV << std::endl; std::cout << "Capacitor voltage is : " << QString("%1").arg(v).toStdString() << std::endl; @@ -1111,9 +1113,9 @@ void Simulator::updateCapacitor(ItemBase * part) { * Updates and checks a resistor. Checks that the power is less than the maximum power. * @param[in] part A resistor that is going to be checked and updated. */ -void Simulator::updateResistor(ItemBase * part) { +void Simulator::updateResistor(unsigned long timeStep, ItemBase * part) { double maxPower = getMaxPropValue(part, "power"); - double power = getPower(part); + double power = getPower(timeStep, part); std::cout << "Power: " << power < maxPower) { drawSmoke(part); @@ -1125,10 +1127,10 @@ void Simulator::updateResistor(ItemBase * part) { * for the two resistors "A" and "B". * @param[in] part A potentiometer that is going to be checked and updated. */ -void Simulator::updatePotentiometer(ItemBase * part) { +void Simulator::updatePotentiometer(unsigned long timeStep, ItemBase * part) { double maxPower = getMaxPropValue(part, "power"); - double powerA = getPower(part, "A"); //power through resistor A - double powerB = getPower(part, "B"); //power through resistor B + double powerA = getPower(timeStep, part, "A"); //power through resistor A + double powerB = getPower(timeStep, part, "B"); //power through resistor B double power = powerA + powerB; if (power > maxPower) { drawSmoke(part); @@ -1139,12 +1141,12 @@ void Simulator::updatePotentiometer(ItemBase * part) { * Updates and checks a battery. Checks that there are no short circuits. * @param[in] part A battery that is going to be checked and updated. */ -void Simulator::updateBattery(ItemBase * part) { +void Simulator::updateBattery(unsigned long timeStep, ItemBase * part) { double voltage = getMaxPropValue(part, "voltage"); double resistance = getMaxPropValue(part, "internal resistance"); double safetyMargin = 0.1; //TODO: This should be adjusted double maxCurrent = voltage/resistance * safetyMargin; - double current = getCurrent(part); //current that the battery delivers + double current = getCurrent(timeStep, part); //current that the battery delivers std::cout << "Battery: voltage=" << voltage << ", resistance=" << resistance <family().contains("line sensor")) { //digital sensor (push-pull output) QString spicename = part->instanceTitle().toLower(); spicename.prepend("q"); - i = getTransistorCurrent(spicename, COLLECTOR); //voltage applied to the motor + i = getTransistorCurrent(timeStep, spicename, COLLECTOR); //voltage applied to the motor } else { //analogue sensor (modelled by a voltage source and a resistor) - i = getCurrent(part, "a"); //voltage applied to the motor + i = getCurrent(timeStep, part, "a"); //voltage applied to the motor } std::cout << "IR sensor Max Iout: " << maxIout << ", current Iout " << i << std::endl; std::cout << "IR sensor Max V: " << maxV << ", current V " << v << std::endl; @@ -1209,7 +1211,7 @@ void Simulator::updateIRSensor(ItemBase * part) { * TODO: The number of arrows are proportional to the voltage applied. * @param[in] part A DC motor that is going to be checked and updated. */ -void Simulator::updateDcMotor(ItemBase * part) { +void Simulator::updateDcMotor(unsigned long timeStep, ItemBase * part) { double maxV = getMaxPropValue(part, "voltage (max)"); double minV = getMaxPropValue(part, "voltage (min)"); std::cout << "Motor1: " << std::endl; @@ -1222,7 +1224,7 @@ void Simulator::updateDcMotor(ItemBase * part) { if(!terminal1 || !terminal2 ) return; - double v = calculateVoltage(terminal1, terminal2); //voltage applied to the motor + double v = calculateVoltage(timeStep, terminal1, terminal2); //voltage applied to the motor if (abs(v) > maxV) { drawSmoke(part); return; @@ -1279,7 +1281,7 @@ void Simulator::updateDcMotor(ItemBase * part) { * Calculates the parameter to measure and updates the display of the multimeter. * @param[in] part A multimeter that is going to be checked and updated. */ -void Simulator::updateMultimeter(ItemBase * part) { +void Simulator::updateMultimeter(unsigned long timeStep, ItemBase * part) { QString variant = part->getProperty("variant").toLower(); ConnectorItem * comProbe = nullptr, * vProbe = nullptr, * aProbe = nullptr; QList probes = part->cachedConnectorItems(); @@ -1306,7 +1308,7 @@ void Simulator::updateMultimeter(ItemBase * part) { } if(comProbe->connectedToWires() && vProbe->connectedToWires()) { std::cout << "Multimeter (v_dc) connected with two terminals. " << std::endl; - double v = calculateVoltage(vProbe, comProbe); + double v = calculateVoltage(timeStep, vProbe, comProbe); updateMultimeterScreen(part, v); } return; @@ -1317,7 +1319,7 @@ void Simulator::updateMultimeter(ItemBase * part) { updateMultimeterScreen(part, "ERR"); return; } - updateMultimeterScreen(part, getCurrent(part)); + updateMultimeterScreen(part, getCurrent(timeStep, part)); return; } else if (variant.compare("ohmmeter") == 0) { std::cout << "Ohmmeter found. " << std::endl; @@ -1326,8 +1328,8 @@ void Simulator::updateMultimeter(ItemBase * part) { updateMultimeterScreen(part, "ERR"); return; } - double v = calculateVoltage(vProbe, comProbe); - double a = getCurrent(part); + double v = calculateVoltage(timeStep, vProbe, comProbe); + double a = getCurrent(timeStep, part); double r = abs(v/a); std::cout << "Ohmmeter: Volt: " << v <<", Curr: " << a <<", Ohm: " << r << std::endl; updateMultimeterScreen(part, r); @@ -1340,7 +1342,7 @@ void Simulator::updateMultimeter(ItemBase * part) { * Calculates the parameter to measure and updates the display of the multimeter. * @param[in] part An oscilloscope that is going to be checked and updated. */ -void Simulator::updateOscilloscope(ItemBase * part, int currTimeStep) { +void Simulator::updateOscilloscope(unsigned long timeStep, ItemBase * part) { std::cout << "updateOscilloscope: " << std::endl; ConnectorItem * comProbe = nullptr, * v1Probe = nullptr, * v2Probe = nullptr, * v3Probe = nullptr, * v4Probe = nullptr; QList probes = part->cachedConnectorItems(); @@ -1423,7 +1425,7 @@ void Simulator::updateOscilloscope(ItemBase * part, int currTimeStep) { //Draw the signal QString pathId = QString("ch%1-path").arg(channel+1); - QString signalPath = generateSvgPath(v, vCom, currTimeStep, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], chOffsets[channel], + QString signalPath = generateSvgPath(v, vCom, timeStep, pathId, m_simStartTime, m_simStepTime, hPos, timeDiv, divisionSize/voltsDiv[channel], chOffsets[channel], screenHeight, screenWidth, lineColor[channel], "20"); bbSvg += signalPath.arg(bbScreenOffsetX).arg(bbScreenOffsetY); schSvg += signalPath.arg(schScreenOffsetX).arg(schScreenOffsetY); diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 9fedcf596..d6060a892 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -67,25 +67,25 @@ public slots: QChar getDeviceType (ItemBase*); double getMaxPropValue(ItemBase*, QString); QString getSymbol(ItemBase*, QString); - double getVectorValueOrDefault(const std::string & vecName, double defaultValue); - double calculateVoltage(ConnectorItem *, ConnectorItem *); + double getVectorValueOrDefault(unsigned long timeStep, const std::string & vecName, double defaultValue); + double calculateVoltage(unsigned long, ConnectorItem *, ConnectorItem *); std::vector voltageVector(ConnectorItem *); QString generateSvgPath(std::vector, std::vector, int, QString, double, double, double, double, double, double, double, double, QString, QString); - double getCurrent(ItemBase*, QString subpartName=""); - double getTransistorCurrent(QString spicePartName, TransistorLeg leg); - double getPower(ItemBase*, QString subpartName=""); + double getCurrent(unsigned long, ItemBase*, QString subpartName=""); + double getTransistorCurrent(unsigned long timeStep, QString spicePartName, TransistorLeg leg); + double getPower(unsigned long, ItemBase*, QString subpartName=""); //Functions to update the parts - void updateCapacitor(ItemBase *); - void updateDiode(ItemBase *); - void updateLED(ItemBase *); - void updateMultimeter(ItemBase *); - void updateOscilloscope(ItemBase *, int); - void updateResistor(ItemBase *); - void updatePotentiometer(ItemBase *); - void updateDcMotor(ItemBase *); - void updateIRSensor(ItemBase *); - void updateBattery(ItemBase *); + void updateCapacitor(unsigned long, ItemBase *); + void updateDiode(unsigned long, ItemBase *); + void updateLED(unsigned long, ItemBase *); + void updateMultimeter(unsigned long, ItemBase *); + void updateOscilloscope(unsigned long, ItemBase *); + void updateResistor(unsigned long, ItemBase *); + void updatePotentiometer(unsigned long, ItemBase *); + void updateDcMotor(unsigned long, ItemBase *); + void updateIRSensor(unsigned long, ItemBase *); + void updateBattery(unsigned long, ItemBase *); bool m_simulating = false; MainWindow *m_mainWindow; From 5d3fc495e7f59ccfd6c166f54805451fe5e19409 Mon Sep 17 00:00:00 2001 From: Kjell Morgenstern Date: Tue, 13 Feb 2024 11:08:49 +0100 Subject: [PATCH 37/45] Revert elcos --- .../trans/flipflop_led_flashing_circuit.fzz | Bin 9438 -> 77650 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz b/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz index 17ca14a5407d5fd2332904622e4ecd894e736341..971c98a4d99eb5743842f760bb1cf3ea81682734 100644 GIT binary patch literal 77650 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 LYxI9ECYb*P{ip$G From 560f296a8ef001ac3f72a2e16ec7e6079e08519b Mon Sep 17 00:00:00 2001 From: Kjell Morgenstern Date: Fri, 15 Dec 2023 15:33:15 +0100 Subject: [PATCH 38/45] Added functionality to set sim frequency and save it to project. (cherry picked from commit 38e4fb6a92f3962cf283f864de77eb09c15cab66) --- src/dialogs/prefsdialog.cpp | 38 ++++++++++++++++++++++++++++++++++- src/dialogs/prefsdialog.h | 8 +++++++- src/fapplication.cpp | 2 +- src/mainwindow/mainwindow.cpp | 4 ++++ src/mainwindow/mainwindow.h | 1 + src/model/modelbase.cpp | 2 ++ src/project_properties.cpp | 4 ++++ src/project_properties.h | 3 +++ 8 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/dialogs/prefsdialog.cpp b/src/dialogs/prefsdialog.cpp index 6728fc5b2..f66979a93 100644 --- a/src/dialogs/prefsdialog.cpp +++ b/src/dialogs/prefsdialog.cpp @@ -37,6 +37,7 @@ along with Fritzing. If not, see . #include #include #include +#include #define MARGIN 5 #define FORMLABELWIDTH 195 @@ -64,8 +65,10 @@ void PrefsDialog::initViewInfo(int index, const QString & viewName, const QStrin m_viewInfoThings[index].curvy = curvy; } -void PrefsDialog::initLayout(QFileInfoList & languages, QList platforms) +void PrefsDialog::initLayout(QFileInfoList & languages, QList platforms, MainWindow * mainWindow) { + m_mainWindow = mainWindow; + m_projectProperties = mainWindow->getProjectProperties(); m_tabWidget = new QTabWidget(); m_general = new QWidget(); m_breadboard = new QWidget(); @@ -166,6 +169,7 @@ void PrefsDialog::initBetaFeatures(QWidget * widget) QVBoxLayout * vLayout = new QVBoxLayout(); vLayout->addWidget(createSimulatorBetaFeaturesForm()); vLayout->addWidget(createGerberBetaFeaturesForm()); + vLayout->addWidget(createProjectPropertiesForm()); vLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); widget->setLayout(vLayout); } @@ -491,6 +495,38 @@ QWidget * PrefsDialog::createSimulatorBetaFeaturesForm() { return simulator; } +QWidget *PrefsDialog::createProjectPropertiesForm() { + QGroupBox * projectPropertiesBox = new QGroupBox(tr("Project properties"), this ); + + QVBoxLayout * layout = new QVBoxLayout(); + layout->setSpacing(SPACING); + + QLabel * label = new QLabel(tr("Here you can set some settings that will be saved with the project")); + label->setWordWrap(true); + layout->addWidget(label); + layout->addSpacing(10); + + QLabel *simFrequencyLabel = new QLabel(tr("Simulation Frequency (Hz):")); + layout->addWidget(simFrequencyLabel); + + QLineEdit *simFrequencyEdit = new QLineEdit(); + + simFrequencyEdit->setText(m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorFrequencyHz)); + simFrequencyEdit->setFixedWidth(FORMLABELWIDTH * 2); + layout->addWidget(simFrequencyEdit); + + projectPropertiesBox->setLayout(layout); + + connect(simFrequencyEdit, SIGNAL(textChanged(QString)), this, SLOT(setSimulationFrequency(QString))); + + return projectPropertiesBox; + +} + +void PrefsDialog::setSimulationFrequency(const QString &frequency) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorFrequencyHz, frequency); +} + void PrefsDialog::clear() { m_cleared = true; accept(); diff --git a/src/dialogs/prefsdialog.h b/src/dialogs/prefsdialog.h index 9fbf3134b..f11c70c1b 100644 --- a/src/dialogs/prefsdialog.h +++ b/src/dialogs/prefsdialog.h @@ -33,6 +33,8 @@ along with Fritzing. If not, see . #include #include "../program/platform.h" +#include "../mainwindow/mainwindow.h" +#include "../project_properties.h" struct ViewInfoThing { @@ -54,7 +56,7 @@ class PrefsDialog : public QDialog bool cleared(); QHash & settings(); QHash & tempSettings(); - void initLayout(QFileInfoList & languages, QList platforms); + void initLayout(QFileInfoList & languages, QList platforms, MainWindow * mainWindow); void initViewInfo(int index, const QString & viewName, const QString & shortName, bool curvy); protected: @@ -66,6 +68,7 @@ class PrefsDialog : public QDialog QWidget *createProgrammerForm(QList platforms); QWidget *createSimulatorBetaFeaturesForm(); QWidget *createGerberBetaFeaturesForm(); + QWidget *createProjectPropertiesForm(); void updateWheelText(); void initGeneral(QWidget * general, QFileInfoList & languages); void initBreadboard(QWidget *, ViewInfoThing *); @@ -85,6 +88,7 @@ protected Q_SLOTS: void changeAutosavePeriod(int); void curvyChanged(); void chooseProgrammer(); + void setSimulationFrequency(const QString &frequency); protected: QPointer m_tabWidget; @@ -106,6 +110,8 @@ protected Q_SLOTS: bool m_cleared = false; int m_wheelMapping = 0; ViewInfoThing m_viewInfoThings[3]; + MainWindow * m_mainWindow; + QSharedPointer m_projectProperties; }; #endif diff --git a/src/fapplication.cpp b/src/fapplication.cpp index 8d5c28e23..6f36c08c5 100644 --- a/src/fapplication.cpp +++ b/src/fapplication.cpp @@ -1425,7 +1425,7 @@ void FApplication::preferencesAfter() QList platforms = mainWindow->programmingWidget()->getAvailablePlatforms(); - prefsDialog.initLayout(languages, platforms); + prefsDialog.initLayout(languages, platforms, mainWindow); if (QDialog::Accepted == prefsDialog.exec()) { updatePrefs(prefsDialog); } diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp index 0a0dc075d..97e55dbfc 100644 --- a/src/mainwindow/mainwindow.cpp +++ b/src/mainwindow/mainwindow.cpp @@ -3333,6 +3333,10 @@ void MainWindow::triggerSimulator() { m_simulator->triggerSimulation(); } +QSharedPointer MainWindow::getProjectProperties() { + return m_projectProperties; +} + bool MainWindow::isSimulatorEnabled() { return m_simulator->isEnabled(); } diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h index f19c0846f..9665dcc90 100644 --- a/src/mainwindow/mainwindow.h +++ b/src/mainwindow/mainwindow.h @@ -219,6 +219,7 @@ class MainWindow : public FritzingWindow bool isSimulatorEnabled(); void enableSimulator(bool); void triggerSimulator(); + QSharedPointer getProjectProperties(); public: static void initNames(); diff --git a/src/model/modelbase.cpp b/src/model/modelbase.cpp index f271fe0fe..e7d07c209 100644 --- a/src/model/modelbase.cpp +++ b/src/model/modelbase.cpp @@ -156,6 +156,8 @@ bool ModelBase::loadFromFile(const QString & fileName, ModelBase * referenceMode } ModelPartSharedRoot * modelPartSharedRoot = this->rootModelPartShared(); + Q_EMIT loadedProjectProperties(root.firstChildElement("project_properties")); + QDomElement title = root.firstChildElement("title"); if (!title.isNull()) { if (modelPartSharedRoot != nullptr) { diff --git a/src/project_properties.cpp b/src/project_properties.cpp index 5122371fb..0f1fe2b3b 100644 --- a/src/project_properties.cpp +++ b/src/project_properties.cpp @@ -23,6 +23,7 @@ along with Fritzing. If not, see . #include ProjectProperties::ProjectProperties() { + m_propertiesMap[ProjectPropertyKeySimulatorFrequencyHz] = "400"; m_keys = QStringList(m_propertiesMap.keys()); } @@ -66,3 +67,6 @@ QString ProjectProperties::getProjectProperty(const QString & key) { return m_propertiesMap[key]; } +void ProjectProperties::setProjectProperty(const QString & key, QString value) { + m_propertiesMap[key] = value; +} diff --git a/src/project_properties.h b/src/project_properties.h index 6212bbaaf..98454b085 100644 --- a/src/project_properties.h +++ b/src/project_properties.h @@ -26,6 +26,8 @@ along with Fritzing. If not, see . #include #include +const QString ProjectPropertyKeySimulatorFrequencyHz = "simulator_frequency_hz"; + class ProjectProperties { public: ProjectProperties(); @@ -35,6 +37,7 @@ class ProjectProperties { void saveProperties(QXmlStreamWriter & streamWriter); void load(const QDomElement & projectProperties); QString getProjectProperty(const QString & key); + void setProjectProperty(const QString & key, QString value); private: QMap m_propertiesMap; From a70ce548ecfcae4b8fc2a334b990d39586834838 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sun, 18 Feb 2024 23:13:36 +0100 Subject: [PATCH 39/45] added the simulation properties to the project properties menu --- src/dialogs/prefsdialog.cpp | 77 ++++++++++++++++++++++++++++++------ src/dialogs/prefsdialog.h | 5 ++- src/project_properties.cpp | 5 ++- src/project_properties.h | 5 ++- src/simulation/simulator.cpp | 25 ++++++++++-- src/simulation/simulator.h | 3 +- 6 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/dialogs/prefsdialog.cpp b/src/dialogs/prefsdialog.cpp index f66979a93..349fc53ea 100644 --- a/src/dialogs/prefsdialog.cpp +++ b/src/dialogs/prefsdialog.cpp @@ -506,25 +506,78 @@ QWidget *PrefsDialog::createProjectPropertiesForm() { layout->addWidget(label); layout->addSpacing(10); - QLabel *simFrequencyLabel = new QLabel(tr("Simulation Frequency (Hz):")); - layout->addWidget(simFrequencyLabel); + bool timeStepMode = m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorTimeStepMode).toLower().contains("true"); + QLabel *simTimeStepModeLabel = new QLabel(tr("Select the way to define the time step: (1) " + "Number of points (max simulation time divided by the number of points) " + "or (2) fixed time step.")); + simTimeStepModeLabel->setWordWrap(true); + layout->addWidget(simTimeStepModeLabel); + QHBoxLayout * simStepslayout = new QHBoxLayout(); + QRadioButton *simPointsRB = new QRadioButton("Number of Points (recommended)"); + simPointsRB->setChecked(!timeStepMode); + simStepslayout->addWidget(simPointsRB); + simStepslayout->addSpacing(1); + QLabel * numPointslabel = new QLabel(tr("Number of points: ")); + simStepslayout->addWidget(numPointslabel); + QLineEdit *simNumStepsEdit = new QLineEdit(); + simNumStepsEdit->setToolTip("The time step is calculated as the simulation time divided by the numper of steps\n" + "Low number of steps could cause inestabilities in the simulation and render artifacts in the oscilloscope."); + simStepslayout->addWidget(simNumStepsEdit); + simNumStepsEdit->setText(m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorNumberOfSteps)); + + layout->addLayout(simStepslayout); + + QHBoxLayout * simTimeSteplayout = new QHBoxLayout(); + QRadioButton *simTimeStepRB = new QRadioButton("Fixed Time Step"); + simTimeStepRB->setChecked(timeStepMode); + simTimeSteplayout->addWidget(simTimeStepRB); + simTimeSteplayout->addSpacing(10); + QLabel *simTimeStepLabel = new QLabel(tr("Time Step (s):")); + simTimeSteplayout->addWidget(simTimeStepLabel); + QLineEdit *simTimeStepEdit = new QLineEdit(); + + simTimeStepEdit->setText(m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorTimeStepS)); + simTimeStepEdit->setToolTip("The time step to be used in transitory simulations.\n" + "Small time steps and long simulations could cause prformance issues."); + simTimeSteplayout->addWidget(simTimeStepEdit); + + layout->addLayout(simTimeSteplayout); + + QLabel * simAnimationTimelabel = new QLabel(tr("Animation time for the transitory simulation (s): ")); + layout->addWidget(simAnimationTimelabel); + QLineEdit *simAnimationTimeEdit = new QLineEdit(); + simAnimationTimeEdit->setText(m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorAnimationTimeS)); + simAnimationTimeEdit->setFixedWidth(FORMLABELWIDTH * 2); + simAnimationTimeEdit->setToolTip("This is the time used to animate the effects of a transitory simulation.\n" + "Set it to 0 if you do not want an animation."); + layout->addWidget(simAnimationTimeEdit); + + projectPropertiesBox->setLayout(layout); + + connect(simTimeStepRB, SIGNAL(toggled(bool)), this, SLOT(setSimulationTimeStepMode(bool))); + connect(simNumStepsEdit, SIGNAL(textChanged(QString)), this, SLOT(setSimulationNumberOfSteps(QString))); + connect(simTimeStepEdit, SIGNAL(textChanged(QString)), this, SLOT(setSimulationTimeStep(QString))); + connect(simAnimationTimeEdit, SIGNAL(textChanged(QString)), this, SLOT(setSimulationAnimationTime(QString))); - QLineEdit *simFrequencyEdit = new QLineEdit(); - - simFrequencyEdit->setText(m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorFrequencyHz)); - simFrequencyEdit->setFixedWidth(FORMLABELWIDTH * 2); - layout->addWidget(simFrequencyEdit); + return projectPropertiesBox; - projectPropertiesBox->setLayout(layout); +} - connect(simFrequencyEdit, SIGNAL(textChanged(QString)), this, SLOT(setSimulationFrequency(QString))); +void PrefsDialog::setSimulationTimeStepMode(const bool &timeStepMode) { + QString mode = timeStepMode ? "true" : "false"; + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorTimeStepMode, mode); +} - return projectPropertiesBox; +void PrefsDialog::setSimulationNumberOfSteps(const QString &numberOfSteps) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorNumberOfSteps, numberOfSteps); +} +void PrefsDialog::setSimulationTimeStep(const QString &timeStep) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorTimeStepS, timeStep); } -void PrefsDialog::setSimulationFrequency(const QString &frequency) { - m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorFrequencyHz, frequency); +void PrefsDialog::setSimulationAnimationTime(const QString &animationTime) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorAnimationTimeS, animationTime); } void PrefsDialog::clear() { diff --git a/src/dialogs/prefsdialog.h b/src/dialogs/prefsdialog.h index f11c70c1b..26d2595fe 100644 --- a/src/dialogs/prefsdialog.h +++ b/src/dialogs/prefsdialog.h @@ -88,7 +88,10 @@ protected Q_SLOTS: void changeAutosavePeriod(int); void curvyChanged(); void chooseProgrammer(); - void setSimulationFrequency(const QString &frequency); + void setSimulationTimeStepMode(const bool &timeStepMode); + void setSimulationNumberOfSteps(const QString &numberOfSteps); + void setSimulationTimeStep(const QString &timeStep); + void setSimulationAnimationTime(const QString &animationTime); protected: QPointer m_tabWidget; diff --git a/src/project_properties.cpp b/src/project_properties.cpp index 0f1fe2b3b..07895cf79 100644 --- a/src/project_properties.cpp +++ b/src/project_properties.cpp @@ -23,7 +23,10 @@ along with Fritzing. If not, see . #include ProjectProperties::ProjectProperties() { - m_propertiesMap[ProjectPropertyKeySimulatorFrequencyHz] = "400"; + m_propertiesMap[ProjectPropertyKeySimulatorTimeStepMode] = "false"; + m_propertiesMap[ProjectPropertyKeySimulatorNumberOfSteps] = "400"; + m_propertiesMap[ProjectPropertyKeySimulatorTimeStepS] = "1us"; + m_propertiesMap[ProjectPropertyKeySimulatorAnimationTimeS] = "5s"; m_keys = QStringList(m_propertiesMap.keys()); } diff --git a/src/project_properties.h b/src/project_properties.h index 98454b085..7229c222c 100644 --- a/src/project_properties.h +++ b/src/project_properties.h @@ -26,7 +26,10 @@ along with Fritzing. If not, see . #include #include -const QString ProjectPropertyKeySimulatorFrequencyHz = "simulator_frequency_hz"; +const QString ProjectPropertyKeySimulatorTimeStepS = "simulator_time_step_s"; +const QString ProjectPropertyKeySimulatorNumberOfSteps = "simulator_number_of_steps"; +const QString ProjectPropertyKeySimulatorTimeStepMode = "simulator_time_step_mode"; +const QString ProjectPropertyKeySimulatorAnimationTimeS = "simulator_animation_time_s"; class ProjectProperties { public: diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 3bce595ae..cefb2bc88 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -34,6 +34,7 @@ along with Fritzing. If not, see . #include #include #include +#include #include "../mainwindow/mainwindow.h" #include "../items/note.h" @@ -69,7 +70,6 @@ Simulator::Simulator(MainWindow *mainWindow) : QObject(mainWindow) { // Configure the timer to show the simulation results m_showResultsTimer = new QTimer(this); - m_showResultsTimer->setInterval(50); connect(m_showResultsTimer, &QTimer::timeout, this, &Simulator::showSimulationResults); QSettings settings; @@ -217,8 +217,27 @@ void Simulator::simulate() { } } } + + //Read the project properties + QString timeStepModeStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorTimeStepMode); + QString numStepsStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorNumberOfSteps); + QString timeStepStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorTimeStepS); + QString animationTimeStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorAnimationTimeS); + + std::cout << "" << timeStepModeStr.toStdString() << " " << numStepsStr.toStdString() << " " << timeStepStr.toStdString() + << " " << animationTimeStr.toStdString() << std::endl; if (m_simEndTime > 0) { - m_simStepTime = (m_simEndTime-m_simStartTime)/Simulator::SimSteps; + if (timeStepModeStr.contains("true", Qt::CaseInsensitive)) { + m_simStepTime = TextUtils::convertFromPowerPrefixU(timeStepStr, "s"); + m_simNumberOfSteps = (m_simEndTime-m_simStartTime)/m_simStepTime; + } else { + m_simNumberOfSteps = TextUtils::convertFromPowerPrefixU(numStepsStr, ""); + m_simStepTime = (m_simEndTime-m_simStartTime)/m_simNumberOfSteps; + } + + int timerInterval = TextUtils::convertFromPowerPrefixU(animationTimeStr, "")/m_simNumberOfSteps*1000; + m_showResultsTimer->setInterval(timerInterval); + //We have found at least one oscilloscope QString tranAnalysis = QString(".TRAN %1 %2 %3").arg(m_simStepTime).arg(m_simEndTime).arg(m_simStartTime); spiceNetlist.replace(".OP", tranAnalysis); @@ -360,7 +379,7 @@ void Simulator::simulate() { } void Simulator::showSimulationResults() { - if (currSimStep < Simulator::SimSteps) { + if (currSimStep < m_simNumberOfSteps) { removeSimItems(); updateParts(itemBases, currSimStep); currSimStep++; diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index d6060a892..575ad26f7 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -92,7 +92,7 @@ public slots: std::shared_ptr m_simulator; QPointer m_breadboardGraphicsView; QPointer m_schematicGraphicsView; - double m_simStartTime, m_simStepTime, m_simEndTime; + double m_simStartTime, m_simStepTime, m_simEndTime, m_simNumberOfSteps; bool m_enabled = false; @@ -105,7 +105,6 @@ public slots: int currSimStep; static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5; - static constexpr double SimSteps = 400; }; From 4fa9783120c10dc31867aa58e5f075955f0de1b5 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Wed, 21 Feb 2024 00:12:58 +0100 Subject: [PATCH 40/45] added simulator message (sim time) at the top right corner when simulating transitory circuits --- src/simulation/simulator.cpp | 33 ++++++++++++++++++++++++++------- src/simulation/simulator.h | 3 ++- src/sketch/sketchwidget.cpp | 28 ++++++++++++++++++++++++++++ src/sketch/sketchwidget.h | 3 +++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index cefb2bc88..914af9f69 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -77,6 +77,7 @@ Simulator::Simulator(MainWindow *mainWindow) : QObject(mainWindow) { enable(true); m_simulating = false; + } Simulator::~Simulator() { @@ -149,7 +150,9 @@ void Simulator::stopSimulation() { m_showResultsTimer->stop(); m_simulating = false; removeSimItems(); - emit simulationStartedOrStopped(m_simulating); + emit simulationStartedOrStopped(m_simulating); + m_breadboardGraphicsView->setSimulatorMessage(""); + m_schematicGraphicsView->setSimulatorMessage(""); } /** @@ -370,19 +373,35 @@ void Simulator::simulate() { netList.clear(); - //The spice simulation has finished, iterate over each part being simulated and update it (if it is necessary). - currSimStep = 1; - m_showResultsTimer->start(); + + //The spice simulation has finished, iterate over each part being simulated and update it (if it is necessary). removeSimItems(); updateParts(itemBases, 0); + //If this a transitory simulation, set the timer for the animation + if (m_simEndTime > 0) { + m_currSimStep = 1; + m_showResultsTimer->start(); + } + } void Simulator::showSimulationResults() { - if (currSimStep < m_simNumberOfSteps) { + if (m_currSimStep < m_simNumberOfSteps) { removeSimItems(); - updateParts(itemBases, currSimStep); - currSimStep++; + updateParts(itemBases, m_currSimStep); + + double simTime = m_simStartTime + m_currSimStep * m_simStepTime; + + QString simMessage = QString::number(simTime, 'f', 3) + " s"; + + m_breadboardGraphicsView->setSimulatorMessage(simMessage); + m_schematicGraphicsView->setSimulatorMessage(simMessage); + + + + + m_currSimStep++; } else { m_showResultsTimer->stop(); } diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 575ad26f7..655f3908f 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -102,7 +102,8 @@ public slots: QList* m_instanceTitleSim; QTimer *m_simTimer, *m_showResultsTimer; - int currSimStep; + int m_currSimStep; + static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5; diff --git a/src/sketch/sketchwidget.cpp b/src/sketch/sketchwidget.cpp index 483aa5520..7da96d5dc 100644 --- a/src/sketch/sketchwidget.cpp +++ b/src/sketch/sketchwidget.cpp @@ -7657,9 +7657,37 @@ void SketchWidget::drawBackground( QPainter * painter, const QRectF & rect ) painter->restore(); } } +} + +void SketchWidget::drawForeground ( QPainter * painter, const QRectF & rect ) { + if(!m_simMessage.isEmpty()) { + //Set the font size based on the zoom + QFont font = painter->font(); + qreal baseFontSize = 18; // Base font size at zoom level 1 + font.setPointSizeF(baseFontSize); + painter->setFont(font); + int margin = 0; // Margin from the top and right edges + QFontMetrics metrics = painter->fontMetrics(); + int textWidth = metrics.horizontalAdvance(m_simMessage); + // 2. Get the View's Top-Right Corner in View Coordinates + QPointF viewTopRight = this->viewport()->rect().topRight(); + QPointF viewTexPos = viewTopRight - QPointF(textWidth, -1*metrics.ascent()); // Adjust for the width of the text item + + //save current transformations, remove them temporaly, draw the text in the view and restore the transformations + painter->save(); + painter->resetTransform(); + painter->drawText(viewTexPos.x(), viewTexPos.y(), m_simMessage); + painter->restore(); + } +} +void SketchWidget::setSimulatorMessage(QString message) { + m_simMessage = message; + if (isVisible()) { + update(); + } } /* diff --git a/src/sketch/sketchwidget.h b/src/sketch/sketchwidget.h index dceb245a4..085b3f35a 100644 --- a/src/sketch/sketchwidget.h +++ b/src/sketch/sketchwidget.h @@ -261,6 +261,7 @@ class SketchWidget : public InfoGraphicsView double retrieveZoom(); void initGrid(); virtual double defaultGridSizeInches(); + void setSimulatorMessage(QString); void clearPasteOffset(); virtual ViewLayer::ViewLayerPlacement defaultViewLayerPlacement(ModelPart *); void collectAllNets( @@ -433,6 +434,7 @@ class SketchWidget : public InfoGraphicsView virtual const QString & hoverEnterPartConnectorMessage(QGraphicsSceneHoverEvent * event, ConnectorItem * item); void partLabelChangedAux(ItemBase * pitem,const QString & oldText, const QString &newText); void drawBackground( QPainter * painter, const QRectF & rect ); + void drawForeground( QPainter * painter, const QRectF & rect ); void handleConnect(QDomElement & connect, ModelPart *, const QString & fromConnectorID, ViewLayer::ViewLayerID, QStringList & alreadyConnected, QHash & newItems, QUndoCommand * parentCommand, bool seekOutsideConnections); void setUpSwapReconnect(SwapThing &, ItemBase * itemBase, long newID, bool master); @@ -755,6 +757,7 @@ public Q_SLOTS: bool m_everZoomed = false; double m_ratsnestOpacity = 0.0; double m_ratsnestWidth = 0.0; + QString m_simMessage = ""; public: static ViewLayer::ViewLayerID defaultConnectorLayer(ViewLayer::ViewID viewId); From 9eb7edb9bc3f0fd5c72b47d50c8e94617c6e39d1 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sat, 6 Apr 2024 23:38:58 +0200 Subject: [PATCH 41/45] Added sawtooth generator and modified properties of the triangular signal to avoid problems when swapping parts --- resources/properties.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/properties.xml b/resources/properties.xml index 6de5ce130..7f57ce4e8 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -142,6 +142,7 @@ + @@ -150,6 +151,7 @@ + @@ -162,21 +164,22 @@ + + - - + @@ -192,11 +195,13 @@ + + From 171269704001eeb5cfee005ca0586df23f8226d6 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sun, 7 Apr 2024 15:10:43 +0200 Subject: [PATCH 42/45] changed ac poewer supply to sine, added generic pulse power supply and now internal resistance can be changed in power supplies --- resources/properties.xml | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/resources/properties.xml b/resources/properties.xml index 7f57ce4e8..0ca9424ba 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -139,7 +139,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -170,11 +170,14 @@ + + + - + - + @@ -184,7 +187,8 @@ - + + @@ -193,21 +197,27 @@ - + - + - + - + + + + + + + From 234eb9a6eed759042f5407dd83aea56c89956a98 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Sun, 7 Apr 2024 23:55:48 +0200 Subject: [PATCH 43/45] =?UTF-8?q?make=20the=20transitory=20simulation=20no?= =?UTF-8?q?t=20blocking.=20Results=20will=20be=20shown=20while=20sim=20ana?= =?UTF-8?q?lysis=20is=20still=20on=20going.=20Adapt=20the=20the=20speed=20?= =?UTF-8?q?of=20the=20animation=20to=20the=20user=C2=B4s=20value=20if=20th?= =?UTF-8?q?e=20animation=20takes=20too=20long.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/simulation/simulator.cpp | 35 +++++++++++++++++++++++++++-------- src/simulation/simulator.h | 2 +- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 914af9f69..0b11decd6 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -334,9 +334,15 @@ void Simulator::simulate() { std::cout << "Waiting for simulator thread to stop" <isBGThreadRunning() && elapsedTime < simTimeOut) { - QThread::usleep(1000); + auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); + QThread::usleep(100); elapsedTime++; - } + //If this a transitory simulation and we have partial results, start the animation + if (m_simEndTime > 0 && timeInfo.size() > 0) + break; + } + std::cout << "-------- SIM END or TRANS SIM WITH PARTIAL RESULTS ------------" << std::endl; + if (elapsedTime >= simTimeOut) { m_simulator->command("bg_halt"); stopSimulation(); @@ -364,7 +370,7 @@ void Simulator::simulate() { } std::cout << "No fatal error found, continuing..." <command("bg_halt"); + //m_simulator->command("bg_halt"); //Delete the pointers foreach (QList * net, netList) { @@ -387,7 +393,15 @@ void Simulator::simulate() { } void Simulator::showSimulationResults() { - if (m_currSimStep < m_simNumberOfSteps) { + if (m_currSimStep <= m_simNumberOfSteps) { + //Check that we have the sim results for this time step + auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); + std::cout << "Time showSimulationResults (" << timeInfo.size() << " points): "; + if (m_currSimStep > timeInfo.size()) + return; + + QElapsedTimer elapsedTimer; + elapsedTimer.start(); removeSimItems(); updateParts(itemBases, m_currSimStep); @@ -398,10 +412,15 @@ void Simulator::showSimulationResults() { m_breadboardGraphicsView->setSimulatorMessage(simMessage); m_schematicGraphicsView->setSimulatorMessage(simMessage); - - - - m_currSimStep++; + if (elapsedTimer.elapsed() < m_showResultsTimer->interval()) { + m_currSimStep++; + } else { + //Animation is going very slowly, skip some time steps + m_currSimStep += (unsigned int) (elapsedTimer.elapsed()/m_showResultsTimer->interval()); + if (m_currSimStep > m_simNumberOfSteps) + m_currSimStep = m_simNumberOfSteps; + } + std::cout << "Time to perform the animation (" << elapsedTimer.elapsed() << " ms): "; } else { m_showResultsTimer->stop(); } diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index 655f3908f..36b729a60 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -102,7 +102,7 @@ public slots: QList* m_instanceTitleSim; QTimer *m_simTimer, *m_showResultsTimer; - int m_currSimStep; + unsigned long m_currSimStep; static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5; From 1c56001b1cd01d4434094535559c3045ae3dbc6b Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Mon, 8 Apr 2024 00:28:28 +0200 Subject: [PATCH 44/45] added exception for the oscilloscope as it does not have a pcb view --- src/connectors/debugconnectors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/connectors/debugconnectors.cpp b/src/connectors/debugconnectors.cpp index fa9fca97f..08fc7cf0b 100644 --- a/src/connectors/debugconnectors.cpp +++ b/src/connectors/debugconnectors.cpp @@ -62,7 +62,7 @@ void DebugConnectors::monitorConnections(bool enabled) QSet DebugConnectors::getItemConnectorSet(ConnectorItem *connectorItem) { QSet set; - static QRegularExpression re("^(AM|VM|OM)\\d+"); + static QRegularExpression re("^(AM|VM|OM|Oscilloscope)\\d+"); Q_FOREACH (ConnectorItem * toConnectorItem, connectorItem->connectedToItems()) { ItemBase * attachedToItem = toConnectorItem->attachedTo(); VirtualWire * virtualWire = qobject_cast(attachedToItem); From 39b0dadee6602926aaa2d6f819ab0d186996f1f8 Mon Sep 17 00:00:00 2001 From: Andres Faina Date: Fri, 3 May 2024 23:53:33 +0200 Subject: [PATCH 45/45] Load analog code model to have access to current limiting devices for lab power supplies. Add max current as property to change in lab power supplies. Added behavioral sources as valid spice type. WARNING: lab power supplies need lo load the analog code mode.. Right now, the code mode should be in the CWD as the executable. TODO: Check for several paths to find the code models. --- resources/properties.xml | 3 +++ src/simulation/simulator.cpp | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/properties.xml b/resources/properties.xml index 0ca9424ba..01648bc11 100644 --- a/resources/properties.xml +++ b/resources/properties.xml @@ -345,6 +345,9 @@ + + + diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 0b11decd6..d2ff2e7a4 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -256,7 +256,8 @@ void Simulator::simulate() { std::cout << "Running m_simulator->command('reset'):" <command("reset"); m_simulator->clearLog(); - + std::cout << "Loading codemodel analog.cm, which should be in the CWD:" <command("codemodel ./analog.cm"); std::cout << "-----------------------------------" <