kicad-source/eeschema/dialogs/dialog_sim_settings.cpp
Maciej Suminski fcedef836a Revert "Do not use double component primitives in Spice netlist exporter"
This commit creates more issues than resolves. It could happen that
there are components with different reference types (e.g. U1 and IC1)
that would be later converted to X1, causing a conflict.
2016-08-31 11:51:17 +02:00

416 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 CERN
* @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
* 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 <sim/netlist_exporter_pspice_sim.h>
#include <confirm.h>
#include <wx/tokenzr.h>
/// @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 );
m_sdbSizerOK->SetDefault();
updateNetlistOpts();
FixOSXCancelButtonIssue();
}
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_dcEnable1->IsChecked() )
{
DisplayError( this, wxT( "You need to enable at least one source" ) );
return false;
}
wxString simCmd = wxString( ".dc " );
if( m_dcEnable1->IsChecked() )
{
if( empty( m_dcSource1 ) )
{
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;
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( empty( m_dcSource2 ) )
{
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;
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;
}
// 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() ) );
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(),
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 allow 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( 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 allow to select power sources
std::map<wxComboBox*, wxString> 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;
}