From 65c7520544426f434581b295093411334a1a0675 Mon Sep 17 00:00:00 2001 From: Maciej Suminski Date: Thu, 11 Aug 2016 14:41:54 +0200 Subject: [PATCH] Bulletproof Simulation settings dialog --- eeschema/dialogs/dialog_sim_settings.cpp | 107 ++++++++++++++++------- eeschema/dialogs/dialog_sim_settings.h | 5 +- eeschema/sim/spice_value.cpp | 39 +++++++++ eeschema/sim/spice_value.h | 22 +++++ 4 files changed, 141 insertions(+), 32 deletions(-) diff --git a/eeschema/dialogs/dialog_sim_settings.cpp b/eeschema/dialogs/dialog_sim_settings.cpp index 462cd5f125..813f76b80a 100644 --- a/eeschema/dialogs/dialog_sim_settings.cpp +++ b/eeschema/dialogs/dialog_sim_settings.cpp @@ -24,36 +24,35 @@ #include "dialog_sim_settings.h" #include +#include /// @todo ngspice offers more types of analysis, //so there are a few tabs missing (e.g. pole-zero, distortion, sensitivity) DIALOG_SIM_SETTINGS::DIALOG_SIM_SETTINGS( wxWindow* aParent ) - : DIALOG_SIM_SETTINGS_BASE( aParent ), m_exporter( nullptr ) + : DIALOG_SIM_SETTINGS_BASE( aParent ), m_exporter( nullptr ), m_spiceEmptyValidator( true ) { - m_posFloatValidator.SetMin( 0 ); - m_posFloatValidator.SetPrecision( 6 ); m_posIntValidator.SetMin( 1 ); m_acPointsNumber->SetValidator( m_posIntValidator ); - m_acFreqStart->SetValidator( m_posFloatValidator ); - m_acFreqStop->SetValidator( m_posFloatValidator ); + m_acFreqStart->SetValidator( m_spiceValidator ); + m_acFreqStop->SetValidator( m_spiceValidator ); - m_dcStart1->SetValidator( m_posFloatValidator ); - m_dcStop1->SetValidator( m_posFloatValidator ); - m_dcIncr1->SetValidator( m_posFloatValidator ); + m_dcStart1->SetValidator( m_spiceValidator ); + m_dcStop1->SetValidator( m_spiceValidator ); + m_dcIncr1->SetValidator( m_spiceValidator ); - m_dcStart2->SetValidator( m_posFloatValidator ); - m_dcStop2->SetValidator( m_posFloatValidator ); - m_dcIncr2->SetValidator( m_posFloatValidator ); + m_dcStart2->SetValidator( m_spiceValidator ); + m_dcStop2->SetValidator( m_spiceValidator ); + m_dcIncr2->SetValidator( m_spiceValidator ); m_noisePointsNumber->SetValidator( m_posIntValidator ); - m_noiseFreqStart->SetValidator( m_posFloatValidator ); - m_noiseFreqStop->SetValidator( m_posFloatValidator ); + m_noiseFreqStart->SetValidator( m_spiceValidator ); + m_noiseFreqStop->SetValidator( m_spiceValidator ); - m_transStep->SetValidator( m_posFloatValidator ); - m_transFinal->SetValidator( m_posFloatValidator ); - m_transInitial->SetValidator( m_posFloatValidator ); + m_transStep->SetValidator( m_spiceValidator ); + m_transFinal->SetValidator( m_spiceValidator ); + m_transInitial->SetValidator( m_spiceEmptyValidator ); m_sdbSizerOK->SetDefault(); updateNetlistOpts(); @@ -70,12 +69,14 @@ bool DIALOG_SIM_SETTINGS::TransferDataFromWindow() // AC analysis if( page == m_pgAC ) { - if( m_acPointsNumber->IsEmpty() || m_acFreqStart->IsEmpty() || m_acFreqStop->IsEmpty() ) + if( !m_pgAC->Validate() ) return false; m_simCommand = wxString::Format( ".ac %s %s %s %s", scaleToString( m_acScale->GetSelection() ), - m_acPointsNumber->GetValue(), m_acFreqStart->GetValue(), m_acFreqStop->GetValue() ); + m_acPointsNumber->GetValue(), + SPICE_VALUE( m_acFreqStart->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_acFreqStop->GetValue() ).ToSpiceString() ); } @@ -84,30 +85,67 @@ bool DIALOG_SIM_SETTINGS::TransferDataFromWindow() { // At least one source has to be enabled if( !m_dcEnable1->IsChecked() && !m_dcEnable1->IsChecked() ) + { + DisplayError( this, wxT( "You need to enable at least one source" ) ); return false; + } wxString simCmd = wxString( ".dc " ); if( m_dcEnable1->IsChecked() ) { - if( m_dcSource1->GetValue().IsEmpty() || m_dcStart1->IsEmpty() || - m_dcStop1->IsEmpty() || m_dcIncr1->IsEmpty() ) + if( m_dcSource1->GetValue().IsEmpty() ) + { + DisplayError( this, wxT( "You need to select DC source (sweep 1)" ) ); + return false; + } + + /// @todo for some reason it does not trigger the assigned SPICE_VALIDATOR, + // hence try..catch below + if( !m_dcStart1->Validate() || !m_dcStop1->Validate() || !m_dcIncr1->Validate() ) return false; - simCmd += wxString::Format( "v%s %s %s %s", - m_dcSource1->GetValue(), m_dcStart1->GetValue(), - m_dcStop1->GetValue(), m_dcIncr1->GetValue() ); + try + { + simCmd += wxString::Format( "v%s %s %s %s", + m_dcSource1->GetValue(), + SPICE_VALUE( m_dcStart1->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_dcStop1->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_dcIncr1->GetValue() ).ToSpiceString() ); + } + catch( std::exception& e ) + { + DisplayError( this, e.what() ); + return false; + } } if( m_dcEnable2->IsChecked() ) { - if( m_dcSource2->GetValue().IsEmpty() || m_dcStart2->IsEmpty() || - m_dcStop2->IsEmpty() || m_dcIncr2->IsEmpty() ) + if( m_dcSource2->GetValue().IsEmpty() ) + { + DisplayError( this, wxT( "You need to select DC source (sweep 2)" ) ); + return false; + } + + /// @todo for some reason it does not trigger the assigned SPICE_VALIDATOR, + // hence try..catch below + if( !m_dcStart2->Validate() || !m_dcStop2->Validate() || !m_dcIncr2->Validate() ) return false; - simCmd += wxString::Format( "v%s %s %s %s", - m_dcSource2->GetValue(), m_dcStart2->GetValue(), - m_dcStop2->GetValue(), m_dcIncr2->GetValue() ); + try + { + simCmd += wxString::Format( "v%s %s %s %s", + m_dcSource2->GetValue(), + SPICE_VALUE( m_dcStart2->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_dcStop2->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_dcIncr2->GetValue() ).ToSpiceString() ); + } + catch( std::exception& e ) + { + DisplayError( this, e.what() ); + return false; + } } m_simCommand = simCmd; @@ -130,7 +168,9 @@ bool DIALOG_SIM_SETTINGS::TransferDataFromWindow() m_simCommand = wxString::Format( ".noise v(%d%s) v%s %s %s %s %s", netMap.at( m_noiseMeas->GetValue() ), ref, m_noiseSrc->GetValue(), scaleToString( m_noiseScale->GetSelection() ), - m_noisePointsNumber->GetValue(), m_noiseFreqStart->GetValue(), m_noiseFreqStop->GetValue() ); + m_noisePointsNumber->GetValue(), + SPICE_VALUE( m_noiseFreqStart->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString() ); } @@ -144,11 +184,16 @@ bool DIALOG_SIM_SETTINGS::TransferDataFromWindow() // Transient analysis else if( page == m_pgTransient ) { - if( m_transStep->IsEmpty() || m_transFinal->IsEmpty() ) + if( !m_pgTransient->Validate() ) return false; + wxString initial = + m_transInitial->IsEmpty() ? "" : SPICE_VALUE( m_transInitial->GetValue() ).ToSpiceString(); + m_simCommand = wxString::Format( ".tran %s %s %s", - m_transStep->GetValue(), m_transFinal->GetValue(), m_transInitial->GetValue() ); + SPICE_VALUE( m_transStep->GetValue() ).ToSpiceString(), + SPICE_VALUE( m_transFinal->GetValue() ).ToSpiceString(), + initial ); } diff --git a/eeschema/dialogs/dialog_sim_settings.h b/eeschema/dialogs/dialog_sim_settings.h index b757104de6..9ac8829ce4 100644 --- a/eeschema/dialogs/dialog_sim_settings.h +++ b/eeschema/dialogs/dialog_sim_settings.h @@ -26,6 +26,8 @@ #define DIALOG_SIM_SETTINGS_BASE_H #include "dialog_sim_settings_base.h" +#include + #include class NETLIST_EXPORTER_PSPICE_SIM; @@ -94,7 +96,8 @@ private: int m_netlistOpts; NETLIST_EXPORTER_PSPICE_SIM* m_exporter; - wxFloatingPointValidator m_posFloatValidator; + SPICE_VALIDATOR m_spiceValidator; + SPICE_VALIDATOR m_spiceEmptyValidator; wxIntegerValidator m_posIntValidator; }; diff --git a/eeschema/sim/spice_value.cpp b/eeschema/sim/spice_value.cpp index b4f1d9130c..dadb4be3de 100644 --- a/eeschema/sim/spice_value.cpp +++ b/eeschema/sim/spice_value.cpp @@ -27,10 +27,16 @@ #include #include +#include +#include + SPICE_VALUE::SPICE_VALUE( const wxString& aString ) { char buf[8] = { 0, }; + if( aString.IsEmpty() ) + throw std::invalid_argument( "Spice value cannot be empty" ); + if( sscanf( (const char*) aString.c_str(), "%lf%7s", &m_base, buf ) == 0 ) throw std::invalid_argument( "Invalid Spice value string" ); @@ -221,3 +227,36 @@ void SPICE_VALUE::stripZeros( wxString& aString ) if( aString.EndsWith( '.' ) || aString.EndsWith( ',' ) ) aString.RemoveLast(); } + + +bool SPICE_VALIDATOR::Validate( wxWindow* aParent ) +{ + wxTextEntry* const text = GetTextEntry(); + + if( !text ) + return false; + + if( text->IsEmpty() ) + { + if( m_emptyAllowed ) + return true; + + DisplayError( aParent, wxString::Format( wxT( "Fill required fields" ) ) ); + return false; + } + + try + { + // If SPICE_VALUE can be constructed, then it is a valid Spice value + SPICE_VALUE val( text->GetValue() ); + } + catch( ... ) + { + DisplayError( aParent, + wxString::Format( wxT( "'%s' is not a valid Spice value" ), text->GetValue() ) ); + + return false; + } + + return true; +} diff --git a/eeschema/sim/spice_value.h b/eeschema/sim/spice_value.h index c5d00a191e..b6d47da857 100644 --- a/eeschema/sim/spice_value.h +++ b/eeschema/sim/spice_value.h @@ -26,6 +26,7 @@ #define SPICE_VALUE_H #include +#include ///> Helper class to handle Spice way of expressing values (e.g. 10.5 Meg) class SPICE_VALUE @@ -120,4 +121,25 @@ private: static void stripZeros( wxString& aString ); }; + +///> Helper class to recognize Spice formatted values +class SPICE_VALIDATOR : public wxTextValidator +{ +public: + SPICE_VALIDATOR( bool aEmptyAllowed = false ) + : m_emptyAllowed( aEmptyAllowed ) + { + } + + wxObject* Clone() const override + { + return new SPICE_VALIDATOR( *this ); + } + + bool Validate( wxWindow* aParent ) override; + +private: + bool m_emptyAllowed; +}; + #endif /* SPICE_VALUE_H */