diff --git a/phoenix.pro b/phoenix.pro index 72755b970..14fb729d8 100644 --- a/phoenix.pro +++ b/phoenix.pro @@ -208,3 +208,9 @@ TARGET = Fritzing TEMPLATE = app !build_pass:message("libs $$LIBS") + +HEADERS += \ + src/items/oscilloscope.h + +SOURCES += \ + src/items/oscilloscope.cpp 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 c3465d560..245690781 100644 Binary files a/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz and b/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz differ diff --git a/src/dialogs/prefsdialog.cpp b/src/dialogs/prefsdialog.cpp index 1c1b32a5a..f2de0ec17 100644 --- a/src/dialogs/prefsdialog.cpp +++ b/src/dialogs/prefsdialog.cpp @@ -525,12 +525,26 @@ QWidget *PrefsDialog::createProjectPropertiesForm() { "Set a negative number for real time."); layout->addWidget(simAnimationTimeEdit); + QLabel * simSpiceOptionsLabel = new QLabel(tr("Specific options for the SPICE simulator: ")); + layout->addWidget(simSpiceOptionsLabel); + QTextEdit *simSpiceOptionsEdit = new QTextEdit(); + simSpiceOptionsEdit->setText(m_projectProperties->getProjectProperty(ProjectPropertyKeySimulatorSpiceOptions)); + simSpiceOptionsEdit->setFixedWidth(FORMLABELWIDTH * 2); + simSpiceOptionsEdit->setFixedHeight(FORMLABELWIDTH * 0.5); + simSpiceOptionsEdit->setToolTip("These are the SPICE options for the simulation.\n" + "Check the ngSpice manual for the options."); + layout->addWidget(simSpiceOptionsEdit); + 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))); + connect(simSpiceOptionsEdit, &QTextEdit::textChanged, this, [this, simSpiceOptionsEdit]() { + QString currentText = simSpiceOptionsEdit->toPlainText(); + setSimulationSpiceOptions(currentText); + }); return projectPropertiesBox; @@ -553,6 +567,10 @@ void PrefsDialog::setSimulationAnimationTime(const QString &animationTime) { m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorAnimationTimeS, animationTime); } +void PrefsDialog::setSimulationSpiceOptions(const QString &spiceOptions) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorSpiceOptions, spiceOptions); +} + void PrefsDialog::clear() { m_cleared = true; accept(); diff --git a/src/dialogs/prefsdialog.h b/src/dialogs/prefsdialog.h index cb74083b4..7cad89220 100644 --- a/src/dialogs/prefsdialog.h +++ b/src/dialogs/prefsdialog.h @@ -91,6 +91,7 @@ protected Q_SLOTS: void setSimulationNumberOfSteps(const QString &numberOfSteps); void setSimulationTimeStep(const QString &timeStep); void setSimulationAnimationTime(const QString &animationTime); + void setSimulationSpiceOptions(const QString &spiceOptions); protected: QPointer m_tabWidget; diff --git a/src/items/moduleidnames.cpp b/src/items/moduleidnames.cpp index aac1a5e8b..47ac73c6a 100644 --- a/src/items/moduleidnames.cpp +++ b/src/items/moduleidnames.cpp @@ -79,3 +79,4 @@ const QString ModuleIDNames::SchematicFrameModuleIDName = "SchematicFrameModuleI const QString ModuleIDNames::CopperBlockerModuleIDName = "BlockerModuleID"; const QString ModuleIDNames::Copper0BlockerModuleIDName = "Copper0BlockerModuleID"; const QString ModuleIDNames::Copper1BlockerModuleIDName = "Copper1BlockerModuleID"; +const QString ModuleIDNames::OscilloscopeModuleIDName = "OscilloscopeModuleID"; diff --git a/src/items/moduleidnames.h b/src/items/moduleidnames.h index 679f83c9a..096ea3039 100644 --- a/src/items/moduleidnames.h +++ b/src/items/moduleidnames.h @@ -81,6 +81,7 @@ class ModuleIDNames static const QString CopperBlockerModuleIDName; static const QString Copper1BlockerModuleIDName; static const QString Copper0BlockerModuleIDName; + static const QString OscilloscopeModuleIDName; }; #endif diff --git a/src/items/oscilloscope.cpp b/src/items/oscilloscope.cpp new file mode 100644 index 000000000..922474d7c --- /dev/null +++ b/src/items/oscilloscope.cpp @@ -0,0 +1,297 @@ +/******************************************************************* + +Part of the Fritzing project - http://fritzing.org +Copyright (c) 2007-2019 Fritzing + +Fritzing is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Fritzing is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Fritzing. If not, see . + +********************************************************************/ + +#include "oscilloscope.h" + +#include + +#include "debugdialog.h" +#include "symbolpaletteitem.h" +#include "connectors/connectoritem.h" +#include "../utils/textutils.h" +#include "../simulation/simulator.h" + + +Oscilloscope::Oscilloscope( ModelPart * modelPart, ViewLayer::ViewID viewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool doLabel) + : Capacitor(modelPart, viewID, viewGeometry, id, itemMenu, doLabel) +{ +} + +Oscilloscope::~Oscilloscope() { +} + +/** + * Updates and checks a oscilloscope. If the ground connection is not connected, plots a noisy signal. + * 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 Oscilloscope::updateOscilloscope(unsigned long timeStep, double simStartTime, double simStepTime, Simulator * sim, ItemBase * bbOscilloscope) { + ConnectorItem * comProbe = nullptr, * v1Probe = nullptr, * v2Probe = nullptr, * v3Probe = nullptr, * v4Probe = nullptr; + QList probes = 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()) { + DebugDialog::stream() << "Oscilloscope does not have any wire connected to the probe terminals. "; + return; + } + ConnectorItem * probesArray[4] = {v1Probe, v2Probe, v3Probe, v4Probe}; + + //TODO: use convertFromPowerPrefixU + int nChannels = TextUtils::convertFromPowerPrefix(getProperty("channels"), ""); + double timeDiv = TextUtils::convertFromPowerPrefix(getProperty("time/div"), "s"); + double hPos = TextUtils::convertFromPowerPrefix(getProperty("horizontal position"), "s"); + double ch1_volsDiv = TextUtils::convertFromPowerPrefix(getProperty("ch1 volts/div"), "V"); + double ch1_offset = TextUtils::convertFromPowerPrefix(getProperty("ch1 offset"), "V"); + double ch2_volsDiv = TextUtils::convertFromPowerPrefix(getProperty("ch2 volts/div"), "V"); + double ch2_offset = TextUtils::convertFromPowerPrefix(getProperty("ch2 offset"), "V"); + double ch3_volsDiv = TextUtils::convertFromPowerPrefix(getProperty("ch3 volts/div"), "V"); + double ch3_offset = TextUtils::convertFromPowerPrefix(getProperty("ch3 offset"), "V"); + double ch4_volsDiv = TextUtils::convertFromPowerPrefix(getProperty("ch4 volts/div"), "V"); + double ch4_offset = TextUtils::convertFromPowerPrefix(getProperty("ch4 offset"), "V"); + QString lineColor[4] = {"#ffff50", "lightgreen", "lightblue", "pink"}; + double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; + double chOffsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; + + double screenWidth = 3376.022, screenHeight = 2700.072, bbScreenStrokeWidth= 20; + double verDivisions = 8, horDivisions = 10, divisionSize = screenHeight/verDivisions; + double bbScreenOffsetX = 290.544, bbScreenOffsetY = 259.061, schScreenOffsetX = 906.07449, schScreenOffsetY = 354.60801; + QString svgHeader = "\n%5" + "\n"; + QString bbSvg = QString(svgHeader) + .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) + .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++) { + if (!probesArray[channel]->connectedToWires()) continue; + + //Get the signal and com voltages + auto v = sim->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); + } + } else { + vCom = sim->voltageVector(comProbe); + } + + //Draw the signal + QString pathId = QString("ch%1-path").arg(channel+1); + QString signalPath = generateSvgPath(v, vCom, timeStep, pathId, simStartTime, 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); + + //Add text label about volts/div for each channel + bbSvg += QString("CH%4: %5V\n") + .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*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.62, + screenWidth + schScreenOffsetX*1.05, screenWidth + schScreenOffsetX*1.4}; + 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) + .arg(schScreenOffsetY+screenHeight) + .arg(lineColor[channel]); + + double tickSize = 10; + 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 + 1); + QList connectorItems; + connectorItems.append(probesArray[channel]); + ConnectorItem::collectEqualPotential(connectorItems, false, ViewGeometry::RatsnestFlag); + + Q_FOREACH ( ConnectorItem * cItem, connectorItems) { + SymbolPaletteItem* symbolItem = dynamic_cast(cItem->attachedTo()); + if(symbolItem && symbolItem->isOnlyNetLabel() ) { + netName = symbolItem->getLabel(); + netName += " (V)"; + break; + } + } + + schSvg += QString("%4\n") + .arg(xOffset[channel] + paddingAlignment * 180 + (1+paddingAlignment)*30) + .arg(schScreenOffsetY + screenHeight/2) + .arg(lineColor[channel], netName); + + + + for (int tick = 0; tick < (verDivisions+1); ++tick) { + double vTick = voltsDiv[channel]*(verDivisions/2-tick)-chOffsets[channel]; + QString voltageText = TextUtils::convertToPowerPrefix(vTick); + schSvg += QString("%5\n") + .arg(xOffset[channel] + paddingAlignment * 10) + .arg(schScreenOffsetY + divisionSize * tick + 20) + .arg(lineColor[channel], textAlignment, voltageText); + + schSvg += QString("\n") + .arg(xOffset[channel] - tickSize + paddingAlignment * tickSize * -1) + .arg(schScreenOffsetY + divisionSize * tick) + .arg(xOffset[channel] + tickSize + paddingAlignment * tickSize * -1) + .arg(lineColor[channel]); + } + + + } //End of for each channel + + //Add time scale axis in bb + bbSvg += QString("time/div: %3s ") + .arg(bbScreenOffsetX + screenWidth / 2) + .arg(bbScreenOffsetY * 0.85) + .arg(TextUtils::convertToPowerPrefix(timeDiv)); + bbSvg += QString(" pos: %4s") + .arg(bbScreenOffsetX + screenWidth/2) + .arg(bbScreenOffsetY * 0.85) + .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.25) + .arg("white", TextUtils::convertToPowerPrefix(hPos + timeDiv*tick)); + } + schSvg += QString("Time (s)") + .arg(schScreenOffsetX + screenWidth / 2) + .arg(screenHeight + schScreenOffsetY * 1.5) + .arg("white"); + + bbSvg += ""; + schSvg += ""; + + QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(this); + QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(bbOscilloscope); + QSvgRenderer *schGraphRender = new QSvgRenderer(schSvg.toUtf8()); + QSvgRenderer *bbGraphRender = new QSvgRenderer(bbSvg.toUtf8()); + if(!schGraphRender->isValid()) + DebugDialog::stream() << "SCH SVG Graph is NOT VALID \n"; + + if(!bbGraphRender->isValid()) + DebugDialog::stream() << "BB SVG Graph is NOT VALID\n"; + + schGraph->setSharedRenderer(schGraphRender); + schGraph->setZValue(std::numeric_limits::max()); + bbGraph->setSharedRenderer(bbGraphRender); + bbGraph->setZValue(std::numeric_limits::max()); + + addSimulationGraphicsItem(schGraph); + bbOscilloscope->addSimulationGraphicsItem(bbGraph); +} + +QString Oscilloscope::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 ) { + // DebugDialog::stream() << "OSCILLOSCOPE: pos " << timePos << ", timeScale: " << timeScale; + // DebugDialog::stream() << "OSCILLOSCOPE: VOLTAGE VALUES " << nameId.toStdString() << ": "; + + QString svg; + double screenOffset = 0;//132.87378; + if (!nameId.isEmpty()) + svg += QString(" 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 (screenPoint == 0) { + svg.append("M "+ QString::number(screenOffset, 'f', 3) +" " + QString::number( vPos, 'f', 3) + " "); + } else { + svg.append("L " + QString::number(screenPoint*horScale + screenOffset, 'f', 3) + " " + QString::number(vPos, 'f', 3) + " "); + } + //DebugDialog::stream() <<" ("<< time << "): " << voltage << ' '; + screenPoint++; + } + svg += "' transform='translate(%1,%2)' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // + return svg; +} diff --git a/src/items/oscilloscope.h b/src/items/oscilloscope.h new file mode 100644 index 000000000..e15ce068e --- /dev/null +++ b/src/items/oscilloscope.h @@ -0,0 +1,43 @@ +/******************************************************************* + +Part of the Fritzing project - http://fritzing.org +Copyright (c) 2007-2019 Fritzing + +Fritzing is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Fritzing is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Fritzing. If not, see . + +********************************************************************/ + +#ifndef OSCILLOSCOPE_H +#define OSCILLOSCOPE_H + +#include "capacitor.h" +#include "simulation/simulator.h" + + +class Oscilloscope : public Capacitor +{ + Q_OBJECT + +public: + // after calling this constructor if you want to render the loaded svg (either from model or from file), MUST call + explicit Oscilloscope(ModelPart *, ViewLayer::ViewID, const ViewGeometry & viewGeometry, long id, QMenu * itemMenu, bool doLabel); + ~Oscilloscope(); + void updateOscilloscope(unsigned long timeStep, double simStartTime, double simStepTime, Simulator * sim, ItemBase * bbOscilloscope); + +protected: + QString 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 ); + +}; + +#endif // OSCILLOSCOPE_H diff --git a/src/items/partfactory.cpp b/src/items/partfactory.cpp index bd3badc9b..656feab1a 100644 --- a/src/items/partfactory.cpp +++ b/src/items/partfactory.cpp @@ -51,6 +51,7 @@ along with Fritzing. If not, see . #include "stripboard.h" #include "led.h" #include "schematicsubpart.h" +#include "oscilloscope.h" #include "../utils/folderutils.h" #include "../utils/lockmanager.h" #include "../utils/textutils.h" @@ -185,6 +186,9 @@ ItemBase * PartFactory::createPartAux( ModelPart * modelPart, ViewLayer::ViewID if (moduleID.endsWith(ModuleIDNames::CopperBlockerModuleIDName)) { return new CopperBlocker(modelPart, viewID, viewGeometry, id, itemMenu, doLabel); } + if (moduleID.endsWith(ModuleIDNames::OscilloscopeModuleIDName)) { + return new Oscilloscope(modelPart, viewID, viewGeometry, id, itemMenu, doLabel); + } // must get the subclasses first if (modelPart->itemType() == ModelPart::Breadboard) { return new Breadboard(modelPart, viewID, viewGeometry, id, itemMenu, doLabel); diff --git a/src/project_properties.cpp b/src/project_properties.cpp index 771668cf4..2f754d3fc 100644 --- a/src/project_properties.cpp +++ b/src/project_properties.cpp @@ -25,6 +25,7 @@ ProjectProperties::ProjectProperties() { m_propertiesMap[ProjectPropertyKeySimulatorNumberOfSteps] = "400"; m_propertiesMap[ProjectPropertyKeySimulatorTimeStepS] = "1us"; m_propertiesMap[ProjectPropertyKeySimulatorAnimationTimeS] = "5s"; + m_propertiesMap[ProjectPropertyKeySimulatorSpiceOptions] = ""; } ProjectProperties::~ProjectProperties() { diff --git a/src/project_properties.h b/src/project_properties.h index f4b70fee8..160e7741a 100644 --- a/src/project_properties.h +++ b/src/project_properties.h @@ -30,6 +30,7 @@ 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"; +const QString ProjectPropertyKeySimulatorSpiceOptions = "simulator_spice_options"; class ProjectProperties { public: diff --git a/src/simulation/ngspice_simulator.cpp b/src/simulation/ngspice_simulator.cpp index 6ea1bb7c8..89d403830 100644 --- a/src/simulation/ngspice_simulator.cpp +++ b/src/simulation/ngspice_simulator.cpp @@ -29,8 +29,6 @@ along with Fritzing. If not, see . #include #include #include -#include - #include "debugdialog.h" // Macro for serializing variable/function name into a string. @@ -40,15 +38,7 @@ along with Fritzing. If not, see . #define GET_FUNC(func) std::function((decltype(func)*) m_handles[STRFY(func)]) // Macro for getting pointer to duplicated string for use in ngspice library function and automatically deleting the duplicate after the function call via unique_ptr. -#include -#include -#include - -#define UNIQ(str) ([&]() {\ - auto ptr = std::make_unique(str.size() + 1);\ - std::strcpy(ptr.get(), str.c_str());\ - return ptr.release();\ -})() +#define UNIQ(str) std::unique_ptr(strdup(str.c_str())).get() NgSpiceSimulator::NgSpiceSimulator() : m_isInitialized(false) @@ -67,87 +57,87 @@ std::shared_ptr NgSpiceSimulator::getInstance() { NgSpiceSimulator::~NgSpiceSimulator() { } -void NgSpiceSimulator::init() -{ - if (m_isInitialized) - return; - - QString ngspiceDir("invalid"); - if (!m_library.isLoaded()) { - QStringList libPaths = QCoreApplication::libraryPaths(); - -#ifdef Q_OS_LINUX - QString appDir = QCoreApplication::applicationDirPath(); +bool NgSpiceSimulator::init() { + if (m_isInitialized) return m_isInitialized; - if (appDir.endsWith("/usr/bin")) { - QString libPath = appDir.left(appDir.length() - 4) + "/lib"; - if (QDir(libPath).exists()) { - libPaths.prepend(libPath); - } - } -#endif // Q_OS_LINUX + m_library.setFileName("ngspice"); + m_library.load(); + QStringList libPaths = QStringList({ QCoreApplication::applicationDirPath() + }) + // TODO Not sure if we can place the library there on macOS + + QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation); - if (DebugDialog::enabled()) { - DebugDialog::debug("Searching for ngspice in the following directories:"); - for (const auto& path : libPaths) { - DebugDialog::debug(" " + path); - } - } -#ifdef Q_OS_LINUX + if( !m_library.isLoaded() ) { // fallback custom paths + #ifdef Q_OS_LINUX const QString libName = "libngspice.so"; -#elif defined Q_OS_MACOS + #elif defined Q_OS_MACOS const QString libName = "libngspice.0.dylib"; -#elif defined Q_OS_WIN + #elif defined Q_OS_WIN const QString libName = "ngspice.dll"; -#endif - for (const auto& path : libPaths) { - QFileInfo library(path + "/" + libName); - if (!library.canonicalFilePath().isEmpty()) { + #endif + DebugDialog::debug("Couldn't load ngspice " + m_library.errorString()); + for( const auto& path : libPaths ) { + QFileInfo library(QString(path + "/" + libName)); + DebugDialog::debug("Try path " + library.absoluteFilePath()); + if(!library.canonicalFilePath().isEmpty()) { m_library.setFileName(library.canonicalFilePath()); m_library.load(); - if (m_library.isLoaded()) { - ngspiceDir = library.absolutePath(); + if( m_library.isLoaded() ) { break; + } else { + DebugDialog::debug("Couldn't load ngspice " + m_library.errorString()); + throw std::runtime_error( "Error loading ngspice shared library" ); } } } - if (!m_library.isLoaded()) { - DebugDialog::debug("Error loading ngspice shared library: " + m_library.errorString()); - throw std::runtime_error("Error loading ngspice shared library: " - + m_library.errorString().toStdString()); - } - } else { - QFileInfo loadedLibrary(m_library.fileName()); - ngspiceDir = loadedLibrary.absolutePath(); - DebugDialog::debug("ngspice already loaded: " + loadedLibrary.absoluteFilePath()); } + if (!m_library.isLoaded()) { + DebugDialog::debug("Could not find ngspice."); + return false; + } + DebugDialog::debug("Loaded ngspice " + m_library.fileName()); + + setErrorTitle(std::nullopt); - std::vector symbols{STRFY(ngSpice_Command), STRFY(ngSpice_Init), STRFY(ngSpice_Circ), STRFY(ngGet_Vec_Info)}; + std::vector symbols{STRFY(ngSpice_Command), STRFY(ngSpice_Init), STRFY(ngSpice_Circ), STRFY(ngGet_Vec_Info), + STRFY(ngSpice_SetBkpt), STRFY(ngSpice_Init_Sync), STRFY(ngSpice_CurPlot), STRFY(ngSpice_AllPlots), STRFY(ngSpice_AllVecs)}; for (auto & symbol: symbols) { m_handles[symbol] = (void *) m_library.resolve(symbol.c_str()); } - std::string previousLocale = setlocale(LC_NUMERIC, nullptr); setlocale(LC_NUMERIC, "C"); - GET_FUNC(ngSpice_Init)(&SendCharFunc, &SendStatFunc, &ControlledExitFunc, nullptr, nullptr, &BGThreadRunningFunc, nullptr); + GET_FUNC(ngSpice_Init)(&SendCharFunc, &SendStatFunc, &ControlledExitFunc, &SendDataFunc, &SendInitDataFunc, &BGThreadRunningFunc, nullptr); + //For now we do not use the external voltages or currents. + //int dll1 = 1; + //GET_FUNC(ngSpice_Init_Sync)(&VSRCData, &ISRCData, &SyncData, &dll1, nullptr); setlocale(LC_NUMERIC, previousLocale.c_str()); m_isBGThreadRunning = true; m_isInitialized = true; + return m_isInitialized; +} - QString analogCmPath = ngspiceDir + "/ngspice/analog.cm"; - if (QFileInfo::exists(analogCmPath)) { - DebugDialog::stream() << "Loading codemodel analog.cm from: " << analogCmPath; - command("codemodel " + analogCmPath.toStdString()); - } else { - DebugDialog::stream() << "Warning: analog.cm not found at " << analogCmPath; - } +/* Callback. Set the input voltages for the external voltage sources. */ +int NgSpiceSimulator::VSRCData(double* retvoltval, double acttime, char* vinstancename, int ident, void* userdata) +{ + return 0; +} +/* Callback. Set the input currents for the external current sources. */ +int NgSpiceSimulator::ISRCData(double* retcurrval, double acttime, char* nodename, int ident, void* userdata) +{ + return 0; } +/* Callback to synchronize the diiferent ngspice threads. */ +int NgSpiceSimulator::SyncData(double acttime, double* deltatime, double olddeltatime, + int redostep, int ident, int location, void* userdata) +{ + return 0; +} void NgSpiceSimulator::resetIsBGThreadRunning() { m_isBGThreadRunning = true; @@ -161,33 +151,27 @@ void NgSpiceSimulator::loadCircuit(const std::string& netList) { std::stringstream stream(netList); std::string component; std::vector components; - std::vector> garbageCollector; + std::vector garbageCollector; while(std::getline(stream, component)) { - auto shared = std::shared_ptr(new char[component.size() + 1], - std::default_delete()); - std::strncpy(shared.get(), component.c_str(), component.size()); - shared.get()[component.size()] = '\0'; // Ensure null termination + std::shared_ptr shared(strdup(component.c_str())); components.push_back(shared.get()); garbageCollector.push_back(shared); } components.push_back(nullptr); - std::string previousLocale = setlocale(LC_NUMERIC, nullptr); setlocale(LC_NUMERIC, "C"); GET_FUNC(ngSpice_Circ)(components.data()); setlocale(LC_NUMERIC, previousLocale.c_str()); } - - void NgSpiceSimulator::command(const std::string& command) { - // if (!m_isInitialized) { - // init(); - // } - // if (!m_isInitialized) { - // return; - // } + if (!m_isInitialized) { + init(); + } + if (!m_isInitialized) { + return; + } m_isInitialized = !errorOccured(); if (!m_isInitialized) { init(); @@ -216,6 +200,15 @@ std::vector NgSpiceSimulator::getVecInfo(const std::string& vecName) { return std::vector(); } +bool NgSpiceSimulator::setBreakPoint(const double& breakPointTime) { + std::string previousLocale = setlocale(LC_NUMERIC, nullptr); + setlocale(LC_NUMERIC, "C"); + std::function setBkptFunc = GET_FUNC(ngSpice_SetBkpt); + bool b = setBkptFunc(breakPointTime); + setlocale(LC_NUMERIC, previousLocale.c_str()); + return b; +} + stdx::optional NgSpiceSimulator::errorOccured() { return m_errorTitle; } @@ -269,7 +262,7 @@ int NgSpiceSimulator::ControlledExitFunc(int exitStatus, bool, bool, int libId, } int NgSpiceSimulator::SendDataFunc(pvecvaluesall, int numStructs, int libId, void*) { - std::cout << "SendDataFunc numStructs (libId:" << libId << "): " << numStructs << std::endl; + //std::cout << "SendDataFunc numStructs (libId:" << libId << "): " << numStructs << std::endl; return 0; } @@ -284,3 +277,31 @@ int NgSpiceSimulator::BGThreadRunningFunc(bool notRunning, int libId, void*) { simulator->m_isBGThreadRunning = !notRunning; return 0; } + +QString NgSpiceSimulator::getCurrPlot(void) { + char * name = GET_FUNC(ngSpice_CurPlot)(); + return QString(name); +} + +QList NgSpiceSimulator::getAllPlots(void) { + char ** plots = GET_FUNC(ngSpice_AllPlots)(); + QList plotList; + while (*plots) { + plotList.append(QString(*plots)); + plots++; + } + return plotList; +} + +QList NgSpiceSimulator::getAllVecs(const std::string& plotName) { + std::string previousLocale = setlocale(LC_NUMERIC, nullptr); + setlocale(LC_NUMERIC, "C"); + char ** vecs = GET_FUNC(ngSpice_AllVecs)(UNIQ(plotName)); + setlocale(LC_NUMERIC, previousLocale.c_str()); + QList vecList; + while (*vecs) { + vecList.append(QString(*vecs)); + vecs++; + } + return vecList; +} diff --git a/src/simulation/ngspice_simulator.h b/src/simulation/ngspice_simulator.h index e2d1a2163..d95cd804c 100644 --- a/src/simulation/ngspice_simulator.h +++ b/src/simulation/ngspice_simulator.h @@ -85,8 +85,9 @@ class NgSpiceSimulator { /** * @brief Initialize ngspice simulator and load ngspice library. + * @return true if ngspice has been initialized succesfully. */ - void init(); + bool init(); /** * @brief Return true if ngspice library background thread is running. @@ -118,6 +119,32 @@ class NgSpiceSimulator { */ std::vector getVecInfo(const std::string& vecName); + /** + * @brief Get the name of the current plot from the ngspice library ngSpice_CurPlot function. + * @return name of the current plot returned by ngspice library ngSpice_CurPlot function + */ + QString getCurrPlot(void); + + /** + * @brief Get the name of all the plot from the ngspice library ngSpice_AllPlots function. + * @return name a list with all the plot names returned by ngspice library ngSpice_AllPlots function + */ + QList getAllPlots(void); + + /** + * @brief Get the name of all the vectors from the ngspice library ngSpice_AllVecs function for the specified plot. + * @param[in] plotName name of plot to get the vector names + * @return name a list with all the vector names returned by ngspice library ngSpice_AllPlots function for that plot + */ + QList getAllVecs(const std::string& plotName); + + /** + * @brief Set a breakpoint to the ngspice simulator. + * @param[in] time for the breakpoint + * @return true if the breakpoint has been set and false if not + */ + bool setBreakPoint(const double& breakPointTime); + /** * @brief Return optional error title if an error occurred. * @return optional error title if an error occurred @@ -163,6 +190,10 @@ class NgSpiceSimulator { static int SendDataFunc(pvecvaluesall allVecValues, int numStructs, int libId, void* userData); static int SendInitDataFunc(pvecinfoall allVecInitInfo, int libId, void* userData); static int BGThreadRunningFunc(bool notRunning, int libId, void* userData); + static int SyncData(double acttime, double* deltatime, double olddeltatime, + int redostep, int ident, int location, void* userdata); + static int VSRCData(double* retvoltval, double acttime, char* vinstancename, int ident, void* userdata); + static int ISRCData(double* retcurrval, double acttime, char* nodename, int ident, void* userdata); /** * @brief Map for handles of ngspice library functions. diff --git a/src/simulation/simulator.cpp b/src/simulation/simulator.cpp index 2cf8be711..9f711d9f4 100644 --- a/src/simulation/simulator.cpp +++ b/src/simulation/simulator.cpp @@ -51,6 +51,7 @@ along with Fritzing. If not, see . #include "../items/symbolpaletteitem.h" #include "../items/perfboard.h" #include "../items/partlabel.h" +#include "../items/oscilloscope.h" #include "debugdialog.h" #include @@ -81,9 +82,10 @@ Simulator::~Simulator() { /** * This function triggers a simulation if the simulator has been created, and the - * the simulator is sumulating. is Simulating is controlled by "Start Simulation" and - * "Stop Simulator" buttons. Of corse, to be able to simulate, the simulator needs to - * be enabled. This function can be called from everywhere in the code as it is a static. + * the simulator is simulating. m_simulating is controlled by "Start Simulation" and + * "Stop Simulator" buttons. Of cuorse, to be able to simulate, the simulator needs to + * be enabled. In transitory simulations, we do not trigguer a new simulation. + * This function can be called from everywhere in the code as it is a static. */ void Simulator::triggerSimulation() { @@ -153,6 +155,8 @@ void Simulator::startSimulation() void Simulator::stopSimulation() { m_showResultsTimer->stop(); m_simulating = false; + m_previousVoltages.clear(); + m_interactionStep = 0; removeSimItems(); emit simulationStartedOrStopped(m_simulating); m_breadboardGraphicsView->setSimulatorMessage(""); @@ -185,8 +189,9 @@ void Simulator::simulate() { } m_simulator = NgSpiceSimulator::getInstance(); + bool simInitOK; try { - m_simulator->init(); + simInitOK = m_simulator->init(); } catch (std::exception& e) { FMessageBox::warning(nullptr, tr("Simulator Error"), tr("An error occurred when starting the simulation.")); @@ -200,12 +205,23 @@ void Simulator::simulate() { return; } + if (!simInitOK) { + FMessageBox::warning(nullptr, tr("Unable to load the NgSpice library"), tr("Fritzing could not load the ngspice library (not found or wrong version). " + "This is usually a problem found in third party binaries. Please, use the " + "official binary or notify the mantainers of the packaging.")); + stopSimulation(); + return; + } + //Empty the stderr and stdout buffers m_simulator->clearLog(); QList< QList* > netList; itemBases.clear(); - QString spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); + m_spiceNetlist = m_mainWindow->getSpiceNetlist("Simulator Netlist", netList, itemBases); + + //TODO: Fix this in the parts + m_spiceNetlist.replace("IC=0", ""); //Select the type of analysis based on if there is an oscilloscope in the simulation m_simEndTime = -1, m_simStartTime = std::numeric_limits::max();; @@ -215,6 +231,7 @@ void Simulator::simulate() { double time_div = TextUtils::convertFromPowerPrefix(item->getProperty("time/div"), "s"); double pos = TextUtils::convertFromPowerPrefix(item->getProperty("horizontal position"), "s"); DebugDialog::stream() << "Found oscilloscope: time/div: " << item->getProperty("time/div").toStdString() << " " << time_div << item->getProperty("horizontal position").toStdString() << " " << pos; + m_transientSimulationEnabled = true; if (pos < m_simStartTime) { m_simStartTime = pos; } @@ -235,11 +252,15 @@ void Simulator::simulate() { QString numStepsStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorNumberOfSteps); QString timeStepStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorTimeStepS); QString animationTimeStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorAnimationTimeS); + QString spiceOptionsStr = m_mainWindow->getProjectProperties()->getProjectProperty(ProjectPropertyKeySimulatorSpiceOptions); - DebugDialog::stream() << "timeStepModeStr: " << timeStepModeStr.toStdString() << ", numStepsStr: " << numStepsStr.toStdString() - << ", timeStepStr: " << timeStepStr.toStdString() - << ", animationTimeStr: " << animationTimeStr.toStdString() << std::endl; - if (m_simEndTime > 0 && m_mainWindow->isTransientSimulationEnabled()) { + //Add spice options to the netlist + m_spiceNetlist.replace(".OP", spiceOptionsStr.append("\n.OP")); + + DebugDialog::stream() << "timeStepModeStr: " << timeStepModeStr.toStdString() << ", numStepsStr: " + << numStepsStr.toStdString() << ", timeStepStr: " << timeStepStr.toStdString() + << ", animationTimeStr: " << animationTimeStr.toStdString(); + if (m_transientSimulationEnabled && m_mainWindow->isTransientSimulationEnabled()) { if (timeStepModeStr.contains("true", Qt::CaseInsensitive)) { m_simStepTime = TextUtils::convertFromPowerPrefixU(timeStepStr, "s"); m_simNumberOfSteps = (m_simEndTime-m_simStartTime)/m_simStepTime; @@ -248,44 +269,102 @@ void Simulator::simulate() { m_simStepTime = (m_simEndTime-m_simStartTime)/m_simNumberOfSteps; } - m_showResultsTimerInterval = TextUtils::convertFromPowerPrefixU(animationTimeStr, "s")/m_simNumberOfSteps*1000; - //A negative animation times, means real time - if (m_showResultsTimerInterval < 0) - m_showResultsTimerInterval = (m_simEndTime-m_simStartTime)/m_simNumberOfSteps*1000; - std::cout << "Animation timerInterval: " << m_showResultsTimerInterval << std::endl; - m_showResultsTimer->setInterval(m_showResultsTimerInterval); - if (m_showResultsTimerInterval < 10) { - //Do not block Fritzing with calls to animate the results. Leave some time to ngSpice. 100Hz for the rendering is OK. - m_showResultsTimer->setInterval(10); + m_showResultsTimerInterval = TextUtils::convertFromPowerPrefixU(animationTimeStr, "s")/m_simNumberOfSteps*1000; + //A negative animation times, means real time + if (m_showResultsTimerInterval < 0) + m_showResultsTimerInterval = (m_simEndTime-m_simStartTime)/m_simNumberOfSteps*1000; + DebugDialog::stream() << "Animation timerInterval: " << m_showResultsTimerInterval; + m_showResultsTimer->setInterval(m_showResultsTimerInterval); + if (m_showResultsTimerInterval < 10) { + //Do not block Fritzing with calls to animate the results. Leave some time to ngSpice. 100Hz for the rendering is OK. + m_showResultsTimer->setInterval(10); + } + + //If the circuit is modified while we were already simulating the circuit, + //We start a new simulation from that point + QString initConditions="", useIC=""; + double startTimePartialSim = m_simStartTime; + double endTimePartialSim = m_simEndTime; + if(m_showResultsTimer->isActive()) { + + + m_interactionStep = (unsigned long) (m_elapsedSimTotalTimer.elapsed()/ m_showResultsTimerInterval) + 1; + if (m_interactionStep > m_simNumberOfSteps) + return; + + DebugDialog::stream() << "INTERACTION! interactionStep: " <getVecInfo(QString("time").toStdString()).size() + m_previousInteractionStep) < m_interactionStep) ; + + + m_simulator->command("bg_halt"); + + //Save previous voltages and prepare init conditions + initConditions.append(".ic "); + auto vecs = m_simulator->getAllVecs(m_simulator->getCurrPlot().toStdString()); + for (QString vec: vecs) { + //Do not use saved currents from components (@r1[i]) or currents from voltage sources (vcc1#branch) + if(vec.toLower().startsWith("v") && !vec.toLower().contains("branch")){ + //This is a voltage node + + //Get the values from this part of the simulation, crop them and add prevous ones + auto vecPartialValues = m_simulator->getVecInfo(vec.toStdString()); + vecPartialValues.resize(m_interactionStep); + auto vevAllValues = m_previousVoltages.value(vec); + vevAllValues.insert(vevAllValues.end(), vecPartialValues.begin(), vecPartialValues.end()); + + m_previousVoltages.insert(vec.toLower(), vevAllValues); + DebugDialog::stream() << "Storing previous voltages. vevAllValues size: " << vevAllValues.size(); + double val = vecPartialValues[m_interactionStep - 1]; + DebugDialog::stream() << vec << ": " << val; + //TODO: DO NOT LOOSE PRECISION + QString ic = QString("%1=%2 ").arg(vec).arg(QString::number(val, 'f', 8)); + initConditions.append(ic); + } + } + initConditions.append("\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + useIC = "UIC"; + DebugDialog::stream() << "initConditions: " << initConditions; + m_previousInteractionStep = m_interactionStep; + + } else { + //This is the start of a simulation + m_previousInteractionStep = 0; + m_interactionStep = 0; + m_previousVoltages.clear(); } - QString tranAnalysis = QString(".TRAN %1 %2 %3").arg(m_simStepTime).arg(m_simEndTime).arg(m_simStartTime); - spiceNetlist.replace(".OP", tranAnalysis); + QString tranAnalysis = QString("%1.TRAN %2 %3 %4 %5").arg(initConditions).arg(m_simStepTime).arg(endTimePartialSim).arg(startTimePartialSim).arg(useIC); + m_spiceNetlist.replace(".OP", tranAnalysis); } - - DebugDialog::stream() << "Netlist: " << spiceNetlist.toStdString(); + m_simulator->clearLog(); + DebugDialog::stream() << "Netlist: " << m_spiceNetlist.toStdString(); DebugDialog::stream() << "Running command(remcirc):"; m_simulator->command("remcirc"); DebugDialog::stream() << "Running m_simulator->command('reset'):"; m_simulator->command("reset"); - m_simulator->clearLog(); - // DebugDialog::stream() << "Loading codemodel analog.cm, which should be in the CWD:"; - // m_simulator->command("codemodel ./usr/lib/ngspice/analog.cm"); + DebugDialog::stream() << "Loading codemodel analog.cm, which should be in the CWD:"; + m_simulator->command("codemodel ./analog.cm"); DebugDialog::stream() << "-----------------------------------"; DebugDialog::stream() << "Running LoadNetlist:"; - m_simulator->loadCircuit(spiceNetlist.toStdString()); + + m_simulator->loadCircuit(m_spiceNetlist.toStdString()); if (QString::fromStdString(m_simulator->getLog(false)).toLower().contains("error") || // "error on line" QString::fromStdString(m_simulator->getLog(true)).toLower().contains("warning")) { // "warning, can't find model" //Ngspice found an error, do not continue - QString errorHint = 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, report the bug in GitHub."); - showSimulatorError(nullptr, errorHint, spiceNetlist, m_simulator); + QString errorHint = 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, report the bug in GitHub."); + showSimulatorError(nullptr, errorHint, m_spiceNetlist, m_simulator); stopSimulation(); return; } @@ -295,8 +374,9 @@ void Simulator::simulate() { DebugDialog::stream() << "-----------------------------------"; DebugDialog::stream() << "Running m_simulator->command(bg_run):"; m_simulator->resetIsBGThreadRunning(); - m_elapsedAnimationTimer.start(); - m_elapsedSimTotalTimer.start(); + m_elapsedAnimationTimer.start(); + if(!m_showResultsTimer->isActive()) + m_elapsedSimTotalTimer.start(); m_simulator->command("bg_run"); DebugDialog::stream() << "-----------------------------------"; DebugDialog::stream() << "Generating a hash table to find the net of specific connectors:"; @@ -343,12 +423,14 @@ void Simulator::simulate() { DebugDialog::stream() << "Waiting for simulator thread to stop"; int elapsedTime = 0, simTimeOut = 3000; // in ms while (m_simulator->isBGThreadRunning() && elapsedTime < simTimeOut) { - 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; + if (m_simEndTime > 0) { + auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); + if(timeInfo.size() > 0) + break; + } } DebugDialog::stream() << "-------- SIM END or TRANS SIM WITH PARTIAL RESULTS ------------"; @@ -358,7 +440,7 @@ void Simulator::simulate() { FMessageBox::warning(m_mainWindow, tr("Simulator Timeout"), tr("The spice simulator did not finish after %1 ms. Aborting simulation.").arg(simTimeOut)); return; } else { - DebugDialog::stream() << "The spice simulator has finished. ElapsedTime: " << m_elapsedAnimationTimer.elapsed() <command("bg_halt"); + //Delete the pointers foreach (QList * net, netList) { delete net; @@ -390,7 +473,7 @@ void Simulator::simulate() { //If this a transitory simulation, set the timer for the animation if (m_simEndTime > 0) { - m_previousRenderedStep = 0; + m_previousRenderedStep = 0; m_showResultsTimer->start(); } @@ -402,7 +485,7 @@ void Simulator::showSimulatorError(QWidget* parent, const QString& errorHint, co parent, QMessageBox::Warning, tr("Simulator Error"), - errorHint, + errorHint, QMessageBox::Ok ); @@ -418,43 +501,75 @@ void Simulator::showSimulatorError(QWidget* parent, const QString& errorHint, co } void Simulator::showSimulationResults() { - //Check that we have the sim results for this time step - auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); - auto elapsedAnimationTime = m_elapsedAnimationTimer.elapsed(); - m_elapsedAnimationTimer.restart(); + //Check for critical errors + if (m_simulator->errorOccured()) { + //Ngspice found a creitical error, do not continue + DebugDialog::stream() << "Fatal error found, stopping the simulation."; + removeSimItems(); + m_showResultsTimer->stop(); + QString errorHint = tr("The simulator gave an error when running the simulation of this circuit. " + "Please, check the error. This could be caused by big modifications of the " + "circuit during the simulation or inestability issues. " + "You may try to decrease the timestep of the simulation."); + QString errorMsg = QString::fromStdString(m_simulator->getLog(true)).toLower(); + if (errorMsg.contains("vector") && errorMsg.contains("not found!")) + errorHint = tr("The simulator gave an error when running the simulation of this circuit. " + "Please, check the error. This probably has been caused by big modifications of the " + "circuit during the simulation. " + "Please, try again without deleting big parts of your circuit."); + showSimulatorError(nullptr, errorHint, m_spiceNetlist, m_simulator); + stopSimulation(); + return; + } - //Calculate the time step to show - if (m_showResultsTimerInterval * m_simNumberOfSteps < 0.1) { - //Animation time is set to 0. Show simulation result asap - m_currSimStep = m_simNumberOfSteps; - } else { + //Check that we have the sim results for this time step + auto timeInfo = m_simulator->getVecInfo(QString("time").toStdString()); + auto elapsedAnimationTime = m_elapsedAnimationTimer.elapsed(); + m_elapsedAnimationTimer.restart(); + + //Calculate the time step to show + if (m_showResultsTimerInterval * m_simNumberOfSteps < 0.1) { + //Animation time is set to 0. Show simulation result asap + m_currSimStep = m_simNumberOfSteps; + } else { m_currSimStep = (unsigned int) (m_elapsedSimTotalTimer.elapsed()/ m_showResultsTimerInterval); - } + } - if ( m_currSimStep > timeInfo.size()) - m_currSimStep = timeInfo.size(); + if ( m_currSimStep > m_interactionStep + timeInfo.size() - 1) + m_currSimStep = m_interactionStep + timeInfo.size() - 1; + unsigned long localTimeStep = m_currSimStep - m_interactionStep; - if (m_currSimStep == m_previousRenderedStep) - return; - m_previousRenderedStep = m_currSimStep; + if (m_currSimStep == m_previousRenderedStep ) + return; + m_previousRenderedStep = m_currSimStep; - DebugDialog::stream() << "showSimulationResults. Time: " << m_elapsedSimTotalTimer.elapsed() << - ", m_currSimStep: " << m_currSimStep << " simStepsAvailable " << timeInfo.size() << "/" << m_simNumberOfSteps; + DebugDialog::stream() << "showSimulationResults. Time: " << m_elapsedSimTotalTimer.elapsed() << + ", m_currSimStep: " << m_currSimStep << " simStepsAvailable " << timeInfo.size() << "/" << m_simNumberOfSteps+1; - QElapsedTimer elapsedTimer; - elapsedTimer.start(); + QElapsedTimer elapsedTimer; + elapsedTimer.start(); - //Render current simulation step - removeSimItems(); - 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); + //Render current simulation step + removeSimItems(); + updateParts(itemBases, localTimeStep); + double simTime = m_simStartTime + m_currSimStep * m_simStepTime; + QString simMessage = QString::number(simTime, 'f', 3) + " s"; + m_breadboardGraphicsView->setSimulatorMessage(simMessage); + m_schematicGraphicsView->setSimulatorMessage(simMessage); - if (m_currSimStep >= m_simNumberOfSteps) { + if (m_currSimStep >= m_simNumberOfSteps) { m_showResultsTimer->stop(); - DebugDialog::stream() << "SIM END. Total time: " << m_elapsedSimTotalTimer.elapsed() << " ms): "; + m_simulator->command("bg_halt"); + DebugDialog::stream() << "SIM END. Total time: " << m_elapsedSimTotalTimer.elapsed() << " ms): "; + DebugDialog::stream() << "Current Plot: " << m_simulator->getCurrPlot(); + // DebugDialog::stream() << "All Plots: "; + // auto plots = m_simulator->getAllPlots(); + // for (QString plot: plots) + // DebugDialog::stream() << plot; + // DebugDialog::stream() << "All Vecs: "; + // auto vecs = m_simulator->getAllVecs(m_simulator->getCurrPlot().toStdString()); + // for (QString vec: vecs) + // DebugDialog::stream() << vec; } } @@ -473,10 +588,10 @@ void Simulator::updateParts(QSet itemBases, int timeStep) { part->setGraphicsEffect(nullptr); m_sch2bbItemHash.value(part)->setGraphicsEffect(nullptr); - if(m_debugSimResult) { - DebugDialog::stream() << "-----------------------------------" ; - DebugDialog::stream() << "Instance Title: " << part->instanceTitle().toStdString(); - } + if(m_debugSimResult) { + DebugDialog::stream() << "-----------------------------------" ; + DebugDialog::stream() << "Instance Title: " << part->instanceTitle().toStdString(); + } QString family = part->family().toLower(); @@ -517,7 +632,9 @@ void Simulator::updateParts(QSet itemBases, int timeStep) { continue; } if (family.contains("oscilloscope")) { - updateOscilloscope(timeStep, part); + Oscilloscope* oscilloscope = dynamic_cast(part); + if(oscilloscope) + oscilloscope->updateOscilloscope(timeStep + m_interactionStep, m_simStartTime, m_simStepTime, this, m_sch2bbItemHash.value(part)); continue; } } @@ -588,8 +705,8 @@ QString Simulator::create7SegmentNumber(double number){ QString textToDisplay = TextUtils::convertToPowerPrefix(number, 'f', 6); int indexPoint = textToDisplay.indexOf('.'); textToDisplay = TextUtils::convertToPowerPrefix(number, 'f', 4 - indexPoint); - textToDisplay.replace('k', 'K'); - return textToDisplay; + textToDisplay.replace('k', 'K'); + return textToDisplay; } /** @@ -600,47 +717,48 @@ QString Simulator::create7SegmentNumber(double number){ * @param[in] current The number to be displayed */ void Simulator::updateLabPowerSupplyScreen(ItemBase * labPowerSupply, double voltage, double current){ - DebugDialog::stream() << "update labPowerSupply with voltage: " << voltage << " and current " << current; - QString vString = create7SegmentNumber(voltage); - QString cString = create7SegmentNumber(current); - - //The '.' does not occupy a position in the screen (is printed with the previous number) - //So, do not take them into account to fill with spaces - QString aux = QString(vString); - QString auxC = QString(cString); - aux.remove(QChar('.')); - if(aux.size() < 5) { - vString.prepend(QString(5-aux.size(),' ')); - } - if(auxC.size() < 5) { - cString.prepend(QString(5-auxC.size(),' ')); - } - vString.append("\n").append(cString); - QGraphicsTextItem * bbScreen = new QGraphicsTextItem(vString, m_sch2bbItemHash.value(labPowerSupply)); - - QFont font("Segment16C", 10, QFont::Normal); - bbScreen->setFont(font); - //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 bbMultBoundingBox = m_sch2bbItemHash.value(labPowerSupply)->boundingRect(); - QRectF bbBoundingBox = bbScreen->boundingRect(); - - //Set the text to be a 80% percent of the multimeter´s width and 50% in sch view - bbScreen->setScale((0.8*bbMultBoundingBox.width())/bbBoundingBox.width()); - - - //Update the bounding box after scaling them - bbBoundingBox = bbScreen->mapRectToParent(bbScreen->boundingRect()); - - - //Center the text - bbScreen->setPos(QPointF((bbMultBoundingBox.width()-bbBoundingBox.width())/2 - ,0.07*bbMultBoundingBox.height())); - - bbScreen->setDefaultTextColor(QColor(48, 48, 48)); - bbScreen->setZValue(std::numeric_limits::max()); - - m_sch2bbItemHash.value(labPowerSupply)->addSimulationGraphicsItem(bbScreen); + DebugDialog::stream() << "update labPowerSupply with voltage: " << voltage << " and current " << current; + QString vString = create7SegmentNumber(voltage); + QString cString = create7SegmentNumber(current); + + //The '.' does not occupy a position in the screen (is printed with the previous number) + //So, do not take them into account to fill with spaces + QString aux = QString(vString); + QString auxC = QString(cString); + aux.remove(QChar('.')); + if(aux.size() < 5) { + vString.prepend(QString(5-aux.size(),' ')); + } + auxC.remove(QChar('.')); + if(auxC.size() < 5) { + cString.prepend(QString(5-auxC.size(),' ')); + } + vString.append("\n").append(cString); + QGraphicsTextItem * bbScreen = new QGraphicsTextItem(vString, m_sch2bbItemHash.value(labPowerSupply)); + + QFont font("Segment16C", 10, QFont::Normal); + bbScreen->setFont(font); + //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 bbMultBoundingBox = m_sch2bbItemHash.value(labPowerSupply)->boundingRect(); + QRectF bbBoundingBox = bbScreen->boundingRect(); + + //Set the text to be a 72% percent of the power supply´s width + bbScreen->setScale((0.72*bbMultBoundingBox.width())/bbBoundingBox.width()); + + + //Update the bounding box after scaling them + bbBoundingBox = bbScreen->mapRectToParent(bbScreen->boundingRect()); + + + //Center the text + bbScreen->setPos(QPointF((bbMultBoundingBox.width()-bbBoundingBox.width()*1.13)/2 + ,0.075*bbMultBoundingBox.height())); + + bbScreen->setDefaultTextColor(QColor(200, 48, 48)); + bbScreen->setZValue(std::numeric_limits::max()); + + m_sch2bbItemHash.value(labPowerSupply)->addSimulationGraphicsItem(bbScreen); } /** @@ -752,8 +870,8 @@ double Simulator::calculateVoltage(unsigned long timeStep, ConnectorItem * c0, C QString net0str = QString("v(%1)").arg(net0); QString net1str = QString("v(%1)").arg(net1); - //DebugDialog::stream() << "net0str: " << net0str.toStdString(); - //DebugDialog::stream() << "net1str: " << net1str.toStdString(); + //DebugDialog::stream() << "net0str: " << net0str.toStdString(); + //DebugDialog::stream() << "net1str: " << net1str.toStdString(); double volt0 = 0.0, volt1 = 0.0; if (net0 != 0) { @@ -774,64 +892,19 @@ std::vector Simulator::voltageVector(ConnectorItem * c0) { QString net0str = QString("v(%1)").arg(net0); if (net0 != 0) { - return m_simulator->getVecInfo(net0str.toStdString()); + auto allValues = m_previousVoltages.value(net0str); + auto newValues = m_simulator->getVecInfo(net0str.prepend(m_simulator->getCurrPlot()+".").toStdString()); + allValues.insert(allValues.end(), newValues.begin(), newValues.end()); + return allValues; } //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); + std::vector voltageVector(timeInfo.size() + m_interactionStep, 0.0); return voltageVector; } -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 ) { - if(m_debugSimResult) { - DebugDialog::stream() << "OSCILLOSCOPE: pos " << timePos << ", timeScale: " << timeScale; - DebugDialog::stream() << "OSCILLOSCOPE: VOLTAGE VALUES " << nameId.toStdString() << ": "; - } - QString svg; - double screenOffset = 0;//132.87378; - if (!nameId.isEmpty()) - svg += QString(" 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 (screenPoint == 0) { - svg.append("M "+ QString::number(screenOffset, 'f', 3) +" " + QString::number( vPos, 'f', 3) + " "); - } else { - svg.append("L " + QString::number(screenPoint*horScale + screenOffset, 'f', 3) + " " + QString::number(vPos, 'f', 3) + " "); - } - //DebugDialog::stream() <<" ("<< time << "): " << voltage << ' '; - screenPoint++; - } - svg += "' transform='translate(%1,%2)' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // - return svg; -} /** * Returns the symbol of a part´s property. It is needed to be able to remove the symbol from the value of the property. @@ -864,7 +937,7 @@ QChar Simulator::getDeviceType (ItemBase* part) { } QString msg = QString("Error getting the device type. The type is not recognized. Part=%1, Spice line=%2").arg(part->instanceTitle(), part->spice()); //TODO: Add tr() - DebugDialog::stream() << msg; + DebugDialog::stream() << msg; throw msg.toStdString(); return QChar('0'); } @@ -907,8 +980,8 @@ double Simulator::getMaxPropValue(ItemBase *part, QString property) { */ double Simulator::getPower(unsigned long timeStep, ItemBase* part, QString subpartName) { //TODO: Handle devices that do not return the power - //TODO: This can cause a crash in ngspice if using a background thread that has not finished and using the callback SendData, see: - //https://sourceforge.net/p/ngspice/discussion/120973/thread/44c8e256c2/ + //TODO: This can cause a crash in ngspice if using a background thread that has not finished and using the callback SendData, see: + //https://sourceforge.net/p/ngspice/discussion/120973/thread/44c8e256c2/ QString instanceStr = part->instanceTitle().toLower(); instanceStr.append(subpartName.toLower()); instanceStr.prepend("@"); @@ -932,42 +1005,39 @@ double Simulator::getCurrent(unsigned long timeStep, ItemBase* part, QString sub instanceStr.append(subpartName.toLower()); QChar deviceType = getDeviceType(part); - //DebugDialog::stream() << "deviceType: " << deviceType.toLatin1(); + //DebugDialog::stream() << "deviceType: " << deviceType.toLatin1(); switch (deviceType.toLatin1()) { case 'd': - if (deviceType == instanceStr.at(0)) { - instanceStr.prepend(QString("@")); - } else { - //f. ex. Leds are DLED1 in ngpice and LED1 in Fritzing - instanceStr.prepend(QString("@%1").arg(deviceType)); - } + if (deviceType == instanceStr.at(0)) { + instanceStr.prepend(QString("@")); + } else { + //f. ex. Leds are DLED1 in ngpice and LED1 in Fritzing + instanceStr.prepend(QString("@%1").arg(deviceType)); + } instanceStr.append("[id]"); break; case 'r': //resistors case 'c': //capacitors case 'l': //inductors - if (deviceType == instanceStr.at(0)) { - instanceStr.prepend(QString("@")); - } else { - //f. ex. Leds are DLED1 in ngpice and LED1 in Fritzing - instanceStr.prepend(QString("@%1").arg(deviceType)); - } + if (deviceType == instanceStr.at(0)) { + instanceStr.prepend(QString("@")); + } else { + //f. ex. Leds are DLED1 in ngpice and LED1 in Fritzing + instanceStr.prepend(QString("@%1").arg(deviceType)); + } instanceStr.append("[i]"); break; - case 'e': //Voltage-controlled voltage source (VCVS) - case 'f': //Current-controlled current source (CCCs) - case 'g': //Voltage-controlled current source (VCCS) - case 'h': //Current-controlled voltage source (CCVS) - case 'b': //Nonlinear dependent source (Behavioral Sources) (ASRC) - case 'i': //Current source - case 'v': //voltage sources - if (deviceType != instanceStr.at(0)) { - instanceStr.prepend(QString(deviceType)); - } - //This is safer than using device parameters [i], see https://sourceforge.net/p/ngspice/discussion/120973/thread/44c8e256c2/ - instanceStr.append("#branch"); - break; + case 'e': //Voltage-controlled voltage source (VCVS) + case 'f': //Current-controlled current source (CCCs) + case 'g': //Voltage-controlled current source (VCCS) + case 'h': //Current-controlled voltage source (CCVS) + case 'b': //Nonlinear dependent source (Behavioral Sources) (ASRC) + case 'i': //Current source + case 'v': //voltage sources + //This is safer than using device parameters [i], see https://sourceforge.net/p/ngspice/discussion/120973/thread/44c8e256c2/ + instanceStr.append("#branch"); + break; default: //TODO: Add tr() throw QString("Error getting the current of the device.The device type is not recognized. First letter is ").arg(deviceType); @@ -1170,25 +1240,25 @@ void Simulator::removeItemsToBeSimulated(QList & parts) { * @param[in] diode A part that is going to be checked and updated. */ void Simulator::updateDiode(unsigned long timeStep, ItemBase * diode) { - //Better to calculate the power using the voltage and current. - //getPower uses the @dx[p] vector which may be not defined + //Better to calculate the power using the voltage and current. + //getPower uses the @dx[p] vector which may be not defined double maxPower = getMaxPropValue(diode, "power"); - double current = getCurrent(timeStep, diode); - ConnectorItem * leg0 = nullptr, * leg1 = nullptr; - QList legs = diode->cachedConnectorItems(); - leg0 = legs.at(0); - leg1 = legs.at(1); - - if(!leg0 || !leg1 ) - return; - - double voltage = calculateVoltage(timeStep, leg0, leg1); - double power = abs(voltage)*abs(current); - - if(m_debugSimResult) { - DebugDialog::stream() << "Diode Power: " << power; - } - if (power > maxPower) { + double current = getCurrent(timeStep, diode); + ConnectorItem * leg0 = nullptr, * leg1 = nullptr; + QList legs = diode->cachedConnectorItems(); + leg0 = legs.at(0); + leg1 = legs.at(1); + + if(!leg0 || !leg1 ) + return; + + double voltage = calculateVoltage(timeStep, leg0, leg1); + double power = abs(voltage)*abs(current); + + if(m_debugSimResult) { + DebugDialog::stream() << "Diode Power: " << power; + } + if (power > maxPower) { drawSmoke(diode); } } @@ -1208,10 +1278,10 @@ void Simulator::updateLED(unsigned long timeStep, ItemBase * part) { // Just one LED double curr = getCurrent(timeStep, part); double maxCurr = getMaxPropValue(part, "current"); - if(m_debugSimResult) { - DebugDialog::stream() << "LED Current: " << curr; - DebugDialog::stream() << "LED MaxCurrent: " << maxCurr; - } + if(m_debugSimResult) { + DebugDialog::stream() << "LED Current: " << curr; + DebugDialog::stream() << "LED MaxCurrent: " << maxCurr; + } LED* bbLed = dynamic_cast(m_sch2bbItemHash.value(part)); bbLed->setBrightness(curr/maxCurr); @@ -1227,8 +1297,8 @@ void Simulator::updateLED(unsigned long timeStep, ItemBase * part) { double curr = std::max({currR, currG, currB}); double maxCurr = getMaxPropValue(part, "current"); - DebugDialog::stream() << "LED Current (R, G, B): " << currR << " " << currG << " " << currB ; - DebugDialog::stream() << "LED MaxCurrent: " << maxCurr; + DebugDialog::stream() << "LED Current (R, G, B): " << currR << " " << currG << " " << currB ; + DebugDialog::stream() << "LED MaxCurrent: " << maxCurr; LED* bbLed = dynamic_cast(m_sch2bbItemHash.value(part)); bbLed->setBrightnessRGB(currR/maxCurr, currG/maxCurr, currB/maxCurr); @@ -1262,10 +1332,10 @@ void Simulator::updateCapacitor(unsigned long timeStep, ItemBase * part) { double maxV = getMaxPropValue(part, "voltage"); double v = calculateVoltage(timeStep, posLeg, negLeg); - if(m_debugSimResult) { - DebugDialog::stream() << "MaxVoltage of the capacitor: " << maxV; - DebugDialog::stream() << "Capacitor voltage is : " << QString("%1").arg(v).toStdString(); - } + if(m_debugSimResult) { + DebugDialog::stream() << "MaxVoltage of the capacitor: " << maxV; + DebugDialog::stream() << "Capacitor voltage is : " << QString("%1").arg(v).toStdString(); + } if (family.contains("bidirectional")) { //This is a ceramic capacitor (or not polarized) @@ -1285,26 +1355,15 @@ void Simulator::updateCapacitor(unsigned long timeStep, ItemBase * part) { * @param[in] part A resistor that is going to be checked and updated. */ void Simulator::updateResistor(unsigned long timeStep, ItemBase * part) { + //It is better to calculate the power using the current + //getPower uses the vector @rx[p], which may not be defined double maxPower = getMaxPropValue(part, "power"); - double current = getCurrent(timeStep, part); - - // Get the voltage across the resistor - ConnectorItem *terminal1 = nullptr, *terminal2 = nullptr; - QList terminals = part->cachedConnectorItems(); - foreach(ConnectorItem * ci, terminals) { - if(ci->connectorSharedName().toLower().compare("pin 0") == 0) terminal1 = ci; - if(ci->connectorSharedName().toLower().compare("pin 1") == 0) terminal2 = ci; - } - if(!terminal1 || !terminal2) return; - - double voltage = calculateVoltage(timeStep, terminal1, terminal2); - - // Calculate power using voltage and current - double power = abs(voltage * current); - - if(m_debugSimResult) { - DebugDialog::stream() << "Resistor Power: " << power; - } + double resistance = getMaxPropValue(part, "resistance"); + double current = getCurrent(timeStep, part); + double power = resistance * pow(abs(current), 2); + if(m_debugSimResult) { + DebugDialog::stream() << "Power: " << power; + } if (power > maxPower) { drawSmoke(part); } @@ -1316,14 +1375,14 @@ void Simulator::updateResistor(unsigned long timeStep, ItemBase * part) { * @param[in] part A potentiometer that is going to be checked and updated. */ void Simulator::updatePotentiometer(unsigned long timeStep, ItemBase * part) { - //It is better to calculate the power using the current - //getPower uses the vector @rx[p], which may not be defined + //It is better to calculate the power using the current + //getPower uses the vector @rx[p], which may not be defined double maxPower = getMaxPropValue(part, "power"); - double currentA = getCurrent(timeStep, part, "A"); //power through resistor A - double currentB = getCurrent(timeStep, part, "B"); //power through resistor B - double maxResistance = getMaxPropValue(part, "maximum resistance"); - double knobStatus = getMaxPropValue(part, "knob status"); - double power = maxResistance*knobStatus/100*pow(abs(currentA),2) + maxResistance*(100-knobStatus)/100*pow(abs(currentB),2); + double currentA = getCurrent(timeStep, part, "A"); //power through resistor A + double currentB = getCurrent(timeStep, part, "B"); //power through resistor B + double resistance = getMaxPropValue(part, "resistance"); + double knobStatus = getMaxPropValue(part, "knob status"); + double power = resistance*knobStatus/100*pow(abs(currentA),2) + resistance*(100-knobStatus)/100*pow(abs(currentB),2); if (power > maxPower) { drawSmoke(part); } @@ -1339,19 +1398,28 @@ void Simulator::updateBattery(unsigned long timeStep, ItemBase * part) { double safetyMargin = 0.1; //TODO: This should be adjusted double maxCurrent = voltage/resistance * safetyMargin; double current = getCurrent(timeStep, part); //current that the battery delivers - if(m_debugSimResult) { - DebugDialog::stream() << "Battery: voltage=" << voltage << ", resistance=" << resistance; - DebugDialog::stream() << "Battery: MaxCurr=" << maxCurrent << ", Curr=" << current; - } + if(m_debugSimResult) { + DebugDialog::stream() << "Battery: voltage=" << voltage << ", resistance=" << resistance; + DebugDialog::stream() << "Battery: MaxCurr=" << maxCurrent << ", Curr=" << current; + } if (abs(current) > maxCurrent) { drawSmoke(part); } - if (part->moduleID().contains("LabDCPowerSupply")) { - maxCurrent = getMaxPropValue(part, "max current"); - updateLabPowerSupplyScreen(part, voltage, current); - } + if (part->moduleID().contains("LabDCPowerSupply")) { + maxCurrent = getMaxPropValue(part, "max current"); + ConnectorItem * leg0 = nullptr, * leg1 = nullptr; + QList legs = part->cachedConnectorItems(); + leg0 = legs.at(0); + leg1 = legs.at(1); + + if(!leg0 || !leg1 ) + return; + + voltage = calculateVoltage(timeStep, leg0, leg1); + updateLabPowerSupplyScreen(part, voltage, -current); + } } bool Simulator::isSimulating() @@ -1368,7 +1436,7 @@ void Simulator::updateIRSensor(unsigned long timeStep, ItemBase * part) { double maxV = getMaxPropValue(part, "voltage (max)"); double minV = getMaxPropValue(part, "voltage (min)"); double maxIout = getMaxPropValue(part, "max output current"); - DebugDialog::stream() << "IR sensor VCC range: " << maxV << " " << minV; + DebugDialog::stream() << "IR sensor VCC range: " << maxV << " " << minV; ConnectorItem *gnd(nullptr); ConnectorItem *vcc(nullptr); ConnectorItem *out(nullptr); @@ -1397,8 +1465,8 @@ void Simulator::updateIRSensor(unsigned long timeStep, ItemBase * part) { //analogue sensor (modelled by a voltage source and a resistor) i = getCurrent(timeStep, part, "a"); //voltage applied to the motor } - DebugDialog::stream() << "IR sensor Max Iout: " << maxIout << ", current Iout " << i; - DebugDialog::stream() << "IR sensor Max V: " << maxV << ", current V " << v; + DebugDialog::stream() << "IR sensor Max Iout: " << maxIout << ", current Iout " << i; + DebugDialog::stream() << "IR sensor Max V: " << maxV << ", current V " << v; if (v > maxV || v < HarmfulNegativeVoltage || abs(i) > maxIout) { drawSmoke(part); return; @@ -1414,7 +1482,7 @@ void Simulator::updateIRSensor(unsigned long timeStep, ItemBase * part) { void Simulator::updateDcMotor(unsigned long timeStep, ItemBase * part) { double maxV = getMaxPropValue(part, "voltage (max)"); double minV = getMaxPropValue(part, "voltage (min)"); - DebugDialog::stream() << "Motor1: "; + DebugDialog::stream() << "Motor1: "; ConnectorItem *terminal1 = nullptr, *terminal2 = nullptr; QList probes = part->cachedConnectorItems(); foreach(ConnectorItem * ci, probes) { @@ -1430,7 +1498,7 @@ void Simulator::updateDcMotor(unsigned long timeStep, ItemBase * part) { return; } if (abs(v) >= minV) { - DebugDialog::stream() << "motor rotates "; + DebugDialog::stream() << "motor rotates "; QGraphicsSvgItem * bbRotate; QGraphicsSvgItem * schRotate; QString image; @@ -1494,258 +1562,47 @@ void Simulator::updateMultimeter(unsigned long timeStep, ItemBase * part) { return; if(comProbe->connectedToWires() && vProbe->connectedToWires() && aProbe->connectedToWires()) { - DebugDialog::stream() << "Multimeter (v_dc) connected with three terminals. "; + DebugDialog::stream() << "Multimeter (v_dc) connected with three terminals. "; updateMultimeterScreen(part, "ERR"); return; } if (variant.compare("voltmeter (dc)") == 0) { - DebugDialog::stream() << "Multimeter (v_dc) found. "; + DebugDialog::stream() << "Multimeter (v_dc) found. "; if(aProbe->connectedToWires()) { - DebugDialog::stream() << "Multimeter (v_dc) has the current terminal connected. "; + DebugDialog::stream() << "Multimeter (v_dc) has the current terminal connected. "; updateMultimeterScreen(part, "ERR"); return; } if(comProbe->connectedToWires() && vProbe->connectedToWires()) { - DebugDialog::stream() << "Multimeter (v_dc) connected with two terminals. "; - double v = calculateVoltage(timeStep, vProbe, comProbe); - updateMultimeterScreen(part, create7SegmentNumber(v)); + DebugDialog::stream() << "Multimeter (v_dc) connected with two terminals. "; + double v = calculateVoltage(timeStep, vProbe, comProbe); + updateMultimeterScreen(part, create7SegmentNumber(v)); } return; } else if (variant.compare("ammeter (dc)") == 0) { - DebugDialog::stream() << "Multimeter (c_dc) found. "; + DebugDialog::stream() << "Multimeter (c_dc) found. "; if(vProbe->connectedToWires()) { - DebugDialog::stream() << "Multimeter (c_dc) has the voltage terminal connected. "; + DebugDialog::stream() << "Multimeter (c_dc) has the voltage terminal connected. "; updateMultimeterScreen(part, "ERR"); return; } - updateMultimeterScreen(part, create7SegmentNumber(getCurrent(timeStep, part))); + updateMultimeterScreen(part, create7SegmentNumber(getCurrent(timeStep, part))); return; } else if (variant.compare("ohmmeter") == 0) { - DebugDialog::stream() << "Ohmmeter found. "; + DebugDialog::stream() << "Ohmmeter found. "; if(aProbe->connectedToWires()) { - DebugDialog::stream() << "Ohmmeter has the current terminal connected. "; + DebugDialog::stream() << "Ohmmeter has the current terminal connected. "; updateMultimeterScreen(part, "ERR"); return; } double v = calculateVoltage(timeStep, vProbe, comProbe); double a = getCurrent(timeStep, part); double r = abs(v/a); - DebugDialog::stream() << "Ohmmeter: Volt: " << v <<", Curr: " << a <<", Ohm: " << r; - updateMultimeterScreen(part, create7SegmentNumber(r)); + DebugDialog::stream() << "Ohmmeter: Volt: " << v <<", Curr: " << a <<", Ohm: " << r; + updateMultimeterScreen(part, create7SegmentNumber(r)); return; } } -/** - * Updates and checks a oscilloscope. If the ground connection is not connected, plots a noisy signal. - * 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(unsigned long timeStep, ItemBase * part) { - 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()) { - DebugDialog::stream() << "Oscilloscope does not have any wire connected to the probe terminals. "; - return; - } - ConnectorItem * probesArray[4] = {v1Probe, v2Probe, v3Probe, v4Probe}; - - - - //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] = {"#ffff50", "lightgreen", "lightblue", "pink"}; - double voltsDiv[4] ={ch1_volsDiv, ch2_volsDiv, ch3_volsDiv, ch4_volsDiv}; - double chOffsets[4] ={ch1_offset, ch2_offset, ch3_offset, ch4_offset}; - - double screenWidth = 3376.022, screenHeight = 2700.072, bbScreenStrokeWidth= 20; - double verDivisions = 8, horDivisions = 10, divisionSize = screenHeight/verDivisions; - double bbScreenOffsetX = 290.544, bbScreenOffsetY = 259.061, schScreenOffsetX = 906.07449, schScreenOffsetY = 354.60801; - QString svgHeader = "\n%5" - "\n"; - QString bbSvg = QString(svgHeader) - .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) - .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++) { - 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); - } - } else { - vCom = voltageVector(comProbe); - } - - //Draw the signal - QString pathId = QString("ch%1-path").arg(channel+1); - 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); - - //Add text label about volts/div for each channel - bbSvg += QString("CH%4: %5V\n") - .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*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.62, - screenWidth + schScreenOffsetX*1.05, screenWidth + schScreenOffsetX*1.4}; - 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) - .arg(schScreenOffsetY+screenHeight) - .arg(lineColor[channel]); - - double tickSize = 10; - 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 + 1); - QList connectorItems; - connectorItems.append(probesArray[channel]); - ConnectorItem::collectEqualPotential(connectorItems, false, ViewGeometry::RatsnestFlag); - - Q_FOREACH ( ConnectorItem * cItem, connectorItems) { - SymbolPaletteItem* symbolItem = dynamic_cast(cItem->attachedTo()); - if(symbolItem && symbolItem->isOnlyNetLabel() ) { - netName = symbolItem->getLabel(); - netName += " (V)"; - break; - } - } - - schSvg += QString("%4\n") - .arg(xOffset[channel] + paddingAlignment * 180 + (1+paddingAlignment)*30) - .arg(schScreenOffsetY + screenHeight/2) - .arg(lineColor[channel], netName); - - - for (int tick = 0; tick < (verDivisions+1); ++tick) { - double vTick = voltsDiv[channel]*(verDivisions/2-tick)-chOffsets[channel]; - QString voltageText = TextUtils::convertToPowerPrefix(vTick); - schSvg += QString("%5\n") - .arg(xOffset[channel] + paddingAlignment * 10) - .arg(schScreenOffsetY + divisionSize * tick + 20) - .arg(lineColor[channel], textAlignment, voltageText); - - schSvg += QString("\n") - .arg(xOffset[channel] - tickSize + paddingAlignment * tickSize * -1) - .arg(schScreenOffsetY + divisionSize * tick) - .arg(xOffset[channel] + tickSize + paddingAlignment * tickSize * -1) - .arg(lineColor[channel]); - } - - - } //End of for each channel - - //Add time scale axis in bb - bbSvg += QString("time/div: %3s ") - .arg(bbScreenOffsetX + screenWidth / 2) - .arg(bbScreenOffsetY * 0.85) - .arg(TextUtils::convertToPowerPrefix(timeDiv)); - bbSvg += QString(" pos: %4s") - .arg(bbScreenOffsetX + screenWidth/2) - .arg(bbScreenOffsetY * 0.85) - .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.25) - .arg("white", TextUtils::convertToPowerPrefix(hPos + timeDiv*tick)); - } - schSvg += QString("Time (s)") - .arg(schScreenOffsetX + screenWidth / 2) - .arg(screenHeight + schScreenOffsetY * 1.5) - .arg("white"); - - bbSvg += ""; - schSvg += ""; - - QGraphicsSvgItem * schGraph = new QGraphicsSvgItem(part); - QGraphicsSvgItem * bbGraph = new QGraphicsSvgItem(m_sch2bbItemHash.value(part)); - QSvgRenderer *schGraphRender = new QSvgRenderer(schSvg.toUtf8()); - QSvgRenderer *bbGraphRender = new QSvgRenderer(bbSvg.toUtf8()); - if(!schGraphRender->isValid()) - DebugDialog::stream() << "SCH SVG Graph is NOT VALID \n"; - - if(!bbGraphRender->isValid()) - DebugDialog::stream() << "BB SVG Graph is NOT VALID\n"; - - 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); - - - -} diff --git a/src/simulation/simulator.h b/src/simulation/simulator.h index a1c208899..5315abedc 100644 --- a/src/simulation/simulator.h +++ b/src/simulation/simulator.h @@ -44,13 +44,14 @@ class Simulator : public QObject private: void resetTimer(); - void showSimulatorError(QWidget *parent, const QString &errorHint, const QString &spiceNetlist, const std::shared_ptr& simulator); + void showSimulatorError(QWidget *parent, const QString &errorHint, const QString &spiceNetlist, const std::shared_ptr& simulator); public slots: void enable(bool); void enableTransientSimulation(bool); void stopSimulation(); void startSimulation(); void showSimulationResults(); + std::vector voltageVector(ConnectorItem *); signals: @@ -61,8 +62,8 @@ public slots: void updateParts(QSet, int); void drawSmoke(ItemBase* part); void updateMultimeterScreen(ItemBase *, QString); - void updateLabPowerSupplyScreen(ItemBase *, double, double); - QString create7SegmentNumber(double); + void updateLabPowerSupplyScreen(ItemBase *, double, double); + QString create7SegmentNumber(double); void removeSimItems(); void removeSimItems(QList); void greyOutNonSimParts(const QSet&); @@ -74,7 +75,6 @@ public slots: QString getSymbol(ItemBase*, QString); 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(unsigned long, ItemBase*, QString subpartName=""); double getTransistorCurrent(unsigned long timeStep, QString spicePartName, TransistorLeg leg); @@ -97,21 +97,27 @@ public slots: std::shared_ptr m_simulator; QPointer m_breadboardGraphicsView; QPointer m_schematicGraphicsView; - double m_simStartTime, m_simStepTime, m_simEndTime, m_simNumberOfSteps; + double m_simStartTime, m_simStepTime, m_simEndTime; + unsigned long m_interactionStep = 0, m_previousInteractionStep = 0, m_simNumberOfSteps; + QHash> m_previousVoltages; bool m_enabled = false; bool m_transientSimulationEnabled = false; - bool m_debugSimResult = false; + bool m_transitorySimRunning = false; + unsigned long m_previousSimTime = 0; + bool m_debugSimResult = false; QSet itemBases; QHash m_sch2bbItemHash; QHash m_connector2netHash; QTimer *m_simTimer, *m_showResultsTimer; - unsigned long m_currSimStep, m_previousRenderedStep; - double m_showResultsTimerInterval; - QElapsedTimer m_elapsedAnimationTimer; - QElapsedTimer m_elapsedSimTotalTimer; + unsigned long m_currSimStep, m_previousRenderedStep; + double m_showResultsTimerInterval; + QElapsedTimer m_elapsedAnimationTimer; + QElapsedTimer m_elapsedSimTotalTimer; + + QString m_spiceNetlist; static constexpr int SimDelay = 200; static constexpr double HarmfulNegativeVoltage = -0.5;