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:** + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -274,6 +345,9 @@ + + + @@ -329,4 +403,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 000000000..427da430c Binary files /dev/null and b/sketches/core/Simulator/EN/trans/555_IC_astable.fzz differ 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 000000000..971c98a4d Binary files /dev/null and b/sketches/core/Simulator/EN/trans/flipflop_led_flashing_circuit.fzz differ 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 000000000..0e89dbe85 Binary files /dev/null and b/sketches/core/Simulator/EN/trans/full_wave_rectifier.fzz differ 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 000000000..51a796489 Binary files /dev/null and b/sketches/core/Simulator/EN/trans/oscilloscope_pulse_test.fzz differ 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 @@ + + + + + + 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); diff --git a/src/dialogs/prefsdialog.cpp b/src/dialogs/prefsdialog.cpp index 6728fc5b2..349fc53ea 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,91 @@ 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); + + 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))); + + return projectPropertiesBox; + +} + +void PrefsDialog::setSimulationTimeStepMode(const bool &timeStepMode) { + QString mode = timeStepMode ? "true" : "false"; + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorTimeStepMode, mode); +} + +void PrefsDialog::setSimulationNumberOfSteps(const QString &numberOfSteps) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorNumberOfSteps, numberOfSteps); +} + +void PrefsDialog::setSimulationTimeStep(const QString &timeStep) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorTimeStepS, timeStep); +} + +void PrefsDialog::setSimulationAnimationTime(const QString &animationTime) { + m_projectProperties->setProjectProperty(ProjectPropertyKeySimulatorAnimationTimeS, animationTime); +} + void PrefsDialog::clear() { m_cleared = true; accept(); diff --git a/src/dialogs/prefsdialog.h b/src/dialogs/prefsdialog.h index 9fbf3134b..26d2595fe 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,10 @@ protected Q_SLOTS: void changeAutosavePeriod(int); void curvyChanged(); void chooseProgrammer(); + void setSimulationTimeStepMode(const bool &timeStepMode); + void setSimulationNumberOfSteps(const QString &numberOfSteps); + void setSimulationTimeStep(const QString &timeStep); + void setSimulationAnimationTime(const QString &animationTime); protected: QPointer m_tabWidget; @@ -106,6 +113,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/items/capacitor.cpp b/src/items/capacitor.cpp index fe28817bb..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))); diff --git a/src/mainwindow/getspice.cpp b/src/mainwindow/getspice.cpp index 3ca869821..bbf205fae 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) || knownTokens.contains(token.toLower())) { // Property exists but is empty. Or it is one of a few known tokens. Just assume zero. replacement = "0"; } else { @@ -100,6 +100,7 @@ QString GetSpice::getSpice(ItemBase * itemBase, const QList< QListtriggerSimulation(); } +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/mainwindow/mainwindow_export.cpp b/src/mainwindow/mainwindow_export.cpp index 4f7ea7748..906d326ba 100644 --- a/src/mainwindow/mainwindow_export.cpp +++ b/src/mainwindow/mainwindow_export.cpp @@ -1465,6 +1465,7 @@ QString MainWindow::getSpiceNetlist(QString simulationName, QList< QListrootModelPartShared(); + 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..07895cf79 100644 --- a/src/project_properties.cpp +++ b/src/project_properties.cpp @@ -23,6 +23,10 @@ along with Fritzing. If not, see . #include ProjectProperties::ProjectProperties() { + m_propertiesMap[ProjectPropertyKeySimulatorTimeStepMode] = "false"; + m_propertiesMap[ProjectPropertyKeySimulatorNumberOfSteps] = "400"; + m_propertiesMap[ProjectPropertyKeySimulatorTimeStepS] = "1us"; + m_propertiesMap[ProjectPropertyKeySimulatorAnimationTimeS] = "5s"; m_keys = QStringList(m_propertiesMap.keys()); } @@ -66,3 +70,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..7229c222c 100644 --- a/src/project_properties.h +++ b/src/project_properties.h @@ -26,6 +26,11 @@ along with Fritzing. If not, see . #include #include +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: ProjectProperties(); @@ -35,6 +40,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; diff --git a/src/simulation/ngspice_simulator.cpp b/src/simulation/ngspice_simulator.cpp index 6c991fa5c..8851a72a3 100644 --- a/src/simulation/ngspice_simulator.cpp +++ b/src/simulation/ngspice_simulator.cpp @@ -167,7 +167,8 @@ std::vector NgSpiceSimulator::getVecInfo(const std::string& vecName) { std::vector realValues; if (vecInfo->v_realdata) { - realValues.push_back(vecInfo->v_realdata[0]); + 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..d2ff2e7a4 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" @@ -67,11 +68,16 @@ 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); + connect(m_showResultsTimer, &QTimer::timeout, this, &Simulator::showSimulationResults); + QSettings settings; int enabled = settings.value("simulatorEnabled", 0).toInt(); - enable(enabled); + enable(true); m_simulating = false; + } Simulator::~Simulator() { @@ -141,9 +147,12 @@ 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); + emit simulationStartedOrStopped(m_simulating); + m_breadboardGraphicsView->setSimulatorMessage(""); + m_schematicGraphicsView->setSimulatorMessage(""); } /** @@ -191,9 +200,53 @@ 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 + 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 < m_simStartTime) { + m_simStartTime = pos; + } + double maxSimTimeOsc = pos + time_div * 10; + if (maxSimTimeOsc > m_simEndTime) { + m_simEndTime = maxSimTimeOsc; + } + } + } + + //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) { + 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); + } + + std::cout << "Netlist: " << spiceNetlist.toStdString() << std::endl; //std::cout << "-----------------------------------" <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 << "-----------------------------------" <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; } @@ -277,9 +335,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(); @@ -307,66 +371,123 @@ void Simulator::simulate() { } std::cout << "No fatal error found, continuing..." <setGraphicsEffect(nullptr); - m_sch2bbItemHash.value(part)->setGraphicsEffect(nullptr); + //m_simulator->command("bg_halt"); - std::cout << "-----------------------------------" <instanceTitle().toStdString() << std::endl; + //Delete the pointers + foreach (QList * net, netList) { + delete net; + } + netList.clear(); - 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; - } + //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(); + } - //Delete the pointers - foreach (QList * net, netList) { - delete net; - } - netList.clear(); +} + +void Simulator::showSimulationResults() { + 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); + + 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 (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(); + } + +} + +/** + * 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(timeStep, part); + continue; + } + if (family.contains("diode")) { + updateDiode(timeStep, part); + continue; + } + if (family.contains("led")) { + updateLED(timeStep, part); + continue; + } + if (family.contains("resistor")) { + updateResistor(timeStep, part); + continue; + } + if (family.contains("multimeter")) { + updateMultimeter(timeStep, part); + continue; + } + if (family.contains("dc motor")) { + updateDcMotor(timeStep, part); + continue; + } + if (family.contains("line sensor") || family.contains("distance sensor")) { + updateIRSensor(timeStep, part); + continue; + } + if (family.contains("battery") || family.contains("voltage source")) { + updateBattery(timeStep, part); + continue; + } + if (family.contains("potentiometer") || family.contains("sparkfun trimpot")) { + updatePotentiometer(timeStep, part); + continue; + } + if (family.contains("oscilloscope")) { + updateOscilloscope(timeStep, part); + continue; + } + } } /** @@ -530,12 +651,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[0]; + } else { + if (timeStep < 0 || timeStep >= vecInfo.size()) + return defaultValue; + return vecInfo[timeStep]; } } @@ -545,7 +668,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); @@ -558,16 +681,81 @@ 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; } +std::vector Simulator::voltageVector(ConnectorItem * c0) { + int net0 = m_connector2netHash.value(c0); + QString net0str = QString("v(%1)").arg(net0); + + if (net0 != 0) { + 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, 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; + double screenOffset = 0;//132.87378; + //svg += QString("\n").arg(screenOffset).arg(screenWidth).arg(screenHeight).arg(strokeWidth); + 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) + " "); + } + //std::cout <<" ("<< time << "): " << voltage << ' '; + screenPoint++; + } + svg += "' transform='translate(%1,%2)' stroke='"+ color + "' stroke-width='"+ strokeWidth + "' fill='none' /> \n"; // + + std::cout << std::endl; + 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. * @param[in] part The part that has a property @@ -639,13 +827,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); } /** @@ -659,7 +847,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()); @@ -683,6 +871,7 @@ double Simulator::getCurrent(ItemBase* part, QString subpartName) { 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 instanceStr.append("[i]"); break; @@ -692,7 +881,7 @@ double Simulator::getCurrent(ItemBase* part, QString subpartName) { break; } - return getVectorValueOrDefault(instanceStr.toStdString(), 0.0); + return getVectorValueOrDefault(timeStep, instanceStr.toStdString(), 0.0); } /** @@ -700,7 +889,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); @@ -720,7 +909,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); } /** @@ -887,9 +1076,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); } @@ -900,7 +1089,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 @@ -908,7 +1097,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; @@ -962,7 +1151,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; @@ -983,9 +1172,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); @@ -997,10 +1186,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); @@ -1011,12 +1200,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; @@ -1081,7 +1270,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; @@ -1094,7 +1283,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; @@ -1151,7 +1340,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(); @@ -1178,7 +1367,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; @@ -1189,7 +1378,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; @@ -1198,11 +1387,234 @@ 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); 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) { + std::cout << "updateOscilloscope: " << std::endl; + 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; + } + ConnectorItem * probesArray[4] = {v1Probe, v2Probe, v3Probe, v4Probe}; + + + 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] = {"#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]) + .arg(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]).arg(textAlignment).arg(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").arg(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()) + 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; + + 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); + 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 0ec5165bb..36b729a60 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); @@ -64,36 +67,43 @@ 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 getCurrent(ItemBase*, QString subpartName=""); - double getTransistorCurrent(QString spicePartName, TransistorLeg leg); - double getPower(ItemBase*, QString subpartName=""); + 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); + 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 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; std::shared_ptr m_simulator; QPointer m_breadboardGraphicsView; QPointer m_schematicGraphicsView; + double m_simStartTime, m_simStepTime, m_simEndTime, m_simNumberOfSteps; bool m_enabled = false; + QSet itemBases; QHash m_sch2bbItemHash; QHash m_connector2netHash; QList* m_instanceTitleSim; - QTimer *m_simTimer; + QTimer *m_simTimer, *m_showResultsTimer; + unsigned long 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); diff --git a/src/utils/textutils.cpp b/src/utils/textutils.cpp index 790dae5d4..81bf2c778 100644 --- a/src/utils/textutils.cpp +++ b/src/utils/textutils.cpp @@ -66,6 +66,8 @@ static const QRegularExpression Xmlns("xmlns=([\"|'])[^\"']*\\1"); const ushort TextUtils::MicroSymbolCode = 181; const QString TextUtils::MicroSymbol = QString::fromUtf16(&MicroSymbolCode, 1); +const ushort TextUtils::AltMicroSymbolCode = 956; +const QString TextUtils::AltMicroSymbol = QString::fromUtf16(&AltMicroSymbolCode, 1); const QString TextUtils::AdobeIllustratorIdentifier = "Generator: Adobe Illustrator"; diff --git a/src/utils/textutils.h b/src/utils/textutils.h index dfdaf16c5..673370f96 100644 --- a/src/utils/textutils.h +++ b/src/utils/textutils.h @@ -140,6 +140,8 @@ class TextUtils static const QString SMDFlipSuffix; static const QString MicroSymbol; static const ushort MicroSymbolCode; + static const QString AltMicroSymbol; + static const ushort AltMicroSymbolCode; static const QString PowerPrefixesString; static const QString CreatedWithFritzingString; static const QString CreatedWithFritzingXmlComment;