From ce84a48037b1f04ea331764ac1fd6b5a86c20b22 Mon Sep 17 00:00:00 2001 From: Mikolaj Wielgus Date: Fri, 1 Apr 2022 06:30:50 +0200 Subject: [PATCH] Sim: Implement loading Spice library files Implement parsing and loading Spice libraries into KiCad. This is done without any involvement of Ngspice -- we create our own in-tree parser using PEGTL -- because Ngspice doesn't offer any intermediate output we could plug ourselves into. We don't parse everything -- just the library content, so this won't be that much effort. We implement some basic Spice code preview to give the user a hint what Spice code eir model will correspond to. --- eeschema/CMakeLists.txt | 2 + eeschema/dialogs/dialog_spice_model.cpp | 419 ++++++--- eeschema/dialogs/dialog_spice_model.h | 37 +- eeschema/dialogs/dialog_spice_model_base.cpp | 68 +- eeschema/dialogs/dialog_spice_model_base.fbp | 272 ++++-- eeschema/dialogs/dialog_spice_model_base.h | 34 +- eeschema/lib_symbol.cpp | 1 + eeschema/sim/sim_library.cpp | 43 + eeschema/sim/sim_library.h | 56 ++ eeschema/sim/sim_library_spice.cpp | 111 +++ eeschema/sim/sim_library_spice.h | 40 + eeschema/sim/sim_model.cpp | 941 ++++++++++++++----- eeschema/sim/sim_model.h | 163 +++- eeschema/sim/sim_model_behavioral.cpp | 70 +- eeschema/sim/sim_model_behavioral.h | 11 +- eeschema/sim/sim_model_codemodel.cpp | 17 +- eeschema/sim/sim_model_codemodel.h | 5 +- eeschema/sim/sim_model_ideal.cpp | 40 +- eeschema/sim/sim_model_ideal.h | 11 +- eeschema/sim/sim_model_ngspice.cpp | 33 +- eeschema/sim/sim_model_ngspice.h | 9 +- eeschema/sim/sim_model_rawspice.cpp | 50 +- eeschema/sim/sim_model_rawspice.h | 10 +- eeschema/sim/sim_model_source.cpp | 65 +- eeschema/sim/sim_model_source.h | 15 +- eeschema/sim/sim_model_subcircuit.cpp | 18 +- eeschema/sim/sim_model_subcircuit.h | 5 +- eeschema/sim/sim_property.cpp | 32 +- eeschema/sim/sim_property.h | 25 +- eeschema/sim/sim_value.cpp | 64 +- eeschema/sim/sim_value.h | 37 +- eeschema/sim/spice_grammar.h | 156 +++ 32 files changed, 2135 insertions(+), 725 deletions(-) create mode 100644 eeschema/sim/sim_library.cpp create mode 100644 eeschema/sim/sim_library.h create mode 100644 eeschema/sim/sim_library_spice.cpp create mode 100644 eeschema/sim/sim_library_spice.h create mode 100644 eeschema/sim/spice_grammar.h diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 6e46e7a5d6..b5f37b089a 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -320,6 +320,8 @@ if( KICAD_SPICE ) sim/sim_plot_panel.cpp sim/sim_property.cpp sim/sim_workbook.cpp + sim/sim_library.cpp + sim/sim_library_spice.cpp sim/sim_model.cpp sim/sim_model_behavioral.cpp sim/sim_model_codemodel.cpp diff --git a/eeschema/dialogs/dialog_spice_model.cpp b/eeschema/dialogs/dialog_spice_model.cpp index d42f667c30..0f3a86ccd6 100644 --- a/eeschema/dialogs/dialog_spice_model.cpp +++ b/eeschema/dialogs/dialog_spice_model.cpp @@ -24,10 +24,12 @@ #include #include +#include #include #include #include #include +#include using TYPE = SIM_VALUE_BASE::TYPE; using CATEGORY = SIM_MODEL::PARAM::CATEGORY; @@ -42,26 +44,18 @@ DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbo : DIALOG_SPICE_MODEL_BASE( aParent ), m_symbol( aSymbol ), m_fields( aFields ), + m_library( std::make_shared() ), + m_prevModel( nullptr ), m_firstCategory( nullptr ) { try { - SIM_MODEL::TYPE typeFromFields = SIM_MODEL::ReadTypeFromFields( aFields ); - for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) { - if( type == typeFromFields ) - { - m_models.push_back( SIM_MODEL::Create( type, m_symbol.GetAllPins().size(), - &aFields ) ); - m_curModelType = type; - } - else - m_models.push_back( SIM_MODEL::Create( type, m_symbol.GetAllPins().size() ) ); + m_models.push_back( SIM_MODEL::Create( type, m_symbol.GetAllPins().size() ) ); SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( type ).deviceType; - - // By default choose the first model type of each device type. + if( !m_curModelTypeOfDeviceType.count( deviceType ) ) m_curModelTypeOfDeviceType[deviceType] = type; } @@ -114,30 +108,40 @@ DIALOG_SPICE_MODEL::DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbo } -template -bool DIALOG_SPICE_MODEL::TransferDataFromWindow() -{ - if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() ) - return false; - - getCurModel().WriteFields( m_fields ); - - return true; -} - - template bool DIALOG_SPICE_MODEL::TransferDataToWindow() { - try + wxString libraryFilename = SIM_MODEL::GetFieldValue( &m_fields, LIBRARY_FIELD ); + + if( !libraryFilename.IsEmpty() ) { - m_models.at( static_cast( SIM_MODEL::ReadTypeFromFields( m_fields ) ) ) - = SIM_MODEL::Create( m_symbol.GetAllPins().size(), m_fields ); + // The model is sourced from a library, optionally with instance overrides. + loadLibrary( libraryFilename ); + + // Must be set before curModel() is used since the latter checks the combobox value. + m_modelNameCombobox->SetStringSelection( SIM_MODEL::GetFieldValue( &m_fields, NAME_FIELD ) ); + + curModel().ReadDataFields( m_symbol.GetAllPins().size(), &m_fields ); + + m_overrideCheckbox->SetValue( curModel().HasNonPrincipalOverrides() ); } - catch( KI_PARAM_ERROR& e ) + else { - DisplayErrorMessage( this, e.What() ); - return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow(); + // The model is sourced from the instance. + SIM_MODEL::TYPE type = SIM_MODEL::ReadTypeFromFields( m_fields ); + + try + { + m_models.at( static_cast( SIM_MODEL::ReadTypeFromFields( m_fields ) ) ) + = SIM_MODEL::Create( m_symbol.GetAllPins().size(), m_fields ); + } + catch( KI_PARAM_ERROR& e ) + { + DisplayErrorMessage( this, e.What() ); + return DIALOG_SPICE_MODEL_BASE::TransferDataToWindow(); + } + + m_curModelType = type; } updateWidgets(); @@ -146,94 +150,145 @@ bool DIALOG_SPICE_MODEL::TransferDataToWindow() } +template +bool DIALOG_SPICE_MODEL::TransferDataFromWindow() +{ + if( !DIALOG_SPICE_MODEL_BASE::TransferDataFromWindow() ) + return false; + + if( m_useLibraryModelRadioButton->GetValue() ) + { + SIM_MODEL::SetFieldValue( m_fields, NAME_FIELD, m_modelNameCombobox->GetValue() ); + SIM_MODEL::SetFieldValue( m_fields, LIBRARY_FIELD, m_library->GetFilename() ); + } + + curModel().WriteFields( m_fields ); + + return true; +} + + template void DIALOG_SPICE_MODEL::updateWidgets() { updateModelParamsTab(); updateModelCodeTab(); updatePinAssignmentsTab(); + + m_prevModel = &curModel(); } template void DIALOG_SPICE_MODEL::updateModelParamsTab() { - SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( m_curModelType ).deviceType; - - m_deviceTypeChoice->SetSelection( static_cast( deviceType ) ); - - m_typeChoice->Clear(); - - for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) + if( &curModel() != m_prevModel ) { - if( SIM_MODEL::TypeInfo( type ).deviceType == deviceType ) + SIM_MODEL::DEVICE_TYPE deviceType = SIM_MODEL::TypeInfo( curModel().GetType() ).deviceType; + m_deviceTypeChoice->SetSelection( static_cast( deviceType ) ); + + m_typeChoice->Clear(); + + for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() ) { - wxString description = SIM_MODEL::TypeInfo( type ).description; + if( SIM_MODEL::TypeInfo( type ).deviceType == deviceType ) + { + wxString description = SIM_MODEL::TypeInfo( type ).description; - if( !description.IsEmpty() ) - m_typeChoice->Append( description ); + if( !description.IsEmpty() ) + m_typeChoice->Append( description ); - if( type == m_curModelType ) - m_typeChoice->SetSelection( m_typeChoice->GetCount() - 1 ); + if( type == curModel().GetType() ) + m_typeChoice->SetSelection( m_typeChoice->GetCount() - 1 ); + } } + + + // This wxPropertyGridManager column and header stuff has to be here because it segfaults in + // the constructor. + + m_paramGridMgr->SetColumnCount( static_cast( PARAM_COLUMN::END_ ) ); + + m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::UNIT ), "Unit" ); + m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::DEFAULT ), "Default" ); + m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::TYPE ), "Type" ); + + m_paramGridMgr->ShowHeader(); + + + m_paramGrid->Clear(); + + m_firstCategory = m_paramGrid->Append( new wxPropertyCategory( "DC" ) ); + m_paramGrid->HideProperty( "DC" ); + + m_paramGrid->Append( new wxPropertyCategory( "Temperature" ) ); + m_paramGrid->HideProperty( "Temperature" ); + + m_paramGrid->Append( new wxPropertyCategory( "Noise" ) ); + m_paramGrid->HideProperty( "Noise" ); + + m_paramGrid->Append( new wxPropertyCategory( "Distributed Quantities" ) ); + m_paramGrid->HideProperty( "Distributed Quantities" ); + + m_paramGrid->Append( new wxPropertyCategory( "Geometry" ) ); + m_paramGrid->HideProperty( "Geometry" ); + + m_paramGrid->Append( new wxPropertyCategory( "Limiting Values" ) ); + m_paramGrid->HideProperty( "Limiting Values" ); + + m_paramGrid->Append( new wxPropertyCategory( "Advanced" ) ); + m_paramGrid->HideProperty( "Advanced" ); + + m_paramGrid->Append( new wxPropertyCategory( "Flags" ) ); + m_paramGrid->HideProperty( "Flags" ); + + for( int i = 0; i < curModel().GetParamCount(); ++i ) + addParamPropertyIfRelevant( i ); + + m_paramGrid->CollapseAll(); } + // Either enable all properties or disable all except the principal ones. + for( wxPropertyGridIterator it = m_paramGrid->GetIterator(); !it.AtEnd(); ++it ) + { + SIM_PROPERTY* prop = dynamic_cast( *it ); - // This wxPropertyGridManager stuff has to be here because it segfaults in the constructor. + if( !prop ) // Not all properties are SIM_PROPERTY yet. TODO. + continue; - m_paramGridMgr->SetColumnCount( static_cast( PARAM_COLUMN::END_ ) ); + // Model values other than the currently edited value may have changed. Update them. + // This feature is called "autofill" and present only in certain models. Don't do it for + // models that don't have it for performance reasons. + if( curModel().HasAutofill() ) + prop->SetValueFromString( prop->GetParam().value->ToString() ); - m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::UNIT ), "Unit" ); - m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::DEFAULT ), "Default" ); - m_paramGridMgr->SetColumnTitle( static_cast( PARAM_COLUMN::TYPE ), "Type" ); - - m_paramGridMgr->ShowHeader(); - - - m_paramGrid->Clear(); - - m_firstCategory = m_paramGrid->Append( new wxPropertyCategory( "DC" ) ); - m_paramGrid->HideProperty( "DC" ); - - m_paramGrid->Append( new wxPropertyCategory( "Temperature" ) ); - m_paramGrid->HideProperty( "Temperature" ); - - m_paramGrid->Append( new wxPropertyCategory( "Noise" ) ); - m_paramGrid->HideProperty( "Noise" ); - - m_paramGrid->Append( new wxPropertyCategory( "Distributed Quantities" ) ); - m_paramGrid->HideProperty( "Distributed Quantities" ); - - m_paramGrid->Append( new wxPropertyCategory( "Geometry" ) ); - m_paramGrid->HideProperty( "Geometry" ); - - m_paramGrid->Append( new wxPropertyCategory( "Limiting Values" ) ); - m_paramGrid->HideProperty( "Limiting Values" ); - - m_paramGrid->Append( new wxPropertyCategory( "Advanced" ) ); - m_paramGrid->HideProperty( "Advanced" ); - - m_paramGrid->Append( new wxPropertyCategory( "Flags" ) ); - m_paramGrid->HideProperty( "Flags" ); - - for( const SIM_MODEL::PARAM& param : getCurModel().Params() ) - addParamPropertyIfRelevant( param ); - - m_paramGrid->CollapseAll(); + // Most of the values are disabled when the override checkbox is unchecked. + prop->Enable( m_useInstanceModelRadioButton->GetValue() + || prop->GetParam().info.category == CATEGORY::PRINCIPAL + || m_overrideCheckbox->GetValue() ); + } } template void DIALOG_SPICE_MODEL::updateModelCodeTab() { + wxString modelName = m_modelNameCombobox->GetStringSelection(); + + if( m_useInstanceModelRadioButton->GetValue() || modelName.IsEmpty() ) + modelName = m_fields.at( REFERENCE_FIELD ).GetText(); + + m_codePreview->SetText( curModel().GenerateSpicePreview( modelName ) ); } template void DIALOG_SPICE_MODEL::updatePinAssignmentsTab() { - m_pinAssignmentsGrid->ClearRows(); + if( &curModel() == m_prevModel ) + return; + m_pinAssignmentsGrid->ClearRows(); std::vector pinList = m_symbol.GetAllPins(); m_pinAssignmentsGrid->AppendRows( static_cast( pinList.size() ) ); @@ -254,9 +309,9 @@ void DIALOG_SPICE_MODEL::updatePinAssignmentsTab() "Not Connected" ); } - for( unsigned int i = 0; i < getCurModel().Pins().size(); ++i ) + for( int i = 0; i < curModel().GetPinCount(); ++i ) { - int symbolPinNumber = getCurModel().Pins().at( i ).symbolPinNumber; + int symbolPinNumber = curModel().GetPin( i ).symbolPinNumber; if( symbolPinNumber == SIM_MODEL::PIN::NOT_CONNECTED ) continue; @@ -282,9 +337,9 @@ void DIALOG_SPICE_MODEL::updatePinAssignmentsGridEditors() wxString modelPinChoicesString = ""; bool isFirst = true; - for( unsigned int i = 0; i < getCurModel().Pins().size(); ++i ) + for( int i = 0; i < curModel().GetPinCount(); ++i ) { - const SIM_MODEL::PIN& modelPin = getCurModel().Pins().at( i ); + const SIM_MODEL::PIN& modelPin = curModel().GetPin( i ); int modelPinNumber = static_cast( i + 1 ); if( modelPin.symbolPinNumber != SIM_MODEL::PIN::NOT_CONNECTED ) @@ -329,62 +384,78 @@ void DIALOG_SPICE_MODEL::updatePinAssignmentsGridEditors() template -void DIALOG_SPICE_MODEL::addParamPropertyIfRelevant( const SIM_MODEL::PARAM& aParam ) +void DIALOG_SPICE_MODEL::loadLibrary( const wxString& aFilePath ) { - if( aParam.info.dir == SIM_MODEL::PARAM::DIR::OUT ) + m_library->ReadFile( aFilePath ); + m_libraryFilenameInput->SetValue( aFilePath ); + + m_libraryModels.clear(); + for( const SIM_MODEL& baseModel : m_library->GetModels() ) + m_libraryModels.push_back( SIM_MODEL::Create( baseModel ) ); + + m_modelNameCombobox->Clear(); + for( const wxString& name : m_library->GetModelNames() ) + m_modelNameCombobox->Append( name ); + + m_useLibraryModelRadioButton->SetValue( true ); +} + + +template +void DIALOG_SPICE_MODEL::addParamPropertyIfRelevant( int aParamIndex ) +{ + if( curModel().GetParam( aParamIndex ).info.dir == SIM_MODEL::PARAM::DIR::OUT ) return; - switch( aParam.info.category ) + switch( curModel().GetParam( aParamIndex ).info.category ) { case CATEGORY::DC: m_paramGrid->HideProperty( "DC", false ); - m_paramGrid->AppendIn( "DC", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "DC", newParamProperty( aParamIndex ) ); break; case CATEGORY::CAPACITANCE: m_paramGrid->HideProperty( "Capacitance", false ); - m_paramGrid->AppendIn( "Capacitance", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Capacitance", newParamProperty( aParamIndex ) ); break; case CATEGORY::TEMPERATURE: m_paramGrid->HideProperty( "Temperature", false ); - m_paramGrid->AppendIn( "Temperature", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Temperature", newParamProperty( aParamIndex ) ); break; case CATEGORY::NOISE: m_paramGrid->HideProperty( "Noise", false ); - m_paramGrid->AppendIn( "Noise", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Noise", newParamProperty( aParamIndex ) ); break; case CATEGORY::DISTRIBUTED_QUANTITIES: m_paramGrid->HideProperty( "Distributed Quantities", false ); - m_paramGrid->AppendIn( "Distributed Quantities", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Distributed Quantities", newParamProperty( aParamIndex ) ); break; case CATEGORY::GEOMETRY: m_paramGrid->HideProperty( "Geometry", false ); - m_paramGrid->AppendIn( "Geometry", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Geometry", newParamProperty( aParamIndex ) ); break; case CATEGORY::LIMITING_VALUES: m_paramGrid->HideProperty( "Limiting Values", false ); - m_paramGrid->AppendIn( "Limiting Values", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Limiting Values", newParamProperty( aParamIndex ) ); break; case CATEGORY::ADVANCED: m_paramGrid->HideProperty( "Advanced", false ); - m_paramGrid->AppendIn( "Advanced", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Advanced", newParamProperty( aParamIndex ) ); break; case CATEGORY::FLAGS: m_paramGrid->HideProperty( "Flags", false ); - m_paramGrid->AppendIn( "Flags", newParamProperty( aParam ) ); + m_paramGrid->AppendIn( "Flags", newParamProperty( aParamIndex ) ); break; default: - //m_paramGrid->AppendIn( nullptr, newParamProperty( aParam ) ); - m_paramGrid->Insert( m_firstCategory, newParamProperty( aParam ) ); - //m_paramGrid->Append( newParamProperty( aParam ) ); + m_paramGrid->Insert( m_firstCategory, newParamProperty( aParamIndex ) ); break; case CATEGORY::INITIAL_CONDITIONS: @@ -394,46 +465,47 @@ void DIALOG_SPICE_MODEL::addParamPropertyIfRelevant( const SIM_MODEL::PARAM& } template -wxPGProperty* DIALOG_SPICE_MODEL::newParamProperty( const SIM_MODEL::PARAM& aParam ) const +wxPGProperty* DIALOG_SPICE_MODEL::newParamProperty( int aParamIndex ) const { + const SIM_MODEL::PARAM& param = curModel().GetParam( aParamIndex ); wxString paramDescription = wxString::Format( "%s (%s)", - aParam.info.description, - aParam.info.name ); + param.info.description, + param.info.name ); wxPGProperty* prop = nullptr; - switch( aParam.info.type ) + switch( param.info.type ) { case TYPE::INT: - prop = new SIM_PROPERTY( paramDescription,aParam.info.name, *aParam.value, - SIM_VALUE_BASE::TYPE::INT ); + prop = new SIM_PROPERTY( paramDescription, param.info.name, m_library, curModelSharedPtr(), + aParamIndex, SIM_VALUE_BASE::TYPE::INT ); break; case TYPE::FLOAT: - prop = new SIM_PROPERTY( paramDescription,aParam.info.name, *aParam.value, - SIM_VALUE_BASE::TYPE::FLOAT ); + prop = new SIM_PROPERTY( paramDescription, param.info.name, m_library, curModelSharedPtr(), + aParamIndex, SIM_VALUE_BASE::TYPE::FLOAT ); break; case TYPE::BOOL: - prop = new wxBoolProperty( paramDescription, aParam.info.name ); + prop = new wxBoolProperty( paramDescription, param.info.name ); prop->SetAttribute( wxPG_BOOL_USE_CHECKBOX, true ); break; default: - prop = new wxStringProperty( paramDescription, aParam.info.name ); + prop = new wxStringProperty( paramDescription, param.info.name ); break; } - prop->SetAttribute( wxPG_ATTR_UNITS, aParam.info.unit ); + prop->SetAttribute( wxPG_ATTR_UNITS, param.info.unit ); // Legacy due to the way we extracted the parameters from Ngspice. - if( aParam.isOtherVariant ) - prop->SetCell( 3, aParam.info.defaultValueOfOtherVariant ); + if( param.isOtherVariant ) + prop->SetCell( 3, param.info.defaultValueOfOtherVariant ); else - prop->SetCell( 3, aParam.info.defaultValue ); + prop->SetCell( 3, param.info.defaultValue ); wxString typeStr; - switch( aParam.info.type ) + switch( param.info.type ) { case TYPE::BOOL: typeStr = wxString( "Bool" ); break; case TYPE::INT: typeStr = wxString( "Int" ); break; @@ -448,14 +520,34 @@ wxPGProperty* DIALOG_SPICE_MODEL::newParamProperty( const SIM_MODEL::PARAM& a prop->SetCell( static_cast( PARAM_COLUMN::TYPE ), typeStr ); + if( m_useLibraryModelRadioButton->GetValue() + && !m_overrideCheckbox->GetValue() + && param.info.category != SIM_MODEL::PARAM::CATEGORY::PRINCIPAL ) + { + prop->Enable( false ); + } + return prop; } template -SIM_MODEL& DIALOG_SPICE_MODEL::getCurModel() const +SIM_MODEL& DIALOG_SPICE_MODEL::curModel() const { - return *m_models.at( static_cast( m_curModelType ) ); + return *curModelSharedPtr(); +} + + +template +std::shared_ptr DIALOG_SPICE_MODEL::curModelSharedPtr() const +{ + if( m_useLibraryModelRadioButton->GetValue() + && m_modelNameCombobox->GetSelection() != wxNOT_FOUND ) + { + return m_libraryModels.at( m_modelNameCombobox->GetSelection() ); + } + else + return m_models.at( static_cast( m_curModelType ) ); } @@ -480,7 +572,7 @@ wxString DIALOG_SPICE_MODEL::getSymbolPinString( int symbolPinNumber ) const template wxString DIALOG_SPICE_MODEL::getModelPinString( int modelPinNumber ) const { - const wxString& pinName = getCurModel().Pins().at( modelPinNumber - 1 ).name; + const wxString& pinName = curModel().GetPin( modelPinNumber - 1 ).name; LOCALE_IO toggle; @@ -509,6 +601,39 @@ int DIALOG_SPICE_MODEL::getModelPinNumber( const wxString& aModelPinString ) } +template +void DIALOG_SPICE_MODEL::onRadioButton( wxCommandEvent& aEvent ) +{ + updateWidgets(); +} + + +template +void DIALOG_SPICE_MODEL::onBrowseButtonClick( wxCommandEvent& aEvent ) +{ + wxFileDialog dlg( this, _( "Browse Models" ) ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return; + + loadLibrary( dlg.GetPath() ); +} + + +template +void DIALOG_SPICE_MODEL::onModelNameCombobox( wxCommandEvent& aEvent ) +{ + updateWidgets(); +} + + +template +void DIALOG_SPICE_MODEL::onOverrideCheckbox( wxCommandEvent& aEvent ) +{ + updateWidgets(); +} + + template void DIALOG_SPICE_MODEL::onDeviceTypeChoice( wxCommandEvent& aEvent ) { @@ -543,6 +668,13 @@ void DIALOG_SPICE_MODEL::onTypeChoice( wxCommandEvent& aEvent ) } +template +void DIALOG_SPICE_MODEL::onParamGridChanged( wxPropertyGridEvent& aEvent ) +{ + updateWidgets(); +} + + template void DIALOG_SPICE_MODEL::onPinAssignmentsGridCellChange( wxGridEvent& aEvent ) { @@ -552,11 +684,10 @@ void DIALOG_SPICE_MODEL::onPinAssignmentsGridCellChange( wxGridEvent& aEvent m_pinAssignmentsGrid->GetCellValue( aEvent.GetRow(), aEvent.GetCol() ) ); if( oldModelPinNumber != SIM_MODEL::PIN::NOT_CONNECTED ) - getCurModel().Pins().at( oldModelPinNumber - 1 ).symbolPinNumber = - SIM_MODEL::PIN::NOT_CONNECTED; + curModel().SetPinSymbolPinNumber( oldModelPinNumber - 1, SIM_MODEL::PIN::NOT_CONNECTED ); if( modelPinNumber != SIM_MODEL::PIN::NOT_CONNECTED ) - getCurModel().Pins().at( modelPinNumber - 1 ).symbolPinNumber = symbolPinNumber; + curModel().SetPinSymbolPinNumber( modelPinNumber - 1, symbolPinNumber ); updatePinAssignmentsGridEditors(); @@ -577,6 +708,48 @@ void DIALOG_SPICE_MODEL::onPinAssignmentsGridSize( wxSizeEvent& aEvent ) } +template +void DIALOG_SPICE_MODEL::onLibraryFilenameInputUpdate( wxUpdateUIEvent& aEvent ) +{ + aEvent.Enable( m_useLibraryModelRadioButton->GetValue() ); +} + + +template +void DIALOG_SPICE_MODEL::onBrowseButtonUpdate( wxUpdateUIEvent& aEvent ) +{ + aEvent.Enable( m_useLibraryModelRadioButton->GetValue() ); +} + + +template +void DIALOG_SPICE_MODEL::onModelNameComboboxUpdate( wxUpdateUIEvent& aEvent ) +{ + aEvent.Enable( m_useLibraryModelRadioButton->GetValue() ); +} + + +template +void DIALOG_SPICE_MODEL::onOverrideCheckboxUpdate( wxUpdateUIEvent& aEvent ) +{ + aEvent.Enable( m_useLibraryModelRadioButton->GetValue() ); +} + + +template +void DIALOG_SPICE_MODEL::onDeviceTypeChoiceUpdate( wxUpdateUIEvent& aEvent ) +{ + aEvent.Enable( m_useInstanceModelRadioButton->GetValue() ); +} + + +template +void DIALOG_SPICE_MODEL::onTypeChoiceUpdate( wxUpdateUIEvent& aEvent ) +{ + aEvent.Enable( m_useInstanceModelRadioButton->GetValue() ); +} + + template void DIALOG_SPICE_MODEL::onSelectionChange( wxPropertyGridEvent& aEvent ) { diff --git a/eeschema/dialogs/dialog_spice_model.h b/eeschema/dialogs/dialog_spice_model.h index 7add60b4aa..56d7d75fc7 100644 --- a/eeschema/dialogs/dialog_spice_model.h +++ b/eeschema/dialogs/dialog_spice_model.h @@ -30,6 +30,7 @@ #include #include +#include #include // Some probable wxWidgets bugs encountered when writing this class: @@ -41,44 +42,68 @@ template class DIALOG_SPICE_MODEL : public DIALOG_SPICE_MODEL_BASE { public: + static constexpr auto LIBRARY_FIELD = "Model_Library"; + static constexpr auto NAME_FIELD = "Model_Name"; + enum class PARAM_COLUMN : int { DESCRIPTION, VALUE, UNIT, DEFAULT, TYPE, END_ }; enum class PIN_COLUMN : int { SYMBOL, MODEL }; DIALOG_SPICE_MODEL( wxWindow* aParent, SCH_SYMBOL& aSymbol, std::vector& aSchFields ); private: - bool TransferDataFromWindow() override; bool TransferDataToWindow() override; + bool TransferDataFromWindow() override; void updateWidgets(); void updateModelParamsTab(); void updateModelCodeTab(); void updatePinAssignmentsTab(); void updatePinAssignmentsGridEditors(); - - void addParamPropertyIfRelevant( const SIM_MODEL::PARAM& aParam ); - wxPGProperty* newParamProperty( const SIM_MODEL::PARAM& aParam ) const; - SIM_MODEL& getCurModel() const; + void loadLibrary( const wxString& aFilePath ); + + void addParamPropertyIfRelevant( int aParamIndex ); + wxPGProperty* newParamProperty( int aParamIndex ) const; + + SIM_MODEL& curModel() const; + std::shared_ptr curModelSharedPtr() const; + wxString getSymbolPinString( int aSymbolPinNumber ) const; wxString getModelPinString( int aModelPinNumber ) const; int getModelPinNumber( const wxString& aModelPinString ) const; + void onRadioButton( wxCommandEvent& aEvent ) override; + void onBrowseButtonClick( wxCommandEvent& aEvent ) override; + void onModelNameCombobox( wxCommandEvent& aEvent ) override; + void onOverrideCheckbox( wxCommandEvent& aEvent ) override; void onDeviceTypeChoice( wxCommandEvent& aEvent ) override; void onTypeChoice( wxCommandEvent& aEvent ) override; + void onParamGridChanged( wxPropertyGridEvent& aEvent ) override; void onPinAssignmentsGridCellChange( wxGridEvent& aEvent ) override; void onPinAssignmentsGridSize( wxSizeEvent& aEvent ) override; + void onLibraryFilenameInputUpdate( wxUpdateUIEvent& aEvent ) override; + void onBrowseButtonUpdate( wxUpdateUIEvent& aEvent ) override; + void onModelNameComboboxUpdate( wxUpdateUIEvent& aEvent ) override; + void onOverrideCheckboxUpdate( wxUpdateUIEvent& aEvent ) override; + void onDeviceTypeChoiceUpdate( wxUpdateUIEvent& aEvent ) override; + void onTypeChoiceUpdate( wxUpdateUIEvent& aEvent ) override; + virtual void onSelectionChange( wxPropertyGridEvent& aEvent ); //void onPropertyChanged( wxPropertyGridEvent& aEvent ) override; + SCH_SYMBOL& m_symbol; std::vector& m_fields; - std::vector> m_models; + std::vector> m_models; std::map m_curModelTypeOfDeviceType; SIM_MODEL::TYPE m_curModelType = SIM_MODEL::TYPE::NONE; + std::shared_ptr m_library; + std::vector> m_libraryModels; + const SIM_MODEL* m_prevModel; + wxPGProperty* m_firstCategory; // Used to add principal parameters to root (any better ideas?) std::unique_ptr m_scintillaTricks; }; diff --git a/eeschema/dialogs/dialog_spice_model_base.cpp b/eeschema/dialogs/dialog_spice_model_base.cpp index 7ee8057ec3..5b222dbecb 100644 --- a/eeschema/dialogs/dialog_spice_model_base.cpp +++ b/eeschema/dialogs/dialog_spice_model_base.cpp @@ -24,31 +24,38 @@ DIALOG_SPICE_MODEL_BASE::DIALOG_SPICE_MODEL_BASE( wxWindow* parent, wxWindowID i bSizer9 = new wxBoxSizer( wxVERTICAL ); wxStaticBoxSizer* sbSizer4; - sbSizer4 = new wxStaticBoxSizer( new wxStaticBox( m_modelPanel, wxID_ANY, wxT("Properties") ), wxVERTICAL ); + sbSizer4 = new wxStaticBoxSizer( new wxStaticBox( m_modelPanel, wxID_ANY, wxT("Source") ), wxVERTICAL ); wxFlexGridSizer* fgSizer15; - fgSizer15 = new wxFlexGridSizer( 0, 3, 0, 0 ); - fgSizer15->AddGrowableCol( 1 ); + fgSizer15 = new wxFlexGridSizer( 0, 4, 0, 0 ); + fgSizer15->AddGrowableCol( 2 ); fgSizer15->SetFlexibleDirection( wxBOTH ); fgSizer15->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED ); - m_staticText122 = new wxStaticText( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Model Name:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText122->Wrap( -1 ); - fgSizer15->Add( m_staticText122, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + m_useInstanceModelRadioButton = new wxRadioButton( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Instance"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); + fgSizer15->Add( m_useInstanceModelRadioButton, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - m_modelName = new wxTextCtrl( sbSizer4->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); - fgSizer15->Add( m_modelName, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5 ); + m_useLibraryModelRadioButton = new wxRadioButton( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Library:"), wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer15->Add( m_useLibraryModelRadioButton, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - m_browseButton = new wxButton( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Browse..."), wxDefaultPosition, wxDefaultSize, 0 ); - fgSizer15->Add( m_browseButton, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + m_libraryFilenameInput = new wxTextCtrl( sbSizer4->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer15->Add( m_libraryFilenameInput, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5 ); - m_staticText124 = new wxStaticText( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Location:"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText124->Wrap( -1 ); - fgSizer15->Add( m_staticText124, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + m_browseButton = new wxBitmapButton( sbSizer4->GetStaticBox(), wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); + fgSizer15->Add( m_browseButton, 0, wxALL, 5 ); - m_staticText125 = new wxStaticText( sbSizer4->GetStaticBox(), wxID_ANY, wxT("etc/kicad-sim/diodes.lib"), wxDefaultPosition, wxDefaultSize, 0 ); - m_staticText125->Wrap( -1 ); - fgSizer15->Add( m_staticText125, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); + + fgSizer15->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_modelNameLabel = new wxStaticText( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Model:"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT ); + m_modelNameLabel->Wrap( -1 ); + fgSizer15->Add( m_modelNameLabel, 0, wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND, 5 ); + + m_modelNameCombobox = new wxComboBox( sbSizer4->GetStaticBox(), wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + fgSizer15->Add( m_modelNameCombobox, 0, wxALL|wxEXPAND, 5 ); + + m_overrideCheckbox = new wxCheckBox( sbSizer4->GetStaticBox(), wxID_ANY, wxT("Override"), wxDefaultPosition, wxDefaultSize, 0 ); + fgSizer15->Add( m_overrideCheckbox, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); sbSizer4->Add( fgSizer15, 1, wxEXPAND, 5 ); @@ -59,9 +66,6 @@ DIALOG_SPICE_MODEL_BASE::DIALOG_SPICE_MODEL_BASE( wxWindow* parent, wxWindowID i wxStaticBoxSizer* sbSizer5; sbSizer5 = new wxStaticBoxSizer( new wxStaticBox( m_modelPanel, wxID_ANY, wxT("Model") ), wxVERTICAL ); - m_checkBox2 = new wxCheckBox( sbSizer5->GetStaticBox(), wxID_ANY, wxT("Change parameters for this symbol"), wxDefaultPosition, wxDefaultSize, 0 ); - sbSizer5->Add( m_checkBox2, 0, wxALL, 5 ); - m_notebook4 = new wxNotebook( sbSizer5->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ); m_parametersPanel = new wxPanel( m_notebook4, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxBoxSizer* bSizer12; @@ -226,9 +230,20 @@ DIALOG_SPICE_MODEL_BASE::DIALOG_SPICE_MODEL_BASE( wxWindow* parent, wxWindowID i this->Centre( wxBOTH ); // Connect Events + m_useInstanceModelRadioButton->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onRadioButton ), NULL, this ); + m_useLibraryModelRadioButton->Connect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onRadioButton ), NULL, this ); + m_libraryFilenameInput->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onLibraryFilenameInputUpdate ), NULL, this ); + m_browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onBrowseButtonClick ), NULL, this ); + m_browseButton->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onBrowseButtonUpdate ), NULL, this ); + m_modelNameCombobox->Connect( wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onModelNameCombobox ), NULL, this ); + m_modelNameCombobox->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onModelNameComboboxUpdate ), NULL, this ); + m_overrideCheckbox->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onOverrideCheckbox ), NULL, this ); + m_overrideCheckbox->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onOverrideCheckboxUpdate ), NULL, this ); m_deviceTypeChoice->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onDeviceTypeChoice ), NULL, this ); + m_deviceTypeChoice->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onDeviceTypeChoiceUpdate ), NULL, this ); m_typeChoice->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onTypeChoice ), NULL, this ); - m_paramGridMgr->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( DIALOG_SPICE_MODEL_BASE::onPropertyChanged ), NULL, this ); + m_typeChoice->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onTypeChoiceUpdate ), NULL, this ); + m_paramGridMgr->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( DIALOG_SPICE_MODEL_BASE::onParamGridChanged ), NULL, this ); m_pinAssignmentsGrid->Connect( wxEVT_GRID_CELL_CHANGED, wxGridEventHandler( DIALOG_SPICE_MODEL_BASE::onPinAssignmentsGridCellChange ), NULL, this ); m_pinAssignmentsGrid->Connect( wxEVT_SIZE, wxSizeEventHandler( DIALOG_SPICE_MODEL_BASE::onPinAssignmentsGridSize ), NULL, this ); } @@ -236,9 +251,20 @@ DIALOG_SPICE_MODEL_BASE::DIALOG_SPICE_MODEL_BASE( wxWindow* parent, wxWindowID i DIALOG_SPICE_MODEL_BASE::~DIALOG_SPICE_MODEL_BASE() { // Disconnect Events + m_useInstanceModelRadioButton->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onRadioButton ), NULL, this ); + m_useLibraryModelRadioButton->Disconnect( wxEVT_COMMAND_RADIOBUTTON_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onRadioButton ), NULL, this ); + m_libraryFilenameInput->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onLibraryFilenameInputUpdate ), NULL, this ); + m_browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onBrowseButtonClick ), NULL, this ); + m_browseButton->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onBrowseButtonUpdate ), NULL, this ); + m_modelNameCombobox->Disconnect( wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onModelNameCombobox ), NULL, this ); + m_modelNameCombobox->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onModelNameComboboxUpdate ), NULL, this ); + m_overrideCheckbox->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onOverrideCheckbox ), NULL, this ); + m_overrideCheckbox->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onOverrideCheckboxUpdate ), NULL, this ); m_deviceTypeChoice->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onDeviceTypeChoice ), NULL, this ); + m_deviceTypeChoice->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onDeviceTypeChoiceUpdate ), NULL, this ); m_typeChoice->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( DIALOG_SPICE_MODEL_BASE::onTypeChoice ), NULL, this ); - m_paramGridMgr->Disconnect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( DIALOG_SPICE_MODEL_BASE::onPropertyChanged ), NULL, this ); + m_typeChoice->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( DIALOG_SPICE_MODEL_BASE::onTypeChoiceUpdate ), NULL, this ); + m_paramGridMgr->Disconnect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( DIALOG_SPICE_MODEL_BASE::onParamGridChanged ), NULL, this ); m_pinAssignmentsGrid->Disconnect( wxEVT_GRID_CELL_CHANGED, wxGridEventHandler( DIALOG_SPICE_MODEL_BASE::onPinAssignmentsGridCellChange ), NULL, this ); m_pinAssignmentsGrid->Disconnect( wxEVT_SIZE, wxSizeEventHandler( DIALOG_SPICE_MODEL_BASE::onPinAssignmentsGridSize ), NULL, this ); diff --git a/eeschema/dialogs/dialog_spice_model_base.fbp b/eeschema/dialogs/dialog_spice_model_base.fbp index a333550be9..72a86e9835 100644 --- a/eeschema/dialogs/dialog_spice_model_base.fbp +++ b/eeschema/dialogs/dialog_spice_model_base.fbp @@ -184,7 +184,7 @@ 0 wxID_ANY - Properties + Source sbSizer4 wxVERTICAL @@ -195,9 +195,9 @@ wxEXPAND 1 - 3 + 4 wxBOTH - 1 + 2 0 @@ -206,11 +206,11 @@ none 0 0 - + 5 wxALIGN_CENTER_VERTICAL|wxALL 0 - + 1 1 1 @@ -238,8 +238,7 @@ 0 0 wxID_ANY - Model Name: - 0 + Instance 0 @@ -247,7 +246,72 @@ 0 1 - m_staticText122 + m_useInstanceModelRadioButton + 1 + + + protected + 1 + + Resizable + 1 + + wxRB_GROUP + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + onRadioButton + + + + 5 + wxALIGN_CENTER_VERTICAL|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Library: + + 0 + + + 0 + + 1 + m_useLibraryModelRadioButton 1 @@ -261,10 +325,15 @@ ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + + 0 - -1 + onRadioButton @@ -307,7 +376,7 @@ 0 1 - m_modelName + m_libraryFilenameInput 1 @@ -329,13 +398,14 @@ + onLibraryFilenameInputUpdate - + 5 - wxALIGN_CENTER_VERTICAL|wxALL + wxALL 0 - + 1 1 1 @@ -369,7 +439,7 @@ 0 0 wxID_ANY - Browse... + MyButton 0 @@ -402,11 +472,23 @@ + onBrowseButtonClick + onBrowseButtonUpdate + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 5 - wxALIGN_CENTER_VERTICAL|wxALL + wxALIGN_CENTER_VERTICAL|wxALL|wxEXPAND 0 1 @@ -436,7 +518,7 @@ 0 0 wxID_ANY - Location: + Model: 0 0 @@ -445,7 +527,7 @@ 0 1 - m_staticText124 + m_modelNameLabel 1 @@ -455,7 +537,7 @@ Resizable 1 - + wxALIGN_RIGHT ; ; forward_declare 0 @@ -465,11 +547,11 @@ -1 - + 5 - wxALIGN_CENTER_VERTICAL|wxALL + wxALL|wxEXPAND 0 - + 1 1 1 @@ -483,6 +565,7 @@ 1 0 + 1 1 @@ -497,8 +580,6 @@ 0 0 wxID_ANY - etc/kicad-sim/diodes.lib - 0 0 @@ -506,7 +587,75 @@ 0 1 - m_staticText125 + m_modelNameCombobox + 1 + + + protected + 1 + + Resizable + -1 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + onModelNameCombobox + onModelNameComboboxUpdate + + + + 5 + wxALIGN_CENTER_VERTICAL|wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Override + + 0 + + + 0 + + 1 + m_overrideCheckbox 1 @@ -520,10 +669,15 @@ ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + - -1 + onOverrideCheckbox + onOverrideCheckboxUpdate @@ -542,70 +696,6 @@ wxVERTICAL 1 none - - 5 - wxALL - 0 - - 1 - 1 - 1 - 1 - - - - - - - - 1 - 0 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 1 - - 1 - - 0 - 0 - wxID_ANY - Change parameters for this symbol - - 0 - - - 0 - - 1 - m_checkBox2 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - 5 wxEXPAND | wxALL @@ -863,6 +953,7 @@ onDeviceTypeChoice + onDeviceTypeChoiceUpdate @@ -989,6 +1080,7 @@ onTypeChoice + onTypeChoiceUpdate @@ -1051,7 +1143,7 @@ - onPropertyChanged + onParamGridChanged Page diff --git a/eeschema/dialogs/dialog_spice_model_base.h b/eeschema/dialogs/dialog_spice_model_base.h index 9a618d39f0..58ab4b10e9 100644 --- a/eeschema/dialogs/dialog_spice_model_base.h +++ b/eeschema/dialogs/dialog_spice_model_base.h @@ -12,19 +12,22 @@ class WX_GRID; #include -#include +#include #include #include #include #include #include -#include +#include #include #include #include +#include +#include +#include +#include #include #include -#include #include #include #include @@ -49,12 +52,13 @@ class DIALOG_SPICE_MODEL_BASE : public wxDialog protected: wxNotebook* m_notebook; wxPanel* m_modelPanel; - wxStaticText* m_staticText122; - wxTextCtrl* m_modelName; - wxButton* m_browseButton; - wxStaticText* m_staticText124; - wxStaticText* m_staticText125; - wxCheckBox* m_checkBox2; + wxRadioButton* m_useInstanceModelRadioButton; + wxRadioButton* m_useLibraryModelRadioButton; + wxTextCtrl* m_libraryFilenameInput; + wxBitmapButton* m_browseButton; + wxStaticText* m_modelNameLabel; + wxComboBox* m_modelNameCombobox; + wxCheckBox* m_overrideCheckbox; wxNotebook* m_notebook4; wxPanel* m_parametersPanel; wxStaticText* m_staticText127; @@ -74,9 +78,19 @@ class DIALOG_SPICE_MODEL_BASE : public wxDialog wxButton* m_sdbSizer1Cancel; // Virtual event handlers, override them in your derived class + virtual void onRadioButton( wxCommandEvent& event ) { event.Skip(); } + virtual void onLibraryFilenameInputUpdate( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void onBrowseButtonClick( wxCommandEvent& event ) { event.Skip(); } + virtual void onBrowseButtonUpdate( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void onModelNameCombobox( wxCommandEvent& event ) { event.Skip(); } + virtual void onModelNameComboboxUpdate( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void onOverrideCheckbox( wxCommandEvent& event ) { event.Skip(); } + virtual void onOverrideCheckboxUpdate( wxUpdateUIEvent& event ) { event.Skip(); } virtual void onDeviceTypeChoice( wxCommandEvent& event ) { event.Skip(); } + virtual void onDeviceTypeChoiceUpdate( wxUpdateUIEvent& event ) { event.Skip(); } virtual void onTypeChoice( wxCommandEvent& event ) { event.Skip(); } - virtual void onPropertyChanged( wxPropertyGridEvent& event ) { event.Skip(); } + virtual void onTypeChoiceUpdate( wxUpdateUIEvent& event ) { event.Skip(); } + virtual void onParamGridChanged( wxPropertyGridEvent& event ) { event.Skip(); } virtual void onPinAssignmentsGridCellChange( wxGridEvent& event ) { event.Skip(); } virtual void onPinAssignmentsGridSize( wxSizeEvent& event ) { event.Skip(); } diff --git a/eeschema/lib_symbol.cpp b/eeschema/lib_symbol.cpp index 7e48061d86..afa9ca75f5 100644 --- a/eeschema/lib_symbol.cpp +++ b/eeschema/lib_symbol.cpp @@ -148,6 +148,7 @@ LIB_SYMBOL::LIB_SYMBOL( const LIB_SYMBOL& aSymbol, SYMBOL_LIB* aLibrary ) : catch( ... ) { wxFAIL_MSG( "Failed to clone LIB_ITEM." ); + return; } } diff --git a/eeschema/sim/sim_library.cpp b/eeschema/sim/sim_library.cpp new file mode 100644 index 0000000000..6176a7c65e --- /dev/null +++ b/eeschema/sim/sim_library.cpp @@ -0,0 +1,43 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + + +bool SIM_LIBRARY::ReadFile( const wxString& aFilename ) +{ + m_filename = aFilename; + return true; +} + + +std::vector> SIM_LIBRARY::GetModels() +{ + std::vector> ret; + + for( const std::unique_ptr& model : m_models ) + ret.emplace_back( *model ); + + return ret; +} diff --git a/eeschema/sim/sim_library.h b/eeschema/sim/sim_library.h new file mode 100644 index 0000000000..c130e57ae6 --- /dev/null +++ b/eeschema/sim/sim_library.h @@ -0,0 +1,56 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + */ + +#ifndef SIM_LIBRARY_H +#define SIM_LIBRARY_H + +#include + + +class SIM_LIBRARY +{ +public: + virtual ~SIM_LIBRARY() = default; + SIM_LIBRARY() = default; + + virtual bool ReadFile( const wxString& aFilename ) = 0; + virtual void WriteFile( const wxString& aFilename ) = 0; + + std::vector> GetModels(); + const std::vector& GetModelNames() { return m_modelNames; } + + wxString GetFilename() const { return m_filename; } + wxString GetErrorMessage() const { return m_errorMessage; } + +protected: + std::vector> m_models; + std::vector m_modelNames; + + wxString m_filename; + wxString m_errorMessage; +}; + + + +#endif // SIM_LIBRARY_H diff --git a/eeschema/sim/sim_library_spice.cpp b/eeschema/sim/sim_library_spice.cpp new file mode 100644 index 0000000000..e5fd40d399 --- /dev/null +++ b/eeschema/sim/sim_library_spice.cpp @@ -0,0 +1,111 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 +#include +#include +#include +#include + + +namespace SIM_LIBRARY_SPICE_PARSER +{ + using namespace SPICE_GRAMMAR; + + struct unknownLine : until {}; + + struct library : star> {}; + + struct libraryGrammar : must {}; + + + template struct librarySelector : std::false_type {}; + + template <> struct librarySelector : std::true_type {}; + template <> struct librarySelector : std::true_type {}; + + // For debugging. + template <> struct librarySelector : std::true_type {}; +}; + + +bool SIM_LIBRARY_SPICE::ReadFile( const wxString& aFilename ) +{ + if( !SIM_LIBRARY::ReadFile( aFilename ) ) + return false; + + LOCALE_IO toggle; + + tao::pegtl::file_input in( aFilename.ToStdString() ); + std::unique_ptr root; + + try + { + root = tao::pegtl::parse_tree::parse + ( in ); + } + catch( tao::pegtl::parse_error& e ) + { + m_errorMessage = wxString::Format( "Parsing failed: %s", e.what() ); + return false; + } + + wxASSERT( root ); + + for( const auto& node : root->children ) + { + if( node->is_type() ) + { + m_models.push_back( SIM_MODEL::Create( node->string() ) ); + + if( node->children.size() != 1 ) + { + m_errorMessage = wxString::Format( + "Captured %d name tokens, expected one", node->children.size() ); + return false; + } + + m_modelNames.emplace_back( node->children.at( 0 )->string() ); + } + else if( node->is_type() ) + { + // Do nothing. + } + else + { + m_errorMessage = wxString::Format( "Unhandled parse tree node: '%s'", node->string() ); + return false; + } + } + + return true; +} + + +void SIM_LIBRARY_SPICE::WriteFile( const wxString& aFileName ) +{ + +} diff --git a/eeschema/sim/sim_library_spice.h b/eeschema/sim/sim_library_spice.h new file mode 100644 index 0000000000..bcb62a3fde --- /dev/null +++ b/eeschema/sim/sim_library_spice.h @@ -0,0 +1,40 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + */ + +#ifndef SIM_LIBRARY_SPICE_H +#define SIM_LIBRARY_SPICE_H + +#include + + +class SIM_LIBRARY_SPICE : public SIM_LIBRARY +{ + // We'll make SIM_LIBRARY have no subclasses probably. + +public: + bool ReadFile( const wxString& aFilename ) override; + void WriteFile( const wxString& aFilename ) override; +}; + +#endif // SIM_LIBRARY_SPICE_H diff --git a/eeschema/sim/sim_model.cpp b/eeschema/sim/sim_model.cpp index c7734c8664..b4300fe714 100644 --- a/eeschema/sim/sim_model.cpp +++ b/eeschema/sim/sim_model.cpp @@ -39,45 +39,14 @@ using DEVICE_TYPE = SIM_MODEL::DEVICE_TYPE; using TYPE = SIM_MODEL::TYPE; + namespace SIM_MODEL_PARSER { - using namespace SIM_VALUE_PARSER; - - struct spaces : plus {}; - - - struct pinNumber : sor> {}; - - struct pinSequence : seq, - opt>, - opt> {}; - - struct pinSequenceGrammar : must {}; - - template struct pinSequenceSelector : std::false_type {}; - template <> struct pinSequenceSelector : std::true_type {}; - - - struct param : plus {}; - - template - struct paramValuePair : seq, - one<'='>, - opt, - number> {}; - - template - struct paramValuePairs : seq, - opt, - star>>, - opt> {}; - - template - struct paramValuePairsGrammar : must, eof> {}; + using namespace SIM_MODEL_GRAMMAR; template struct paramValuePairsSelector : std::false_type {}; + template <> struct paramValuePairsSelector : std::true_type {}; template <> struct paramValuePairsSelector> : std::true_type {}; @@ -87,6 +56,28 @@ namespace SIM_MODEL_PARSER : std::true_type {}; template <> struct paramValuePairsSelector> : std::true_type {}; + + + template struct spiceUnitSelector : std::false_type {}; + + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector : std::true_type {}; + template <> struct spiceUnitSelector> + : std::true_type {}; + template <> struct spiceUnitSelector> + : std::true_type {}; + template <> struct spiceUnitSelector> + : std::true_type {}; + template <> struct spiceUnitSelector> + : std::true_type {}; + + template <> struct spiceUnitSelector : std::true_type {}; + + + template struct pinSequenceSelector : std::false_type {}; + template <> struct pinSequenceSelector : std::true_type {}; } @@ -255,7 +246,193 @@ SIM_MODEL::INFO SIM_MODEL::TypeInfo( TYPE aType ) } wxFAIL; - return { }; + return {}; +} + + +SIM_MODEL::SPICE_INFO SIM_MODEL::SpiceInfo( TYPE aType ) +{ + switch( aType ) + { + case TYPE::RESISTOR_IDEAL: return { "R", "" }; + case TYPE::RESISTOR_ADVANCED: return { "R", "R" }; + case TYPE::RESISTOR_BEHAVIORAL: return { "R", "", "", 0, true }; + + case TYPE::CAPACITOR_IDEAL: return { "C", "" }; + case TYPE::CAPACITOR_ADVANCED: return { "C", "C", }; + case TYPE::CAPACITOR_BEHAVIORAL: return { "C", "", "", 0, true }; + + case TYPE::INDUCTOR_IDEAL: return { "L", "" }; + case TYPE::INDUCTOR_ADVANCED: return { "L", "L" }; + case TYPE::INDUCTOR_BEHAVIORAL: return { "L", "", "", 0, true }; + + case TYPE::TLINE_LOSSY: return { "O", "LTRA" }; + case TYPE::TLINE_LOSSLESS: return { "T" }; + case TYPE::TLINE_UNIFORM_RC: return { "U" }; + case TYPE::TLINE_KSPICE: return { "Y" }; + + case TYPE::SWITCH_VCTRL: return { "S", "switch" }; + case TYPE::SWITCH_ICTRL: return { "W", "cswitch" }; + + case TYPE::DIODE: return { "D", "D" }; + + case TYPE::NPN_GUMMEL_POON: return { "Q", "NPN", "", 1 }; + case TYPE::PNP_GUMMEL_POON: return { "Q", "PNP", "", 1 }; + + case TYPE::NPN_VBIC: return { "Q", "NPN", "", 4 }; + case TYPE::PNP_VBIC: return { "Q", "PNP", "", 4 }; + + case TYPE::NPN_HICUM_L2: return { "Q", "NPN", "", 8 }; + case TYPE::PNP_HICUM_L2: return { "Q", "PNP", "", 8 }; + + case TYPE::NJF_SHICHMAN_HODGES: return { "M", "NJF", "", 1 }; + case TYPE::PJF_SHICHMAN_HODGES: return { "M", "PJF", "", 1 }; + case TYPE::NJF_PARKER_SKELLERN: return { "M", "NJF", "", 2 }; + case TYPE::PJF_PARKER_SKELLERN: return { "M", "PJF", "", 2 }; + + case TYPE::NMES_STATZ: return { "Z", "NMF", "", 1 }; + case TYPE::PMES_STATZ: return { "Z", "PMF", "", 1 }; + case TYPE::NMES_YTTERDAL: return { "Z", "NMF", "", 2 }; + case TYPE::PMES_YTTERDAL: return { "Z", "PMF", "", 2 }; + case TYPE::NMES_HFET1: return { "Z", "NMF", "", 5 }; + case TYPE::PMES_HFET1: return { "Z", "PMF", "", 5 }; + case TYPE::PMES_HFET2: return { "Z", "NMF", "", 6 }; + case TYPE::NMES_HFET2: return { "Z", "PMF", "", 6 }; + + case TYPE::NMOS_MOS1: return { "M", "NMOS", "", 1 }; + case TYPE::PMOS_MOS1: return { "M", "PMOS", "", 1 }; + case TYPE::NMOS_MOS2: return { "M", "NMOS", "", 2 }; + case TYPE::PMOS_MOS2: return { "M", "PMOS", "", 2 }; + case TYPE::NMOS_MOS3: return { "M", "NMOS", "", 3 }; + case TYPE::PMOS_MOS3: return { "M", "PMOS", "", 3 }; + case TYPE::NMOS_BSIM1: return { "M", "NMOS", "", 4 }; + case TYPE::PMOS_BSIM1: return { "M", "PMOS", "", 4 }; + case TYPE::NMOS_BSIM2: return { "M", "NMOS", "", 5 }; + case TYPE::PMOS_BSIM2: return { "M", "PMOS", "", 5 }; + case TYPE::NMOS_MOS6: return { "M", "NMOS", "", 6 }; + case TYPE::PMOS_MOS6: return { "M", "PMOS", "", 6 }; + case TYPE::NMOS_BSIM3: return { "M", "NMOS", "", 8 }; + case TYPE::PMOS_BSIM3: return { "M", "PMOS", "", 8 }; + case TYPE::NMOS_MOS9: return { "M", "NMOS", "", 9 }; + case TYPE::PMOS_MOS9: return { "M", "PMOS", "", 9 }; + case TYPE::NMOS_B4SOI: return { "M", "NMOS", "", 10 }; + case TYPE::PMOS_B4SOI: return { "M", "PMOS", "", 10 }; + case TYPE::NMOS_BSIM4: return { "M", "NMOS", "", 14 }; + case TYPE::PMOS_BSIM4: return { "M", "PMOS", "", 14 }; + //case TYPE::NMOS_EKV2_6: return {}; + //case TYPE::PMOS_EKV2_6: return {}; + //case TYPE::NMOS_PSP: return {}; + //case TYPE::PMOS_PSP: return {}; + case TYPE::NMOS_B3SOIFD: return { "M", "NMOS", "", 55 }; + case TYPE::PMOS_B3SOIFD: return { "M", "PMOS", "", 55 }; + case TYPE::NMOS_B3SOIDD: return { "M", "NMOS", "", 56 }; + case TYPE::PMOS_B3SOIDD: return { "M", "PMOS", "", 56 }; + case TYPE::NMOS_B3SOIPD: return { "M", "NMOS", "", 57 }; + case TYPE::PMOS_B3SOIPD: return { "M", "PMOS", "", 57 }; + //case TYPE::NMOS_STAG: return {}; + //case TYPE::PMOS_STAG: return {}; + case TYPE::NMOS_HISIM2: return { "M", "NMOS", "", 68 }; + case TYPE::PMOS_HISIM2: return { "M", "PMOS", "", 68 }; + case TYPE::NMOS_HISIM_HV1: return { "M", "NMOS", "", 73, false, "1.2.4" }; + case TYPE::PMOS_HISIM_HV1: return { "M", "PMOS", "", 73, false, "1.2.4" }; + case TYPE::NMOS_HISIM_HV2: return { "M", "NMOS", "", 73, false, "2.2.0" }; + case TYPE::PMOS_HISIM_HV2: return { "M", "PMOS", "", 73, false, "2.2.0" }; + + case TYPE::VSOURCE_PULSE: return { "V", "", "PULSE" }; + case TYPE::VSOURCE_SIN: return { "V", "", "SIN" }; + case TYPE::VSOURCE_EXP: return { "V", "", "EXP" }; + case TYPE::VSOURCE_SFAM: return { "V", "", "AM" }; + case TYPE::VSOURCE_SFFM: return { "V", "", "SFFM" }; + case TYPE::VSOURCE_PWL: return { "V", "", "PWL" }; + case TYPE::VSOURCE_WHITE_NOISE: return { "V", "", "TRNOISE" }; + case TYPE::VSOURCE_PINK_NOISE: return { "V", "", "TRNOISE" }; + case TYPE::VSOURCE_BURST_NOISE: return { "V", "", "TRNOISE" }; + case TYPE::VSOURCE_RANDOM_UNIFORM: return { "V", "", "TRRANDOM" }; + case TYPE::VSOURCE_RANDOM_NORMAL: return { "V", "", "TRRANDOM" }; + case TYPE::VSOURCE_RANDOM_EXP: return { "V", "", "TRRANDOM" }; + case TYPE::VSOURCE_RANDOM_POISSON: return { "V", "", "TRRANDOM" }; + case TYPE::VSOURCE_BEHAVIORAL: return { "B" }; + + case TYPE::ISOURCE_PULSE: return { "V", "", "PULSE" }; + case TYPE::ISOURCE_SIN: return { "V", "", "SIN" }; + case TYPE::ISOURCE_EXP: return { "V", "", "EXP" }; + case TYPE::ISOURCE_SFAM: return { "V", "", "AM" }; + case TYPE::ISOURCE_SFFM: return { "V", "", "SFFM" }; + case TYPE::ISOURCE_PWL: return { "V", "", "PWL" }; + case TYPE::ISOURCE_WHITE_NOISE: return { "V", "", "TRNOISE" }; + case TYPE::ISOURCE_PINK_NOISE: return { "V", "", "TRNOISE" }; + case TYPE::ISOURCE_BURST_NOISE: return { "V", "", "TRNOISE" }; + case TYPE::ISOURCE_RANDOM_UNIFORM: return { "V", "", "TRRANDOM" }; + case TYPE::ISOURCE_RANDOM_NORMAL: return { "V", "", "TRRANDOM" }; + case TYPE::ISOURCE_RANDOM_EXP: return { "V", "", "TRRANDOM" }; + case TYPE::ISOURCE_RANDOM_POISSON: return { "V", "", "TRRANDOM" }; + case TYPE::ISOURCE_BEHAVIORAL: return { "B" }; + + case TYPE::SUBCIRCUIT: return { "X" }; + case TYPE::CODEMODEL: return { "A" }; + + case TYPE::NONE: + case TYPE::RAWSPICE: + return {}; + + case TYPE::_ENUM_END: + break; + } + + wxFAIL; + return {}; +} + + +TYPE SIM_MODEL::ReadTypeFromSpiceCode( const std::string& aSpiceCode ) +{ + tao::pegtl::string_input<> in( aSpiceCode, "from_input" ); + std::unique_ptr root; + + try + { + root = tao::pegtl::parse_tree::parse + ( in ); + } + catch( tao::pegtl::parse_error& e ) + { + throw KI_PARAM_ERROR( wxString::Format( _( "Failed to parse '%s': %s" ), aSpiceCode, + e.what() ) ); + } + + wxASSERT( root ); + + for( const auto& node : root->children ) + { + if( node->is_type() ) + { + for( const auto& subnode : node->children ) + { + if( subnode->is_type() ) + { + // Do nothing. + } + else if( subnode->is_type() ) + return readTypeFromSpiceTypeString( subnode->string() ); + else + { + wxFAIL_MSG( "Unhandled parse tree subnode" ); + return TYPE::NONE; + } + } + } + else if( node->is_type() ) + return TYPE::SUBCIRCUIT; + else + { + wxFAIL_MSG( "Unhandled parse tree node" ); + return TYPE::NONE; + } + } + + wxFAIL_MSG( "Could not derive type from Spice code" ); + return TYPE::NONE; } @@ -265,8 +442,8 @@ template TYPE SIM_MODEL::ReadTypeFromFields( const std::vector& aFiel template TYPE SIM_MODEL::ReadTypeFromFields( const std::vector& aFields ) { - wxString typeFieldValue = getFieldValue( &aFields, TYPE_FIELD ); - wxString deviceTypeFieldValue = getFieldValue( &aFields, DEVICE_TYPE_FIELD ); + wxString typeFieldValue = GetFieldValue( &aFields, TYPE_FIELD ); + wxString deviceTypeFieldValue = GetFieldValue( &aFields, DEVICE_TYPE_FIELD ); bool typeFound = false; for( TYPE type : TYPE_ITERATOR() ) @@ -280,6 +457,8 @@ TYPE SIM_MODEL::ReadTypeFromFields( const std::vector& aFields ) } } + // TODO: Return TYPE::NONE instead of throwing an exception. + if( !typeFound ) throw KI_PARAM_ERROR( wxString::Format( _( "Invalid '%s' field value: '%s'" ), TYPE_FIELD, typeFieldValue ) ); @@ -289,124 +468,233 @@ TYPE SIM_MODEL::ReadTypeFromFields( const std::vector& aFields ) } -template std::unique_ptr SIM_MODEL::Create( int symbolPinCount, +std::unique_ptr SIM_MODEL::Create( TYPE aType, int aSymbolPinCount ) +{ + std::unique_ptr model = create( aType ); + + // Passing nullptr to ReadDataFields will make it act as if all fields were empty. + model->ReadDataFields( aSymbolPinCount, static_cast*>( nullptr ) ); + return model; +} + + +std::unique_ptr SIM_MODEL::Create( const std::string& aSpiceCode ) +{ + std::unique_ptr model = create( ReadTypeFromSpiceCode( aSpiceCode ) ); + + if( !model->ReadSpiceCode( aSpiceCode ) ) + { + // Demote to raw Spice element and try again. + std::unique_ptr rawSpiceModel = create( TYPE::RAWSPICE ); + + rawSpiceModel->ReadSpiceCode( aSpiceCode ); + return rawSpiceModel; + } + + return model; +} + + +std::unique_ptr SIM_MODEL::Create( const SIM_MODEL& aBaseModel ) +{ + std::unique_ptr model = create( aBaseModel.GetType() ); + + model->SetBaseModel( aBaseModel ); + return model; +} + + +template std::unique_ptr SIM_MODEL::Create( int aSymbolPinCount, const std::vector& aFields ); -template std::unique_ptr SIM_MODEL::Create( int symbolPinCount, +template std::unique_ptr SIM_MODEL::Create( int aSymbolPinCount, const std::vector& aFields ); template -std::unique_ptr SIM_MODEL::Create( int symbolPinCount, const std::vector& aFields ) +std::unique_ptr SIM_MODEL::Create( int aSymbolPinCount, const std::vector& aFields ) { - return SIM_MODEL::Create( ReadTypeFromFields( aFields ), symbolPinCount, &aFields ); + std::unique_ptr model = SIM_MODEL::create( ReadTypeFromFields( aFields ) ); + + model->ReadDataFields( aSymbolPinCount, &aFields ); + return model; } -template std::unique_ptr SIM_MODEL::Create( TYPE aType, - int symbolPinCount, - const std::vector* aFields ); -template std::unique_ptr SIM_MODEL::Create( TYPE aType, - int symbolPinCount, - const std::vector* aFields ); -template std::unique_ptr SIM_MODEL::Create( TYPE aType, - int symbolPinCount, - const std::vector* aFields ); - template -std::unique_ptr SIM_MODEL::Create( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +wxString SIM_MODEL::GetFieldValue( const std::vector* aFields, const wxString& aFieldName ) { - switch( aType ) + static_assert( std::is_same::value || std::is_same::value ); + + if( !aFields ) + return wxEmptyString; // Should not happen, T=void specialization will be called instead. + + auto fieldIt = std::find_if( aFields->begin(), aFields->end(), + [aFieldName]( const T& field ) + { + return field.GetName() == aFieldName; + } ); + + if( fieldIt != aFields->end() ) + return fieldIt->GetText(); + + return wxEmptyString; +} + + +// This specialization is used when no fields are passed. +template <> +wxString SIM_MODEL::GetFieldValue( const std::vector* aFields, const wxString& aFieldName ) +{ + return wxEmptyString; +} + + +template +void SIM_MODEL::SetFieldValue( std::vector& aFields, const wxString& aFieldName, + const wxString& aValue ) +{ + static_assert( std::is_same::value || std::is_same::value ); + + auto fieldIt = std::find_if( aFields.begin(), aFields.end(), + [&]( const T& f ) + { + return f.GetName() == aFieldName; + } ); + + if( fieldIt != aFields.end() ) { - case TYPE::RESISTOR_IDEAL: - case TYPE::CAPACITOR_IDEAL: - case TYPE::INDUCTOR_IDEAL: - return std::make_unique( aType, symbolPinCount, aFields ); + if( aValue.IsEmpty() ) + aFields.erase( fieldIt ); + else + fieldIt->SetText( aValue ); - case TYPE::RESISTOR_BEHAVIORAL: - case TYPE::CAPACITOR_BEHAVIORAL: - case TYPE::INDUCTOR_BEHAVIORAL: - case TYPE::VSOURCE_BEHAVIORAL: - case TYPE::ISOURCE_BEHAVIORAL: - return std::make_unique( aType, symbolPinCount, aFields ); - - case TYPE::VSOURCE_PULSE: - case TYPE::ISOURCE_PULSE: - case TYPE::VSOURCE_SIN: - case TYPE::ISOURCE_SIN: - case TYPE::VSOURCE_EXP: - case TYPE::ISOURCE_EXP: - case TYPE::VSOURCE_SFAM: - case TYPE::ISOURCE_SFAM: - case TYPE::VSOURCE_SFFM: - case TYPE::ISOURCE_SFFM: - case TYPE::VSOURCE_PWL: - case TYPE::ISOURCE_PWL: - case TYPE::VSOURCE_WHITE_NOISE: - case TYPE::ISOURCE_WHITE_NOISE: - case TYPE::VSOURCE_PINK_NOISE: - case TYPE::ISOURCE_PINK_NOISE: - case TYPE::VSOURCE_BURST_NOISE: - case TYPE::ISOURCE_BURST_NOISE: - case TYPE::VSOURCE_RANDOM_UNIFORM: - case TYPE::ISOURCE_RANDOM_UNIFORM: - case TYPE::VSOURCE_RANDOM_NORMAL: - case TYPE::ISOURCE_RANDOM_NORMAL: - case TYPE::VSOURCE_RANDOM_EXP: - case TYPE::ISOURCE_RANDOM_EXP: - case TYPE::VSOURCE_RANDOM_POISSON: - case TYPE::ISOURCE_RANDOM_POISSON: - return std::make_unique( aType, symbolPinCount, aFields ); - - case TYPE::SUBCIRCUIT: - return std::make_unique( aType, symbolPinCount, aFields ); - - case TYPE::CODEMODEL: - return std::make_unique( aType, symbolPinCount, aFields ); - - case TYPE::RAWSPICE: - return std::make_unique( aType, symbolPinCount, aFields ); - - default: - return std::make_unique( aType, symbolPinCount, aFields ); + return; } + + if( aValue.IsEmpty() ) + return; + + if constexpr( std::is_same::value ) + { + wxASSERT( aFields.size() >= 1 ); + + SCH_ITEM* parent = static_cast( aFields.at( 0 ).GetParent() ); + aFields.emplace_back( wxPoint(), aFields.size(), parent, aFieldName ); + } + else if constexpr( std::is_same::value ) + aFields.emplace_back( aFields.size(), aFieldName ); + + aFields.back().SetText( aValue ); } -SIM_MODEL::SIM_MODEL( TYPE aType ) : m_type( aType ) +bool SIM_MODEL::ReadSpiceCode( const std::string& aSpiceCode ) { + // The default behavior is to treat the Spice param=value pairs as the model parameters and + // values (for many models the correspondence is not exact, so this function is overridden). + + tao::pegtl::string_input<> in( aSpiceCode, "from_input" ); + std::unique_ptr root; + + try + { + root = tao::pegtl::parse_tree::parse + ( in ); + } + catch( tao::pegtl::parse_error& e ) + { + return false; + } + + + wxASSERT( root ); + + std::cout << "BEGIN" << std::endl; // DEBUG TRACE + + for( const auto& node : root->children ) + { + std::cout << "node: " << node->string() << std::endl; // DEBUG TRACE + if( node->is_type() + || node->is_type() ) + { + wxString paramName = ""; + + for( const auto& subnode : node->children ) + { + if( subnode->is_type() ) + { + // Do nothing. + } + else if( subnode->is_type() ) + { + // Do nothing. + } + else if( subnode->is_type() ) + { + paramName = subnode->string(); + } + // TODO: Do something with number. + // It doesn't seem too useful? + else if( subnode->is_type< + SIM_MODEL_PARSER::number>() ) + { + wxASSERT( !paramName.IsEmpty() ); + + if( !setParamFromSpiceCode( paramName, subnode->string() ) ) + return false; + } + else + { + wxFAIL_MSG( "Unhandled parse tree subnode" ); + return false; + } + } + } + else + { + wxFAIL_MSG( "Unhandled parse tree node" ); + return false; + } + } + + std::cout << "END" << std::endl; // DEBUG TRACE + + m_spiceCode = aSpiceCode; + return true; +} + + +template +void SIM_MODEL::ReadDataFields( int aSymbolPinCount, const std::vector* aFields ) +{ + doReadDataFields( aSymbolPinCount, aFields ); } template <> -void SIM_MODEL::ReadDataFields( int symbolPinCount, const std::vector* aFields ) +void SIM_MODEL::ReadDataFields( int aSymbolPinCount, const std::vector* aFields ) { - doReadDataFields( symbolPinCount, aFields ); + ReadDataSchFields( aSymbolPinCount, aFields ); } template <> -void SIM_MODEL::ReadDataFields( int symbolPinCount, const std::vector* aFields ) +void SIM_MODEL::ReadDataFields( int aSymbolPinCount, const std::vector* aFields ) { - ReadDataSchFields( symbolPinCount, aFields ); + ReadDataLibFields( aSymbolPinCount, aFields ); } -template <> -void SIM_MODEL::ReadDataFields( int symbolPinCount, const std::vector* aFields ) +void SIM_MODEL::ReadDataSchFields( int aSymbolPinCount, const std::vector* aFields ) { - ReadDataLibFields( symbolPinCount, aFields ); + doReadDataFields( aSymbolPinCount, aFields ); } -void SIM_MODEL::ReadDataSchFields( int symbolPinCount, const std::vector* aFields ) +void SIM_MODEL::ReadDataLibFields( int aSymbolPinCount, const std::vector* aFields ) { - doReadDataFields( symbolPinCount, aFields ); -} - - -void SIM_MODEL::ReadDataLibFields( int symbolPinCount, const std::vector* aFields ) -{ - doReadDataFields( symbolPinCount, aFields ); + doReadDataFields( aSymbolPinCount, aFields ); } @@ -436,67 +724,320 @@ void SIM_MODEL::WriteDataLibFields( std::vector& aFields ) } -template -void SIM_MODEL::doReadDataFields( int symbolPinCount, const std::vector* aFields ) +wxString SIM_MODEL::GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const { - SetFile( getFieldValue( aFields, FILE_FIELD ) ); - parsePinSequence( symbolPinCount, getFieldValue( aFields, PIN_SEQUENCE_FIELD ) ); - parseParamValuePairs( getFieldValue( aFields, PARAMS_FIELD ) ); + LOCALE_IO toggle; + + if( GetBaseModel() && !HasOverrides() ) + return wxString::Format( ".include \"%s\"\n", aLibraryFilename ); + + return ""; +} + + +wxString SIM_MODEL::GenerateSpiceModelLine( const wxString& aModelName ) const +{ + LOCALE_IO toggle; + wxString result = ""; + wxString line = ""; + + line << wxString::Format( ".model %s %s(\n+", aModelName, GetSpiceInfo().typeString ); + + for( int paramIndex = 0; paramIndex < GetParamCount(); ++paramIndex ) + { + const PARAM& param = GetParam( paramIndex ); + wxString valueStr = param.value->ToString(); + + if( valueStr.IsEmpty() ) + continue; + + wxString append = ""; + + append << " "; + append << param.info.name; + append << "="; + append << param.value->ToString(); + + if( line.Length() + append.Length() > 60 ) + { + result << line + "\n"; + line = "+" + append; + } + else + line << append; + } + + result << line + ")\n"; + return result; +} + + +SIM_MODEL::SPICE_INFO SIM_MODEL::GetSpiceInfo() const +{ + return SpiceInfo( GetType() ); +} + + +wxString SIM_MODEL::GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName ) const +{ + return GenerateSpiceItemLine( aRefName, aModelName, getPinNames() ); +} + + +wxString SIM_MODEL::GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const +{ + wxString result = ""; + + if( aRefName.Length() >= 1 && aRefName.StartsWith( GetSpiceInfo().itemType ) ) + result << aRefName << " "; + else + result << GetSpiceInfo().itemType << aRefName << " "; + + for( const wxString& pinNetName : aPinNetNames ) + result << pinNetName << " "; + + result << aModelName; + + return result; +} + + +wxString SIM_MODEL::GenerateSpicePreview( const wxString& aModelName ) const +{ + if( !m_spiceCode.IsEmpty() ) + return m_spiceCode; // `aModelName` is ignored in this case. + + if( GetBaseModel() && !HasOverrides() ) + return GetBaseModel()->GenerateSpicePreview( aModelName ); + + wxString modelLine = GenerateSpiceModelLine( aModelName ); + + if( !modelLine.IsEmpty() ) + return modelLine; + + return GenerateSpiceItemLine( "", aModelName ); +} + + +void SIM_MODEL::AddParam( const PARAM::INFO& aInfo, bool aIsOtherVariant ) +{ + m_params.emplace_back( aInfo ); +} + + +const SIM_MODEL::PARAM& SIM_MODEL::GetParam( int aParamIndex ) const +{ + if( m_baseModel && m_params.at( aParamIndex ).value->ToString().IsEmpty() ) + return m_baseModel->GetParam( aParamIndex ); + else + return m_params.at( aParamIndex ); +} + + +const SIM_MODEL::PARAM& SIM_MODEL::GetUnderlyingParam( int aParamIndex ) const +{ + return m_params.at( aParamIndex ); +} + + +const SIM_MODEL::PARAM& SIM_MODEL::GetBaseParam( int aParamIndex ) const +{ + if( m_baseModel ) + return m_baseModel->GetParam( aParamIndex ); + else + return m_params.at( aParamIndex ); +} + + +bool SIM_MODEL::SetParamValue( int aParamIndex, const wxString& aValue ) +{ + // Models sourced from a library are immutable. + if( !m_spiceCode.IsEmpty() ) + return false; + + m_params.at( aParamIndex ).value->FromString( aValue ); + return true; +} + + +bool SIM_MODEL::HasOverrides() const +{ + for( const PARAM& param : m_params ) + { + if( !param.value->ToString().IsEmpty() ) + return true; + } + + return false; +} + + +bool SIM_MODEL::HasNonPrincipalOverrides() const +{ + for( const PARAM& param : m_params ) + { + if( param.info.category != PARAM::CATEGORY::PRINCIPAL + && !param.value->ToString().IsEmpty() ) + { + return true; + } + } + + return false; +} + + +SIM_MODEL::SIM_MODEL( TYPE aType ) : m_baseModel( nullptr ), m_type( aType ) +{ +} + + +std::unique_ptr SIM_MODEL::create( TYPE aType ) +{ + switch( aType ) + { + case TYPE::RESISTOR_IDEAL: + case TYPE::CAPACITOR_IDEAL: + case TYPE::INDUCTOR_IDEAL: + return std::make_unique( aType ); + + case TYPE::RESISTOR_BEHAVIORAL: + case TYPE::CAPACITOR_BEHAVIORAL: + case TYPE::INDUCTOR_BEHAVIORAL: + case TYPE::VSOURCE_BEHAVIORAL: + case TYPE::ISOURCE_BEHAVIORAL: + return std::make_unique( aType ); + + case TYPE::VSOURCE_PULSE: + case TYPE::ISOURCE_PULSE: + case TYPE::VSOURCE_SIN: + case TYPE::ISOURCE_SIN: + case TYPE::VSOURCE_EXP: + case TYPE::ISOURCE_EXP: + case TYPE::VSOURCE_SFAM: + case TYPE::ISOURCE_SFAM: + case TYPE::VSOURCE_SFFM: + case TYPE::ISOURCE_SFFM: + case TYPE::VSOURCE_PWL: + case TYPE::ISOURCE_PWL: + case TYPE::VSOURCE_WHITE_NOISE: + case TYPE::ISOURCE_WHITE_NOISE: + case TYPE::VSOURCE_PINK_NOISE: + case TYPE::ISOURCE_PINK_NOISE: + case TYPE::VSOURCE_BURST_NOISE: + case TYPE::ISOURCE_BURST_NOISE: + case TYPE::VSOURCE_RANDOM_UNIFORM: + case TYPE::ISOURCE_RANDOM_UNIFORM: + case TYPE::VSOURCE_RANDOM_NORMAL: + case TYPE::ISOURCE_RANDOM_NORMAL: + case TYPE::VSOURCE_RANDOM_EXP: + case TYPE::ISOURCE_RANDOM_EXP: + case TYPE::VSOURCE_RANDOM_POISSON: + case TYPE::ISOURCE_RANDOM_POISSON: + return std::make_unique( aType ); + + case TYPE::SUBCIRCUIT: + return std::make_unique( aType ); + + case TYPE::CODEMODEL: + return std::make_unique( aType ); + + case TYPE::RAWSPICE: + return std::make_unique( aType ); + + default: + return std::make_unique( aType ); + } +} + + +TYPE SIM_MODEL::readTypeFromSpiceTypeString( const std::string& aTypeString ) +{ + for( TYPE type : TYPE_ITERATOR() ) + { + if( SpiceInfo( type ).typeString == aTypeString ) + return type; + } + + // If the type string is not recognized, demote to raw Spice element. This way the user won't + // have an error if there is a type KiCad does not recognize. + return TYPE::RAWSPICE; +} + + +template +void SIM_MODEL::doReadDataFields( int aSymbolPinCount, const std::vector* aFields ) +{ + parsePinsField( aSymbolPinCount, GetFieldValue( aFields, PINS_FIELD ) ); + parseParamsField( GetFieldValue( aFields, PARAMS_FIELD ) ); } template void SIM_MODEL::doWriteFields( std::vector& aFields ) { - setFieldValue( aFields, DEVICE_TYPE_FIELD, - DeviceTypeInfo( TypeInfo( m_type ).deviceType ).fieldValue ); - setFieldValue( aFields, TYPE_FIELD, TypeInfo( m_type ).fieldValue ); - setFieldValue( aFields, FILE_FIELD, GetFile() ); - setFieldValue( aFields, PIN_SEQUENCE_FIELD, generatePinSequence() ); - setFieldValue( aFields, PARAMS_FIELD, generateParamValuePairs() ); + SetFieldValue( aFields, DEVICE_TYPE_FIELD, generateDeviceTypeField() ); + SetFieldValue( aFields, TYPE_FIELD, generateTypeField() ); + SetFieldValue( aFields, PINS_FIELD, generatePinsField() ); + SetFieldValue( aFields, PARAMS_FIELD, generateParamsField( " " ) ); } -wxString SIM_MODEL::generatePinSequence() +wxString SIM_MODEL::generateDeviceTypeField() const +{ + return DeviceTypeInfo( TypeInfo( m_type ).deviceType ).fieldValue; +} + + +wxString SIM_MODEL::generateTypeField() const +{ + return TypeInfo( m_type ).fieldValue; +} + + +wxString SIM_MODEL::generatePinsField() const { wxString result = ""; bool isFirst = true; - for( const PIN& modelPin : Pins() ) + for( int i = 0; i < GetPinCount(); ++i ) { if( isFirst ) isFirst = false; else result << " "; - if( modelPin.symbolPinNumber == PIN::NOT_CONNECTED ) + if( GetPin( i ).symbolPinNumber == PIN::NOT_CONNECTED ) result << "X"; else - result << modelPin.symbolPinNumber; + result << GetPin( i ).symbolPinNumber; } return result; } -void SIM_MODEL::parsePinSequence( int symbolPinCount, const wxString& aPinSequence ) +void SIM_MODEL::parsePinsField( int aSymbolPinCount, const wxString& aPinsField ) { // Default pin sequence: model pins are the same as symbol pins. // Excess model pins are set as Not Connected. for( int i = 0; i < static_cast( getPinNames().size() ); ++i ) { - if( i < symbolPinCount ) - Pins().push_back( { i + 1, getPinNames().at( i ) } ); + if( i < aSymbolPinCount ) + m_pins.push_back( { i + 1, getPinNames().at( i ) } ); else - Pins().push_back( { PIN::NOT_CONNECTED, getPinNames().at( i ) } ); + m_pins.push_back( { PIN::NOT_CONNECTED, getPinNames().at( i ) } ); } - if( aPinSequence.IsEmpty() ) + if( aPinsField.IsEmpty() ) return; LOCALE_IO toggle; - tao::pegtl::string_input<> in( aPinSequence.ToStdString(), "from_input" ); + tao::pegtl::string_input<> in( aPinsField.ToStdString(), "from_input" ); std::unique_ptr root; try @@ -512,7 +1053,7 @@ void SIM_MODEL::parsePinSequence( int symbolPinCount, const wxString& aPinSequen wxASSERT( root ); - if( root->children.size() != Pins().size() ) + if( static_cast( root->children.size() ) != GetPinCount() ) throw KI_PARAM_ERROR( wxString::Format( _( "The model pin sequence has a different number of values (%d) " "than the number of model pins (%d)" ) ) ); @@ -520,19 +1061,20 @@ void SIM_MODEL::parsePinSequence( int symbolPinCount, const wxString& aPinSequen for( unsigned int i = 0; i < root->children.size(); ++i ) { if( root->children.at( i )->string() == "X" ) - Pins().at( i ).symbolPinNumber = PIN::NOT_CONNECTED; + SetPinSymbolPinNumber( static_cast( i ), PIN::NOT_CONNECTED ); else - Pins().at( i ).symbolPinNumber = std::stoi( root->children.at( i )->string() ); + SetPinSymbolPinNumber( static_cast( i ), + std::stoi( root->children.at( i )->string() ) ); } } -wxString SIM_MODEL::generateParamValuePairs() +wxString SIM_MODEL::generateParamsField( const wxString& aPairSeparator ) const { bool isFirst = true; wxString result = ""; - for( const PARAM& param : m_params) + for( const PARAM& param : m_params ) { wxString valueStr = param.value->ToString(); @@ -553,18 +1095,19 @@ wxString SIM_MODEL::generateParamValuePairs() } -void SIM_MODEL::parseParamValuePairs( const wxString& aParamValuePairs ) +void SIM_MODEL::parseParamsField( const wxString& aParamsField ) { LOCALE_IO toggle; - tao::pegtl::string_input<> in( aParamValuePairs.ToStdString(), "from_input" ); + tao::pegtl::string_input<> in( aParamsField.ToStdString(), "from_input" ); std::unique_ptr root; try { + // Using parse tree instead of actions because we don't care about performance that much, + // and having a tree greatly simplifies some things. root = tao::pegtl::parse_tree::parse< - SIM_MODEL_PARSER::paramValuePairsGrammar, + SIM_MODEL_PARSER::paramValuePairsGrammar, SIM_MODEL_PARSER::paramValuePairsSelector> ( in ); } @@ -582,98 +1125,46 @@ void SIM_MODEL::parseParamValuePairs( const wxString& aParamValuePairs ) { if( node->is_type() ) paramName = node->string(); + // TODO: Do something with number. + // It doesn't seem too useful? else if( node->is_type>() ) { - wxASSERT( paramName != "" ); - - auto it = std::find_if( Params().begin(), Params().end(), - [paramName]( const PARAM& param ) - { - return param.info.name == paramName; - } ); - - if( it == Params().end() ) - throw KI_PARAM_ERROR( wxString::Format( _( "Unknown parameter '%s'" ), - paramName ) ); - - try - { - it->value->FromString( wxString( node->string() ) ); - } - catch( KI_PARAM_ERROR& e ) - { - Params().clear(); - throw KI_PARAM_ERROR( wxString::Format( _( "Invalid '%s' parameter value: %s" ), - paramName, node->string() ) ); - } + wxASSERT( !paramName.IsEmpty() ); + // TODO: Shouldn't be named "...fromSpiceCode" here... + setParamFromSpiceCode( paramName, node->string() ); } else + { wxFAIL; + return; + } } } -template -wxString SIM_MODEL::getFieldValue( const std::vector* aFields, const wxString& aFieldName ) +bool SIM_MODEL::setParamFromSpiceCode( const wxString& aParamName, const wxString& aParamValue ) { - static_assert( std::is_same::value || std::is_same::value ); + int i = 0; - if( !aFields ) - return wxEmptyString; // Should not happen, T=void specialization should be called instead. - - auto fieldIt = std::find_if( aFields->begin(), aFields->end(), - [&]( const T& f ) - { - return f.GetName() == aFieldName; - } ); - - if( fieldIt != aFields->end() ) - return fieldIt->GetText(); - - return wxEmptyString; -} - - -// This specialization is used when no fields are passed. -template <> -wxString SIM_MODEL::getFieldValue( const std::vector* aFields, const wxString& aFieldName ) -{ - return wxEmptyString; -} - - -template -void SIM_MODEL::setFieldValue( std::vector& aFields, const wxString& aFieldName, - const wxString& aValue ) -{ - static_assert( std::is_same::value || std::is_same::value ); - - if( aValue.IsEmpty() ) - return; - - auto fieldIt = std::find_if( aFields.begin(), aFields.end(), - [&]( const T& f ) - { - return f.GetName() == aFieldName; - } ); - - if( fieldIt != aFields.end() ) + for(; i < GetParamCount(); ++i ) { - fieldIt->SetText( aValue ); - return; + if( GetParam( i ).info.name == aParamName.Lower() ) + break; } + if( i == GetParamCount() ) + return false; // No parameter with this name exists. - if constexpr( std::is_same::value ) + try { - wxASSERT( aFields.size() >= 1 ); - - SCH_ITEM* parent = static_cast( aFields.at( 0 ).GetParent() ); - aFields.emplace_back( wxPoint(), aFields.size(), parent, aFieldName ); + SetParamValue( i, wxString( aParamValue ) ); + } + catch( KI_PARAM_ERROR& e ) + { + m_params.clear(); + return false; } - else if constexpr( std::is_same::value ) - aFields.emplace_back( aFields.size(), aFieldName ); - aFields.back().SetText( aValue ); + return true; } diff --git a/eeschema/sim/sim_model.h b/eeschema/sim/sim_model.h index a61ee9aa0b..f6ec3155f1 100644 --- a/eeschema/sim/sim_model.h +++ b/eeschema/sim/sim_model.h @@ -25,22 +25,46 @@ #ifndef SIM_MODEL_H #define SIM_MODEL_H +#include #include #include #include -#include #include #include #include +class SIM_LIBRARY; + + +namespace SIM_MODEL_GRAMMAR +{ + using namespace SPICE_GRAMMAR; + + + struct pinNumber : sor> {}; + struct pinSequence : seq>> {}; + + struct pinSequenceGrammar : must, + pinSequence, + opt, + eof> {}; + + template + struct paramValuePairsGrammar : must, + paramValuePairs, + opt, + eof> {}; +} + class SIM_MODEL { public: static constexpr auto DEVICE_TYPE_FIELD = "Model_Device"; static constexpr auto TYPE_FIELD = "Model_Type"; - static constexpr auto FILE_FIELD = "Model_File"; - static constexpr auto PIN_SEQUENCE_FIELD = "Model_Pin_Sequence"; + static constexpr auto PINS_FIELD = "Model_Pins"; static constexpr auto PARAMS_FIELD = "Model_Params"; @@ -238,6 +262,17 @@ public: }; + struct SPICE_INFO + { + wxString itemType; + wxString typeString = ""; + wxString inlineTypeString = ""; + int level = 0; + bool hasExpression = false; + wxString version = ""; + }; + + struct PIN { static constexpr auto NOT_CONNECTED = 0; @@ -278,41 +313,52 @@ public: { wxString name; unsigned int id = 0; // Legacy. - DIR dir; + DIR dir = DIR::INOUT; SIM_VALUE_BASE::TYPE type; FLAGS flags = {}; // Legacy - wxString unit; - CATEGORY category; + wxString unit = ""; + CATEGORY category = CATEGORY::PRINCIPAL; wxString defaultValue = ""; wxString defaultValueOfOtherVariant = ""; // Legacy. - wxString description; + wxString description = ""; }; std::unique_ptr value; const INFO& info; bool isOtherVariant = false; // Legacy. - PARAM( const INFO& aInfo ) : - value( SIM_VALUE_BASE::Create( aInfo.type ) ), - info( aInfo ) + PARAM( const INFO& aInfo, bool aIsOtherVariant = false ) + : value( SIM_VALUE_BASE::Create( aInfo.type ) ), + info( aInfo ), + isOtherVariant( aIsOtherVariant ) {} }; static DEVICE_INFO DeviceTypeInfo( DEVICE_TYPE aDeviceType ); static INFO TypeInfo( TYPE aType ); + static SPICE_INFO SpiceInfo( TYPE aType ); + + + static TYPE ReadTypeFromSpiceCode( const std::string& aSpiceCode ); template static TYPE ReadTypeFromFields( const std::vector& aFields ); - template - static std::unique_ptr Create( int symbolPinCount, const std::vector& aFields ); + static std::unique_ptr Create( TYPE aType, int aSymbolPinCount = 0 ); + static std::unique_ptr Create( const std::string& aSpiceCode ); + static std::unique_ptr Create( const SIM_MODEL& aBaseModel ); - template - static std::unique_ptr Create( TYPE aType, - int symbolPinCount, - const std::vector* aFields = nullptr ); + template + static std::unique_ptr Create( int aSymbolPinCount, const std::vector& aFields ); + + template + static wxString GetFieldValue( const std::vector* aFields, const wxString& aFieldName ); + + template + static void SetFieldValue( std::vector& aFields, const wxString& aFieldName, + const wxString& aValue ); // Move semantics. @@ -321,17 +367,17 @@ public: SIM_MODEL() = delete; SIM_MODEL( const SIM_MODEL& aOther ) = delete; SIM_MODEL( SIM_MODEL&& aOther ) = default; - SIM_MODEL& operator=(SIM_MODEL&& aOther ) = default; + SIM_MODEL& operator=(SIM_MODEL&& aOther ) = delete; - SIM_MODEL( TYPE aType ); + virtual bool ReadSpiceCode( const std::string& aSpiceCode ); template - void ReadDataFields( int symbolPinCount, const std::vector* aFields ); + void ReadDataFields( int aSymbolPinCount, const std::vector* aFields ); // C++ doesn't allow virtual template methods, so we do this: - virtual void ReadDataSchFields( int symbolPinCount, const std::vector* aFields ); - virtual void ReadDataLibFields( int symbolPinCount, const std::vector* aFields ); + virtual void ReadDataSchFields( int aSymbolPinCount, const std::vector* aFields ); + virtual void ReadDataLibFields( int aSymbolPinCount, const std::vector* aFields ); template @@ -341,46 +387,83 @@ public: virtual void WriteDataSchFields( std::vector& aFields ); virtual void WriteDataLibFields( std::vector& aFields ); - virtual void WriteCode( wxString& aCode ) = 0; + + virtual wxString GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const; + virtual wxString GenerateSpiceModelLine( const wxString& aModelName ) const; + + virtual SPICE_INFO GetSpiceInfo() const; + + wxString GenerateSpiceItemLine( const wxString& aRefName, const wxString& aModelName ) const; + virtual wxString GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const; + + virtual wxString GenerateSpicePreview( const wxString& aModelName ) const; - TYPE GetType() { return m_type; } + void AddParam( const PARAM::INFO& aInfo, bool aIsOtherVariant = false ); - virtual wxString GetFile() { return m_file; } - virtual void SetFile( const wxString& aFile ) { m_file = aFile; } + TYPE GetType() const { return m_type; } - std::vector& Pins() { return m_pins; } - std::vector& Params() { return m_params; } + const SIM_MODEL* GetBaseModel() const { return m_baseModel; } + void SetBaseModel( const SIM_MODEL& aBaseModel ) { m_baseModel = &aBaseModel; } + int GetPinCount() const { return static_cast( m_pins.size() ); } + const PIN& GetPin( int aIndex ) const { return m_pins.at( aIndex ); } + + void SetPinSymbolPinNumber( int aIndex, int aSymbolPinNumber ) + { + m_pins.at( aIndex ).symbolPinNumber = aSymbolPinNumber; + } + + + int GetParamCount() const { return static_cast( m_params.size() ); } + const PARAM& GetParam( int aParamIndex ) const; // Return base parameter unless it's overridden. + const PARAM& GetUnderlyingParam( int aParamIndex ) const; // Return the actual parameter. + const PARAM& GetBaseParam( int aParamIndex ) const; // Always return base parameter if it exists. + virtual bool SetParamValue( int aParamIndex, const wxString& aValue ); + + bool HasOverrides() const; + bool HasNonPrincipalOverrides() const; + + // Can modifying a model parameter also modify other parameters? + virtual bool HasAutofill() const { return false; } + +protected: + SIM_MODEL( TYPE aType ); private: - TYPE m_type; - wxString m_file; + static std::unique_ptr create( TYPE aType ); + static TYPE readTypeFromSpiceTypeString( const std::string& aTypeString ); + + wxString m_spiceCode; + const SIM_MODEL* m_baseModel; + + const TYPE m_type; std::vector m_pins; std::vector m_params; template - void doReadDataFields( int symbolPinCount, const std::vector* aFields ); + void doReadDataFields( int aSymbolPinCount, const std::vector* aFields ); template void doWriteFields( std::vector& aFields ); - template - static wxString getFieldValue( const std::vector* aFields, const wxString& aFieldName ); + virtual std::vector getPinNames() const { return {}; } - template - static void setFieldValue( std::vector& aFields, const wxString& aFieldName, - const wxString& aValue ); + wxString generateDeviceTypeField() const; + wxString generateTypeField() const; - virtual std::vector getPinNames() { return {}; } + wxString generatePinsField() const; + void parsePinsField( int aSymbolPinCount, const wxString& aPinsField ); - wxString generatePinSequence(); - void parsePinSequence( int symbolPinCount, const wxString& aPinSequence ); + + wxString generateParamsField( const wxString& aPairSeparator ) const; + void parseParamsField( const wxString& aParamsField ); - virtual wxString generateParamValuePairs(); - virtual void parseParamValuePairs( const wxString& aParamValuePairs ); + virtual bool setParamFromSpiceCode( const wxString& aParamName, const wxString& aParamValue ); }; #endif // SIM_MODEL_H diff --git a/eeschema/sim/sim_model_behavioral.cpp b/eeschema/sim/sim_model_behavioral.cpp index b223679856..e3a8df7363 100644 --- a/eeschema/sim/sim_model_behavioral.cpp +++ b/eeschema/sim/sim_model_behavioral.cpp @@ -23,18 +23,10 @@ */ #include +#include -template SIM_MODEL_BEHAVIORAL::SIM_MODEL_BEHAVIORAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_BEHAVIORAL::SIM_MODEL_BEHAVIORAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_BEHAVIORAL::SIM_MODEL_BEHAVIORAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_BEHAVIORAL::SIM_MODEL_BEHAVIORAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +SIM_MODEL_BEHAVIORAL::SIM_MODEL_BEHAVIORAL( TYPE aType ) : SIM_MODEL( aType ) { static PARAM::INFO resistor = makeParamInfo( "r", "Expression for resistance", "ohm" ); @@ -45,22 +37,62 @@ SIM_MODEL_BEHAVIORAL::SIM_MODEL_BEHAVIORAL( TYPE aType, int symbolPinCount, switch( aType ) { - case TYPE::RESISTOR_BEHAVIORAL: Params().emplace_back( resistor ); break; - case TYPE::CAPACITOR_BEHAVIORAL: Params().emplace_back( capacitor ); break; - case TYPE::INDUCTOR_BEHAVIORAL: Params().emplace_back( inductor ); break; - case TYPE::VSOURCE_BEHAVIORAL: Params().emplace_back( vsource ); break; - case TYPE::ISOURCE_BEHAVIORAL: Params().emplace_back( isource ); break; + case TYPE::RESISTOR_BEHAVIORAL: AddParam( resistor ); break; + case TYPE::CAPACITOR_BEHAVIORAL: AddParam( capacitor ); break; + case TYPE::INDUCTOR_BEHAVIORAL: AddParam( inductor ); break; + case TYPE::VSOURCE_BEHAVIORAL: AddParam( vsource ); break; + case TYPE::ISOURCE_BEHAVIORAL: AddParam( isource ); break; default: wxFAIL_MSG( "Unhandled SIM_MODEL type in SIM_MODEL_IDEAL" ); } - - ReadDataFields( symbolPinCount, aFields ); } -void SIM_MODEL_BEHAVIORAL::WriteCode( wxString& aCode ) +wxString SIM_MODEL_BEHAVIORAL::GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const { - // TODO + return ""; +} + + +wxString SIM_MODEL_BEHAVIORAL::GenerateSpiceModelLine( const wxString& aModelName ) const +{ + return ""; +} + + +wxString SIM_MODEL_BEHAVIORAL::GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const +{ + LOCALE_IO toggle; + + switch( GetType() ) + { + case TYPE::RESISTOR_BEHAVIORAL: + case TYPE::CAPACITOR_BEHAVIORAL: + case TYPE::INDUCTOR_BEHAVIORAL: + return SIM_MODEL::GenerateSpiceItemLine( aRefName, + GetParam( 0 ).value->ToString(), + aPinNetNames ); + + case TYPE::VSOURCE_BEHAVIORAL: + return SIM_MODEL::GenerateSpiceItemLine( aRefName, + wxString::Format( "V=%s", GetParam( 0 ).value->ToString() ), aPinNetNames ); + + case TYPE::ISOURCE_BEHAVIORAL: + return SIM_MODEL::GenerateSpiceItemLine( aRefName, + wxString::Format( "I=%s", GetParam( 0 ).value->ToString() ), aPinNetNames ); + + default: + wxFAIL_MSG( "Unhandled SIM_MODEL type in SIM_MODEL_BEHAVIORAL" ); + return ""; + } +} + + +std::vector SIM_MODEL_BEHAVIORAL::getPinNames() const +{ + return { "+", "-" }; } diff --git a/eeschema/sim/sim_model_behavioral.h b/eeschema/sim/sim_model_behavioral.h index b6dce70c5a..237f82ebfe 100644 --- a/eeschema/sim/sim_model_behavioral.h +++ b/eeschema/sim/sim_model_behavioral.h @@ -31,12 +31,17 @@ class SIM_MODEL_BEHAVIORAL : public SIM_MODEL { public: - template - SIM_MODEL_BEHAVIORAL( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); + SIM_MODEL_BEHAVIORAL( TYPE aType ); - void WriteCode( wxString& aCode ) override; + wxString GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const override; + wxString GenerateSpiceModelLine( const wxString& aModelName ) const override; + wxString GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const override; private: + std::vector getPinNames() const override; + static PARAM::INFO makeParamInfo( wxString name, wxString description, wxString unit ); }; diff --git a/eeschema/sim/sim_model_codemodel.cpp b/eeschema/sim/sim_model_codemodel.cpp index bd649ae95e..6ea2f6b5de 100644 --- a/eeschema/sim/sim_model_codemodel.cpp +++ b/eeschema/sim/sim_model_codemodel.cpp @@ -25,22 +25,7 @@ #include -template SIM_MODEL_CODEMODEL::SIM_MODEL_CODEMODEL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_CODEMODEL::SIM_MODEL_CODEMODEL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_CODEMODEL::SIM_MODEL_CODEMODEL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_CODEMODEL::SIM_MODEL_CODEMODEL( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +SIM_MODEL_CODEMODEL::SIM_MODEL_CODEMODEL( TYPE aType ) : SIM_MODEL( aType ) { } - - -void SIM_MODEL_CODEMODEL::WriteCode( wxString& aCode ) -{ - // TODO -} diff --git a/eeschema/sim/sim_model_codemodel.h b/eeschema/sim/sim_model_codemodel.h index 5234abf424..74ea4709c4 100644 --- a/eeschema/sim/sim_model_codemodel.h +++ b/eeschema/sim/sim_model_codemodel.h @@ -31,10 +31,7 @@ class SIM_MODEL_CODEMODEL : public SIM_MODEL { public: - template - SIM_MODEL_CODEMODEL( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); - - void WriteCode( wxString& aCode ) override; + SIM_MODEL_CODEMODEL( TYPE aType ); }; #endif // SIM_MODEL_CODEMODEL_H diff --git a/eeschema/sim/sim_model_ideal.cpp b/eeschema/sim/sim_model_ideal.cpp index f897739709..99fa7e9a7f 100644 --- a/eeschema/sim/sim_model_ideal.cpp +++ b/eeschema/sim/sim_model_ideal.cpp @@ -27,16 +27,7 @@ using PARAM = SIM_MODEL::PARAM; -template SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType ) : SIM_MODEL( aType ) { static PARAM::INFO resistor = makeParamInfo( "r", "Resistance", "ohm" ); @@ -45,24 +36,37 @@ SIM_MODEL_IDEAL::SIM_MODEL_IDEAL( TYPE aType, int symbolPinCount, switch( aType ) { - case TYPE::RESISTOR_IDEAL: Params().emplace_back( resistor ); break; - case TYPE::CAPACITOR_IDEAL: Params().emplace_back( capacitor ); break; - case TYPE::INDUCTOR_IDEAL: Params().emplace_back( inductor ); break; + case TYPE::RESISTOR_IDEAL: AddParam( resistor ); break; + case TYPE::CAPACITOR_IDEAL: AddParam( capacitor ); break; + case TYPE::INDUCTOR_IDEAL: AddParam( inductor ); break; default: wxFAIL_MSG( "Unhandled SIM_MODEL type in SIM_MODEL_IDEAL" ); } - - ReadDataFields( symbolPinCount, aFields ); } -void SIM_MODEL_IDEAL::WriteCode( wxString& aCode ) +wxString SIM_MODEL_IDEAL::GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const { - // TODO + return ""; } -std::vector SIM_MODEL_IDEAL::getPinNames() +wxString SIM_MODEL_IDEAL::GenerateSpiceModelLine( const wxString& aModelName ) const +{ + return ""; +} + + +wxString SIM_MODEL_IDEAL::GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const +{ + return SIM_MODEL::GenerateSpiceItemLine( aRefName, GetParam( 0 ).value->ToString(), + aPinNetNames ); +} + + +std::vector SIM_MODEL_IDEAL::getPinNames() const { return { "+", "-" }; } diff --git a/eeschema/sim/sim_model_ideal.h b/eeschema/sim/sim_model_ideal.h index cd651e99ef..90d25f6247 100644 --- a/eeschema/sim/sim_model_ideal.h +++ b/eeschema/sim/sim_model_ideal.h @@ -31,13 +31,16 @@ class SIM_MODEL_IDEAL : public SIM_MODEL { public: - template - SIM_MODEL_IDEAL( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); + SIM_MODEL_IDEAL( TYPE aType ); - void WriteCode( wxString& aCode ) override; + wxString GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const override; + wxString GenerateSpiceModelLine( const wxString& aModelName ) const override; + wxString GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const override; private: - std::vector getPinNames() override; + std::vector getPinNames() const override; static PARAM::INFO makeParamInfo( wxString aName, wxString aDescription, wxString aUnit ); }; diff --git a/eeschema/sim/sim_model_ngspice.cpp b/eeschema/sim/sim_model_ngspice.cpp index 76efd51872..817de3a1cf 100644 --- a/eeschema/sim/sim_model_ngspice.cpp +++ b/eeschema/sim/sim_model_ngspice.cpp @@ -27,49 +27,26 @@ using TYPE = SIM_MODEL::TYPE; -template SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType ) : SIM_MODEL( aType ) { const NGSPICE::MODEL_INFO& modelInfo = NGSPICE::ModelInfo( getModelType() ); for( const SIM_MODEL::PARAM::INFO& paramInfo : modelInfo.modelParams ) - { - Params().emplace_back( paramInfo ); - Params().back().isOtherVariant = getIsOtherVariant(); - } + AddParam( paramInfo, getIsOtherVariant() ); for( const SIM_MODEL::PARAM::INFO& paramInfo : modelInfo.instanceParams ) - { - Params().emplace_back( paramInfo ); - Params().back().isOtherVariant = getIsOtherVariant(); - } - - ReadDataFields( symbolPinCount, aFields ); + AddParam( paramInfo, getIsOtherVariant() ); } -void SIM_MODEL_NGSPICE::WriteCode( wxString& aCode ) -{ - // TODO -} - - -std::vector SIM_MODEL_NGSPICE::getPinNames() +std::vector SIM_MODEL_NGSPICE::getPinNames() const { return NGSPICE::ModelInfo( getModelType() ).pinNames; } -NGSPICE::MODEL_TYPE SIM_MODEL_NGSPICE::getModelType() +NGSPICE::MODEL_TYPE SIM_MODEL_NGSPICE::getModelType() const { switch( GetType() ) { diff --git a/eeschema/sim/sim_model_ngspice.h b/eeschema/sim/sim_model_ngspice.h index 038f574511..cf89374ed1 100644 --- a/eeschema/sim/sim_model_ngspice.h +++ b/eeschema/sim/sim_model_ngspice.h @@ -32,15 +32,12 @@ class SIM_MODEL_NGSPICE : public SIM_MODEL { public: - template - SIM_MODEL_NGSPICE( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); - - void WriteCode( wxString& aCode ) override; + SIM_MODEL_NGSPICE( TYPE aType ); private: - std::vector getPinNames() override; + std::vector getPinNames() const override; - NGSPICE::MODEL_TYPE getModelType(); + NGSPICE::MODEL_TYPE getModelType() const; bool getIsOtherVariant(); }; diff --git a/eeschema/sim/sim_model_rawspice.cpp b/eeschema/sim/sim_model_rawspice.cpp index 1ccb4b23bb..bed38ceaf6 100644 --- a/eeschema/sim/sim_model_rawspice.cpp +++ b/eeschema/sim/sim_model_rawspice.cpp @@ -23,25 +23,49 @@ */ #include +#include +#include -template SIM_MODEL_RAWSPICE::SIM_MODEL_RAWSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_RAWSPICE::SIM_MODEL_RAWSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_RAWSPICE::SIM_MODEL_RAWSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_RAWSPICE::SIM_MODEL_RAWSPICE( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +SIM_MODEL_RAWSPICE::SIM_MODEL_RAWSPICE( TYPE aType ) : SIM_MODEL( aType ) { - ReadDataFields( symbolPinCount, aFields ); } -void SIM_MODEL_RAWSPICE::WriteCode( wxString& aCode ) +bool SIM_MODEL_RAWSPICE::setParamFromSpiceCode( const wxString& aParamName, + const wxString& aParamValue ) { - // TODO + int i = 0; + + for(; i < GetParamCount(); ++i ) + { + if( GetParam( i ).info.name == aParamName.Lower() ) + break; + } + + + if( i == GetParamCount() ) + { + // No parameter with this name found. Create a new one. + std::unique_ptr paramInfo = std::make_unique(); + + paramInfo->name = aParamName.Lower(); + paramInfo->type = SIM_VALUE_BASE::TYPE::STRING; + m_paramInfos.push_back( std::move( paramInfo ) ); + + AddParam( *m_paramInfos.back() ); + } + + try + { + GetParam( i ).value->FromString( wxString( aParamValue ) ); + } + catch( KI_PARAM_ERROR& e ) + { + // Shouldn't happen since it's TYPE::STRING. + return false; + } + + return true; } diff --git a/eeschema/sim/sim_model_rawspice.h b/eeschema/sim/sim_model_rawspice.h index 5dc17a9c6d..c07220194c 100644 --- a/eeschema/sim/sim_model_rawspice.h +++ b/eeschema/sim/sim_model_rawspice.h @@ -31,10 +31,14 @@ class SIM_MODEL_RAWSPICE : public SIM_MODEL { public: - template - SIM_MODEL_RAWSPICE( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); + SIM_MODEL_RAWSPICE( TYPE aType ); - void WriteCode( wxString& aCode ) override; + //bool ReadSpiceCode( const std::string& aSpiceCode ) override; + +private: + bool setParamFromSpiceCode( const wxString& aParamName, const wxString& aParamValue ) override; + + std::vector> m_paramInfos; }; #endif // SIM_MODEL_RAWSPICE_H diff --git a/eeschema/sim/sim_model_source.cpp b/eeschema/sim/sim_model_source.cpp index bf24d4479d..3d631268d5 100644 --- a/eeschema/sim/sim_model_source.cpp +++ b/eeschema/sim/sim_model_source.cpp @@ -27,21 +27,38 @@ using PARAM = SIM_MODEL::PARAM; -template SIM_MODEL_SOURCE::SIM_MODEL_SOURCE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_SOURCE::SIM_MODEL_SOURCE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_SOURCE::SIM_MODEL_SOURCE( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_SOURCE::SIM_MODEL_SOURCE( TYPE aType, int symbolPinCount, const std::vector* aFields ) +SIM_MODEL_SOURCE::SIM_MODEL_SOURCE( TYPE aType ) : SIM_MODEL( aType ) { for( const PARAM::INFO& paramInfo : makeParams( aType ) ) - Params().emplace_back( paramInfo ); + AddParam( paramInfo ); +} - ReadDataFields( symbolPinCount, aFields ); + +wxString SIM_MODEL_SOURCE::GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const +{ + return ""; +} + + +wxString SIM_MODEL_SOURCE::GenerateSpiceModelLine( const wxString& aModelName ) const +{ + return ""; +} + + +wxString SIM_MODEL_SOURCE::GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const +{ + wxString argList = ""; + + for( int i = 0; i < GetParamCount(); ++i ) + argList << GetParam( i ).value->ToString() << " "; + + wxString model = wxString::Format( GetSpiceInfo().inlineTypeString + "( %s)", argList ); + + return SIM_MODEL::GenerateSpiceItemLine( aRefName, model, aPinNetNames ); } @@ -122,9 +139,31 @@ const std::vector& SIM_MODEL_SOURCE::makeParams( TYPE aType ) } -void SIM_MODEL_SOURCE::WriteCode( wxString& aCode ) +bool SIM_MODEL_SOURCE::SetParamValue( int aParamIndex, const wxString& aValue ) { - // TODO + // Sources are special. All preceding parameter values must be filled. If they are not, fill + // them out automatically. If a value is nulled, delete everything after it. + if( aValue.IsEmpty() ) + { + for( int i = aParamIndex; i < GetParamCount(); ++i ) + SIM_MODEL::SetParamValue( i, "" ); + } + else + { + for( int i = 0; i < aParamIndex; ++i ) + { + if( GetParam( i ).value->ToString().IsEmpty() ) + SIM_MODEL::SetParamValue( i, "0" ); + } + } + + return SIM_MODEL::SetParamValue( aParamIndex, aValue ); +} + + +std::vector SIM_MODEL_SOURCE::getPinNames() const +{ + return { "+", "-" }; } diff --git a/eeschema/sim/sim_model_source.h b/eeschema/sim/sim_model_source.h index 8b276e0182..e4dd61bfc2 100644 --- a/eeschema/sim/sim_model_source.h +++ b/eeschema/sim/sim_model_source.h @@ -31,12 +31,21 @@ class SIM_MODEL_SOURCE : public SIM_MODEL { public: - template - SIM_MODEL_SOURCE( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); + SIM_MODEL_SOURCE( TYPE aType ); - void WriteCode( wxString& aCode ) override; + wxString GenerateSpiceIncludeLine( const wxString& aLibraryFilename ) const override; + wxString GenerateSpiceModelLine( const wxString& aModelName ) const override; + wxString GenerateSpiceItemLine( const wxString& aRefName, + const wxString& aModelName, + const std::vector& aPinNetNames ) const override; + + bool SetParamValue( int aParamIndex, const wxString& aValue ) override; + + bool HasAutofill() const override { return true; } private: + std::vector getPinNames() const override; + static const std::vector& makeParams( TYPE aType ); static std::vector makePulse( wxString aPrefix, wxString aUnit ); diff --git a/eeschema/sim/sim_model_subcircuit.cpp b/eeschema/sim/sim_model_subcircuit.cpp index c91663a806..871cf93406 100644 --- a/eeschema/sim/sim_model_subcircuit.cpp +++ b/eeschema/sim/sim_model_subcircuit.cpp @@ -25,23 +25,7 @@ #include -template SIM_MODEL_SUBCIRCUIT::SIM_MODEL_SUBCIRCUIT( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_SUBCIRCUIT::SIM_MODEL_SUBCIRCUIT( TYPE aType, int symbolPinCount, - const std::vector* aFields ); -template SIM_MODEL_SUBCIRCUIT::SIM_MODEL_SUBCIRCUIT( TYPE aType, int symbolPinCount, - const std::vector* aFields ); - -template -SIM_MODEL_SUBCIRCUIT::SIM_MODEL_SUBCIRCUIT( TYPE aType, int symbolPinCount, - const std::vector* aFields ) +SIM_MODEL_SUBCIRCUIT::SIM_MODEL_SUBCIRCUIT( TYPE aType ) : SIM_MODEL( aType ) { - ReadDataFields( symbolPinCount, aFields ); -} - - -void SIM_MODEL_SUBCIRCUIT::WriteCode( wxString& aCode ) -{ - // TODO } diff --git a/eeschema/sim/sim_model_subcircuit.h b/eeschema/sim/sim_model_subcircuit.h index 7ecbe74add..0133c59806 100644 --- a/eeschema/sim/sim_model_subcircuit.h +++ b/eeschema/sim/sim_model_subcircuit.h @@ -31,10 +31,7 @@ class SIM_MODEL_SUBCIRCUIT : public SIM_MODEL { public: - template - SIM_MODEL_SUBCIRCUIT( TYPE aType, int symbolPinCount, const std::vector* aFields = nullptr ); - - void WriteCode( wxString& aCode ) override; + SIM_MODEL_SUBCIRCUIT( TYPE aType ); }; #endif // SIM_MODEL_SUBCIRCUIT_H diff --git a/eeschema/sim/sim_property.cpp b/eeschema/sim/sim_property.cpp index 6be5656c90..0811d2fdb0 100644 --- a/eeschema/sim/sim_property.cpp +++ b/eeschema/sim/sim_property.cpp @@ -38,7 +38,7 @@ wxEND_EVENT_TABLE() SIM_VALIDATOR::SIM_VALIDATOR( SIM_VALUE_BASE::TYPE aValueType, - SIM_VALUE_PARSER::NOTATION aNotation ) + SIM_VALUE_GRAMMAR::NOTATION aNotation ) : wxValidator(), m_valueType( aValueType ), m_notation( aNotation ) @@ -85,7 +85,7 @@ bool SIM_VALIDATOR::TransferFromWindow() bool SIM_VALIDATOR::isValid( const wxString& aString ) { - return SIM_VALUE_PARSER::IsValid( aString, m_valueType, m_notation ); + return SIM_VALUE_GRAMMAR::IsValid( aString, m_valueType, m_notation ); } @@ -151,14 +151,19 @@ void SIM_VALIDATOR::onMouse( wxMouseEvent& aEvent ) SIM_PROPERTY::SIM_PROPERTY( const wxString& aLabel, const wxString& aName, - SIM_VALUE_BASE& aValue, + std::shared_ptr aLibrary, + std::shared_ptr aModel, + int aParamIndex, SIM_VALUE_BASE::TYPE aValueType, - SIM_VALUE_PARSER::NOTATION aNotation ) - : wxStringProperty( aLabel, aName, aValue.ToString() ), + SIM_VALUE_GRAMMAR::NOTATION aNotation ) + : wxStringProperty( aLabel, aName ), m_valueType( aValueType ), m_notation( aNotation ), - m_value( aValue ) + m_library( aLibrary ), + m_model( aModel ), + m_paramIndex( aParamIndex ) { + SetValueFromString( GetParam().value->ToString() ); } @@ -172,7 +177,19 @@ bool SIM_PROPERTY::StringToValue( wxVariant& aVariant, const wxString& aText, in { try { - m_value.FromString( aText ); + wxString paramValueStr = m_model->GetBaseParam( m_paramIndex ).value->ToString(); + + // TODO: Don't use string comparison. + if( m_model->GetBaseModel() && ( aText.IsEmpty() || aText == paramValueStr ) ) + { + m_model->SetParamValue( m_paramIndex, "" ); // Nullify. + aVariant = paramValueStr; // Use the inherited value (if it exists) if null. + } + else + { + m_model->SetParamValue( m_paramIndex, aText ); + aVariant = GetParam().value->ToString(); + } } catch( KI_PARAM_ERROR& e ) { @@ -180,6 +197,5 @@ bool SIM_PROPERTY::StringToValue( wxVariant& aVariant, const wxString& aText, in return false; } - aVariant = m_value.ToString(); return true; } diff --git a/eeschema/sim/sim_property.h b/eeschema/sim/sim_property.h index ad0283f469..5d9f1fd0be 100644 --- a/eeschema/sim/sim_property.h +++ b/eeschema/sim/sim_property.h @@ -25,14 +25,14 @@ #ifndef SIM_PROPERTY_H #define SIM_PROPERTY_H -#include +#include #include class SIM_VALIDATOR : public wxValidator { public: - SIM_VALIDATOR( SIM_VALUE_BASE::TYPE aValueType, SIM_VALUE_PARSER::NOTATION aNotation ); + SIM_VALIDATOR( SIM_VALUE_BASE::TYPE aValueType, SIM_VALUE_GRAMMAR::NOTATION aNotation ); SIM_VALIDATOR( const SIM_VALIDATOR& aValidator ) = default; wxObject* Clone() const override; @@ -51,7 +51,7 @@ private: void onMouse( wxMouseEvent& aEvent ); SIM_VALUE_BASE::TYPE m_valueType; - SIM_VALUE_PARSER::NOTATION m_notation; + SIM_VALUE_GRAMMAR::NOTATION m_notation; wxString m_prevText; long m_prevInsertionPoint; @@ -62,19 +62,28 @@ private: class SIM_PROPERTY : public wxStringProperty { public: - SIM_PROPERTY( const wxString& aLabel, const wxString& aName, SIM_VALUE_BASE& aValue, + // We pass shared_ptrs because we need to make sure they are destroyed only after the last time + // SIM_PROPERTY uses them. + SIM_PROPERTY( const wxString& aLabel, const wxString& aName, + std::shared_ptr aLibrary, + std::shared_ptr aModel, + int aParamIndex, SIM_VALUE_BASE::TYPE aValueType = SIM_VALUE_BASE::TYPE::FLOAT, - SIM_VALUE_PARSER::NOTATION aNotation = SIM_VALUE_PARSER::NOTATION::SI ); + SIM_VALUE_GRAMMAR::NOTATION aNotation = SIM_VALUE_GRAMMAR::NOTATION::SI ); wxValidator* DoGetValidator() const override; bool StringToValue( wxVariant& aVariant, const wxString& aText, int aArgFlags = 0 ) const override; + const SIM_MODEL::PARAM& GetParam() const { return m_model->GetParam( m_paramIndex ); } + protected: - SIM_VALUE_BASE::TYPE m_valueType; - SIM_VALUE_PARSER::NOTATION m_notation; - SIM_VALUE_BASE& m_value; + SIM_VALUE_BASE::TYPE m_valueType; + SIM_VALUE_GRAMMAR::NOTATION m_notation; + std::shared_ptr m_library; + std::shared_ptr m_model; + int m_paramIndex; }; #endif // SIM_PROPERTY_H diff --git a/eeschema/sim/sim_value.cpp b/eeschema/sim/sim_value.cpp index 336700c9b9..2b19809592 100644 --- a/eeschema/sim/sim_value.cpp +++ b/eeschema/sim/sim_value.cpp @@ -71,13 +71,56 @@ } +namespace SIM_VALUE_PARSER +{ + using namespace SIM_VALUE_GRAMMAR; + + template + struct numberSelector : std::false_type {}; + + template <> struct numberSelector> + : std::true_type {}; + template <> struct numberSelector> + : std::true_type {}; + template <> struct numberSelector : std::true_type {}; + template <> struct numberSelector : std::true_type {}; + template <> struct numberSelector : std::true_type {}; + template <> struct numberSelector> + : std::true_type {}; + template <> struct numberSelector> + : std::true_type {}; + template <> struct numberSelector> + : std::true_type {}; + template <> struct numberSelector> + : std::true_type {}; + + struct PARSE_RESULT + { + bool isEmpty = true; + std::string significand; + OPT intPart; + OPT fracPart; + OPT exponent; + OPT metricSuffixExponent; + }; + + PARSE_RESULT Parse( const wxString& aString, + SIM_VALUE_BASE::TYPE aValueType = SIM_VALUE_BASE::TYPE::FLOAT, + NOTATION aNotation = NOTATION::SI ); + + long MetricSuffixToExponent( std::string aMetricSuffix, NOTATION aNotation = NOTATION::SI ); + wxString ExponentToMetricSuffix( double aExponent, long& aReductionExponent, + NOTATION aNotation = NOTATION::SI ); +} + + template static inline void doIsValid( tao::pegtl::string_input<>& aIn ) { tao::pegtl::parse>( aIn ); } -bool SIM_VALUE_PARSER::IsValid( const wxString& aString, +bool SIM_VALUE_GRAMMAR::IsValid( const wxString& aString, SIM_VALUE_BASE::TYPE aValueType, NOTATION aNotation ) { @@ -163,7 +206,9 @@ SIM_VALUE_PARSER::PARSE_RESULT SIM_VALUE_PARSER::Parse( const wxString& aString, try { for( const auto& node : root->children ) + { CALL_INSTANCE( aValueType, aNotation, handleNodeForParse, *node, result ); + } } catch( std::invalid_argument& e ) { @@ -482,14 +527,19 @@ wxString SIM_VALUE::ToString() const if( m_value.has_value() ) { - double exponent = std::log10( *m_value ); - long reductionExponent = 0; + long value = *m_value; + long exponent = 0; - wxString metricSuffix = SIM_VALUE_PARSER::ExponentToMetricSuffix( exponent, - reductionExponent ); - long reducedValue = *m_value / static_cast( std::pow( 10, reductionExponent ) ); + while( value % 1000 == 0 ) + { + exponent += 3; + value /= 1000; + } - return wxString::Format( "%d%s", reducedValue, metricSuffix ); + long dummy = 0; + wxString metricSuffix = SIM_VALUE_PARSER::ExponentToMetricSuffix( + static_cast( exponent ), dummy ); + return wxString::Format( "%d%s", value, metricSuffix ); } return ""; diff --git a/eeschema/sim/sim_value.h b/eeschema/sim/sim_value.h index e1f269877e..e55a4984fd 100644 --- a/eeschema/sim/sim_value.h +++ b/eeschema/sim/sim_value.h @@ -86,7 +86,7 @@ private: }; -namespace SIM_VALUE_PARSER +namespace SIM_VALUE_GRAMMAR { using namespace tao::pegtl; @@ -100,7 +100,6 @@ namespace SIM_VALUE_PARSER wxString allowedIntChars; - struct spaces : plus {}; struct digits : plus {}; // For some reason it fails on just "digit". struct sign : one<'+', '-'> {}; @@ -165,43 +164,9 @@ namespace SIM_VALUE_PARSER struct numberGrammar : must>, eof> {}; - template - struct numberSelector : std::false_type {}; - - template <> struct numberSelector> : std::true_type {}; - template <> struct numberSelector> : std::true_type {}; - template <> struct numberSelector : std::true_type {}; - template <> struct numberSelector : std::true_type {}; - template <> struct numberSelector : std::true_type {}; - template <> struct numberSelector> - : std::true_type {}; - template <> struct numberSelector> - : std::true_type {}; - template <> struct numberSelector> - : std::true_type {}; - template <> struct numberSelector> - : std::true_type {}; - - struct PARSE_RESULT - { - bool isEmpty = true; - std::string significand; - OPT intPart; - OPT fracPart; - OPT exponent; - OPT metricSuffixExponent; - }; - bool IsValid( const wxString& aString, SIM_VALUE_BASE::TYPE aValueType = SIM_VALUE_BASE::TYPE::FLOAT, NOTATION aNotation = NOTATION::SI ); - PARSE_RESULT Parse( const wxString& aString, - SIM_VALUE_BASE::TYPE aValueType = SIM_VALUE_BASE::TYPE::FLOAT, - NOTATION aNotation = NOTATION::SI ); - - long MetricSuffixToExponent( std::string aMetricSuffix, NOTATION aNotation = NOTATION::SI ); - wxString ExponentToMetricSuffix( double aExponent, long& aReductionExponent, - NOTATION aNotation = NOTATION::SI ); } #endif // SIM_VALUE_H diff --git a/eeschema/sim/spice_grammar.h b/eeschema/sim/spice_grammar.h new file mode 100644 index 0000000000..8b363302b9 --- /dev/null +++ b/eeschema/sim/spice_grammar.h @@ -0,0 +1,156 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2022 Mikolaj Wielgus + * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 + */ + +#ifndef SPICE_GRAMMAR_H +#define SPICE_GRAMMAR_H + +#include + + +namespace SPICE_GRAMMAR +{ + using namespace SIM_VALUE_GRAMMAR; + + + struct eolComment : seq, until> {}; + struct commentLine : seq, until> {}; + + + struct linespaces : plus, + space> {}; + struct newline : seq, + not_at>> {}; + + struct continuation : seq, + sor, + star, + one<'+'>, + opt> {}; + + struct sep : sor {}; + + + struct param : plus {}; + + template + struct paramValuePair : seq, + one<'='>, + opt, + number> {}; + + template + struct paramValuePairs : seq, + star>>> {}; + + + struct modelName : plus>>> {}; + /*seq>>> {};*/ + + + struct dotModelType : sor {}; + + struct dotModel : seq, + TAO_PEGTL_ISTRING( ".model" ), + sep, + modelName, + sep, + dotModelType, + sor, + one<'('>, + opt, + paramValuePairs, + opt, + // Ngspice doesn't require the parentheses to match, though. + one<')'>>, + seq>>, + opt, + newline> {}; + + + struct dotSubcktPinNumber : digits {}; + struct dotSubcktPinSequence : seq, + opt>, + opt> {}; + + struct dotSubcktEnd : seq, + newline> {}; + + struct dotSubckt : seq, + TAO_PEGTL_ISTRING( ".subckt" ), + sep, + modelName, + sep, + dotSubcktPinSequence, + opt, + newline, + until> {}; + + struct modelUnit : sor {}; + + struct dotLine : seq, + one<'.'>, + until> {}; + + struct unknownLine : until {}; + + struct spiceUnit : sor {}; + + struct spiceUnitGrammar : must {}; +} + +#endif // SPICE_GRAMMAR_H