/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2016 CERN * @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_settings.h" #include #include #include /// @todo ngspice offers more types of analysis, //so there are a few tabs missing (e.g. pole-zero, distortion, sensitivity) // Helper function to shorten conditions static bool empty( const wxTextEntryBase* aCtrl ) { return aCtrl->GetValue().IsEmpty(); } DIALOG_SIM_SETTINGS::DIALOG_SIM_SETTINGS( wxWindow* aParent ) : DIALOG_SIM_SETTINGS_BASE( aParent ), m_exporter( nullptr ), m_spiceEmptyValidator( true ) { m_posIntValidator.SetMin( 1 ); m_acPointsNumber->SetValidator( m_posIntValidator ); m_acFreqStart->SetValidator( m_spiceValidator ); m_acFreqStop->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 ); // Hide pages that aren't fully implemented yet // wxPanel::Hide() isn't enough on some platforms m_simPages->RemovePage( m_simPages->FindPage( m_pgDistortion ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgNoise ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgOP ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgPoleZero ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgSensitivity ) ); m_simPages->RemovePage( m_simPages->FindPage( m_pgTransferFunction ) ); m_sdbSizerOK->SetDefault(); updateNetlistOpts(); } bool DIALOG_SIM_SETTINGS::TransferDataFromWindow() { if( !wxDialog::TransferDataFromWindow() ) return false; wxWindow* page = m_simPages->GetCurrentPage(); // AC analysis if( page == m_pgAC ) { if( !m_pgAC->Validate() ) return false; m_simCommand = wxString::Format( ".ac %s %s %s %s", scaleToString( m_acScale->GetSelection() ), m_acPointsNumber->GetValue(), SPICE_VALUE( m_acFreqStart->GetValue() ).ToSpiceString(), SPICE_VALUE( m_acFreqStop->GetValue() ).ToSpiceString() ); } // DC transfer analysis else if( page == m_pgDC ) { // At least one source has to be enabled if( !m_dcEnable1->IsChecked() && !m_dcEnable2->IsChecked() ) { DisplayError( this, _( "You need to enable at least one source" ) ); return false; } wxString simCmd = wxString( ".dc " ); if( m_dcEnable1->IsChecked() ) { if( empty( m_dcSource1 ) ) { DisplayError( this, _( "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; try { wxString dcSource = m_exporter->GetSpiceDevice( m_dcSource1->GetValue() ); simCmd += wxString::Format( "%s %s %s %s", dcSource, 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; } catch( const KI_PARAM_ERROR& e ) { DisplayError( this, e.What() ); return false; } catch( ... ) { return false; } } if( m_dcEnable2->IsChecked() ) { if( empty( m_dcSource2 ) ) { DisplayError( this, _( "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; try { wxString dcSource = m_exporter->GetSpiceDevice( m_dcSource2->GetValue() ); if( m_dcEnable1->IsChecked() ) simCmd += " "; simCmd += wxString::Format( "%s %s %s %s", dcSource, 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; } catch( const KI_PARAM_ERROR& e ) { DisplayError( this, e.What() ); return false; } catch( ... ) { return false; } } m_simCommand = simCmd; } // Noise analysis else if( page == m_pgNoise ) { const NETLIST_EXPORTER_PSPICE::NET_INDEX_MAP& netMap = m_exporter->GetNetIndexMap(); if( empty( m_noiseMeas ) || empty( m_noiseSrc ) || empty( m_noisePointsNumber ) || empty( m_noiseFreqStart ) || empty( m_noiseFreqStop ) ) return false; wxString ref = empty( m_noiseRef ) ? wxString() : wxString::Format( ", %d", netMap.at( m_noiseRef->GetValue() ) ); wxString noiseSource = m_exporter->GetSpiceDevice( m_noiseSrc->GetValue() ); // Add voltage source prefix if needed if( noiseSource[0] != 'v' && noiseSource[0] != 'V' ) noiseSource += 'v' + noiseSource; m_simCommand = wxString::Format( ".noise v(%d%s) %s %s %s %s %s", netMap.at( m_noiseMeas->GetValue() ), ref, noiseSource, scaleToString( m_noiseScale->GetSelection() ), m_noisePointsNumber->GetValue(), SPICE_VALUE( m_noiseFreqStart->GetValue() ).ToSpiceString(), SPICE_VALUE( m_noiseFreqStop->GetValue() ).ToSpiceString() ); } // DC operating point analysis else if( page == m_pgOP ) { m_simCommand = wxString( ".op" ); } // Transient analysis else if( page == m_pgTransient ) { if( !m_pgTransient->Validate() ) return false; wxString initial = empty( m_transInitial ) ? "" : SPICE_VALUE( m_transInitial->GetValue() ).ToSpiceString(); m_simCommand = wxString::Format( ".tran %s %s %s", SPICE_VALUE( m_transStep->GetValue() ).ToSpiceString(), SPICE_VALUE( m_transFinal->GetValue() ).ToSpiceString(), initial ); } // Custom directives else if( page == m_pgCustom ) { m_simCommand = m_customTxt->GetValue(); } else { return false; } m_simCommand.Trim(); updateNetlistOpts(); return true; } bool DIALOG_SIM_SETTINGS::TransferDataToWindow() { /// @todo one day it could interpret the sim command and fill out appropriate fields.. if( empty( m_customTxt ) ) loadDirectives(); if( m_simCommand.IsEmpty() && !empty( m_customTxt ) ) return parseCommand( m_customTxt->GetValue() ); return true; } int DIALOG_SIM_SETTINGS::ShowModal() { // Fill out comboboxes that allows one to select nets // Map comoboxes to their current values std::map cmbNet = { { m_noiseMeas, m_noiseMeas->GetStringSelection() }, { m_noiseRef, m_noiseRef->GetStringSelection() } }; for( auto c : cmbNet ) c.first->Clear(); for( auto net : m_exporter->GetNetIndexMap() ) { for( auto c : cmbNet ) c.first->Append( net.first ); } // Try to restore the previous selection, if possible for( auto c : cmbNet ) { int idx = c.first->FindString( c.second ); if( idx != wxNOT_FOUND ) c.first->SetSelection( idx ); } // Fill out comboboxes that allows one to select power sources std::map cmbSrc = { { m_dcSource1, m_dcSource1->GetStringSelection() }, { m_dcSource2, m_dcSource2->GetStringSelection() }, { m_noiseSrc, m_noiseSrc->GetStringSelection() }, }; for( auto c : cmbSrc ) c.first->Clear(); for( auto item : m_exporter->GetSpiceItems() ) { if( item.m_primitive == 'V' ) { for( auto c : cmbSrc ) c.first->Append( item.m_refName ); } } // Try to restore the previous selection, if possible for( auto c : cmbSrc ) { int idx = c.first->FindString( c.second ); if( idx != wxNOT_FOUND ) c.first->SetSelection( idx ); } return DIALOG_SIM_SETTINGS_BASE::ShowModal(); } bool DIALOG_SIM_SETTINGS::parseCommand( const wxString& aCommand ) { if( aCommand.IsEmpty() ) return false; wxStringTokenizer tokenizer( aCommand, " " ); wxString tkn = tokenizer.GetNextToken().Lower(); try { if( tkn == ".ac" ) { m_simPages->SetSelection( m_simPages->FindPage( m_pgAC ) ); tkn = tokenizer.GetNextToken().Lower(); if( tkn == "dec" ) m_acScale->SetSelection( 0 ); if( tkn == "oct" ) m_acScale->SetSelection( 1 ); if( tkn == "lin" ) m_acScale->SetSelection( 2 ); else return false; // If the fields below are empty, it will be caught by the exception handler m_acPointsNumber->SetValue( tokenizer.GetNextToken() ); m_acFreqStart->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_acFreqStop->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); } else if( tkn == ".dc" ) { m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) ); tkn = tokenizer.GetNextToken(); if( !tkn.IsEmpty() ) { m_dcSource1->SetValue( tkn ); m_dcStart1->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_dcStop1->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_dcIncr1->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); // Check the 'Enabled' field, if all values are filled m_dcEnable1->SetValue( !empty( m_dcSource1 ) && !empty( m_dcStart1 ) && !empty( m_dcStop1 ) && !empty( m_dcIncr1 ) ); } tkn = tokenizer.GetNextToken(); if( !tkn.IsEmpty() ) { m_dcSource2->SetValue( tkn ); m_dcStart2->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_dcStop2->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); m_dcIncr2->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() ); // Check the 'Enabled' field, if all values are filled m_dcEnable2->SetValue( !empty( m_dcSource2 ) && !empty( m_dcStart2 ) && !empty( m_dcStop2 ) && !empty( m_dcIncr2 ) ); } // Check if the directive is complete if( !m_dcEnable1->IsChecked() || !m_dcEnable2->IsChecked() ) return false; } else if( tkn == ".tran" ) { m_simPages->SetSelection( m_simPages->FindPage( m_pgTransient ) ); // 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 tkn = tokenizer.GetNextToken(); if( !tkn.IsEmpty() ) m_transInitial->SetValue( SPICE_VALUE( tkn ).ToSpiceString() ); } // Custom directives else if( !empty( m_customTxt ) ) { m_simPages->SetSelection( m_simPages->FindPage( m_pgCustom ) ); } } catch( ... ) { // Nothing really bad has happened return false; } return true; } void DIALOG_SIM_SETTINGS::loadDirectives() { if( m_exporter ) m_customTxt->SetValue( m_exporter->GetSheetSimCommand() ); } void DIALOG_SIM_SETTINGS::updateNetlistOpts() { m_netlistOpts = NET_ALL_FLAGS; if( !m_fixPassiveVals->IsChecked() ) m_netlistOpts &= ~NET_ADJUST_PASSIVE_VALS; if( !m_fixIncludePaths->IsChecked() ) m_netlistOpts &= ~NET_ADJUST_INCLUDE_PATHS; }