mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
This moves EESchema DLIST structures to rtree. These changes are more fundamental than the pcbnew changes from 9163ac543 888c01d11 d1877d7c1 and 961b22d60 as eeschema operations were more dependent on passing drawing list references around with SCH_ITEM* objects.
903 lines
27 KiB
C++
903 lines
27 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2019 KiCad Developers, see CHANGELOG.TXT for contributors.
|
|
* Copyright (C) 2016-2017 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 "wildcards_and_files_ext.h"
|
|
#include "dialog_spice_model.h"
|
|
|
|
#include <sim/spice_value.h>
|
|
#include <confirm.h>
|
|
#include <project.h>
|
|
|
|
#include <wx/tokenzr.h>
|
|
#include <wx/wupdlock.h>
|
|
|
|
#include <cctype>
|
|
#include <cstring>
|
|
|
|
// Helper function to shorten conditions
|
|
static bool empty( const wxTextCtrl* aCtrl )
|
|
{
|
|
return aCtrl->GetValue().IsEmpty();
|
|
}
|
|
|
|
|
|
// Function to sort PWL values list
|
|
static int wxCALLBACK comparePwlValues( wxIntPtr aItem1, wxIntPtr aItem2, wxIntPtr WXUNUSED( aSortData ) )
|
|
{
|
|
float* t1 = reinterpret_cast<float*>( &aItem1 );
|
|
float* t2 = reinterpret_cast<float*>( &aItem2 );
|
|
|
|
if( *t1 > *t2 )
|
|
return 1;
|
|
|
|
if( *t1 < *t2 )
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Structure describing a type of Spice model
|
|
struct SPICE_MODEL_INFO
|
|
{
|
|
SPICE_PRIMITIVE type; ///< Character identifying the model
|
|
wxString description; ///< Human-readable description
|
|
std::vector<std::string> keywords; ///< Keywords indicating the model
|
|
};
|
|
|
|
|
|
// Recognized model types
|
|
static const std::vector<SPICE_MODEL_INFO> modelTypes =
|
|
{
|
|
{ SP_DIODE, _( "Diode" ), { "d" } },
|
|
{ SP_BJT, _( "BJT" ), { "npn", "pnp" } },
|
|
{ SP_MOSFET, _( "MOSFET" ), { "nmos", "pmos", "vdmos" } },
|
|
{ SP_JFET, _( "JFET" ), { "njf", "pjf" } },
|
|
{ SP_SUBCKT, _( "Subcircuit" ), {} },
|
|
};
|
|
|
|
|
|
// Returns index of an entry in modelTypes array (above) corresponding to a Spice primitive
|
|
static int getModelTypeIdx( char aPrimitive )
|
|
{
|
|
const char prim = std::toupper( aPrimitive );
|
|
|
|
for( size_t i = 0; i < modelTypes.size(); ++i )
|
|
{
|
|
if( modelTypes[i].type == prim )
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_COMPONENT& aComponent, SCH_FIELDS* aFields )
|
|
: DIALOG_SPICE_MODEL_BASE( aParent ), m_component( aComponent ), m_schfields( aFields ),
|
|
m_libfields( nullptr ), m_useSchFields( true ),
|
|
m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
|
|
{
|
|
Init();
|
|
}
|
|
|
|
|
|
DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_COMPONENT& aComponent, LIB_FIELDS* aFields )
|
|
: DIALOG_SPICE_MODEL_BASE( aParent ), m_component( aComponent ), m_schfields( nullptr ),
|
|
m_libfields( aFields ), m_useSchFields( false ),
|
|
m_spiceEmptyValidator( true ), m_notEmptyValidator( wxFILTER_EMPTY )
|
|
{
|
|
Init();
|
|
}
|
|
|
|
|
|
void DIALOG_SPICE_MODEL::Init()
|
|
{
|
|
m_pasValue->SetValidator( m_spiceValidator );
|
|
|
|
m_modelType->SetValidator( m_notEmptyValidator );
|
|
m_modelType->Clear();
|
|
|
|
// Create a list of handled models
|
|
for( const auto& model : modelTypes )
|
|
m_modelType->Append( model.description );
|
|
|
|
m_modelName->SetValidator( m_notEmptyValidator );
|
|
|
|
m_genDc->SetValidator( m_spiceEmptyValidator );
|
|
m_genAcMag->SetValidator( m_spiceEmptyValidator );
|
|
m_genAcPhase->SetValidator( m_spiceEmptyValidator );
|
|
|
|
m_pulseInit->SetValidator( m_spiceEmptyValidator );
|
|
m_pulseNominal->SetValidator( m_spiceEmptyValidator );
|
|
m_pulseDelay->SetValidator( m_spiceEmptyValidator );
|
|
m_pulseRise->SetValidator( m_spiceEmptyValidator );
|
|
m_pulseFall->SetValidator( m_spiceEmptyValidator );
|
|
m_pulseWidth->SetValidator( m_spiceEmptyValidator );
|
|
m_pulsePeriod->SetValidator( m_spiceEmptyValidator );
|
|
|
|
m_sinOffset->SetValidator( m_spiceEmptyValidator );
|
|
m_sinAmplitude->SetValidator( m_spiceEmptyValidator );
|
|
m_sinFreq->SetValidator( m_spiceEmptyValidator );
|
|
m_sinDelay->SetValidator( m_spiceEmptyValidator );
|
|
m_sinDampFactor->SetValidator( m_spiceEmptyValidator );
|
|
|
|
m_expInit->SetValidator( m_spiceEmptyValidator );
|
|
m_expPulsed->SetValidator( m_spiceEmptyValidator );
|
|
m_expRiseDelay->SetValidator( m_spiceEmptyValidator );
|
|
m_expRiseConst->SetValidator( m_spiceEmptyValidator );
|
|
m_expFallDelay->SetValidator( m_spiceEmptyValidator );
|
|
m_expFallConst->SetValidator( m_spiceEmptyValidator );
|
|
|
|
m_pwlTimeCol = m_pwlValList->AppendColumn( "Time [s]", wxLIST_FORMAT_LEFT, 100 );
|
|
m_pwlValueCol = m_pwlValList->AppendColumn( "Value [V/A]", wxLIST_FORMAT_LEFT, 100 );
|
|
|
|
m_sdbSizerOK->SetDefault();
|
|
}
|
|
|
|
|
|
bool DIALOG_SPICE_MODEL::TransferDataFromWindow()
|
|
{
|
|
if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() )
|
|
return false;
|
|
|
|
wxWindow* page = m_notebook->GetCurrentPage();
|
|
|
|
// Passive
|
|
if( page == m_passive )
|
|
{
|
|
if( !m_passive->Validate() )
|
|
return false;
|
|
|
|
switch( m_pasType->GetSelection() )
|
|
{
|
|
case 0: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_RESISTOR; break;
|
|
case 1: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_CAPACITOR; break;
|
|
case 2: m_fieldsTmp[SF_PRIMITIVE] = (char) SP_INDUCTOR; break;
|
|
|
|
default:
|
|
wxASSERT_MSG( false, "Unhandled passive type" );
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
m_fieldsTmp[SF_MODEL] = m_pasValue->GetValue();
|
|
}
|
|
|
|
|
|
// Model
|
|
else if( page == m_model )
|
|
{
|
|
if( !m_model->Validate() )
|
|
return false;
|
|
|
|
int modelIdx = m_modelType->GetSelection();
|
|
|
|
if( modelIdx > 0 && modelIdx < (int)modelTypes.size() )
|
|
m_fieldsTmp[SF_PRIMITIVE] = static_cast<char>( modelTypes[modelIdx].type );
|
|
|
|
m_fieldsTmp[SF_MODEL] = m_modelName->GetValue();
|
|
|
|
if( !empty( m_modelLibrary ) )
|
|
m_fieldsTmp[SF_LIB_FILE] = m_modelLibrary->GetValue();
|
|
}
|
|
|
|
// Power source
|
|
else if( page == m_power )
|
|
{
|
|
wxString model;
|
|
|
|
if( !generatePowerSource( model ) )
|
|
return false;
|
|
|
|
m_fieldsTmp[SF_PRIMITIVE] = (char)( m_pwrType->GetSelection() ? SP_ISOURCE : SP_VSOURCE );
|
|
m_fieldsTmp[SF_MODEL] = model;
|
|
}
|
|
|
|
|
|
else
|
|
{
|
|
wxASSERT_MSG( false, "Unhandled model type" );
|
|
return false;
|
|
}
|
|
|
|
m_fieldsTmp[SF_ENABLED] = !m_disabled->GetValue() ? "Y" : "N"; // note bool inversion
|
|
m_fieldsTmp[SF_NODE_SEQUENCE] = m_nodeSeqCheck->IsChecked() ? m_nodeSeqVal->GetValue() : "";
|
|
|
|
// Apply the settings
|
|
for( int i = 0; i < SF_END; ++i )
|
|
{
|
|
if( m_fieldsTmp.count( (SPICE_FIELD) i ) > 0 && !m_fieldsTmp.at( i ).IsEmpty() )
|
|
{
|
|
if( m_useSchFields )
|
|
getSchField( i ).SetText( m_fieldsTmp[i] );
|
|
else
|
|
getLibField( i ).SetText( m_fieldsTmp[i] );
|
|
}
|
|
else
|
|
{
|
|
// Erase empty fields (having empty fields causes a warning in the properties dialog)
|
|
const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) i );
|
|
|
|
if( m_useSchFields )
|
|
m_schfields->erase( std::remove_if( m_schfields->begin(), m_schfields->end(),
|
|
[&]( const SCH_FIELD& f ) { return f.GetName() == spiceField; } ), m_schfields->end() );
|
|
else
|
|
m_libfields->erase( std::remove_if( m_libfields->begin(), m_libfields->end(),
|
|
[&]( const LIB_FIELD& f ) { return f.GetName() == spiceField; } ), m_libfields->end() );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DIALOG_SPICE_MODEL::TransferDataToWindow()
|
|
{
|
|
const auto& spiceFields = NETLIST_EXPORTER_PSPICE::GetSpiceFields();
|
|
|
|
// Fill out the working buffer
|
|
for( unsigned int idx = 0; idx < spiceFields.size(); ++idx )
|
|
{
|
|
const wxString& spiceField = spiceFields[idx];
|
|
|
|
m_fieldsTmp[idx] = NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( (SPICE_FIELD) idx, &m_component,
|
|
NET_ADJUST_INCLUDE_PATHS | NET_ADJUST_PASSIVE_VALS );
|
|
|
|
// Do not modify the existing value, just add missing fields with default values
|
|
if( m_useSchFields && m_schfields )
|
|
{
|
|
for( const auto& field : *m_schfields )
|
|
{
|
|
if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
|
|
{
|
|
m_fieldsTmp[idx] = field.GetText();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if( m_libfields)
|
|
{
|
|
// TODO: There must be a good way to template out these repetitive calls
|
|
for( const auto& field : *m_libfields )
|
|
{
|
|
if( field.GetName() == spiceField && !field.GetText().IsEmpty() )
|
|
{
|
|
m_fieldsTmp[idx] = field.GetText();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Analyze the component fields to fill out the dialog
|
|
char primitive = toupper( m_fieldsTmp[SF_PRIMITIVE][0] );
|
|
|
|
switch( primitive )
|
|
{
|
|
case SP_RESISTOR:
|
|
case SP_CAPACITOR:
|
|
case SP_INDUCTOR:
|
|
m_notebook->SetSelection( m_notebook->FindPage( m_passive ) );
|
|
m_pasType->SetSelection( primitive == SP_RESISTOR ? 0
|
|
: primitive == SP_CAPACITOR ? 1
|
|
: primitive == SP_INDUCTOR ? 2
|
|
: -1 );
|
|
m_pasValue->SetValue( m_fieldsTmp[SF_MODEL] );
|
|
break;
|
|
|
|
case SP_DIODE:
|
|
case SP_BJT:
|
|
case SP_MOSFET:
|
|
case SP_JFET:
|
|
case SP_SUBCKT:
|
|
m_notebook->SetSelection( m_notebook->FindPage( m_model ) );
|
|
m_modelType->SetSelection( getModelTypeIdx( primitive ) );
|
|
m_modelName->SetValue( m_fieldsTmp[SF_MODEL] );
|
|
m_modelLibrary->SetValue( m_fieldsTmp[SF_LIB_FILE] );
|
|
|
|
if( !empty( m_modelLibrary ) )
|
|
{
|
|
const wxString& libFile = m_modelLibrary->GetValue();
|
|
m_fieldsTmp[SF_LIB_FILE] = libFile;
|
|
loadLibrary( libFile );
|
|
}
|
|
break;
|
|
|
|
case SP_VSOURCE:
|
|
case SP_ISOURCE:
|
|
if( !parsePowerSource( m_fieldsTmp[SF_MODEL] ) )
|
|
return false;
|
|
|
|
m_notebook->SetSelection( m_notebook->FindPage( m_power ) );
|
|
m_pwrType->SetSelection( primitive == SP_ISOURCE ? 1 : 0 );
|
|
break;
|
|
|
|
default:
|
|
//wxASSERT_MSG( false, "Unhandled Spice primitive type" );
|
|
break;
|
|
}
|
|
|
|
m_disabled->SetValue( !NETLIST_EXPORTER_PSPICE::StringToBool( m_fieldsTmp[SF_ENABLED] ) );
|
|
|
|
// Check if node sequence is different than the default one
|
|
if( m_fieldsTmp[SF_NODE_SEQUENCE]
|
|
!= NETLIST_EXPORTER_PSPICE::GetSpiceFieldDefVal( SF_NODE_SEQUENCE, &m_component, 0 ) )
|
|
{
|
|
m_nodeSeqCheck->SetValue( true );
|
|
m_nodeSeqVal->SetValue( m_fieldsTmp[SF_NODE_SEQUENCE] );
|
|
}
|
|
|
|
return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow();
|
|
}
|
|
|
|
|
|
bool DIALOG_SPICE_MODEL::parsePowerSource( const wxString& aModel )
|
|
{
|
|
if( aModel.IsEmpty() )
|
|
return false;
|
|
|
|
wxStringTokenizer tokenizer( aModel, " ()" );
|
|
wxString tkn = tokenizer.GetNextToken().Lower();
|
|
|
|
while( tokenizer.HasMoreTokens() )
|
|
{
|
|
// Variables used for generic values processing (filling out wxTextCtrls in sequence)
|
|
bool genericProcessing = false;
|
|
unsigned int genericReqParamsCount = 0;
|
|
std::vector<wxTextCtrl*> genericControls;
|
|
|
|
if( tkn == "dc" )
|
|
{
|
|
// There might be an optional "dc" or "trans" directive, skip it
|
|
if( tkn == "dc" || tkn == "trans" )
|
|
tkn = tokenizer.GetNextToken().Lower();
|
|
|
|
// DC value
|
|
try
|
|
{
|
|
m_genDc->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
else if( tkn == "ac" )
|
|
{
|
|
// AC magnitude
|
|
try
|
|
{
|
|
tkn = tokenizer.GetNextToken().Lower();
|
|
m_genAcMag->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// AC phase (optional)
|
|
try
|
|
{
|
|
tkn = tokenizer.GetNextToken().Lower();
|
|
m_genAcPhase->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
continue; // perhaps another directive
|
|
}
|
|
}
|
|
|
|
|
|
else if( tkn == "pulse" )
|
|
{
|
|
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPulse ) );
|
|
|
|
genericProcessing = true;
|
|
genericReqParamsCount = 2;
|
|
genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
|
|
m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
|
|
}
|
|
|
|
|
|
else if( tkn == "sin" )
|
|
{
|
|
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrSin ) );
|
|
|
|
genericProcessing = true;
|
|
genericReqParamsCount = 2;
|
|
genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
|
|
}
|
|
|
|
|
|
else if( tkn == "exp" )
|
|
{
|
|
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrExp ) );
|
|
|
|
genericProcessing = true;
|
|
genericReqParamsCount = 2;
|
|
genericControls = { m_expInit, m_expPulsed,
|
|
m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
|
|
}
|
|
|
|
|
|
else if( tkn == "pwl" )
|
|
{
|
|
m_powerNotebook->SetSelection( m_powerNotebook->FindPage( m_pwrPwl ) );
|
|
|
|
try
|
|
{
|
|
while( tokenizer.HasMoreTokens() )
|
|
{
|
|
tkn = tokenizer.GetNextToken();
|
|
SPICE_VALUE time( tkn );
|
|
|
|
tkn = tokenizer.GetNextToken();
|
|
SPICE_VALUE value( tkn );
|
|
|
|
addPwlValue( time.ToSpiceString(), value.ToSpiceString() );
|
|
}
|
|
}
|
|
catch( ... )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
else
|
|
{
|
|
// Unhandled power source type
|
|
wxASSERT_MSG( false, "Unhandled power source type" );
|
|
return false;
|
|
}
|
|
|
|
|
|
if( genericProcessing )
|
|
{
|
|
try
|
|
{
|
|
for( unsigned int i = 0; i < genericControls.size(); ++i )
|
|
{
|
|
// If there are no more tokens, let's check if we got at least required fields
|
|
if( !tokenizer.HasMoreTokens() )
|
|
return ( i >= genericReqParamsCount );
|
|
|
|
tkn = tokenizer.GetNextToken().Lower();
|
|
genericControls[i]->SetValue( SPICE_VALUE( tkn ).ToSpiceString() );
|
|
}
|
|
}
|
|
catch( ... )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get the next token now, so if any of the branches catches an exception, try to
|
|
// process it in another branch
|
|
tkn = tokenizer.GetNextToken().Lower();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DIALOG_SPICE_MODEL::generatePowerSource( wxString& aTarget ) const
|
|
{
|
|
wxString acdc, trans;
|
|
wxWindow* page = m_powerNotebook->GetCurrentPage();
|
|
bool useTrans = true; // shall we use the transient command part?
|
|
|
|
// Variables for generic processing
|
|
bool genericProcessing = false;
|
|
unsigned int genericReqParamsCount = 0;
|
|
std::vector<wxTextCtrl*> genericControls;
|
|
|
|
/// DC / AC section
|
|
// If SPICE_VALUE can be properly constructed, then it is a valid value
|
|
try
|
|
{
|
|
if( !empty( m_genDc ) )
|
|
acdc += wxString::Format( "dc %s ", SPICE_VALUE( m_genDc->GetValue() ).ToSpiceString() );
|
|
}
|
|
catch( ... )
|
|
{
|
|
DisplayError( NULL, wxT( "Invalid DC value" ) );
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
if( !empty( m_genAcMag ) )
|
|
{
|
|
acdc += wxString::Format( "ac %s ", SPICE_VALUE( m_genAcMag->GetValue() ).ToSpiceString() );
|
|
|
|
if( !empty( m_genAcPhase ) )
|
|
acdc += wxString::Format( "%s ", SPICE_VALUE( m_genAcPhase->GetValue() ).ToSpiceString() );
|
|
}
|
|
}
|
|
catch( ... )
|
|
{
|
|
DisplayError( NULL, wxT( "Invalid AC magnitude or phase" ) );
|
|
return false;
|
|
}
|
|
|
|
/// Transient section
|
|
if( page == m_pwrPulse )
|
|
{
|
|
if( !m_pwrPulse->Validate() )
|
|
return false;
|
|
|
|
genericProcessing = true;
|
|
trans += "pulse";
|
|
genericReqParamsCount = 2;
|
|
genericControls = { m_pulseInit, m_pulseNominal, m_pulseDelay,
|
|
m_pulseRise, m_pulseFall, m_pulseWidth, m_pulsePeriod };
|
|
}
|
|
|
|
|
|
else if( page == m_pwrSin )
|
|
{
|
|
if( !m_pwrSin->Validate() )
|
|
return false;
|
|
|
|
genericProcessing = true;
|
|
trans += "sin";
|
|
genericReqParamsCount = 2;
|
|
genericControls = { m_sinOffset, m_sinAmplitude, m_sinFreq, m_sinDelay, m_sinDampFactor };
|
|
}
|
|
|
|
|
|
else if( page == m_pwrExp )
|
|
{
|
|
if( !m_pwrExp->Validate() )
|
|
return false;
|
|
|
|
genericProcessing = true;
|
|
trans += "exp";
|
|
genericReqParamsCount = 2;
|
|
genericControls = { m_expInit, m_expPulsed,
|
|
m_expRiseDelay, m_expRiseConst, m_expFallDelay, m_expFallConst };
|
|
}
|
|
|
|
|
|
else if( page == m_pwrPwl )
|
|
{
|
|
if( m_pwlValList->GetItemCount() > 0 )
|
|
{
|
|
trans += "pwl(";
|
|
|
|
for( int i = 0; i < m_pwlValList->GetItemCount(); ++i )
|
|
{
|
|
trans += wxString::Format( "%s %s ", m_pwlValList->GetItemText( i, m_pwlTimeCol ),
|
|
m_pwlValList->GetItemText( i, m_pwlValueCol ) );
|
|
}
|
|
|
|
trans.Trim();
|
|
trans += ")";
|
|
}
|
|
}
|
|
|
|
if( genericProcessing )
|
|
{
|
|
trans += "(";
|
|
|
|
auto first_empty = std::find_if( genericControls.begin(), genericControls.end(), empty );
|
|
auto first_not_empty = std::find_if( genericControls.begin(), genericControls.end(),
|
|
[]( const wxTextCtrl* c ){ return !empty( c ); } );
|
|
|
|
if( std::distance( first_not_empty, genericControls.end() ) == 0 )
|
|
{
|
|
// all empty
|
|
useTrans = false;
|
|
}
|
|
else if( std::distance( genericControls.begin(), first_empty ) < (int)genericReqParamsCount )
|
|
{
|
|
DisplayError( nullptr,
|
|
wxString::Format( wxT( "You need to specify at least the "
|
|
"first %d parameters for the transient source" ),
|
|
genericReqParamsCount ) );
|
|
|
|
return false;
|
|
}
|
|
else if( std::find_if_not( first_empty, genericControls.end(),
|
|
empty ) != genericControls.end() )
|
|
{
|
|
DisplayError( nullptr, wxT( "You cannot leave interleaved empty fields "
|
|
"when defining a transient source" ) );
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
std::for_each( genericControls.begin(), first_empty,
|
|
[&trans] ( wxTextCtrl* ctrl ) {
|
|
trans += wxString::Format( "%s ", ctrl->GetValue() );
|
|
} );
|
|
}
|
|
|
|
trans.Trim();
|
|
trans += ")";
|
|
}
|
|
|
|
aTarget = acdc;
|
|
|
|
if( useTrans )
|
|
aTarget += trans;
|
|
|
|
// Remove whitespaces from left and right side
|
|
aTarget.Trim( false );
|
|
aTarget.Trim( true );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath )
|
|
{
|
|
wxString curModel = m_modelName->GetValue();
|
|
m_models.clear();
|
|
wxFileName filePath( aFilePath );
|
|
bool in_subckt = false; // flag indicating that the parser is inside a .subckt section
|
|
|
|
// Look for the file in the project path
|
|
if( !filePath.Exists() )
|
|
{
|
|
filePath.SetPath( Prj().GetProjectPath() + filePath.GetPath() );
|
|
|
|
if( !filePath.Exists() )
|
|
return;
|
|
}
|
|
|
|
// Display the library contents
|
|
wxWindowUpdateLocker updateLock( this );
|
|
m_libraryContents->Clear();
|
|
wxTextFile file;
|
|
file.Open( filePath.GetFullPath() );
|
|
int line_nr = 0;
|
|
|
|
// Stores the libray content. It will be displayed after reading the full library
|
|
wxString fullText;
|
|
|
|
// Process the file, looking for components
|
|
while( !file.Eof() )
|
|
{
|
|
const wxString& line = line_nr == 0 ? file.GetFirstLine() : file.GetNextLine();
|
|
fullText << line << '\n';
|
|
|
|
wxStringTokenizer tokenizer( line );
|
|
|
|
while( tokenizer.HasMoreTokens() )
|
|
{
|
|
wxString token = tokenizer.GetNextToken().Lower();
|
|
|
|
// some subckts contain .model clauses inside,
|
|
// skip them as they are a part of the subckt, not another model
|
|
if( token == ".model" && !in_subckt )
|
|
{
|
|
wxString name = tokenizer.GetNextToken();
|
|
|
|
if( name.IsEmpty() )
|
|
break;
|
|
|
|
token = tokenizer.GetNextToken();
|
|
SPICE_PRIMITIVE type = MODEL::parseModelType( token );
|
|
|
|
if( type != SP_UNKNOWN )
|
|
m_models.emplace( name, MODEL( line_nr, type ) );
|
|
}
|
|
|
|
else if( token == ".subckt" )
|
|
{
|
|
wxASSERT( !in_subckt );
|
|
in_subckt = true;
|
|
|
|
wxString name = tokenizer.GetNextToken();
|
|
|
|
if( name.IsEmpty() )
|
|
break;
|
|
|
|
m_models.emplace( name, MODEL( line_nr, SP_SUBCKT ) );
|
|
}
|
|
|
|
else if( token == ".ends" )
|
|
{
|
|
wxASSERT( in_subckt );
|
|
in_subckt = false;
|
|
}
|
|
}
|
|
|
|
++line_nr;
|
|
}
|
|
|
|
// display the full library content:
|
|
m_libraryContents->AppendText( fullText );
|
|
|
|
wxArrayString modelsList;
|
|
|
|
// Refresh the model name combobox values
|
|
m_modelName->Clear();
|
|
|
|
for( const auto& model : m_models )
|
|
{
|
|
m_modelName->Append( model.first );
|
|
modelsList.Add( model.first );
|
|
}
|
|
|
|
m_modelName->AutoComplete( modelsList );
|
|
|
|
// Restore the previous value or if there is none - pick the first one from the loaded library
|
|
if( !curModel.IsEmpty() )
|
|
m_modelName->SetValue( curModel );
|
|
else if( m_modelName->GetCount() > 0 )
|
|
m_modelName->SetSelection( 0 );
|
|
}
|
|
|
|
|
|
SCH_FIELD& DIALOG_SPICE_MODEL::getSchField( int aFieldType )
|
|
{
|
|
const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
|
|
|
|
auto fieldIt = std::find_if( m_schfields->begin(), m_schfields->end(), [&]( const SCH_FIELD& f ) {
|
|
return f.GetName() == spiceField;
|
|
} );
|
|
|
|
// Found one, so return it
|
|
if( fieldIt != m_schfields->end() )
|
|
return *fieldIt;
|
|
|
|
// Create a new field with requested name
|
|
m_schfields->emplace_back( wxPoint(), m_schfields->size(), &m_component, spiceField );
|
|
return m_schfields->back();
|
|
}
|
|
|
|
|
|
LIB_FIELD& DIALOG_SPICE_MODEL::getLibField( int aFieldType )
|
|
{
|
|
const wxString& spiceField = NETLIST_EXPORTER_PSPICE::GetSpiceFieldName( (SPICE_FIELD) aFieldType );
|
|
|
|
auto fieldIt = std::find_if( m_libfields->begin(), m_libfields->end(), [&]( const LIB_FIELD& f ) {
|
|
return f.GetName() == spiceField;
|
|
} );
|
|
|
|
// Found one, so return it
|
|
if( fieldIt != m_libfields->end() )
|
|
return *fieldIt;
|
|
|
|
// Create a new field with requested name
|
|
LIB_FIELD new_field( m_libfields->size() );
|
|
m_libfields->front().Copy( &new_field );
|
|
new_field.SetName( spiceField );
|
|
|
|
m_libfields->push_back( new_field );
|
|
return m_libfields->back();
|
|
}
|
|
|
|
|
|
bool DIALOG_SPICE_MODEL::addPwlValue( const wxString& aTime, const wxString& aValue )
|
|
{
|
|
// TODO execute validators
|
|
if( aTime.IsEmpty() || aValue.IsEmpty() )
|
|
return false;
|
|
|
|
long idx = m_pwlValList->InsertItem( m_pwlTimeCol, aTime );
|
|
m_pwlValList->SetItem( idx, m_pwlValueCol, aValue );
|
|
|
|
// There is no wxString::ToFloat, but we need to guarantee it fits in 4 bytes
|
|
double timeD;
|
|
float timeF;
|
|
m_pwlTime->GetValue().ToDouble( &timeD );
|
|
timeF = timeD;
|
|
long data;
|
|
std::memcpy( &data, &timeF, sizeof( timeF ) );
|
|
|
|
// Store the time value, so the entries can be sorted
|
|
m_pwlValList->SetItemData( idx, data );
|
|
|
|
// Sort items by timestamp
|
|
m_pwlValList->SortItems( comparePwlValues, -1 );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void DIALOG_SPICE_MODEL::onSelectLibrary( wxCommandEvent& event )
|
|
{
|
|
wxString searchPath = wxFileName( m_modelLibrary->GetValue() ).GetPath();
|
|
|
|
if( searchPath.IsEmpty() )
|
|
searchPath = Prj().GetProjectPath();
|
|
|
|
wxString wildcards = SpiceLibraryFileWildcard() + "|" + AllFilesWildcard();
|
|
wxFileDialog openDlg( this, _( "Select library" ), searchPath, "", wildcards,
|
|
wxFD_OPEN | wxFD_FILE_MUST_EXIST );
|
|
|
|
if( openDlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
wxFileName libPath( openDlg.GetPath() );
|
|
|
|
// Try to convert the path to relative to project
|
|
if( libPath.MakeRelativeTo( Prj().GetProjectPath() ) && !libPath.GetFullPath().StartsWith( ".." ) )
|
|
m_modelLibrary->SetValue( libPath.GetFullPath() );
|
|
else
|
|
m_modelLibrary->SetValue( openDlg.GetPath() );
|
|
|
|
loadLibrary( openDlg.GetPath() );
|
|
m_modelName->Popup();
|
|
}
|
|
|
|
|
|
void DIALOG_SPICE_MODEL::onModelSelected( wxCommandEvent& event )
|
|
{
|
|
// autoselect the model type
|
|
auto it = m_models.find( m_modelName->GetValue() );
|
|
|
|
if( it != m_models.end() )
|
|
{
|
|
m_modelType->SetSelection( getModelTypeIdx( it->second.model ) );
|
|
|
|
// scroll to the bottom, so the model definition is shown in the first line
|
|
m_libraryContents->ShowPosition(
|
|
m_libraryContents->XYToPosition( 0, m_libraryContents->GetNumberOfLines() ) );
|
|
m_libraryContents->ShowPosition( m_libraryContents->XYToPosition( 0, it->second.line ) );
|
|
}
|
|
else
|
|
{
|
|
m_libraryContents->ShowPosition( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SPICE_MODEL::onPwlAdd( wxCommandEvent& event )
|
|
{
|
|
addPwlValue( m_pwlTime->GetValue(), m_pwlValue->GetValue() );
|
|
}
|
|
|
|
|
|
void DIALOG_SPICE_MODEL::onPwlRemove( wxCommandEvent& event )
|
|
{
|
|
long idx = m_pwlValList->GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED );
|
|
m_pwlValList->DeleteItem( idx );
|
|
}
|
|
|
|
|
|
SPICE_PRIMITIVE DIALOG_SPICE_MODEL::MODEL::parseModelType( const wxString& aValue )
|
|
{
|
|
wxCHECK( !aValue.IsEmpty(), SP_UNKNOWN );
|
|
const wxString val( aValue.Lower() );
|
|
|
|
for( const auto& model : modelTypes )
|
|
{
|
|
for( const auto& keyword : model.keywords )
|
|
{
|
|
if( val.StartsWith( keyword ) )
|
|
return model.type;
|
|
}
|
|
}
|
|
|
|
return SP_UNKNOWN;
|
|
}
|