kicad/eeschema/dialogs/dialog_sim_settings.cpp

562 lines
17 KiB
C++
Raw Normal View History

2016-08-11 12:41:29 +00:00
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
2021-03-08 10:51:43 +00:00
* Copyright (C) 2016-2021 CERN
* Copyright (C) 2016-2021 KiCad Developers, see CHANGELOG.TXT for contributors.
2016-08-11 12:41:29 +00:00
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
* 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
2016-08-11 12:41:29 +00:00
* 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,
2016-08-11 12:41:29 +00:00
* 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 <sim/netlist_exporter_pspice_sim.h>
2016-08-11 12:41:54 +00:00
#include <confirm.h>
2016-08-11 12:41:29 +00:00
2016-08-11 12:42:08 +00:00
#include <wx/tokenzr.h>
#include <vector>
#include <utility>
2016-08-11 12:41:29 +00:00
/// @todo ngspice offers more types of analysis,
//so there are a few tabs missing (e.g. pole-zero, distortion, sensitivity)
2016-08-11 12:42:08 +00:00
// Helper function to shorten conditions
static bool empty( const wxTextEntryBase* aCtrl )
{
return aCtrl->GetValue().IsEmpty();
}
2021-03-08 10:51:43 +00:00
static void setStringSelection( wxRadioBox* aCtrl, const wxString& aStr )
{
aCtrl->SetSelection( aCtrl->FindString( aStr ) );
}
static wxString getStringSelection( const wxRadioBox* aCtrl )
{
return aCtrl->GetString( aCtrl->GetSelection() );
}
DIALOG_SIM_SETTINGS::DIALOG_SIM_SETTINGS( wxWindow* aParent ) :
DIALOG_SIM_SETTINGS_BASE( aParent ),
m_exporter( nullptr ),
m_spiceEmptyValidator( true )
2016-08-11 12:41:29 +00:00
{
m_posIntValidator.SetMin( 1 );
m_acPointsNumber->SetValidator( m_posIntValidator );
2016-08-11 12:41:54 +00:00
m_acFreqStart->SetValidator( m_spiceValidator );
m_acFreqStop->SetValidator( m_spiceValidator );
2016-08-11 12:41:29 +00:00
2016-08-11 12:41:54 +00:00
m_dcStart1->SetValidator( m_spiceValidator );
m_dcStop1->SetValidator( m_spiceValidator );
m_dcIncr1->SetValidator( m_spiceValidator );
2016-08-11 12:41:29 +00:00
2016-08-11 12:41:54 +00:00
m_dcStart2->SetValidator( m_spiceValidator );
m_dcStop2->SetValidator( m_spiceValidator );
m_dcIncr2->SetValidator( m_spiceValidator );
2016-08-11 12:41:29 +00:00
m_noisePointsNumber->SetValidator( m_posIntValidator );
2016-08-11 12:41:54 +00:00
m_noiseFreqStart->SetValidator( m_spiceValidator );
m_noiseFreqStop->SetValidator( m_spiceValidator );
2016-08-11 12:41:29 +00:00
2016-08-11 12:41:54 +00:00
m_transStep->SetValidator( m_spiceValidator );
m_transFinal->SetValidator( m_spiceValidator );
m_transInitial->SetValidator( m_spiceEmptyValidator );
refreshUIControls();
// 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_pgPoleZero ) );
m_simPages->RemovePage( m_simPages->FindPage( m_pgSensitivity ) );
m_simPages->RemovePage( m_simPages->FindPage( m_pgTransferFunction ) );
m_sdbSizerOK->SetDefault();
updateNetlistOpts();
2016-08-11 12:41:29 +00:00
}
wxString DIALOG_SIM_SETTINGS::evaluateDCControls( wxChoice* aDcSource, wxTextCtrl* aDcStart,
wxTextCtrl* aDcStop, wxTextCtrl* aDcIncr )
{
wxString dcSource = aDcSource->GetString( aDcSource->GetSelection() );
wxWindow* ctrlWithError = nullptr;
if( dcSource.IsEmpty() )
{
DisplayError( this, _( "You need to select DC source" ) );
ctrlWithError = aDcSource;
}
/// @todo for some reason it does not trigger the assigned SPICE_VALIDATOR,
// hence try..catch below
else if( !aDcStart->Validate() )
ctrlWithError = aDcStart;
else if( !aDcStop->Validate() )
ctrlWithError = aDcStop;
else if( !aDcIncr->Validate() )
ctrlWithError = aDcIncr;
if( ctrlWithError )
{
ctrlWithError->SetFocus();
return wxEmptyString;
}
try
{
// pick device name from exporter when something different than temperature is selected
if( dcSource.Cmp( "TEMP" ) )
dcSource = m_exporter->GetSpiceDevice( dcSource );
return wxString::Format( "%s %s %s %s", dcSource,
SPICE_VALUE( aDcStart->GetValue() ).ToSpiceString(),
SPICE_VALUE( aDcStop->GetValue() ).ToSpiceString(),
SPICE_VALUE( aDcIncr->GetValue() ).ToSpiceString() );
}
catch( std::exception& e )
{
DisplayError( this, e.what() );
return wxEmptyString;
}
catch( const KI_PARAM_ERROR& e )
{
DisplayError( this, e.What() );
return wxEmptyString;
}
catch( ... )
{
return wxEmptyString;
}
}
2016-08-11 12:41:29 +00:00
bool DIALOG_SIM_SETTINGS::TransferDataFromWindow()
{
if( !wxDialog::TransferDataFromWindow() )
return false;
wxWindow* page = m_simPages->GetCurrentPage();
// AC analysis
if( page == m_pgAC )
{
2016-08-11 12:41:54 +00:00
if( !m_pgAC->Validate() )
return false;
2021-03-08 10:51:43 +00:00
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() );
2016-08-11 12:41:29 +00:00
}
// DC transfer analysis
else if( page == m_pgDC )
{
wxString simCmd = wxString( ".dc " );
wxString src1 = evaluateDCControls( m_dcSource1, m_dcStart1, m_dcStop1, m_dcIncr1 );
2021-03-08 10:51:43 +00:00
if( src1.IsEmpty() )
return false;
else
simCmd += src1;
2016-08-11 12:41:29 +00:00
if( m_dcEnable2->IsChecked() )
{
wxString src2 = evaluateDCControls( m_dcSource2, m_dcStart2, m_dcStop2, m_dcIncr2 );
2021-03-08 10:51:43 +00:00
if( src2.IsEmpty() )
2016-08-11 12:41:29 +00:00
return false;
else
simCmd += " " + src2;
2016-08-11 12:41:29 +00:00
if( m_dcSource1->GetSelection() == m_dcSource2->GetSelection() )
{
DisplayError( this, _( "Source 1 and Source 2 must be different" ) );
return false;
}
2016-08-11 12:41:29 +00:00
}
m_simCommand = simCmd;
}
// Noise analysis
else if( page == m_pgNoise )
{
const NETLIST_EXPORTER_PSPICE::NET_INDEX_MAP& netMap = m_exporter->GetNetIndexMap();
2016-08-11 12:42:08 +00:00
if( empty( m_noiseMeas ) || empty( m_noiseSrc ) || empty( m_noisePointsNumber )
|| empty( m_noiseFreqStart ) || empty( m_noiseFreqStop ) )
2021-03-08 10:51:43 +00:00
{
2016-08-11 12:41:29 +00:00
return false;
2021-03-08 10:51:43 +00:00
}
wxString ref;
2016-08-11 12:41:29 +00:00
2021-03-08 10:51:43 +00:00
if( !empty( m_noiseRef ) )
ref = 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;
2021-03-08 10:51:43 +00:00
m_simCommand.Printf( ".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() );
2016-08-11 12:41:29 +00:00
}
// DC operating point analysis
else if( page == m_pgOP )
{
m_simCommand = wxString( ".op" );
}
// Transient analysis
else if( page == m_pgTransient )
{
2016-08-11 12:41:54 +00:00
if( !m_pgTransient->Validate() )
return false;
2021-03-08 10:51:43 +00:00
wxString initial;
if( !empty( m_transInitial ) )
initial = SPICE_VALUE( m_transInitial->GetValue() ).ToSpiceString();
2016-08-11 12:41:54 +00:00
2021-03-08 10:51:43 +00:00
m_simCommand.Printf( ".tran %s %s %s",
SPICE_VALUE( m_transStep->GetValue() ).ToSpiceString(),
SPICE_VALUE( m_transFinal->GetValue() ).ToSpiceString(),
initial );
2016-08-11 12:41:29 +00:00
}
// Custom directives
else if( page == m_pgCustom )
{
m_simCommand = m_customTxt->GetValue();
}
else
{
return false;
}
2016-08-11 12:42:10 +00:00
m_simCommand.Trim();
updateNetlistOpts();
2016-08-11 12:41:29 +00:00
return true;
}
bool DIALOG_SIM_SETTINGS::TransferDataToWindow()
{
/// @todo one day it could interpret the sim command and fill out appropriate fields..
2016-08-11 12:42:08 +00:00
if( empty( m_customTxt ) )
loadDirectives();
2016-08-11 12:42:08 +00:00
if( m_simCommand.IsEmpty() && !empty( m_customTxt ) )
return parseCommand( m_customTxt->GetValue() );
2016-08-11 12:41:29 +00:00
return true;
}
int DIALOG_SIM_SETTINGS::ShowModal()
{
// Fill out comboboxes that allows one to select nets
// Map comoboxes to their current values
std::map<wxComboBox*, wxString> cmbNet = {
{ m_noiseMeas, m_noiseMeas->GetStringSelection() },
{ m_noiseRef, m_noiseRef->GetStringSelection() }
};
for( auto c : cmbNet )
c.first->Clear();
for( const 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 );
}
return DIALOG_SIM_SETTINGS_BASE::ShowModal();
}
void DIALOG_SIM_SETTINGS::updateDCSources( wxChar aType, wxChoice* aSource )
{
2021-03-08 11:45:00 +00:00
wxString prevSelection;
if( aSource->GetCount() )
aSource->GetString( aSource->GetSelection() );
std::vector<wxString> sourcesList;
bool enableSrcSelection = true;
if( aType != 'T' )
{
for( const auto& item : m_exporter->GetSpiceItems() )
{
if( item.m_primitive == aType )
sourcesList.push_back( item.m_refName );
}
std::sort( sourcesList.begin(), sourcesList.end(),
2021-03-08 11:45:00 +00:00
[](wxString& a, wxString& b) -> bool
{
return a.Len() < b.Len() || b.Cmp( a ) > 0;
} );
if( aSource == m_dcSource2 && !m_dcEnable2->IsChecked() )
enableSrcSelection = false;
}
else
{
prevSelection = wxT( "TEMP" );
sourcesList.push_back( prevSelection );
enableSrcSelection = false;
}
aSource->Enable( enableSrcSelection );
aSource->Clear();
2021-03-08 10:51:43 +00:00
for( auto& src : sourcesList )
aSource->Append( src );
// Try to restore the previous selection, if possible
2021-03-08 10:51:43 +00:00
aSource->SetStringSelection( prevSelection );
}
2016-08-11 12:42:08 +00:00
bool DIALOG_SIM_SETTINGS::parseCommand( const wxString& aCommand )
{
if( aCommand.IsEmpty() )
return false;
wxStringTokenizer tokenizer( aCommand, " " );
wxString tkn = tokenizer.GetNextToken().Lower();
2021-03-08 10:51:43 +00:00
try
{
2016-08-11 12:42:08 +00:00
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;
2016-08-11 12:42:10 +00:00
// If the fields below are empty, it will be caught by the exception handler
2016-08-11 12:42:08 +00:00
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" )
{
SPICE_DC_PARAMS src1, src2;
src2.m_vincrement = SPICE_VALUE( -1 );
2021-03-08 10:51:43 +00:00
if( !m_exporter->ParseDCCommand( aCommand, &src1, &src2 ) )
return false;
2016-08-11 12:42:08 +00:00
m_simPages->SetSelection( m_simPages->FindPage( m_pgDC ) );
2016-08-11 12:42:08 +00:00
2021-03-08 10:51:43 +00:00
if( src1.m_source.IsSameAs( wxT( "TEMP" ), false ) )
setStringSelection( m_dcSourceType1, wxT( "TEMP" ) );
else
2021-03-08 10:51:43 +00:00
setStringSelection( m_dcSourceType1, src1.m_source.GetChar( 0 ) );
2016-08-11 12:42:08 +00:00
updateDCSources( src1.m_source.GetChar( 0 ), m_dcSource1 );
2021-03-08 10:51:43 +00:00
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() );
2016-08-11 12:42:08 +00:00
if( src2.m_vincrement.ToDouble() != -1 )
2016-08-11 12:42:10 +00:00
{
2021-03-08 10:51:43 +00:00
if( src2.m_source.IsSameAs( wxT( "TEMP" ), false ) )
setStringSelection( m_dcSourceType2, wxT( "TEMP" ) );
else
2021-03-08 10:51:43 +00:00
setStringSelection( m_dcSourceType2, src2.m_source.GetChar( 0 ) );
updateDCSources( src2.m_source.GetChar( 0 ), m_dcSource2 );
2021-03-08 10:51:43 +00:00
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 );
2016-08-11 12:42:10 +00:00
}
2016-08-11 12:42:08 +00:00
refreshUIControls();
2016-08-11 12:42:08 +00:00
}
else if( tkn == ".tran" )
{
m_simPages->SetSelection( m_simPages->FindPage( m_pgTransient ) );
2016-08-11 12:42:10 +00:00
// If the fields below are empty, it will be caught by the exception handler
2016-08-11 12:42:08 +00:00
m_transStep->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
m_transFinal->SetValue( SPICE_VALUE( tokenizer.GetNextToken() ).ToSpiceString() );
2016-08-11 12:42:10 +00:00
// Initial time is an optional field
tkn = tokenizer.GetNextToken();
if( !tkn.IsEmpty() )
m_transInitial->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
2016-08-11 12:42:08 +00:00
}
else if( tkn == ".op" )
{
m_simPages->SetSelection( m_simPages->FindPage( m_pgOP ) );
}
2016-08-11 12:42:08 +00:00
// 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::onSwapDCSources( wxCommandEvent& event )
{
std::vector<std::pair<wxTextEntry*, wxTextEntry*>> 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 );
2021-03-08 10:51:43 +00:00
wxChar type1 = getStringSelection( m_dcSourceType1 ).Upper().GetChar( 0 );
updateDCSources( type1, m_dcSource1 );
2021-03-08 10:51:43 +00:00
wxChar type2 = getStringSelection( m_dcSourceType2 ).Upper().GetChar( 0 );
updateDCSources( type2, m_dcSource2 );
m_dcSource1->SetSelection( src2 );
m_dcSource2->SetSelection( src1 );
updateDCUnits( type1, m_dcSource1, m_src1DCStartValUnit, m_src1DCEndValUnit, m_src1DCStepUnit );
updateDCUnits( type2, m_dcSource2, m_src2DCStartValUnit, m_src2DCEndValUnit, m_src2DCStepUnit );
}
void DIALOG_SIM_SETTINGS::onDCEnableSecondSource( wxCommandEvent& event )
{
bool is2ndSrcEnabled = m_dcEnable2->IsChecked();
2021-03-08 10:51:43 +00:00
wxChar type = getStringSelection( m_dcSourceType2 ).Upper().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_SETTINGS::updateDCUnits( wxChar aType, wxChoice* aSource,
wxStaticText* aStartValUnit, wxStaticText* aEndValUnit,
wxStaticText* aStepUnit )
{
wxString unit;
switch( aType )
{
2021-03-08 10:51:43 +00:00
case 'V': unit = _( "Volts" ); break;
case 'I': unit = _( "Amperes" ); break;
case 'R': unit = _( "Ohms" ); break;
case 'T': unit = wxT( "\u00B0C" ); break;
}
2021-03-08 10:51:43 +00:00
aStartValUnit->SetLabel( unit );
aEndValUnit->SetLabel( unit );
aStepUnit->SetLabel( unit );
m_pgDC->Fit();
m_pgDC->Refresh();
}
void DIALOG_SIM_SETTINGS::loadDirectives()
2016-08-11 12:41:29 +00:00
{
if( m_exporter )
m_customTxt->SetValue( m_exporter->GetSheetSimCommand() );
2016-08-11 12:41:29 +00:00
}
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;
}