/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016-2022 CERN * Copyright (C) 2016-2023 KiCad Developers, see AUTHORS.txt for contributors. * @author Maciej Suminski * * This program 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. * * This program 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 this program; if not, you may find one here: * https://www.gnu.org/licenses/gpl-3.0.html * or you may search the http://www.gnu.org website for the version 3 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "dialog_sim_command.h" #include #include #include #include #include #include #include #include // Helper function to shorten conditions static bool empty( const wxTextEntryBase* aCtrl ) { return aCtrl->GetValue().IsEmpty(); } static void setStringSelection( wxChoice* aCtrl, const wxString& aStr ) { aCtrl->SetSelection( aCtrl->FindString( aStr ) ); } static wxString getStringSelection( const wxChoice* aCtrl ) { if( aCtrl->GetSelection() >= 0 ) return aCtrl->GetString( aCtrl->GetSelection() ); else return wxEmptyString; } DIALOG_SIM_COMMAND::DIALOG_SIM_COMMAND( SIMULATOR_FRAME* aParent, std::shared_ptr aCircuitModel, std::shared_ptr& aSettings ) : DIALOG_SIM_COMMAND_BASE( aParent ), m_simulatorFrame( aParent ), m_circuitModel( aCircuitModel ), m_settings( aSettings ), m_spiceEmptyValidator( true ) { m_simPages->Hide(); m_posIntValidator.SetMin( 1 ); m_acPointsNumber->SetValidator( m_posIntValidator ); m_acFreqStart->SetValidator( m_spiceValidator ); m_acFreqStop->SetValidator( m_spiceValidator ); m_spPointsNumber->SetValidator( m_posIntValidator ); m_spFreqStart->SetValidator( m_spiceValidator ); m_spFreqStop->SetValidator( m_spiceValidator ); m_dcStart1->SetValidator( m_spiceValidator ); m_dcStop1->SetValidator( m_spiceValidator ); m_dcIncr1->SetValidator( m_spiceValidator ); m_dcStart2->SetValidator( m_spiceValidator ); m_dcStop2->SetValidator( m_spiceValidator ); m_dcIncr2->SetValidator( m_spiceValidator ); m_noisePointsNumber->SetValidator( m_posIntValidator ); m_noiseFreqStart->SetValidator( m_spiceValidator ); m_noiseFreqStop->SetValidator( m_spiceValidator ); m_transStep->SetValidator( m_spiceValidator ); m_transFinal->SetValidator( m_spiceValidator ); m_transInitial->SetValidator( m_spiceEmptyValidator ); m_transMaxStep->SetValidator( m_spiceEmptyValidator ); m_inputSignalsFilter->SetDescriptiveText( _( "Filter" ) ); wxChar type1 = getStringSelection( m_dcSourceType1 ).Upper().GetChar( 0 ); updateDCSources( type1, m_dcSource1 ); wxChar type2 = getStringSelection( m_dcSourceType2 ).Upper().GetChar( 0 ); updateDCSources( type2, m_dcSource2 ); // NoiseRef is optional m_noiseRef->Append( wxEmptyString ); for( const std::string& net : m_circuitModel->GetNets() ) { m_pzInput->Append( net ); m_pzInputRef->Append( net ); m_pzOutput->Append( net ); m_pzOutputRef->Append( net ); m_noiseMeas->Append( net ); m_noiseRef->Append( net ); } for( const SPICE_ITEM& item : m_circuitModel->GetItems() ) { if( item.model->GetDeviceType() == SIM_MODEL::DEVICE_T::V ) m_noiseSrc->Append( item.refName ); } if( !dynamic_cast( aSettings.get() ) ) m_compatibilityModeSizer->Show( false ); int minWidth = GetTextExtent( wxS( "XXX.XXXXXXX" ) ).x; m_y1Min->SetMinSize( wxSize( minWidth, -1 ) ); m_y1Max->SetMinSize( wxSize( minWidth, -1 ) ); m_y2Min->SetMinSize( wxSize( minWidth, -1 ) ); m_y2Max->SetMinSize( wxSize( minWidth, -1 ) ); m_y3Min->SetMinSize( wxSize( minWidth, -1 ) ); m_y3Max->SetMinSize( wxSize( minWidth, -1 ) ); m_bSizerY1->Show( false ); m_bSizerY2->Show( false ); m_bSizerY3->Show( false ); SetupStandardButtons(); } bool DIALOG_SIM_COMMAND::TransferDataToWindow() { /// @todo one day it could interpret the sim command and fill out appropriate fields. if( empty( m_customTxt ) ) loadDirectives(); m_fixIncludePaths->SetValue( m_settings->GetFixIncludePaths() ); if( NGSPICE_SETTINGS* settings = dynamic_cast( m_settings.get() ) ) { switch( settings->GetCompatibilityMode() ) { case NGSPICE_COMPATIBILITY_MODE::USER_CONFIG: m_compatibilityMode->SetSelection( 0 ); break; case NGSPICE_COMPATIBILITY_MODE::NGSPICE: m_compatibilityMode->SetSelection( 1 ); break; case NGSPICE_COMPATIBILITY_MODE::PSPICE: m_compatibilityMode->SetSelection( 2 ); break; case NGSPICE_COMPATIBILITY_MODE::LTSPICE: m_compatibilityMode->SetSelection( 3 ); break; case NGSPICE_COMPATIBILITY_MODE::LT_PSPICE: m_compatibilityMode->SetSelection( 4 ); break; case NGSPICE_COMPATIBILITY_MODE::HSPICE: m_compatibilityMode->SetSelection( 5 ); break; default: wxFAIL_MSG( wxString::Format( "Unknown NGSPICE_COMPATIBILITY_MODE %d.", settings->GetCompatibilityMode() ) ); break; } } return true; } void DIALOG_SIM_COMMAND::OnUpdateUILockY1( wxUpdateUIEvent& event ) { event.Enable( m_lockY1->GetValue() ); } void DIALOG_SIM_COMMAND::OnUpdateUILockY2( wxUpdateUIEvent& event ) { event.Enable( m_lockY2->GetValue() ); } void DIALOG_SIM_COMMAND::OnUpdateUILockY3( wxUpdateUIEvent& event ) { event.Enable( m_lockY3->GetValue() ); } void DIALOG_SIM_COMMAND::SetPlotSettings( const SIM_TAB* aSimTab ) { if( const SIM_PLOT_TAB* plotTab = dynamic_cast( aSimTab ) ) { if( !plotTab->GetLabelY1().IsEmpty() ) { m_bSizerY1->Show( true ); m_lockY1->SetLabel( wxString::Format( m_lockY1->GetLabel(), plotTab->GetLabelY1() ) ); m_y1Units->SetLabel( plotTab->GetUnitsY1() ); double min, max; bool locked = plotTab->GetY1Scale( &min, &max ); m_lockY1->SetValue( locked ); if( !std::isnan( min ) ) m_y1Min->SetValue( SIM_VALUE::Normalize( min ) ); if( !std::isnan( max ) ) m_y1Max->SetValue( SIM_VALUE::Normalize( max ) ); } if( !plotTab->GetLabelY2().IsEmpty() ) { m_bSizerY2->Show( true ); m_lockY2->SetLabel( wxString::Format( m_lockY2->GetLabel(), plotTab->GetLabelY2() ) ); m_y2Units->SetLabel( plotTab->GetUnitsY2() ); double min, max; bool locked = plotTab->GetY2Scale( &min, &max ); m_lockY2->SetValue( locked ); if( !std::isnan( min ) ) m_y2Min->SetValue( SIM_VALUE::Normalize( min ) ); if( !std::isnan( max ) ) m_y2Max->SetValue( SIM_VALUE::Normalize( max ) ); } if( !plotTab->GetLabelY3().IsEmpty() ) { m_bSizerY3->Show( true ); m_lockY3->SetLabel( wxString::Format( m_lockY3->GetLabel(), plotTab->GetLabelY3() ) ); m_y3Units->SetLabel( plotTab->GetUnitsY3() ); double min, max; bool locked = plotTab->GetY3Scale( &min, &max ); m_lockY3->SetValue( locked ); if( !std::isnan( min ) ) m_y3Min->SetValue( SIM_VALUE::Normalize( min ) ); if( !std::isnan( max ) ) m_y3Max->SetValue( SIM_VALUE::Normalize( max ) ); } m_grid->SetValue( plotTab->IsGridShown() ); m_legend->SetValue( plotTab->IsLegendShown() ); m_dottedSecondary->SetValue( plotTab->GetDottedSecondary() ); #define GET_STR( val ) EDA_UNIT_UTILS::UI::MessageTextFromValue( unityScale, EDA_UNITS::UNSCALED, \ val, false /* no units */ ) m_marginLeft->SetValue( GET_STR( plotTab->GetPlotWin()->GetMarginLeft() ) ); m_marginTop->SetValue( GET_STR( plotTab->GetPlotWin()->GetMarginTop() ) ); m_marginRight->SetValue( GET_STR( plotTab->GetPlotWin()->GetMarginRight() ) ); m_marginBottom->SetValue( GET_STR( plotTab->GetPlotWin()->GetMarginBottom() ) ); } } wxString DIALOG_SIM_COMMAND::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl* aDcStart, wxTextCtrl* aDcStop, wxTextCtrl* aDcIncr ) { wxString dcSource; wxWindow* ctrlWithError = nullptr; if( aDcSource->GetSelection() >= 0 ) dcSource = aDcSource->GetString( aDcSource->GetSelection() ); if( dcSource.IsEmpty() ) { DisplayError( this, _( "A DC source must be specified." ) ); ctrlWithError = aDcSource; } else if( !aDcStart->Validate() ) ctrlWithError = aDcStart; else if( !aDcStop->Validate() ) ctrlWithError = aDcStop; else if( !aDcIncr->Validate() ) ctrlWithError = aDcIncr; if( ctrlWithError ) { ctrlWithError->SetFocus(); return wxEmptyString; } // pick device name from exporter when something different than temperature is selected if( dcSource.Cmp( "TEMP" ) ) dcSource = m_circuitModel->GetItemName( std::string( dcSource.ToUTF8() ) ); return wxString::Format( "%s %s %s %s", dcSource, SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(), SPICE_VALUE( aDcStop->GetValue() ).ToSpiceString(), SPICE_VALUE( aDcIncr->GetValue() ).ToSpiceString() ); } bool DIALOG_SIM_COMMAND::TransferDataFromWindow() { if( !wxDialog::TransferDataFromWindow() ) return false; // The simulator dependent settings always get transferred. if( NGSPICE_SETTINGS* settings = dynamic_cast( m_settings.get() ) ) { switch( m_compatibilityMode->GetSelection() ) { case 0: settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::USER_CONFIG ); break; case 1: settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::NGSPICE ); break; case 2: settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::PSPICE ); break; case 3: settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::LTSPICE ); break; case 4: settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::LT_PSPICE ); break; case 5: settings->SetCompatibilityMode( NGSPICE_COMPATIBILITY_MODE::HSPICE ); break; } } wxString previousSimCommand = m_simCommand; wxWindow* page = m_simPages->GetCurrentPage(); if( page == m_pgAC ) // AC small-signal analysis { if( !m_pgAC->Validate() ) return false; m_simCommand.Printf( ".ac %s %s %s %s", scaleToString( m_acScale->GetSelection() ), m_acPointsNumber->GetValue(), SPICE_VALUE( m_acFreqStart->GetValue() ).ToSpiceString(), SPICE_VALUE( m_acFreqStop->GetValue() ).ToSpiceString() ); } else if( page == m_pgSP ) // S-params analysis { if( !m_pgSP->Validate() ) return false; m_simCommand.Printf( ".sp %s %s %s %s %s", scaleToString( m_spScale->GetSelection() ), m_spPointsNumber->GetValue(), SPICE_VALUE( m_spFreqStart->GetValue() ).ToSpiceString(), SPICE_VALUE( m_spFreqStop->GetValue() ).ToSpiceString(), m_spDoNoise ? "1" : "" ); } else if( page == m_pgDC ) // DC transfer analysis { wxString simCmd = wxString( ".dc " ); wxString src1 = evaluateDCControls( m_dcSource1, m_dcStart1, m_dcStop1, m_dcIncr1 ); if( src1.IsEmpty() ) return false; else simCmd += src1; if( m_dcEnable2->IsChecked() ) { wxString src2 = evaluateDCControls( m_dcSource2, m_dcStart2, m_dcStop2, m_dcIncr2 ); if( src2.IsEmpty() ) return false; else simCmd += " " + src2; if( m_dcSource1->GetStringSelection() == m_dcSource2->GetStringSelection() ) { DisplayError( this, _( "Source 1 and Source 2 must be different." ) ); return false; } } m_simCommand = simCmd; } else if( page == m_pgFFT ) // Fast Fourier transform { m_simCommand = wxEmptyString; wxString vectors; for( int ii = 0; ii < (int) m_inputSignalsList->GetCount(); ++ii ) { if( m_inputSignalsList->IsChecked( ii ) ) vectors += wxS( " " ) + m_inputSignalsList->GetString( ii ); } if( m_linearize->IsChecked() ) m_simCommand = wxT( "linearize" ) + vectors + wxS( "\n" ); m_simCommand += wxT( "fft" ) + vectors; } else if( page == m_pgPZ ) // Pole-zero analyses { wxString input = m_pzInput->GetStringSelection(); wxString inputRef = m_pzInputRef->GetStringSelection(); wxString output = m_pzOutput->GetStringSelection(); wxString outputRef = m_pzOutputRef->GetStringSelection(); wxString transferFunction = wxS( "vol" ); wxString analyses = wxS( "pz" ); if( m_pzFunctionType->GetSelection() == 1 ) transferFunction = wxS( "cur" ); if( m_pzAnalyses->GetSelection() == 1 ) analyses = wxS( "pol" ); else if( m_pzAnalyses->GetSelection() == 2 ) analyses = wxS( "zer" ); m_simCommand.Printf( ".pz %s %s %s %s %s %s", input, inputRef, output, outputRef, transferFunction, analyses ); } else if( page == m_pgNOISE ) // Noise analysis { wxString output = m_noiseMeas->GetStringSelection(); wxString ref = m_noiseRef->GetStringSelection(); wxString noiseSource = m_noiseSrc->GetStringSelection(); if( m_noiseFreqStart->IsEmpty() || m_noiseFreqStop->IsEmpty() ) { DisplayError( this, _( "A frequency range must be specified." ) ); return false; } if( !ref.IsEmpty() ) ref = wxS( "," ) + m_circuitModel->GetItemName( std::string( ref.ToUTF8() ) ); m_simCommand.Printf( ".noise v(%s%s) %s %s %s %s %s %s", output, ref, noiseSource, scaleToString( m_noiseScale->GetSelection() ), m_noisePointsNumber->GetValue(), SPICE_VALUE( m_noiseFreqStart->GetValue() ).ToSpiceString(), SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString(), m_saveAllNoise->GetValue() ? "1" : "" ); } else if( page == m_pgOP ) // DC operating point analysis { m_simCommand = wxString( ".op" ); } else if( page == m_pgTRAN ) // Transient analysis { if( !m_pgTRAN->Validate() ) return false; const wxString spc = wxS( " " ); const SPICE_VALUE timeStep( m_transStep->GetValue() ); const SPICE_VALUE finalTime( m_transFinal->GetValue() ); SPICE_VALUE startTime( 0 ); if( !empty( m_transInitial ) ) startTime = SPICE_VALUE( m_transInitial->GetValue() ); wxString optionals; if( m_useInitialConditions->GetValue() ) optionals = wxS( "uic" ); if( !empty( m_transMaxStep ) ) { optionals = SPICE_VALUE( m_transMaxStep->GetValue() ).ToSpiceString() + spc + optionals; } else if( !optionals.IsEmpty() ) { SPICE_VALUE maxStep = ( finalTime - startTime ) / 50.0; if( maxStep > timeStep ) maxStep = timeStep; optionals = maxStep.ToSpiceString() + spc + optionals; } if( !empty( m_transInitial ) ) optionals = startTime.ToSpiceString() + spc + optionals; else if( !optionals.IsEmpty() ) optionals = wxS( "0 " ) + optionals; m_simCommand.Printf( wxS( ".tran %s %s %s" ), timeStep.ToSpiceString(), finalTime.ToSpiceString(), optionals ); } else if( page == m_pgCustom ) // Custom directives { m_simCommand = m_customTxt->GetValue(); } else { return false; } if( previousSimCommand != m_simCommand ) m_simCommand.Trim(); m_settings->SetFixIncludePaths( m_fixIncludePaths->GetValue() ); return true; } void DIALOG_SIM_COMMAND::ApplySettings( SIM_TAB* aTab ) { int options = NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS; if( !m_fixIncludePaths->GetValue() ) options &= ~NETLIST_EXPORTER_SPICE::OPTION_ADJUST_INCLUDE_PATHS; if( !m_saveAllVoltages->GetValue() ) options &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_VOLTAGES; if( !m_saveAllCurrents->GetValue() ) options &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_CURRENTS; if( !m_saveAllDissipations->GetValue() ) options &= ~NETLIST_EXPORTER_SPICE::OPTION_SAVE_ALL_DISSIPATIONS; aTab->SetSimOptions( options ); m_simulatorFrame->ReloadSimulator( m_simCommand, options ); #define TO_INT( ctrl ) (int) EDA_UNIT_UTILS::UI::ValueFromString( unityScale, EDA_UNITS::UNSCALED, \ ctrl->GetValue() ) if( SIM_PLOT_TAB* plotTab = dynamic_cast( aTab ) ) { if( !plotTab->GetLabelY1().IsEmpty() ) { plotTab->SetY1Scale( m_lockY1->GetValue(), SIM_VALUE::ToDouble( m_y1Min->GetValue().ToStdString() ), SIM_VALUE::ToDouble( m_y1Max->GetValue().ToStdString() ) ); } if( !plotTab->GetLabelY2().IsEmpty() ) { plotTab->SetY2Scale( m_lockY2->GetValue(), SIM_VALUE::ToDouble( m_y2Min->GetValue().ToStdString() ), SIM_VALUE::ToDouble( m_y2Max->GetValue().ToStdString() ) ); } if( !plotTab->GetLabelY3().IsEmpty() ) { plotTab->SetY3Scale( m_lockY3->GetValue(), SIM_VALUE::ToDouble( m_y3Min->GetValue().ToStdString() ), SIM_VALUE::ToDouble( m_y3Max->GetValue().ToStdString() ) ); } plotTab->GetPlotWin()->LockY( m_lockY1->GetValue() || m_lockY2->GetValue() || m_lockY3->GetValue() ); plotTab->ShowGrid( m_grid->GetValue() ); plotTab->ShowLegend( m_legend->GetValue() ); plotTab->SetDottedSecondary( m_dottedSecondary->GetValue() ); plotTab->GetPlotWin()->SetMarginLeft( TO_INT( m_marginLeft ) ); plotTab->GetPlotWin()->SetMarginRight( TO_INT( m_marginRight ) ); plotTab->GetPlotWin()->SetMarginTop( TO_INT( m_marginTop ) ); plotTab->GetPlotWin()->SetMarginBottom( TO_INT( m_marginBottom ) ); plotTab->GetPlotWin()->AdjustLimitedView(); plotTab->GetPlotWin()->UpdateAll(); } } void DIALOG_SIM_COMMAND::updateDCSources( wxChar aType, wxChoice* aSource ) { wxString prevSelection; if( !aSource->IsEmpty() ) prevSelection = aSource->GetString( aSource->GetSelection() ); std::set sourcesList; bool enableSrcSelection = true; if( aType != 'T' ) { for( const SPICE_ITEM& item : m_circuitModel->GetItems() ) { if( ( aType == 'R' && item.model->GetDeviceType() == SIM_MODEL::DEVICE_T::R ) || ( aType == 'V' && item.model->GetDeviceType() == SIM_MODEL::DEVICE_T::V ) || ( aType == 'I' && item.model->GetDeviceType() == SIM_MODEL::DEVICE_T::I ) ) { sourcesList.insert( item.refName ); } } if( aSource == m_dcSource2 && !m_dcEnable2->IsChecked() ) enableSrcSelection = false; } else { prevSelection = wxT( "TEMP" ); sourcesList.insert( prevSelection ); enableSrcSelection = false; } aSource->Enable( enableSrcSelection ); aSource->Clear(); for( const wxString& src : sourcesList ) aSource->Append( src ); // Try to restore the previous selection, if possible aSource->SetStringSelection( prevSelection ); } void DIALOG_SIM_COMMAND::parseCommand( const wxString& aCommand ) { if( aCommand.IsEmpty() ) return; if( aCommand == wxT( "*" ) ) { SetTitle( _( "New Simulation Tab" ) ); m_commandType->Clear(); for( SIM_TYPE type : { ST_OP, ST_DC, ST_AC, ST_TRAN, ST_PZ, ST_NOISE, ST_SP, ST_FFT } ) { m_commandType->Append( SPICE_SIMULATOR::TypeToName( type, true ) + wxT( " \u2014 " ) + SPICE_SIMULATOR::TypeToName( type, false ) ); } m_commandTypeSizer->Show( true ); m_commandType->SetSelection( 0 ); m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) ); m_simPages->Show(); return; } SIM_TYPE simType = SPICE_CIRCUIT_MODEL::CommandToSimType( aCommand ); SetTitle( SPICE_SIMULATOR::TypeToName( simType, true ) + wxT( " \u2014 " ) + SPICE_SIMULATOR::TypeToName( simType, false ) ); m_commandTypeSizer->Show( false ); wxStringTokenizer tokenizer( aCommand, wxS( " \t\n\r" ), wxTOKEN_STRTOK ); wxString token = tokenizer.GetNextToken().Lower(); switch( simType ) { case ST_AC: m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) ); token = tokenizer.GetNextToken().Lower(); for( SCALE_TYPE candidate : { DECADE, OCTAVE, LINEAR } ) { if( scaleToString( candidate ) == token ) { m_acScale->SetSelection( candidate ); break; } } m_acPointsNumber->SetValue( tokenizer.GetNextToken() ); m_acFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_acFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); break; case ST_SP: m_simPages->SetSelection( m_simPages->FindPage( m_pgSP ) ); token = tokenizer.GetNextToken().Lower(); for( SCALE_TYPE candidate : { DECADE, OCTAVE, LINEAR } ) { if( scaleToString( candidate ) == token ) { m_spScale->SetSelection( candidate ); break; } } m_spPointsNumber->SetValue( tokenizer.GetNextToken() ); m_spFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_spFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); if( tokenizer.HasMoreTokens() ) m_spDoNoise->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() == "1" ); break; case ST_DC: { m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) ); SPICE_DC_PARAMS src1, src2; src2.m_vincrement = SPICE_VALUE( -1 ); m_circuitModel->ParseDCCommand( aCommand, &src1, &src2 ); if( src1.m_source.IsSameAs( wxT( "TEMP" ), false ) ) setStringSelection( m_dcSourceType1, wxT( "TEMP" ) ); else setStringSelection( m_dcSourceType1, src1.m_source.GetChar( 0 ) ); updateDCSources( src1.m_source.GetChar( 0 ), m_dcSource1 ); m_dcSource1->SetStringSelection( src1.m_source ); m_dcStart1->SetValue( src1.m_vstart.ToSpiceString() ); m_dcStop1->SetValue( src1.m_vend.ToSpiceString() ); m_dcIncr1->SetValue( src1.m_vincrement.ToSpiceString() ); if( src2.m_vincrement.ToDouble() != -1 ) { if( src2.m_source.IsSameAs( wxT( "TEMP" ), false ) ) setStringSelection( m_dcSourceType2, wxT( "TEMP" ) ); else setStringSelection( m_dcSourceType2, src2.m_source.GetChar( 0 ) ); updateDCSources( src2.m_source.GetChar( 0 ), m_dcSource2 ); m_dcSource2->SetStringSelection( src2.m_source ); m_dcStart2->SetValue( src2.m_vstart.ToSpiceString() ); m_dcStop2->SetValue( src2.m_vend.ToSpiceString() ); m_dcIncr2->SetValue( src2.m_vincrement.ToSpiceString() ); m_dcEnable2->SetValue( true ); } break; } case ST_PZ: { m_simPages->SetSelection( m_simPages->FindPage( m_pgPZ ) ); wxString transferFunction; wxString input, inputRef; wxString output, outputRef; SPICE_PZ_ANALYSES analyses; m_circuitModel->ParsePZCommand( aCommand, &transferFunction, &input, &inputRef, &output, &outputRef, &analyses ); m_pzInput->SetStringSelection( input ); m_pzInputRef->SetStringSelection( inputRef ); m_pzOutput->SetStringSelection( output ); m_pzOutputRef->SetStringSelection( outputRef ); m_pzFunctionType->SetSelection( transferFunction.Lower() == "cur" ? 1 : 0 ); if( analyses.m_Poles && analyses.m_Zeros ) m_pzAnalyses->SetSelection( 0 ); else if( analyses.m_Poles ) m_pzAnalyses->SetSelection( 1 ); else m_pzAnalyses->SetSelection( 2 ); break; } case ST_NOISE: { m_simPages->SetSelection( m_simPages->FindPage( m_pgNOISE ) ); wxString output; wxString ref; wxString source; wxString scale; SPICE_VALUE pts; SPICE_VALUE fStart; SPICE_VALUE fStop; bool saveAll; m_circuitModel->ParseNoiseCommand( aCommand, &output, &ref, &source, &scale, &pts, &fStart, &fStop, &saveAll ); m_noiseMeas->SetStringSelection( output ); m_noiseRef->SetStringSelection( ref ); m_noiseSrc->SetStringSelection( source ); for( SCALE_TYPE candidate : { DECADE, OCTAVE, LINEAR } ) { if( scaleToString( candidate ) == scale ) { m_noiseScale->SetSelection( candidate ); break; } } m_noisePointsNumber->SetValue( pts.ToSpiceString() ); m_noiseFreqStart->SetValue( fStart.ToSpiceString() ); m_noiseFreqStop->SetValue( fStop.ToSpiceString() ); m_saveAllNoise->SetValue( saveAll ); break; } case ST_TRAN: m_simPages->SetSelection( m_simPages->FindPage( m_pgTRAN ) ); // If the fields below are empty, it will be caught by the exception handler m_transStep->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_transFinal->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); // Initial time is an optional field token = tokenizer.GetNextToken(); if( !token.IsEmpty() ) m_transInitial->SetValue( SPICE_VALUE( token ).ToSpiceString() ); // Max step is an optional field token = tokenizer.GetNextToken(); if( !token.IsEmpty() ) m_transMaxStep->SetValue( SPICE_VALUE( token ).ToSpiceString() ); // uic is an optional field token = tokenizer.GetNextToken(); if( token.IsSameAs( wxS( "uic" ) ) ) m_useInitialConditions->SetValue( true ); break; case ST_OP: m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) ); break; case ST_FFT: { m_simPages->SetSelection( m_simPages->FindPage( m_pgFFT ) ); while( tokenizer.HasMoreTokens() ) m_fftInputSignals.insert( tokenizer.GetNextToken() ); break; } default: m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) ); break; } m_simPages->Show(); } void DIALOG_SIM_COMMAND::OnCommandType( wxCommandEvent& event ) { int sel = ST_UNKNOWN; wxString str = m_commandType->GetString( event.GetSelection() ); for( int type = ST_UNKNOWN; type < ST_LAST; ++type ) { if( str.StartsWith( SPICE_SIMULATOR::TypeToName( (SIM_TYPE) type, true ) ) ) sel = type; } switch( sel ) { case ST_AC: m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) ); break; case ST_SP: m_simPages->SetSelection( m_simPages->FindPage( m_pgSP ) ); break; case ST_DC: m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) ); break; case ST_PZ: m_simPages->SetSelection( m_simPages->FindPage( m_pgPZ ) ); break; case ST_NOISE: m_simPages->SetSelection( m_simPages->FindPage( m_pgNOISE ) ); break; case ST_TRAN: m_simPages->SetSelection( m_simPages->FindPage( m_pgTRAN ) ); break; case ST_OP: m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) ); break; case ST_FFT: m_simPages->SetSelection( m_simPages->FindPage( m_pgFFT ) ); break; default: m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) ); break; } } void DIALOG_SIM_COMMAND::onSwapDCSources( wxCommandEvent& event ) { std::vector> textCtrl = { { m_dcStart1, m_dcStart2 }, { m_dcStop1, m_dcStop2 }, { m_dcIncr1, m_dcIncr2 } }; for( auto& couple : textCtrl ) { wxString tmp = couple.first->GetValue(); couple.first->SetValue( couple.second->GetValue() ); couple.second->SetValue( tmp ); } int src1 = m_dcSource1->GetSelection(); int src2 = m_dcSource2->GetSelection(); int sel = m_dcSourceType1->GetSelection(); m_dcSourceType1->SetSelection( m_dcSourceType2->GetSelection() ); m_dcSourceType2->SetSelection( sel ); wxChar type1 = getStringSelection( m_dcSourceType1 ).Upper().GetChar( 0 ); updateDCSources( type1, m_dcSource1 ); wxChar type2 = getStringSelection( m_dcSourceType2 ).Upper().GetChar( 0 ); updateDCSources( type2, m_dcSource2 ); m_dcSource1->SetSelection( src2 ); m_dcSource2->SetSelection( src1 ); updateDCUnits( type1, m_src1DCStartValUnit, m_src1DCEndValUnit, m_src1DCStepUnit ); updateDCUnits( type2, m_src2DCStartValUnit, m_src2DCEndValUnit, m_src2DCStepUnit ); } void DIALOG_SIM_COMMAND::onDCSource1Selected( wxCommandEvent& event ) { wxChar type = m_dcSourceType1->GetString( m_dcSourceType1->GetSelection() ).Upper()[ 0 ]; updateDCSources( type, m_dcSource1 ); updateDCUnits( type, m_src1DCStartValUnit, m_src1DCEndValUnit, m_src1DCStepUnit ); } void DIALOG_SIM_COMMAND::onDCSource2Selected( wxCommandEvent& event ) { wxChar type = m_dcSourceType2->GetString( m_dcSourceType2->GetSelection() ).Upper()[ 0 ]; updateDCSources( type, m_dcSource2 ); updateDCUnits( type, m_src2DCStartValUnit, m_src2DCEndValUnit, m_src2DCStepUnit ); } void DIALOG_SIM_COMMAND::onDCEnableSecondSource( wxCommandEvent& event ) { bool is2ndSrcEnabled = m_dcEnable2->IsChecked(); wxChar type = '?'; if( is2ndSrcEnabled ) { wxString fullType = getStringSelection( m_dcSourceType2 ).Upper(); if( fullType.Length() > 0 ) type = fullType.GetChar( 0 ); } m_dcSourceType2->Enable( is2ndSrcEnabled ); m_dcSource2->Enable( is2ndSrcEnabled && type != 'T' ); m_dcStart2->Enable( is2ndSrcEnabled ); m_dcStop2->Enable( is2ndSrcEnabled ); m_dcIncr2->Enable( is2ndSrcEnabled ); } void DIALOG_SIM_COMMAND::updateDCUnits( wxChar aType, wxStaticText* aStartValUnit, wxStaticText* aEndValUnit, wxStaticText* aStepUnit ) { wxString unit; switch( aType ) { case 'V': unit = wxS( "V" ); break; case 'I': unit = wxS( "A" ); break; case 'R': unit = wxS( "Ω" ); break; case 'T': unit = wxS( "°C" ); break; } aStartValUnit->SetLabel( unit ); aEndValUnit->SetLabel( unit ); aStepUnit->SetLabel( unit ); m_pgDC->Refresh(); } void DIALOG_SIM_COMMAND::loadDirectives() { if( m_circuitModel ) m_customTxt->SetValue( m_circuitModel->GetSchTextSimCommand() ); } void DIALOG_SIM_COMMAND::OnFilterText( wxCommandEvent& aEvent ) { for( int ii = 0; ii < (int) m_inputSignalsList->GetCount(); ++ii ) { if( m_inputSignalsList->IsChecked( ii ) ) m_fftInputSignals.insert( m_inputSignalsList->GetString( ii ) ); else m_fftInputSignals.erase( m_inputSignalsList->GetString( ii ) ); } m_inputSignalsList->Clear(); wxString aFilter = m_inputSignalsFilter->GetValue(); if( aFilter.IsEmpty() ) aFilter = wxS( "*" ); EDA_COMBINED_MATCHER matcher( aFilter.Upper(), CTX_SIGNAL ); for( const wxString& signal : m_simulatorFrame->Signals() ) { if( matcher.Find( signal.Upper() ) ) { m_inputSignalsList->Append( signal ); if( m_fftInputSignals.count( signal ) ) m_inputSignalsList->Check( m_inputSignalsList->GetCount() - 1 ); } } } void DIALOG_SIM_COMMAND::OnFilterMouseMoved( wxMouseEvent& aEvent ) { wxPoint pos = aEvent.GetPosition(); wxRect ctrlRect = m_inputSignalsFilter->GetScreenRect(); int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square if( m_inputSignalsFilter->IsSearchButtonVisible() && pos.x < buttonWidth ) SetCursor( wxCURSOR_ARROW ); else if( m_inputSignalsFilter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth ) SetCursor( wxCURSOR_ARROW ); else SetCursor( wxCURSOR_IBEAM ); }