From 33ead34ea55b26e720d34b4f3a57888c795c9d0e Mon Sep 17 00:00:00 2001 From: Wayne Stambaugh Date: Thu, 25 Sep 2025 14:17:49 -0400 Subject: [PATCH] Work in progress variant code commit. Please do not attempt to use this yet as it is a work in progress. Given large number of merge conflicts, I pushed this partial commit to save time rebasing. All of the user interface is hidden behind the "EnableVariantUI" advanced configuration flag until variants are ready. --- common/advanced_config.cpp | 6 + common/string_utils.cpp | 21 + .../dialogs/dialog_symbol_fields_table.cpp | 135 +++++- eeschema/dialogs/dialog_symbol_fields_table.h | 6 + .../dialog_symbol_fields_table_base.cpp | 50 +- .../dialog_symbol_fields_table_base.fbp | 454 +++++++++++++++++- .../dialogs/dialog_symbol_fields_table_base.h | 12 + eeschema/dialogs/dialog_symbol_properties.cpp | 4 +- eeschema/eeschema_id.h | 4 +- eeschema/eeschema_jobs_handler.cpp | 8 +- eeschema/fields_data_model.cpp | 180 +++++-- eeschema/fields_data_model.h | 19 +- eeschema/menubar.cpp | 7 + eeschema/sch_edit_frame.h | 11 + eeschema/sch_field.cpp | 46 +- eeschema/sch_field.h | 6 +- eeschema/sch_file_versions.h | 3 +- .../sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp | 66 ++- .../kicad_sexpr/sch_io_kicad_sexpr_parser.cpp | 123 ++++- eeschema/sch_item.cpp | 20 +- eeschema/sch_item.h | 29 +- eeschema/sch_painter.cpp | 11 +- eeschema/sch_rule_area.h | 32 +- eeschema/sch_screen.cpp | 104 +++- eeschema/sch_screen.h | 8 + eeschema/sch_sheet.cpp | 44 +- eeschema/sch_sheet.h | 40 +- eeschema/sch_sheet_path.cpp | 14 + eeschema/sch_sheet_path.h | 90 +++- eeschema/sch_symbol.cpp | 268 ++++++++++- eeschema/sch_symbol.h | 54 ++- eeschema/sch_text.h | 13 +- eeschema/sch_textbox.h | 13 +- eeschema/schematic.cpp | 52 +- eeschema/schematic.h | 30 ++ eeschema/schematic.keywords | 2 + eeschema/symbol.h | 28 +- eeschema/toolbars_sch_editor.cpp | 54 +++ eeschema/toolbars_sch_editor.h | 11 + eeschema/tools/sch_actions.cpp | 12 + eeschema/tools/sch_actions.h | 4 + include/advanced_config.h | 9 + include/string_utils.h | 4 + kicad/cli/command.cpp | 29 +- kicad/cli/command.h | 18 + qa/tests/common/test_kicad_string.cpp | 20 +- 46 files changed, 1997 insertions(+), 177 deletions(-) diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 84ef2f20f5..d230b4e024 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -138,6 +138,7 @@ static const wxChar MaxPastedTextLength[] = wxT( "MaxPastedTextLength" ); static const wxChar PNSProcessClusterTimeout[] = wxT( "PNSProcessClusterTimeout" ); static const wxChar ImportSkipComponentBodies[] = wxT( "ImportSkipComponentBodies" ); static const wxChar ScreenDPI[] = wxT( "ScreenDPI" ); +static const wxChar EnableVariantsUI[] = wxT( "EnableVariantsUI" ); } // namespace KEYS @@ -335,6 +336,8 @@ ADVANCED_CFG::ADVANCED_CFG() m_ScreenDPI = 91; + m_EnableVariantsUI = false; + loadFromConfigFile(); } @@ -672,6 +675,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) &m_ScreenDPI, m_ScreenDPI, 50, 500 ) ); + m_entries.push_back( std::make_unique( true, AC_KEYS::EnableVariantsUI, &m_EnableVariantsUI, + m_EnableVariantsUI ) ); + // Special case for trace mask setting...we just grab them and set them immediately // Because we even use wxLogTrace inside of advanced config m_entries.push_back( std::make_unique( true, AC_KEYS::TraceMasks, &m_traceMasks, diff --git a/common/string_utils.cpp b/common/string_utils.cpp index 9546721fbd..798ab66a95 100644 --- a/common/string_utils.cpp +++ b/common/string_utils.cpp @@ -49,6 +49,8 @@ */ static const char illegalFileNameChars[] = "\\/:\"<>|*?"; +static const wxChar defaultVariantName[] = wxT( "< Default >" ); + // Checks if a full filename is valid, i.e. does not contains illegal chars bool IsFullFileNameValid( const wxString& aFullFilename ) @@ -1625,3 +1627,22 @@ std::vector ExpandStackedPinNotation( const wxString& aPinName, bool* return expanded; } + + +wxString GetDefaultVariantName() +{ + return wxString( defaultVariantName ); +} + + +int SortVariantNames( const wxString& aLhs, const wxString& aRhs ) +{ + if( ( aLhs == defaultVariantName ) && ( aRhs != defaultVariantName ) ) + return -1; + + if( ( aLhs != defaultVariantName ) && ( aRhs == defaultVariantName ) ) + return 1; + + return StrNumCmp( aLhs, aRhs ); +} + diff --git a/eeschema/dialogs/dialog_symbol_fields_table.cpp b/eeschema/dialogs/dialog_symbol_fields_table.cpp index 908da7059d..1d8d071bf5 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table.cpp +++ b/eeschema/dialogs/dialog_symbol_fields_table.cpp @@ -22,7 +22,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ - +#include #include #include #include @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -208,6 +207,10 @@ DIALOG_SYMBOL_FIELDS_TABLE::DIALOG_SYMBOL_FIELDS_TABLE( SCH_EDIT_FRAME* parent, m_removeFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); m_renameFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) ); + m_addVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) ); + m_deleteVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); + m_renameVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) ); + m_sidebarButton->SetBitmap( KiBitmapBundle( BITMAPS::left ) ); m_viewControlsDataModel = new VIEW_CONTROLS_GRID_DATA_MODEL( true ); @@ -258,6 +261,11 @@ DIALOG_SYMBOL_FIELDS_TABLE::DIALOG_SYMBOL_FIELDS_TABLE( SCH_EDIT_FRAME* parent, m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_viewControlsDataModel, m_dataModel, &m_parent->Schematic() ) ); + m_variantListBox->Set( parent->Schematic().GetVariantNamesForUI() ); + + if( !ADVANCED_CFG::GetCfg().m_EnableVariantsUI ) + bLeftSizer->Hide( variantSizer, true ); + // Load our BOM view presets SetUserBomPresets( m_schSettings.m_BomPresets ); @@ -650,20 +658,23 @@ bool DIALOG_SYMBOL_FIELDS_TABLE::TransferDataFromWindow() return true; } + std::set selectedVariantNames; SCH_COMMIT commit( m_parent ); SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet(); - m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames ); + m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, selectedVariantNames ); - commit.Push( wxS( "Symbol Fields Table Edit" ) ); + if( !commit.Empty() ) + { + commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer. + m_parent->OnModify(); + } // Reset the view to where we left the user m_parent->SetCurrentSheet( currentSheet ); m_parent->SyncView(); m_parent->Refresh(); - m_parent->OnModify(); - return true; } @@ -679,7 +690,7 @@ void DIALOG_SYMBOL_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxS return; } - m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser ); + m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser, getSelectedVariants() ); wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 ); m_grid->ProcessTableMessage( msg ); @@ -1177,7 +1188,6 @@ void DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged( wxGridEvent& aEvent void DIALOG_SYMBOL_FIELDS_TABLE::OnTableValueChanged( wxGridEvent& aEvent ) { m_grid->ForceRefresh(); - OnModify(); } @@ -1949,7 +1959,7 @@ void DIALOG_SYMBOL_FIELDS_TABLE::doApplyBomPreset( const BOM_PRESET& aPreset ) // Basically, we apply the BOM preset to the data model and then // update our UI to reflect resulting the data model state, not the preset. - m_dataModel->ApplyBomPreset( aPreset ); + m_dataModel->ApplyBomPreset( aPreset, getSelectedVariants() ); // BOM Presets can add, but not remove, columns, so make sure the view controls // grid has all of them before starting @@ -2493,7 +2503,7 @@ void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch for( SCH_FIELD& field : symbol->GetFields() ) AddField( field.GetCanonicalName(), field.GetName(), true, false, true ); - m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ) ); + m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ), getSelectedVariants() ); } else if( item->Type() == SCH_SHEET_T ) { @@ -2510,7 +2520,7 @@ void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch AddField( field.GetCanonicalName(), field.GetName(), true, false, true ); } - m_dataModel->UpdateReferences( refs ); + m_dataModel->UpdateReferences( refs, getSelectedVariants() ); } } @@ -2604,3 +2614,106 @@ SCH_REFERENCE_LIST DIALOG_SYMBOL_FIELDS_TABLE::getSheetSymbolReferences( SCH_SHE return sheetRefs; } + + +void DIALOG_SYMBOL_FIELDS_TABLE::onAddVariant( wxCommandEvent& aEvent ) +{ + wxTextEntryDialog dlg( this, _( "Add new variant name:" ), _( "New Variant" ), wxEmptyString, + wxOK | wxCANCEL | wxCENTER ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return; + + // Empty strings and duplicate variant names are not allowed. + if( dlg.GetValue().IsEmpty() || ( m_variantListBox->FindString( dlg.GetValue() ) != wxNOT_FOUND ) ) + { + wxBell(); + return; + } + + wxArrayString ctrlContents = m_variantListBox->GetStrings(); + + ctrlContents.Add( dlg.GetValue() ); + ctrlContents.Sort( SortVariantNames ); + m_variantListBox->Set( ctrlContents ); +} + + +void DIALOG_SYMBOL_FIELDS_TABLE::onDeleteVariant( wxCommandEvent& aEvent ) +{ + wxArrayInt selections; + + // An empty or default selection cannot be deleted. + if( ( m_variantListBox->GetSelections( selections ) == 0 ) || ( selections[0] == 0 ) ) + { + wxBell(); + return; + } + + wxArrayString ctrlContents = m_variantListBox->GetStrings(); + + for( int selection : selections ) + { + wxString variantName = m_variantListBox->GetString( selection ); + ctrlContents.Remove( variantName ); + m_parent->Schematic().DeleteVariant( variantName ); + } + + m_variantListBox->Set( ctrlContents ); +} + + +void DIALOG_SYMBOL_FIELDS_TABLE::onRenameVariant( wxCommandEvent& aEvent ) +{ + wxArrayInt selections; + + // Only allow renaming a single selection that is not the default. + if( ( m_variantListBox->GetSelections( selections ) != 1 ) || ( selections[0] == 0 ) ) + { + wxBell(); + return; + } + + wxArrayString ctrlContents = m_variantListBox->GetStrings(); + wxString oldVariantName = ctrlContents[selections[0]]; + + wxTextEntryDialog dlg( this, _( "Add new variant name:" ), _( "New Variant" ), oldVariantName, + wxOK | wxCANCEL | wxCENTER ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return; + + wxString newVariantName = dlg.GetValue(); + + if( newVariantName.IsEmpty() || ( newVariantName == oldVariantName ) || ( newVariantName == ctrlContents[0] ) ) + { + wxBell(); + return; + } + + ctrlContents.Remove( m_variantListBox->GetString( selections[0] ) ); + ctrlContents.Add( newVariantName ); + ctrlContents.Sort( SortVariantNames ); + m_variantListBox->Set( ctrlContents ); +} + + +std::set DIALOG_SYMBOL_FIELDS_TABLE::getSelectedVariants() const +{ + std::set retv; + + wxArrayInt selections; + + if( m_variantListBox->GetSelections( selections ) ) + { + for( int selection : selections ) + { + if( selection == 0 ) + continue; + + retv.emplace( m_variantListBox->GetString( selection ) ); + } + } + + return retv; +} diff --git a/eeschema/dialogs/dialog_symbol_fields_table.h b/eeschema/dialogs/dialog_symbol_fields_table.h index c8b867bb2b..4f836abe24 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table.h +++ b/eeschema/dialogs/dialog_symbol_fields_table.h @@ -139,6 +139,12 @@ private: void savePresetsToSchematic(); + void onAddVariant( wxCommandEvent& aEvent ) override; + void onDeleteVariant( wxCommandEvent& aEvent ) override; + void onRenameVariant( wxCommandEvent& aEvent ) override; + + std::set getSelectedVariants() const; + private: std::map m_bomPresets; BOM_PRESET* m_currentBomPreset; diff --git a/eeschema/dialogs/dialog_symbol_fields_table_base.cpp b/eeschema/dialogs/dialog_symbol_fields_table_base.cpp index b6a8a7c36e..377db0560f 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table_base.cpp +++ b/eeschema/dialogs/dialog_symbol_fields_table_base.cpp @@ -23,7 +23,6 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare m_splitterMainWindow->SetMinimumPaneSize( 120 ); m_leftPanel = new wxPanel( m_splitterMainWindow, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - wxBoxSizer* bLeftSizer; bLeftSizer = new wxBoxSizer( wxVERTICAL ); wxBoxSizer* bMargins; @@ -71,7 +70,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare m_addFieldButton = new STD_BITMAP_BUTTON( m_leftPanel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); m_addFieldButton->SetToolTip( _("Add a new field") ); - bFieldsButtons->Add( m_addFieldButton, 0, wxBOTTOM|wxLEFT, 5 ); + bFieldsButtons->Add( m_addFieldButton, 0, wxBOTTOM, 5 ); m_renameFieldButton = new STD_BITMAP_BUTTON( m_leftPanel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); m_renameFieldButton->SetToolTip( _("Rename selected field") ); @@ -89,15 +88,48 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare bMargins->Add( bFieldsButtons, 0, wxEXPAND|wxTOP, 5 ); + variantSizer = new wxBoxSizer( wxVERTICAL ); + + m_staticline6 = new wxStaticLine( m_leftPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); + variantSizer->Add( m_staticline6, 0, wxEXPAND|wxTOP, 5 ); + + m_staticText9 = new wxStaticText( m_leftPanel, wxID_ANY, _("Variants:"), wxDefaultPosition, wxDefaultSize, 0 ); + m_staticText9->Wrap( -1 ); + variantSizer->Add( m_staticText9, 0, wxBOTTOM|wxTOP, 5 ); + + m_variantListBox = new wxListBox( m_leftPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + variantSizer->Add( m_variantListBox, 1, wxBOTTOM|wxEXPAND, 5 ); + + wxBoxSizer* bSizer14; + bSizer14 = new wxBoxSizer( wxHORIZONTAL ); + + m_addVariantButton = new wxBitmapButton( m_leftPanel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); + bSizer14->Add( m_addVariantButton, 0, wxRIGHT, 5 ); + + m_renameVariantButton = new wxBitmapButton( m_leftPanel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); + bSizer14->Add( m_renameVariantButton, 0, 0, 5 ); + + + bSizer14->Add( 15, 0, 0, 0, 5 ); + + m_deleteVariantButton = new wxBitmapButton( m_leftPanel, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 ); + bSizer14->Add( m_deleteVariantButton, 0, wxLEFT, 5 ); + + + variantSizer->Add( bSizer14, 0, wxEXPAND, 5 ); + + + bMargins->Add( variantSizer, 1, wxEXPAND, 5 ); + wxBoxSizer* bPresets; bPresets = new wxBoxSizer( wxVERTICAL ); m_staticline11 = new wxStaticLine( m_leftPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); - bPresets->Add( m_staticline11, 0, wxEXPAND|wxBOTTOM, 5 ); + bPresets->Add( m_staticline11, 0, wxBOTTOM|wxEXPAND|wxTOP, 5 ); m_bomPresetsLabel = new wxStaticText( m_leftPanel, wxID_ANY, _("View presets:"), wxDefaultPosition, wxDefaultSize, 0 ); m_bomPresetsLabel->Wrap( -1 ); - bPresets->Add( m_bomPresetsLabel, 0, wxLEFT, 5 ); + bPresets->Add( m_bomPresetsLabel, 0, 0, 5 ); bPresets->Add( 0, 2, 0, 0, 5 ); @@ -106,7 +138,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare int m_cbBomPresetsNChoices = sizeof( m_cbBomPresetsChoices ) / sizeof( wxString ); m_cbBomPresets = new wxChoice( m_leftPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_cbBomPresetsNChoices, m_cbBomPresetsChoices, 0 ); m_cbBomPresets->SetSelection( 0 ); - bPresets->Add( m_cbBomPresets, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + bPresets->Add( m_cbBomPresets, 0, wxEXPAND, 5 ); bPresets->Add( 0, 2, 0, wxEXPAND, 5 ); @@ -115,7 +147,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare bMargins->Add( bPresets, 0, wxEXPAND|wxTOP|wxBOTTOM, 5 ); - bLeftSizer->Add( bMargins, 1, wxEXPAND, 5 ); + bLeftSizer->Add( bMargins, 1, wxEXPAND|wxLEFT|wxRIGHT|wxTOP, 5 ); m_leftPanel->SetSizer( bLeftSizer ); @@ -386,6 +418,9 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare m_addFieldButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnAddField ), NULL, this ); m_renameFieldButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRenameField ), NULL, this ); m_removeFieldButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRemoveField ), NULL, this ); + m_addVariantButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::onAddVariant ), NULL, this ); + m_renameVariantButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::onRenameVariant ), NULL, this ); + m_deleteVariantButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::onDeleteVariant ), NULL, this ); m_nbPages->Connect( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, wxNotebookEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnPageChanged ), NULL, this ); m_filter->Connect( wxEVT_MOTION, wxMouseEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnFilterMouseMoved ), NULL, this ); m_filter->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnFilterText ), NULL, this ); @@ -421,6 +456,9 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::~DIALOG_SYMBOL_FIELDS_TABLE_BASE() m_addFieldButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnAddField ), NULL, this ); m_renameFieldButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRenameField ), NULL, this ); m_removeFieldButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRemoveField ), NULL, this ); + m_addVariantButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::onAddVariant ), NULL, this ); + m_renameVariantButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::onRenameVariant ), NULL, this ); + m_deleteVariantButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::onDeleteVariant ), NULL, this ); m_nbPages->Disconnect( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, wxNotebookEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnPageChanged ), NULL, this ); m_filter->Disconnect( wxEVT_MOTION, wxMouseEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnFilterMouseMoved ), NULL, this ); m_filter->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnFilterText ), NULL, this ); diff --git a/eeschema/dialogs/dialog_symbol_fields_table_base.fbp b/eeschema/dialogs/dialog_symbol_fields_table_base.fbp index 58056f1879..57663ca262 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table_base.fbp +++ b/eeschema/dialogs/dialog_symbol_fields_table_base.fbp @@ -184,10 +184,10 @@ bLeftSizer wxVERTICAL - none + protected 5 - wxEXPAND + wxEXPAND|wxLEFT|wxRIGHT|wxTOP 1 @@ -296,7 +296,7 @@ none 5 - wxBOTTOM|wxLEFT + wxBOTTOM 0 1 @@ -531,6 +531,448 @@ + + 5 + wxEXPAND + 1 + + + variantSizer + wxVERTICAL + protected + + 5 + wxEXPAND|wxTOP + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_staticline6 + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; ; forward_declare + 0 + + + + + + + + 5 + wxBOTTOM|wxTOP + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Variants: + 0 + + 0 + + + 0 + + 1 + m_staticText9 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxBOTTOM|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_variantListBox + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxEXPAND + 0 + + + bSizer14 + wxHORIZONTAL + none + + 5 + wxRIGHT + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + MyButton + + 0 + + 0 + + + 0 + + 1 + m_addVariantButton + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onAddVariant + + + + 5 + + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + MyButton + + 0 + + 0 + + + 0 + + 1 + m_renameVariantButton + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onRenameVariant + + + + 5 + + 0 + + 0 + protected + 15 + + + + 5 + wxLEFT + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + MyButton + + 0 + + 0 + + + 0 + + 1 + m_deleteVariantButton + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + onDeleteVariant + + + + + + 5 wxEXPAND|wxTOP|wxBOTTOM @@ -542,7 +984,7 @@ none 5 - wxEXPAND|wxBOTTOM + wxBOTTOM|wxEXPAND|wxTOP 0 1 @@ -601,7 +1043,7 @@ 5 - wxLEFT + 0 1 @@ -673,7 +1115,7 @@ 5 - wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT + wxEXPAND 0 1 diff --git a/eeschema/dialogs/dialog_symbol_fields_table_base.h b/eeschema/dialogs/dialog_symbol_fields_table_base.h index d992508a65..108d1070c4 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table_base.h +++ b/eeschema/dialogs/dialog_symbol_fields_table_base.h @@ -28,6 +28,7 @@ class WX_GRID; #include #include #include +#include #include #include #include @@ -50,10 +51,18 @@ class DIALOG_SYMBOL_FIELDS_TABLE_BASE : public DIALOG_SHIM protected: wxSplitterWindow* m_splitterMainWindow; wxPanel* m_leftPanel; + wxBoxSizer* bLeftSizer; WX_GRID* m_viewControlsGrid; STD_BITMAP_BUTTON* m_addFieldButton; STD_BITMAP_BUTTON* m_renameFieldButton; STD_BITMAP_BUTTON* m_removeFieldButton; + wxBoxSizer* variantSizer; + wxStaticLine* m_staticline6; + wxStaticText* m_staticText9; + wxListBox* m_variantListBox; + wxBitmapButton* m_addVariantButton; + wxBitmapButton* m_renameVariantButton; + wxBitmapButton* m_deleteVariantButton; wxStaticLine* m_staticline11; wxStaticText* m_bomPresetsLabel; wxChoice* m_cbBomPresets; @@ -103,6 +112,9 @@ class DIALOG_SYMBOL_FIELDS_TABLE_BASE : public DIALOG_SHIM virtual void OnAddField( wxCommandEvent& event ) { event.Skip(); } virtual void OnRenameField( wxCommandEvent& event ) { event.Skip(); } virtual void OnRemoveField( wxCommandEvent& event ) { event.Skip(); } + virtual void onAddVariant( wxCommandEvent& event ) { event.Skip(); } + virtual void onRenameVariant( wxCommandEvent& event ) { event.Skip(); } + virtual void onDeleteVariant( wxCommandEvent& event ) { event.Skip(); } virtual void OnPageChanged( wxNotebookEvent& event ) { event.Skip(); } virtual void OnFilterMouseMoved( wxMouseEvent& event ) { event.Skip(); } virtual void OnFilterText( wxCommandEvent& event ) { event.Skip(); } diff --git a/eeschema/dialogs/dialog_symbol_properties.cpp b/eeschema/dialogs/dialog_symbol_properties.cpp index 6f9b915f09..2e6f1710cd 100644 --- a/eeschema/dialogs/dialog_symbol_properties.cpp +++ b/eeschema/dialogs/dialog_symbol_properties.cpp @@ -526,7 +526,7 @@ bool DIALOG_SYMBOL_PROPERTIES::TransferDataToWindow() m_cbExcludeFromSim->SetValue( m_symbol->GetExcludedFromSim() ); m_cbExcludeFromBom->SetValue( m_symbol->GetExcludedFromBOM() ); m_cbExcludeFromBoard->SetValue( m_symbol->GetExcludedFromBoard() ); - m_cbDNP->SetValue( m_symbol->GetDNP() ); + m_cbDNP->SetValue( m_symbol->GetDNP( &GetParent()->GetCurrentSheet() ) ); if( m_part ) { @@ -759,7 +759,7 @@ bool DIALOG_SYMBOL_PROPERTIES::TransferDataFromWindow() m_symbol->SetExcludedFromSim( m_cbExcludeFromSim->IsChecked() ); m_symbol->SetExcludedFromBOM( m_cbExcludeFromBom->IsChecked() ); m_symbol->SetExcludedFromBoard( m_cbExcludeFromBoard->IsChecked() ); - m_symbol->SetDNP( m_cbDNP->IsChecked() ); + m_symbol->SetDNP( m_cbDNP->IsChecked(), &GetParent()->GetCurrentSheet() ); // Update any assignments if( m_dataModel ) diff --git a/eeschema/eeschema_id.h b/eeschema/eeschema_id.h index cf29a845be..2373b0fb79 100644 --- a/eeschema/eeschema_id.h +++ b/eeschema/eeschema_id.h @@ -109,7 +109,9 @@ enum id_eeschema_frm ID_POPUP_SCH_PIN_TRICKS_END = ID_POPUP_SCH_PIN_TRICKS_GLOBAL_LABEL, ID_POPUP_SCH_ALT_PIN_FUNCTION, - ID_POPUP_SCH_ALT_PIN_FUNCTION_END = ID_POPUP_SCH_ALT_PIN_FUNCTION + MAX_ALT_PIN_FUNCTION_ITEMS + ID_POPUP_SCH_ALT_PIN_FUNCTION_END = ID_POPUP_SCH_ALT_PIN_FUNCTION + MAX_ALT_PIN_FUNCTION_ITEMS, + + ID_TOOLBAR_SCH_SELECT_VARAIANT }; diff --git a/eeschema/eeschema_jobs_handler.cpp b/eeschema/eeschema_jobs_handler.cpp index 5dafd76da8..2bff215cc4 100644 --- a/eeschema/eeschema_jobs_handler.cpp +++ b/eeschema/eeschema_jobs_handler.cpp @@ -530,7 +530,7 @@ int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob ) for( FIELD_T fieldId : MANDATORY_FIELDS ) { dataModel.AddColumn( GetCanonicalFieldName( fieldId ), - GetDefaultFieldName( fieldId, DO_TRANSLATE ), false ); + GetDefaultFieldName( fieldId, DO_TRANSLATE ), false, {} ); } // User field names in symbols second @@ -548,7 +548,7 @@ int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob ) } for( const wxString& fieldName : userFieldNames ) - dataModel.AddColumn( fieldName, GetGeneratedFieldDisplayName( fieldName ), true ); + dataModel.AddColumn( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, {} ); // Add any templateFieldNames which aren't already present in the userFieldNames for( const TEMPLATE_FIELDNAME& templateFieldname : @@ -557,7 +557,7 @@ int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob ) if( userFieldNames.count( templateFieldname.m_Name ) == 0 ) { dataModel.AddColumn( templateFieldname.m_Name, GetGeneratedFieldDisplayName( templateFieldname.m_Name ), - false ); + false, {} ); } } @@ -670,7 +670,7 @@ int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob ) preset.excludeDNP = aBomJob->m_excludeDNP; } - dataModel.ApplyBomPreset( preset ); + dataModel.ApplyBomPreset( preset, {} ); if( aBomJob->GetConfiguredOutputPath().IsEmpty() ) { diff --git a/eeschema/fields_data_model.cpp b/eeschema/fields_data_model.cpp index 927fbbe6d5..d4f9c3239d 100644 --- a/eeschema/fields_data_model.cpp +++ b/eeschema/fields_data_model.cpp @@ -30,6 +30,9 @@ #include "fields_data_model.h" +static wxString multipleValues = wxS( "<...>" ); + + wxString VIEW_CONTROLS_GRID_DATA_MODEL::GetColLabelValue( int aCol ) { switch( aCol ) @@ -182,7 +185,7 @@ const wxString FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITE void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel, - bool aAddedByUser ) + bool aAddedByUser, const std::set& aVariantNames ) { // Don't add a field twice if( GetFieldNameCol( aFieldName ) != -1 ) @@ -191,40 +194,61 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } ); for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) - { - if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() ) - updateDataStoreSymbolField( *symbol, aFieldName ); - } + updateDataStoreSymbolField( m_symbolsList[i], aFieldName, aVariantNames ); } -void FIELDS_EDITOR_GRID_DATA_MODEL::updateDataStoreSymbolField( const SCH_SYMBOL& aSymbol, - const wxString& aFieldName ) +void FIELDS_EDITOR_GRID_DATA_MODEL::updateDataStoreSymbolField( const SCH_REFERENCE& aSymbolRef, + const wxString& aFieldName, + const std::set& aVariantNames ) { + const SCH_SYMBOL* symbol = aSymbolRef.GetSymbol(); + + if( !symbol ) + return; + if( isAttribute( aFieldName ) ) { - m_dataStore[aSymbol.m_Uuid][aFieldName] = getAttributeValue( aSymbol, aFieldName ); + m_dataStore[symbol->m_Uuid][aFieldName] = getAttributeValue( *symbol, aFieldName, aVariantNames ); } - else if( const SCH_FIELD* field = aSymbol.GetField( aFieldName ) ) + else if( const SCH_FIELD* field = symbol->GetField( aFieldName ) ) { if( field->IsPrivate() ) { - m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString; + m_dataStore[symbol->m_Uuid][aFieldName] = wxEmptyString; return; } - wxString value = aSymbol.Schematic()->ConvertKIIDsToRefs( field->GetText() ); - m_dataStore[aSymbol.m_Uuid][aFieldName] = value; + wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field->GetText() ); + + for( const wxString& variantName : aVariantNames ) + { + std::optional variant = symbol->GetVariant( aSymbolRef.GetSheetPath(), variantName ); + + if( !variant || !variant->m_Fields.contains( aFieldName ) ) + continue; + + if( value.IsEmpty() ) + { + value = variant->m_Fields[aFieldName]; + continue; + } + + if( value != variant->m_Fields[aFieldName] ) + value = multipleValues; + } + + m_dataStore[symbol->m_Uuid][aFieldName] = value; } else if( IsGeneratedField( aFieldName ) ) { // Handle generated fields with variables as names (e.g. ${QUANTITY}) that are not present in // the symbol by giving them the correct value - m_dataStore[aSymbol.m_Uuid][aFieldName] = aFieldName; + m_dataStore[symbol->m_Uuid][aFieldName] = aFieldName; } else { - m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString; + m_dataStore[symbol->m_Uuid][aFieldName] = wxEmptyString; } } @@ -703,35 +727,66 @@ bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName ) } -wxString FIELDS_EDITOR_GRID_DATA_MODEL::getAttributeValue( const SCH_SYMBOL& aSymbol, const wxString& aAttributeName ) +wxString FIELDS_EDITOR_GRID_DATA_MODEL::getAttributeValue( const SCH_SYMBOL& aSymbol, const wxString& aAttributeName, + const std::set& aVariantNames ) { - if( aAttributeName == wxS( "${DNP}" ) ) - return aSymbol.GetDNP() ? wxS( "1" ) : wxS( "0" ); + wxString retv; - if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) ) - return aSymbol.GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" ); + auto getAttrString = [&]( const wxString aVariantName = wxEmptyString )->wxString + { + if( aAttributeName == wxS( "${DNP}" ) ) + return aSymbol.GetDNP( nullptr, aVariantName ) ? wxS( "1" ) : wxS( "0" ); + + if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) ) + return aSymbol.GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" ); + + if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) ) + return aSymbol.GetExcludedFromBOM( nullptr, aVariantName ) ? wxS( "1" ) : wxS( "0" ); + + if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) ) + return aSymbol.GetExcludedFromSim( nullptr, aVariantName ) ? wxS( "1" ) : wxS( "0" ); - if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) ) - return aSymbol.GetExcludedFromBOM() ? wxS( "1" ) : wxS( "0" ); + return wxS( "0" ); + }; + + if( aVariantNames.empty() ) + { + retv = getAttrString(); + } + else + { + for( const wxString& variantName : aVariantNames ) + { + if( retv.IsEmpty() ) + { + retv = getAttrString( variantName ); + continue; + } - if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) ) - return aSymbol.GetExcludedFromSim() ? wxS( "1" ) : wxS( "0" ); + if( retv != getAttrString( variantName ) ) + retv = multipleValues; + } + } - return wxS( "0" ); + return retv; } bool FIELDS_EDITOR_GRID_DATA_MODEL::setAttributeValue( SCH_SYMBOL& aSymbol, const wxString& aAttributeName, - const wxString& aValue ) + const wxString& aValue, + const wxString& aVariantName ) { + if( aValue == multipleValues ) + return false; + bool attrChanged = false; bool newValue = aValue == wxS( "1" ); if( aAttributeName == wxS( "${DNP}" ) ) { - attrChanged = aSymbol.GetDNP() != newValue; - aSymbol.SetDNP( newValue ); + attrChanged = aSymbol.GetDNP( nullptr, aVariantName ) != newValue; + aSymbol.SetDNP( newValue, nullptr, aVariantName ); } else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) ) { @@ -740,13 +795,13 @@ bool FIELDS_EDITOR_GRID_DATA_MODEL::setAttributeValue( SCH_SYMBOL& aSymbol, } else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) ) { - attrChanged = aSymbol.GetExcludedFromBOM() != newValue; - aSymbol.SetExcludedFromBOM( newValue ); + attrChanged = aSymbol.GetExcludedFromBOM( nullptr, aVariantName ) != newValue; + aSymbol.SetExcludedFromBOM( newValue, nullptr, aVariantName ); } else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) ) { - attrChanged = aSymbol.GetExcludedFromSim() != newValue; - aSymbol.SetExcludedFromSim( newValue ); + attrChanged = aSymbol.GetExcludedFromSim( nullptr, aVariantName ) != newValue; + aSymbol.SetExcludedFromSim( newValue, nullptr, aVariantName ); } return attrChanged; @@ -791,7 +846,7 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::RebuildRows() if( !m_filter.IsEmpty() && !matcher.Find( ref.GetFullRef().Lower() ) ) continue; - if( m_excludeDNP && ( ref.GetSymbol()->GetDNP() + if( m_excludeDNP && ( ref.GetSymbol()->GetDNP( &ref.GetSheetPath() ) || ref.GetSheetPath().GetDNP() ) ) { continue; @@ -952,7 +1007,8 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandAfterSort() } -void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( SCH_COMMIT& aCommit, TEMPLATES& aTemplateFieldnames ) +void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( SCH_COMMIT& aCommit, TEMPLATES& aTemplateFieldnames, + std::set& aVariantNames ) { bool symbolModified = false; std::unique_ptr symbolCopy; @@ -975,7 +1031,16 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( SCH_COMMIT& aCommit, TEMPLATES& a // Attributes bypass the field logic, so handle them first if( isAttribute( srcName ) ) { - symbolModified |= setAttributeValue( *symbol, srcName, srcValue ); + if( aVariantNames.empty() ) + { + symbolModified |= setAttributeValue( *symbol, srcName, srcValue ); + } + else + { + for( const wxString& name : aVariantNames ) + symbolModified |= setAttributeValue( *symbol, srcName, srcValue, name ); + } + continue; } @@ -984,6 +1049,10 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( SCH_COMMIT& aCommit, TEMPLATES& a if( IsGeneratedField( srcName ) ) continue; + // Don't change values in the case of multiple variants selected. + if( srcValue == multipleValues ) + continue; + SCH_FIELD* destField = symbol->GetField( srcName ); if( destField && destField->IsPrivate() ) @@ -1017,15 +1086,40 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( SCH_COMMIT& aCommit, TEMPLATES& a if( !destField ) continue; + // Reference is not editable from this dialog if( destField->GetId() == FIELD_T::REFERENCE ) - { - // Reference is not editable from this dialog continue; - } wxString previousValue = destField->GetText(); - destField->SetText( symbol->Schematic()->ConvertRefsToKIIDs( srcValue ) ); + if( aVariantNames.empty() ) + { + destField->SetText( symbol->Schematic()->ConvertRefsToKIIDs( srcValue ) ); + } + else + { + for( const wxString& variantName : aVariantNames ) + { + std::optional variant = symbol->GetVariant( m_symbolsList[i].GetSheetPath(), + variantName ); + + if( !variant ) + { + SCH_SYMBOL_VARIANT newVariant( variantName ); + + newVariant.m_Fields[srcName] = srcValue; + symbol->AddVariant( m_symbolsList[i].GetSheetPath(), newVariant ); + symbolModified |= true; + } + else if( !variant->m_Fields.contains( srcName ) + || ( variant->m_Fields[srcName] != srcValue ) ) + { + variant->m_Fields[srcName] = srcValue; + symbol->AddVariant( m_symbolsList[i].GetSheetPath(), *variant ); + symbolModified |= true; + } + } + } if( !createField && ( previousValue != srcValue ) ) symbolModified = true; @@ -1088,7 +1182,8 @@ int FIELDS_EDITOR_GRID_DATA_MODEL::GetDataWidth( int aCol ) } -void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyBomPreset( const BOM_PRESET& aPreset ) +void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyBomPreset( const BOM_PRESET& aPreset, + const std::set& aVariantNames ) { // Hide and un-group everything by default for( size_t i = 0; i < m_cols.size(); i++ ) @@ -1116,7 +1211,7 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyBomPreset( const BOM_PRESET& aPreset ) // they won't be saved to the symbols anyway if( col == -1 ) { - AddColumn( field.name, field.label, true ); + AddColumn( field.name, field.label, true, aVariantNames ); col = GetFieldNameCol( field.name ); } else @@ -1312,7 +1407,8 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::RemoveReferences( const SCH_REFERENCE_LIST& } -void FIELDS_EDITOR_GRID_DATA_MODEL::UpdateReferences( const SCH_REFERENCE_LIST& aRefs ) +void FIELDS_EDITOR_GRID_DATA_MODEL::UpdateReferences( const SCH_REFERENCE_LIST& aRefs, + const std::set& aVariantNames ) { bool refListChanged = false; @@ -1322,7 +1418,7 @@ void FIELDS_EDITOR_GRID_DATA_MODEL::UpdateReferences( const SCH_REFERENCE_LIST& // columns; we must have all fields in the symbol added to the data model at this point, // and some of the data model columns may be variables that are not present in the symbol for( const DATA_MODEL_COL& col : m_cols ) - updateDataStoreSymbolField( *ref.GetSymbol(), col.m_fieldName ); + updateDataStoreSymbolField( ref, col.m_fieldName, aVariantNames ); if( !m_symbolsList.Contains( ref ) ) { diff --git a/eeschema/fields_data_model.h b/eeschema/fields_data_model.h index 007eaa1fae..47aec7bbe1 100644 --- a/eeschema/fields_data_model.h +++ b/eeschema/fields_data_model.h @@ -162,7 +162,8 @@ public: static const wxString QUANTITY_VARIABLE; static const wxString ITEM_NUMBER_VARIABLE; - void AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser ); + void AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser, + const std::set& aVariantNames ); void RemoveColumn( int aCol ); void RenameColumn( int aCol, const wxString& newName ); @@ -270,7 +271,7 @@ public: void CollapseForSort(); void ExpandAfterSort(); - void ApplyData( SCH_COMMIT& aCommit, TEMPLATES& aTemplateFieldnames ); + void ApplyData( SCH_COMMIT& aCommit, TEMPLATES& aTemplateFieldnames, std::set& aVariantNames ); bool IsEdited() { return m_edited; } @@ -322,14 +323,14 @@ public: return m_cols[aCol].m_show; } - void ApplyBomPreset( const BOM_PRESET& preset ); + void ApplyBomPreset( const BOM_PRESET& preset, const std::set& aVariantNames ); BOM_PRESET GetBomSettings(); wxString Export( const BOM_FMT_PRESET& settings ); void AddReferences( const SCH_REFERENCE_LIST& aRefs ); void RemoveReferences( const SCH_REFERENCE_LIST& aRefs ); void RemoveSymbol( const SCH_SYMBOL& aSymbol ); - void UpdateReferences( const SCH_REFERENCE_LIST& aRefs ); + void UpdateReferences( const SCH_REFERENCE_LIST& aRefs, const std::set& aVariantNames ); private: static bool cmp( const DATA_MODEL_ROW& lhGroup, const DATA_MODEL_ROW& rhGroup, @@ -341,7 +342,8 @@ private: // Helper functions to deal with translating wxGrid values to and from // named field values like ${DNP} bool isAttribute( const wxString& aFieldName ); - wxString getAttributeValue( const SCH_SYMBOL&, const wxString& aAttributeName ); + wxString getAttributeValue( const SCH_SYMBOL&, const wxString& aAttributeName, + const std::set& aVariantNames ); /** * Set the attribute value. @@ -349,10 +351,12 @@ private: * @param aSymbol is the symbol to set the attribute. * @param aAttributeName is the name of the symbol attribute. * @param aValue is the value to set the attribute. + * @param aVariantName is an optional variant name to set the variant attribute. * @retval true if the symbol attribute value has changed. * @retval false if the symbol attribute has **not** changed. */ - bool setAttributeValue( SCH_SYMBOL& aSymbol, const wxString& aAttributeName, const wxString& aValue ); + bool setAttributeValue( SCH_SYMBOL& aSymbol, const wxString& aAttributeName, const wxString& aValue, + const wxString& aVariantName = wxEmptyString ); /* Helper function to get the resolved field value. * Handles symbols that are missing fields that would have a variable @@ -362,7 +366,8 @@ private: void Sort(); - void updateDataStoreSymbolField( const SCH_SYMBOL& aSymbol, const wxString& aFieldName ); + void updateDataStoreSymbolField( const SCH_REFERENCE& aSymbolRef, const wxString& aFieldName, + const std::set& aVariantNames ); protected: /** diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp index b643338941..e240821af2 100644 --- a/eeschema/menubar.cpp +++ b/eeschema/menubar.cpp @@ -320,6 +320,13 @@ void SCH_EDIT_FRAME::doReCreateMenuBar() toolsMenu->AppendSeparator(); toolsMenu->Add( ACTIONS::updateSchematicFromPcb )->Enable( !Kiface().IsSingle() ); + toolsMenu->AppendSeparator(); + ACTION_MENU* submenuVariants = new ACTION_MENU( false, selTool ); + submenuVariants->SetTitle( _( "Variants" ) ); + submenuVariants->Add( SCH_ACTIONS::addVariant ); + submenuVariants->Add( SCH_ACTIONS::removeVariant ); + toolsMenu->Add( submenuVariants ); + #ifdef KICAD_IPC_API toolsMenu->AppendSeparator(); toolsMenu->Add( ACTIONS::pluginsReload ); diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index 1d41bd64fd..9bbdf4ff74 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -248,6 +248,15 @@ public: */ void UpdateDesignBlockOptions(); + /** + * Update the variant name control on the main toolbar. + * + * Updating will attempt to maintain the current selection if it's available. If the current + * selection is no longer available, the default (no variant) will be selected. + * + */ + void UpdateVariantSelectionCtrl( const wxArrayString& aVariantNames ); + /** * Test all of the connectable objects in the schematic for unused connection points. * @@ -1031,6 +1040,8 @@ private: std::vector m_designBlockHistoryList; SCH_DESIGN_BLOCK_PANE* m_designBlocksPane; + wxChoice* m_currentVariantCtrl; + #ifdef KICAD_IPC_API std::unique_ptr m_apiHandler; #endif diff --git a/eeschema/sch_field.cpp b/eeschema/sch_field.cpp index b1cbf2b061..3305c5cb9f 100644 --- a/eeschema/sch_field.cpp +++ b/eeschema/sch_field.cpp @@ -189,8 +189,7 @@ wxString SCH_FIELD::GetShownName() const } -wxString SCH_FIELD::GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraText, - int aDepth ) const +wxString SCH_FIELD::GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraText, int aDepth ) const { std::function libSymbolResolver = [&]( wxString* token ) -> bool @@ -248,7 +247,7 @@ wxString SCH_FIELD::GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraT return label->ResolveTextVar( aPath, token, aDepth + 1 ); }; - wxString text = EDA_TEXT::GetShownText( aAllowExtraText, aDepth ); + wxString text = getUnescapedText( aPath ); if( IsNameShown() && aAllowExtraText ) text = GetShownName() << wxS( ": " ) << text; @@ -273,16 +272,6 @@ wxString SCH_FIELD::GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraT } } - if( m_id == FIELD_T::REFERENCE && aPath ) - { - SCH_SYMBOL* parentSymbol = static_cast( m_parent ); - - // For more than one part per package, we must add the part selection - // A, B, ... or 1, 2, .. to the reference. - if( parentSymbol && parentSymbol->GetUnitCount() > 1 ) - text << parentSymbol->SubReference( parentSymbol->GetUnitSelection( aPath ) ); - } - if( m_id == FIELD_T::SHEET_FILENAME && aAllowExtraText && !IsNameShown() ) text = _( "File:" ) + wxS( " " ) + text; @@ -1598,6 +1587,37 @@ int SCH_FIELD::compare( const SCH_ITEM& aOther, int aCompareFlags ) const } +wxString SCH_FIELD::getUnescapedText( const SCH_SHEET_PATH* aPath, const wxString& aVariantName ) const +{ + wxString retv = EDA_TEXT::GetShownText( false ); + + // Special handling for parent object field instance and variant information. + if( m_parent && aPath ) + { + switch( m_parent->Type() ) + { + case SCH_SYMBOL_T: + { + const SCH_SYMBOL* symbol = static_cast( m_parent ); + + if( m_id == FIELD_T::REFERENCE ) + retv = symbol->GetRef( aPath, true ); + + break; + } + + case SCH_SHEET_T: + break; + + default: + break; + } + } + + return retv; +} + + static struct SCH_FIELD_DESC { SCH_FIELD_DESC() diff --git a/eeschema/sch_field.h b/eeschema/sch_field.h index 4ba324d31f..4db7aec81b 100644 --- a/eeschema/sch_field.h +++ b/eeschema/sch_field.h @@ -131,8 +131,7 @@ public: * with the ${} stripped. */ wxString GetShownName() const; - wxString GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraText, - int aDepth = 0 ) const; + wxString GetShownText( const SCH_SHEET_PATH* aPath, bool aAllowExtraText, int aDepth = 0 ) const; wxString GetShownText( bool aAllowExtraText, int aDepth = 0 ) const override; @@ -325,6 +324,9 @@ protected: void setId( FIELD_T aId ); + wxString getUnescapedText( const SCH_SHEET_PATH* aPath = nullptr, + const wxString& aVariantName = wxEmptyString ) const; + private: FIELD_T m_id; ///< Field id, @see enum FIELD_T int m_ordinal; ///< Sort order for non-mandatory fields diff --git a/eeschema/sch_file_versions.h b/eeschema/sch_file_versions.h index 3c5396921a..76285c21a8 100644 --- a/eeschema/sch_file_versions.h +++ b/eeschema/sch_file_versions.h @@ -129,4 +129,5 @@ //#define SEXPR_SCHEMATIC_FILE_VERSION 20250610 // DNP, etc. flags for rule areas //#define SEXPR_SCHEMATIC_FILE_VERSION 20250827 // Custom body styles //#define SEXPR_SCHEMATIC_FILE_VERSION 20250829 // Rounded Rectangles -#define SEXPR_SCHEMATIC_FILE_VERSION 20250901 // Stacked Pin notation +//#define SEXPR_SCHEMATIC_FILE_VERSION 20250901 // Stacked Pin notation +#define SEXPR_SCHEMATIC_FILE_VERSION 20250922 // Schematic variants. diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp index d7713a8a3e..73b6b38012 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp @@ -735,7 +735,7 @@ void SCH_IO_KICAD_SEXPR::saveSymbol( SCH_SYMBOL* aSymbol, const SCHEMATIC& aSche KICAD_FORMAT::FormatBool( m_out, "exclude_from_sim", aSymbol->GetExcludedFromSim() ); KICAD_FORMAT::FormatBool( m_out, "in_bom", !aSymbol->GetExcludedFromBOM() ); KICAD_FORMAT::FormatBool( m_out, "on_board", !aSymbol->GetExcludedFromBoard() ); - KICAD_FORMAT::FormatBool( m_out, "dnp", aSymbol->GetDNP() ); + KICAD_FORMAT::FormatBool( m_out, "dnp", ordinalInstance.m_DNP ); AUTOPLACE_ALGO fieldsAutoplaced = aSymbol->GetFieldsAutoplaced(); @@ -861,10 +861,37 @@ void SCH_IO_KICAD_SEXPR::saveSymbol( SCH_SYMBOL* aSymbol, const SCHEMATIC& aSche path = tmp.AsString(); - m_out->Print( "(path %s (reference %s) (unit %d))", + m_out->Print( "(path %s (reference %s) (unit %d)", m_out->Quotew( path ).c_str(), m_out->Quotew( instance.m_Reference ).c_str(), instance.m_Unit ); + + if( !instance.m_Variants.empty() ) + { + for( const auto&[name, variant] : instance.m_Variants ) + { + m_out->Print( "(variant (name %s)", m_out->Quotew( name ).c_str() ); + + if( variant.m_DNP != aSymbol->GetDNP() ) + KICAD_FORMAT::FormatBool( m_out, "dnp", variant.m_DNP ); + + if( variant.m_ExcludedFromSim != aSymbol->GetExcludedFromSim() ) + KICAD_FORMAT::FormatBool( m_out, "exclude_from_sim", variant.m_ExcludedFromSim ); + + if( variant.m_ExcludedFromBOM != aSymbol->GetExcludedFromBOM() ) + KICAD_FORMAT::FormatBool( m_out, "in_bom", variant.m_ExcludedFromBOM ); + + for( const auto&[fname, fvalue] : variant.m_Fields ) + { + m_out->Print( "(field (name %s) (value %s))", + m_out->Quotew( fname ).c_str(), m_out->Quotew( fvalue ).c_str() ); + } + + m_out->Print( ")" ); // Closes `variant` token. + } + } + + m_out->Print( ")" ); // Closes `path` token. } m_out->Print( ")" ); // Closes `project`. @@ -873,7 +900,7 @@ void SCH_IO_KICAD_SEXPR::saveSymbol( SCH_SYMBOL* aSymbol, const SCHEMATIC& aSche m_out->Print( ")" ); // Closes `instances`. } - m_out->Print( ")" ); // Closes `symbol`. + m_out->Print( ")" ); // Closes `symbol`. } @@ -1070,10 +1097,37 @@ void SCH_IO_KICAD_SEXPR::saveSheet( SCH_SHEET* aSheet, const SCH_SHEET_LIST& aSh wxString path = sheetInstances[i].m_Path.AsString(); - m_out->Print( "(path %s (page %s))", + m_out->Print( "(path %s (page %s)", m_out->Quotew( path ).c_str(), m_out->Quotew( sheetInstances[i].m_PageNumber ).c_str() ); + if( !sheetInstances[i].m_Variants.empty() ) + { + for( const auto&[name, variant] : sheetInstances[i].m_Variants ) + { + m_out->Print( "(variant (name %s)", m_out->Quotew( name ).c_str() ); + + if( variant.m_DNP != aSheet->GetDNP() ) + KICAD_FORMAT::FormatBool( m_out, "dnp", variant.m_DNP ); + + if( variant.m_ExcludedFromSim != aSheet->GetExcludedFromSim() ) + KICAD_FORMAT::FormatBool( m_out, "exclude_from_sim", variant.m_ExcludedFromSim ); + + if( variant.m_ExcludedFromBOM != aSheet->GetExcludedFromBOM() ) + KICAD_FORMAT::FormatBool( m_out, "in_bom", variant.m_ExcludedFromBOM ); + + for( const auto&[fname, fvalue] : variant.m_Fields ) + { + m_out->Print( "(field (name %s) (value %s))", + m_out->Quotew( fname ).c_str(), m_out->Quotew( fvalue ).c_str() ); + } + + m_out->Print( ")" ); // Closes `variant` token. + } + } + + m_out->Print( ")" ); // Closes `path` token. + if( inProjectClause && ( ( i + 1 == sheetInstances.size() ) || lastProjectUuid != sheetInstances[i+1].m_Path[0] ) ) { @@ -1082,10 +1136,10 @@ void SCH_IO_KICAD_SEXPR::saveSheet( SCH_SHEET* aSheet, const SCH_SHEET_LIST& aSh } } - m_out->Print( ")" ); // Closes `instances` token. + m_out->Print( ")" ); // Closes `instances` token. } - m_out->Print( ")" ); // Closes sheet token. + m_out->Print( ")" ); // Closes sheet token. } diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp index 2a32de55e1..c12cbfb5ac 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp @@ -3349,12 +3349,70 @@ SCH_SYMBOL* SCH_IO_KICAD_SEXPR_PARSER::parseSchematicSymbol() NeedRIGHT(); break; - default: - Expecting( "reference, unit, value or footprint" ); + + case T_variant: + { + SCH_SYMBOL_VARIANT variant; + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_name: + NeedSYMBOL(); + variant.m_Name = FromUTF8(); + NeedRIGHT(); + break; + + case T_dnp: + variant.m_DNP = parseBool(); + NeedRIGHT(); + break; + + case T_exclude_from_sim: + variant.m_ExcludedFromSim = parseBool(); + NeedRIGHT(); + break; + + case T_in_bom: + variant.m_ExcludedFromBOM = parseBool(); + NeedRIGHT(); + break; + + case T_field: + { + NeedSYMBOL(); + wxString fieldName = FromUTF8(); + NeedRIGHT(); + NeedSYMBOL(); + wxString fieldValue = FromUTF8(); + NeedRIGHT(); + variant.m_Fields[fieldName] = fieldValue; + break; + } + + default: + Expecting( "dnp, exclude_from_sim, field, in_bom, or name" ); + } + + instance.m_Variants[variant.m_Name] = variant; + } + + NeedRIGHT(); + break; } - symbol->AddHierarchicalReference( instance ); + default: + Expecting( "reference, unit, value, footprint, or variant" ); + } } + + symbol->AddHierarchicalReference( instance ); } } @@ -3721,8 +3779,65 @@ SCH_SHEET* SCH_IO_KICAD_SEXPR_PARSER::parseSheet() break; } + case T_variant: + { + SCH_SHEET_VARIANT variant; + + for( token = NextTok(); token != T_RIGHT; token = NextTok() ) + { + if( token != T_LEFT ) + Expecting( T_LEFT ); + + token = NextTok(); + + switch( token ) + { + case T_name: + NeedSYMBOL(); + variant.m_Name = FromUTF8(); + NeedRIGHT(); + break; + + case T_dnp: + variant.m_DNP = parseBool(); + NeedRIGHT(); + break; + + case T_exclude_from_sim: + variant.m_ExcludedFromSim = parseBool(); + NeedRIGHT(); + break; + + case T_in_bom: + variant.m_ExcludedFromBOM = parseBool(); + NeedRIGHT(); + break; + + case T_field: + { + NeedSYMBOL(); + wxString fieldName = FromUTF8(); + NeedRIGHT(); + NeedSYMBOL(); + wxString fieldValue = FromUTF8(); + NeedRIGHT(); + variant.m_Fields[fieldName] = fieldValue; + break; + } + + default: + Expecting( "dnp, exclude_from_sim, field, in_bom, or name" ); + } + + instance.m_Variants[variant.m_Name] = variant; + } + + NeedRIGHT(); + break; + } + default: - Expecting( "page" ); + Expecting( "page or variant" ); } } diff --git a/eeschema/sch_item.cpp b/eeschema/sch_item.cpp index 7a73bd4147..bf22893a64 100644 --- a/eeschema/sch_item.cpp +++ b/eeschema/sch_item.cpp @@ -244,14 +244,15 @@ SYMBOL* SCH_ITEM::GetParentSymbol() } -bool SCH_ITEM::ResolveExcludedFromSim() const +bool SCH_ITEM::ResolveExcludedFromSim( const SCH_SHEET_PATH* aInstance, + const wxString& aVariantName ) const { - if( GetExcludedFromSim() ) + if( GetExcludedFromSim( aInstance, aVariantName ) ) return true; for( SCH_RULE_AREA* area : m_rule_areas_cache ) { - if( area->GetExcludedFromSim() ) + if( area->GetExcludedFromSim( aInstance, aVariantName ) ) return true; } @@ -259,14 +260,15 @@ bool SCH_ITEM::ResolveExcludedFromSim() const } -bool SCH_ITEM::ResolveExcludedFromBOM() const +bool SCH_ITEM::ResolveExcludedFromBOM( const SCH_SHEET_PATH* aInstance, + const wxString& aVariantName ) const { - if( GetExcludedFromBOM() ) + if( GetExcludedFromBOM( aInstance, aVariantName ) ) return true; for( SCH_RULE_AREA* area : m_rule_areas_cache ) { - if( area->GetExcludedFromBOM() ) + if( area->GetExcludedFromBOM( aInstance, aVariantName ) ) return true; } @@ -289,14 +291,14 @@ bool SCH_ITEM::ResolveExcludedFromBoard() const } -bool SCH_ITEM::ResolveDNP() const +bool SCH_ITEM::ResolveDNP( const SCH_SHEET_PATH* aInstance, const wxString& aVariantName ) const { - if( GetDNP() ) + if( GetDNP( aInstance, aVariantName ) ) return true; for( SCH_RULE_AREA* area : m_rule_areas_cache ) { - if( area->GetDNP() ) + if( area->GetDNP( aInstance, aVariantName ) ) return true; } diff --git a/eeschema/sch_item.h b/eeschema/sch_item.h index 9818dd7f85..81745a268a 100644 --- a/eeschema/sch_item.h +++ b/eeschema/sch_item.h @@ -249,21 +249,30 @@ public: void SetPrivate( bool aPrivate ) { m_private = aPrivate; } bool IsPrivate() const { return m_private; } - virtual void SetExcludedFromSim( bool aExclude ) { } - virtual bool GetExcludedFromSim() const { return false; } - bool ResolveExcludedFromSim() const; - - virtual void SetExcludedFromBOM( bool aExcludeFromBOM ) { } - virtual bool GetExcludedFromBOM() const { return false; } - bool ResolveExcludedFromBOM() const; + virtual void SetExcludedFromSim( bool aExclude, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) { } + virtual bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const { return false; } + bool ResolveExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const; + + virtual void SetExcludedFromBOM( bool aExcludeFromBOM, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) { } + virtual bool GetExcludedFromBOM( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const { return false; } + bool ResolveExcludedFromBOM( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const; virtual void SetExcludedFromBoard( bool aExcludeFromBoard ) { } virtual bool GetExcludedFromBoard() const { return false; } bool ResolveExcludedFromBoard() const; - virtual void SetDNP( bool aDNP ) { } - virtual bool GetDNP() const { return false; } - bool ResolveDNP() const; + virtual void SetDNP( bool aDNP, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) { } + virtual bool GetDNP( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const { return false; } + bool ResolveDNP( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const; /** * Check if object is movable from the anchor point. diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp index 4f64282f3f..8a12cbbfc9 100644 --- a/eeschema/sch_painter.cpp +++ b/eeschema/sch_painter.cpp @@ -2670,9 +2670,14 @@ wxString SCH_PAINTER::expandLibItemTextVars( const wxString& aSourceText, void SCH_PAINTER::draw( const SCH_SYMBOL* aSymbol, int aLayer ) { bool drawingShadows = aLayer == LAYER_SELECTION_SHADOWS; - bool DNP = aSymbol->GetDNP(); - bool markExclusion = eeconfig()->m_Appearance.mark_sim_exclusions - && aSymbol->GetExcludedFromSim(); + + std::optional optSheetPath; + + if( m_schematic ) + optSheetPath = m_schematic->CurrentSheet(); + + bool DNP = aSymbol->GetDNP( nullptr ); + bool markExclusion = eeconfig()->m_Appearance.mark_sim_exclusions && aSymbol->GetExcludedFromSim( nullptr ); if( m_schSettings.IsPrinting() && drawingShadows ) return; diff --git a/eeschema/sch_rule_area.h b/eeschema/sch_rule_area.h index a7cfaedffd..8ffd4269e3 100644 --- a/eeschema/sch_rule_area.h +++ b/eeschema/sch_rule_area.h @@ -61,14 +61,32 @@ public: /** * Set or clear the exclude from simulation flag. */ - void SetExcludedFromSim( bool aExcludeFromSim ) override { m_excludedFromSim = aExcludeFromSim; } - bool GetExcludedFromSim() const override { return m_excludedFromSim; } + void SetExcludedFromSim( bool aExcludeFromSim, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromSim = aExcludeFromSim; + } + + bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromSim; + } /** * Set or clear the exclude from schematic bill of materials flag. */ - void SetExcludedFromBOM( bool aExcludeFromBOM ) override { m_excludedFromBOM = aExcludeFromBOM; } - bool GetExcludedFromBOM() const override { return m_excludedFromBOM; } + void SetExcludedFromBOM( bool aExcludeFromBOM, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromBOM = aExcludeFromBOM; + } + + bool GetExcludedFromBOM( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromBOM; + } /** * Set or clear exclude from board netlist flag. @@ -79,8 +97,10 @@ public: /** * Set or clear the 'Do Not Populate' flag. */ - bool GetDNP() const override { return m_DNP; } - void SetDNP( bool aDNP ) override { m_DNP = aDNP; } + bool GetDNP( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override { return m_DNP; } + void SetDNP( bool aDNP, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override { m_DNP = aDNP; } std::vector ViewGetLayers() const override; diff --git a/eeschema/sch_screen.cpp b/eeschema/sch_screen.cpp index 6740d735bf..6ff9e53a1f 100644 --- a/eeschema/sch_screen.cpp +++ b/eeschema/sch_screen.cpp @@ -977,12 +977,12 @@ void SCH_SCREEN::Plot( PLOTTER* aPlotter, const SCH_PLOT_OPTS& aPlotOpts ) const { field.ClearRenderCache(); field.Plot( aPlotter, false, aPlotOpts, sym->GetUnit(), sym->GetBodyStyle(), { 0, 0 }, - sym->GetDNP() ); + static_cast( sym )->GetDNP() ); } sym->PlotPins( aPlotter ); - if( sym->GetDNP() ) + if( static_cast( sym )->GetDNP() ) sym->PlotDNP( aPlotter ); } @@ -1755,6 +1755,80 @@ bool SCH_SCREEN::InProjectPath() const } +std::set SCH_SCREEN::GetVariantNames() const +{ + std::set variantNames; + + for( const SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) + { + const SCH_SYMBOL* symbol = static_cast( item ); + + wxCHECK2( symbol, continue ); + + const std::vector symbolInstances = symbol->GetInstances(); + + for( const SCH_SYMBOL_INSTANCE& instance : symbolInstances ) + { + for( const auto& [name, variant] : instance.m_Variants ) + variantNames.emplace( name ); + } + } + + for( const SCH_ITEM* item : Items().OfType( SCH_SHEET_T ) ) + { + const SCH_SHEET* sheet = static_cast( item ); + + wxCHECK2( sheet, continue ); + + const std::vector sheetInstances = sheet->GetInstances(); + + for( const SCH_SHEET_INSTANCE& instance : sheetInstances ) + { + for( const auto& [name, variant] : instance.m_Variants ) + variantNames.emplace( name ); + } + } + + return variantNames; +} + + +void SCH_SCREEN::DeleteVariant( const wxString& aVariantName ) +{ + wxCHECK( !aVariantName.IsEmpty(), /* void */ ); + + for( const SCH_ITEM* item : Items().OfType( SCH_SYMBOL_T ) ) + { + const SCH_SYMBOL* symbol = static_cast( item ); + + wxCHECK2( symbol, continue ); + + std::vector symbolInstances = symbol->GetInstances(); + + for( SCH_SYMBOL_INSTANCE& instance : symbolInstances ) + { + if( instance.m_Variants.contains( aVariantName ) ) + instance.m_Variants.erase( aVariantName ); + } + } + + for( const SCH_ITEM* item : Items().OfType( SCH_SHEET_T ) ) + { + const SCH_SHEET* sheet = static_cast( item ); + + wxCHECK2( sheet, continue ); + + std::vector sheetInstances = sheet->GetInstances(); + + for( SCH_SHEET_INSTANCE& instance : sheetInstances ) + { + if( instance.m_Variants.contains( aVariantName ) ) + instance.m_Variants.erase( aVariantName ); + } + } +} + + #if defined(DEBUG) void SCH_SCREEN::Show( int nestLevel, std::ostream& os ) const { @@ -1841,7 +1915,8 @@ void SCH_SCREENS::buildScreenList( SCH_SHEET* aSheet ) { SCH_SCREEN* screen = aSheet->GetScreen(); - wxCHECK_RET( screen, "No screen for aSheet" ); + if( !screen ) + return; addScreenToList( screen, aSheet ); @@ -2185,3 +2260,26 @@ bool SCH_SCREENS::HasSymbolFieldNamesWithWhiteSpace() const return false; } + + +std::set SCH_SCREENS::GetVariantNames() const +{ + std::set variantNames; + + for( const SCH_SCREEN* screen : m_screens ) + { + for( const wxString& variantName : screen->GetVariantNames() ) + variantNames.emplace( variantName ); + } + + return variantNames; +} + + +void SCH_SCREENS::DeleteVariant( const wxString& aVariantName ) +{ + wxCHECK( !aVariantName.IsEmpty(), /* void */ ); + + for( SCH_SCREEN* screen : m_screens ) + screen->DeleteVariant( aVariantName ); +} diff --git a/eeschema/sch_screen.h b/eeschema/sch_screen.h index dc50d17de0..c954cb264d 100644 --- a/eeschema/sch_screen.h +++ b/eeschema/sch_screen.h @@ -619,6 +619,10 @@ public: */ wxString GroupsSanityCheckInternal( bool repair ); + std::set GetVariantNames() const; + + void DeleteVariant( const wxString& aVariantName ); + private: friend SCH_EDIT_FRAME; // Only to populate m_symbolInstances. friend SCH_IO_KICAD_SEXPR_PARSER; // Only to load instance information from schematic file. @@ -854,6 +858,10 @@ public: bool HasSymbolFieldNamesWithWhiteSpace() const; + std::set GetVariantNames() const; + + void DeleteVariant( const wxString& aVariantName ); + private: void addScreenToList( SCH_SCREEN* aScreen, SCH_SHEET* aSheet ); void buildScreenList( SCH_SHEET* aSheet); diff --git a/eeschema/sch_sheet.cpp b/eeschema/sch_sheet.cpp index 9aefe2768d..d695448ad1 100644 --- a/eeschema/sch_sheet.cpp +++ b/eeschema/sch_sheet.cpp @@ -1631,6 +1631,42 @@ double SCH_SHEET::Similarity( const SCH_ITEM& aOther ) const } +bool SCH_SHEET::GetDNPProp() const +{ + return GetDNP( nullptr, Schematic()->GetCurrentVariant() ); +} + + +void SCH_SHEET::SetDNPProp( bool aEnable ) +{ + SetDNP( aEnable, nullptr, Schematic()->GetCurrentVariant() ); +} + + +bool SCH_SHEET::GetExcludedFromSimProp() const +{ + return GetExcludedFromSim( nullptr, Schematic()->GetCurrentVariant() ); +} + + +void SCH_SHEET::SetExcludedFromSimProp( bool aEnable ) +{ + SetExcludedFromSim( aEnable, nullptr, Schematic()->GetCurrentVariant() ); +} + + +bool SCH_SHEET::GetExcludedFromBOMProp() const +{ + return GetExcludedFromBOM( nullptr, Schematic()->GetCurrentVariant() ); +} + + +void SCH_SHEET::SetExcludedFromBOMProp( bool aEnable ) +{ + SetExcludedFromBOM( aEnable, nullptr, Schematic()->GetCurrentVariant() ); +} + + #if defined(DEBUG) void SCH_SHEET::Show( int nestLevel, std::ostream& os ) const @@ -1691,15 +1727,15 @@ static struct SCH_SHEET_DESC &SCH_SHEET::SetExcludedFromBoard, &SCH_SHEET::GetExcludedFromBoard ), groupAttributes ); propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Simulation" ), - &SCH_SHEET::SetExcludedFromSim, &SCH_SHEET::GetExcludedFromSim ), + &SCH_SHEET::SetExcludedFromSimProp, &SCH_SHEET::GetExcludedFromSimProp ), groupAttributes ); propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Bill of Materials" ), - &SCH_SHEET::SetExcludedFromBOM, - &SCH_SHEET::GetExcludedFromBOM ), + &SCH_SHEET::SetExcludedFromBOMProp, + &SCH_SHEET::GetExcludedFromBOMProp ), groupAttributes ); propMgr.AddProperty( new PROPERTY( _HKI( "Do not Populate" ), - &SCH_SHEET::SetDNP, &SCH_SHEET::GetDNP ), + &SCH_SHEET::SetDNPProp, &SCH_SHEET::GetDNPProp ), groupAttributes ); } } _SCH_SHEET_DESC; diff --git a/eeschema/sch_sheet.h b/eeschema/sch_sheet.h index 129306e3d2..c542e71efb 100644 --- a/eeschema/sch_sheet.h +++ b/eeschema/sch_sheet.h @@ -381,14 +381,38 @@ public: /** * Set or clear the exclude from simulation flag. */ - void SetExcludedFromSim( bool aExcludeFromSim ) override { m_excludedFromSim = aExcludeFromSim; } - bool GetExcludedFromSim() const override { return m_excludedFromSim; } + void SetExcludedFromSim( bool aExcludeFromSim, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromSim = aExcludeFromSim; + } + + bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromSim; + } + + bool GetExcludedFromSimProp() const; + void SetExcludedFromSimProp( bool aEnable ); /** * Set or clear the exclude from schematic bill of materials flag. */ - void SetExcludedFromBOM( bool aExcludeFromBOM ) override { m_excludedFromBOM = aExcludeFromBOM; } - bool GetExcludedFromBOM() const override { return m_excludedFromBOM; } + void SetExcludedFromBOM( bool aExcludeFromBOM, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromBOM = aExcludeFromBOM; + } + + bool GetExcludedFromBOM( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromBOM; + } + + bool GetExcludedFromBOMProp() const; + void SetExcludedFromBOMProp( bool aEnable ); /** * Set or clear exclude from board netlist flag. @@ -399,8 +423,12 @@ public: /** * Set or clear the 'Do Not Populate' flags */ - bool GetDNP() const override { return m_DNP; } - void SetDNP( bool aDNP ) override { m_DNP = aDNP; } + bool GetDNP( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override { return m_DNP; } + void SetDNP( bool aDNP, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override { m_DNP = aDNP; } + bool GetDNPProp() const; + void SetDNPProp( bool aEnable ); wxString GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const override; diff --git a/eeschema/sch_sheet_path.cpp b/eeschema/sch_sheet_path.cpp index 4d0ee3c33e..14785140a2 100644 --- a/eeschema/sch_sheet_path.cpp +++ b/eeschema/sch_sheet_path.cpp @@ -96,6 +96,14 @@ public: }; +void SCH_SYMBOL_VARIANT::InitializeAttributes( const SCH_SYMBOL& aSymbol ) +{ + m_DNP = aSymbol.GetDNP(); + m_ExcludedFromBOM = aSymbol.GetExcludedFromBOM(); + m_ExcludedFromSim = aSymbol.GetExcludedFromSim(); +} + + namespace std { size_t hash::operator()( const SCH_SHEET_PATH& path ) const @@ -400,6 +408,12 @@ void SCH_SHEET_PATH::UpdateAllScreenReferences() const || aItem->Type() == SCH_SHAPE_T ); } ); + std::optional variantName; + const SCHEMATIC* schematic = LastScreen()->Schematic(); + + if( schematic ) + variantName = schematic->GetCurrentVariant(); + for( SCH_ITEM* item : items ) { if( item->Type() == SCH_SYMBOL_T ) diff --git a/eeschema/sch_sheet_path.h b/eeschema/sch_sheet_path.h index f6bfbe5b3c..800d925247 100644 --- a/eeschema/sch_sheet_path.h +++ b/eeschema/sch_sheet_path.h @@ -32,11 +32,62 @@ #define CLASS_DRAWSHEET_PATH_H #include +#include #include #include #include +class SCH_SYMBOL; +class SCH_SHEET; + +/** + * Object to store and handle common variant information. + */ +class VARIANT +{ +public: + VARIANT( const wxString& aName = wxEmptyString ) : + m_Name( aName ), + m_ExcludedFromSim( false ), + m_ExcludedFromBOM( false ), + m_DNP( false ) + { + } + + virtual ~VARIANT() = default; + + wxString m_Name; + bool m_ExcludedFromSim; + bool m_ExcludedFromBOM; + bool m_DNP; + std::map m_Fields; +}; + + +/** + * Variant information for a schematic symbol. + * + * Schematic symbol variants are a set of field and/or properties differentials against the default symbol + * values. Each symbol instance may contain 0 or more variants. + * + * @note The two exceptions to this are the #REFERENCE field and the #SYMBOL::m_excludeFromBoard property. + * Changing either of these would effectively be a new board. They are immutable and will always + * be the symbol default value. + */ +class SCH_SYMBOL_VARIANT : public VARIANT +{ +public: + SCH_SYMBOL_VARIANT( const wxString& aName = wxEmptyString ) : + VARIANT( aName ) + {} + + void InitializeAttributes( const SCH_SYMBOL& aSymbol ); + + virtual ~SCH_SYMBOL_VARIANT() = default; +}; + + /** * A simple container for schematic symbol instance information. */ @@ -49,12 +100,42 @@ struct SCH_SYMBOL_INSTANCE int m_Unit = 1; // Do not use. This is left over from the dubious decision to instantiate symbol value - // and footprint fields. + // and footprint fields. This is now handle by variants. wxString m_Value; wxString m_Footprint; // The project name associated with this instance. wxString m_ProjectName; + + bool m_DNP = false; + bool m_ExcludedFromBOM = false; + bool m_ExcludedFromSim = false; + + /// A list of symbol variants. + std::map m_Variants; +}; + + +/** + * Variant information for a schematic sheet. + * + * Schematic sheet variants are a set of field and/or properties differentials against the default sheet + * values. Each sheet instance may contain 0 or more variants. + * + * @note The exceptions to this are the #SHEET_NAME and #SHEET_FILENAME fields and the #SCH_SHEET::m_excludeFromBoard + * property. Changing any of these would effectively be a new board. They are immutable and will always be + * the sheet default value. + */ +class SCH_SHEET_VARIANT : public VARIANT +{ +public: + SCH_SHEET_VARIANT() : + VARIANT() + {} + + virtual ~SCH_SHEET_VARIANT() = default; + + void InitializeAttributes( const SCH_SHEET& aSymbol ); }; @@ -69,6 +150,13 @@ struct SCH_SHEET_INSTANCE // The project name associated with this instance. wxString m_ProjectName; + + bool m_DNP = false; + bool m_ExcludedFromBOM = false; + bool m_ExcludedFromSim = false; + + /// A list of sheet variants. + std::map m_Variants; }; diff --git a/eeschema/sch_symbol.cpp b/eeschema/sch_symbol.cpp index c172184640..1721d65bcf 100644 --- a/eeschema/sch_symbol.cpp +++ b/eeschema/sch_symbol.cpp @@ -767,6 +767,222 @@ void SCH_SYMBOL::SetUnitSelection( const SCH_SHEET_PATH* aSheet, int aUnitSelect } +void SCH_SYMBOL::SetDNP(bool aEnable, const SCH_SHEET_PATH* aInstance, const wxString& aVariantName ) +{ + if( !aInstance || aVariantName.IsEmpty() ) + { + m_DNP = aEnable; + return; + } + + SCH_SYMBOL_INSTANCE instance; + + wxCHECK_MSG( GetInstance( instance, aInstance->Path() ), /* void */, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s'." ), + aInstance->PathHumanReadable() ) ); + + if( aVariantName.IsEmpty() ) + { + instance.m_DNP = aEnable; + } + else + { + if( instance.m_Variants.contains( aVariantName ) ) + { + instance.m_Variants[aVariantName].m_DNP = aEnable; + } + else + { + SCH_SYMBOL_VARIANT variant( aVariantName ); + + variant.InitializeAttributes( *this ); + variant.m_DNP = aEnable; + AddVariant( *aInstance, variant ); + } + } +} + + +bool SCH_SYMBOL::GetDNP( const SCH_SHEET_PATH* aInstance, const wxString& aVariantName ) const +{ + if( !aInstance || aVariantName.IsEmpty() ) + return m_DNP; + + SCH_SYMBOL_INSTANCE instance; + + wxCHECK_MSG( GetInstance( instance, aInstance->Path() ), m_DNP, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s'." ), + aInstance->PathHumanReadable() ) ); + + if( aVariantName.IsEmpty() ) + { + return instance.m_DNP; + } + else + { + wxCHECK_MSG( instance.m_Variants.contains( aVariantName ), false, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s' for variant '%s'." ), + aInstance->PathHumanReadable(), aVariantName ) ); + + return instance.m_Variants[aVariantName].m_DNP; + } + + return m_DNP; +} + + +void SCH_SYMBOL::SetDNP( bool aEnable, const SCH_SHEET_PATH& aInstance, const std::vector& aVariantNames ) +{ + for( const wxString& variantName : aVariantNames ) + SetDNP( aEnable, &aInstance, variantName ); +} + + +void SCH_SYMBOL::SetExcludedFromBOM( bool aEnable, const SCH_SHEET_PATH* aInstance, + const wxString& aVariantName ) +{ + if( !aInstance || aVariantName.IsEmpty() ) + { + m_excludedFromBOM = aEnable; + return; + } + + SCH_SYMBOL_INSTANCE instance; + + wxCHECK_MSG( GetInstance( instance, aInstance->Path() ), /* void */, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s'." ), + aInstance->PathHumanReadable() ) ); + + if( aVariantName.IsEmpty() ) + { + instance.m_ExcludedFromBOM = aEnable; + } + else + { + if( instance.m_Variants.contains( aVariantName ) ) + { + instance.m_Variants[aVariantName].m_ExcludedFromBOM = aEnable; + } + else + { + SCH_SYMBOL_VARIANT variant( aVariantName ); + + variant.InitializeAttributes( *this ); + variant.m_ExcludedFromBOM = aEnable; + AddVariant( *aInstance, variant ); + } + } +} + + +bool SCH_SYMBOL::GetExcludedFromBOM( const SCH_SHEET_PATH* aInstance, const wxString& aVariantName ) const +{ + if( !aInstance || aVariantName.IsEmpty() ) + return m_excludedFromBOM; + + SCH_SYMBOL_INSTANCE instance; + + wxCHECK_MSG( GetInstance( instance, aInstance->Path() ), m_excludedFromBOM, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s'." ), + aInstance->PathHumanReadable() ) ); + + if( aVariantName.IsEmpty() ) + { + return instance.m_ExcludedFromBOM; + } + else + { + wxCHECK_MSG( instance.m_Variants.contains( aVariantName ), false, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s' variant '%s'." ), + aInstance->PathHumanReadable(), aVariantName ) ); + + return instance.m_Variants[aVariantName].m_ExcludedFromBOM; + } + + return m_excludedFromBOM; +} + + +void SCH_SYMBOL::SetExcludedFromBOM( bool aEnable, const SCH_SHEET_PATH& aInstance, + const std::vector& aVariantNames ) +{ + for( const wxString& variantName : aVariantNames ) + SetExcludedFromBOM( aEnable, &aInstance, variantName ); +} + + +void SCH_SYMBOL::SetExcludedFromSim( bool aEnable, const SCH_SHEET_PATH* aInstance, const wxString& aVariantName ) +{ + if( !aInstance || aVariantName.IsEmpty() ) + { + m_excludedFromSim = aEnable; + return; + } + + SCH_SYMBOL_INSTANCE instance; + + wxCHECK_MSG( GetInstance( instance, aInstance->Path() ), /* void */, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s'." ), + aInstance->PathHumanReadable() ) ); + + if( aVariantName.IsEmpty() ) + { + instance.m_ExcludedFromSim = aEnable; + } + else + { + if( instance.m_Variants.contains( aVariantName ) ) + { + instance.m_Variants[aVariantName].m_ExcludedFromSim = aEnable; + } + else + { + SCH_SYMBOL_VARIANT variant( aVariantName ); + + variant.InitializeAttributes( *this ); + variant.m_ExcludedFromSim = aEnable; + AddVariant( *aInstance, variant ); + } + } +} + + +bool SCH_SYMBOL::GetExcludedFromSim( const SCH_SHEET_PATH* aInstance, const wxString& aVariantName ) const +{ + if( !aInstance || aVariantName.IsEmpty() ) + return m_excludedFromSim; + + SCH_SYMBOL_INSTANCE instance; + + wxCHECK_MSG( GetInstance( instance, aInstance->Path() ), m_excludedFromSim, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s'." ), + aInstance->PathHumanReadable() ) ); + + if( aVariantName.IsEmpty() ) + { + return instance.m_ExcludedFromSim; + } + else + { + wxCHECK_MSG( instance.m_Variants.contains( aVariantName ), false, + wxString::Format( wxS( "Cannot get DNP attribute for invalid sheet path '%s' variant '%s'." ), + aInstance->PathHumanReadable(), aVariantName ) ); + + return instance.m_Variants[aVariantName].m_ExcludedFromSim; + } + + return m_excludedFromSim; +} + + +void SCH_SYMBOL::SetExcludedFromSim( bool aEnable, const SCH_SHEET_PATH& aInstance, + const std::vector& aVariantNames ) +{ + for( const wxString& variantName : aVariantNames ) + SetExcludedFromSim( aEnable, &aInstance, variantName ); +} + + void SCH_SYMBOL::SetUnitSelection( int aUnitSelection ) { for( SCH_SYMBOL_INSTANCE& instance : m_instanceReferences ) @@ -1062,7 +1278,7 @@ void SCH_SYMBOL::SyncOtherUnits( const SCH_SHEET_PATH& aSourceSheet, SCH_COMMIT& otherUnit->SetExcludedFromBoard( m_excludedFromBoard ); if( updateDNP ) - otherUnit->SetDNP( m_DNP ); + otherUnit->SetDNP( GetDNP( &aSourceSheet ), &sheet ); if( updatePins ) { @@ -1921,7 +2137,7 @@ void SCH_SYMBOL::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector SCH_SYMBOL::GetComponentClassNames( const SCH_SHEET } +std::optional SCH_SYMBOL::GetVariant( const SCH_SHEET_PATH& aInstance, + const wxString& aVariantName ) const +{ + SCH_SYMBOL_INSTANCE instance; + + if( !GetInstance( instance, aInstance.Path() ) || !instance.m_Variants.contains( aVariantName ) ) + return std::nullopt; + + return instance.m_Variants.find( aVariantName )->second; +} + + +void SCH_SYMBOL::AddVariant( const SCH_SHEET_PATH& aInstance, const SCH_SYMBOL_VARIANT& aVariant ) +{ + SCH_SYMBOL_INSTANCE instance; + + // The instance path must already exist. + if( !GetInstance( instance, aInstance.Path() ) ) + return; + + instance.m_Variants.emplace( std::make_pair( aVariant.m_Name, aVariant ) ); +} + + +void SCH_SYMBOL::DeleteVariant( const SCH_SHEET_PATH& aInstance, const wxString& aVariantName ) +{ + SCH_SYMBOL_INSTANCE instance; + + // The instance path must already exist. + if( !GetInstance( instance, aInstance.Path() ) || !instance.m_Variants.contains( aVariantName ) ) + return; + + instance.m_Variants.erase( aVariantName ); +} + + bool SCH_SYMBOL::operator==( const SCH_ITEM& aOther ) const { if( Type() != aOther.Type() ) @@ -3023,14 +3275,14 @@ static struct SCH_SYMBOL_DESC propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Board" ), &SYMBOL::SetExcludedFromBoard, &SYMBOL::GetExcludedFromBoard ), groupAttributes ); - propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Simulation" ), - &SYMBOL::SetExcludedFromSim, &SYMBOL::GetExcludedFromSim ), + propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Simulation" ), + &SCH_SYMBOL::SetExcludedFromSimProp, &SCH_SYMBOL::GetExcludedFromSimProp ), groupAttributes ); - propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Bill of Materials" ), - &SYMBOL::SetExcludedFromBOM, &SYMBOL::GetExcludedFromBOM ), + propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Bill of Materials" ), + &SCH_SYMBOL::SetExcludedFromBOMProp, &SCH_SYMBOL::GetExcludedFromBOMProp ), groupAttributes ); - propMgr.AddProperty( new PROPERTY( _HKI( "Do not Populate" ), - &SYMBOL::SetDNP, &SYMBOL::GetDNP ), + propMgr.AddProperty( new PROPERTY( _HKI( "Do not Populate" ), + &SCH_SYMBOL::SetDNPProp, &SCH_SYMBOL::GetDNPProp ), groupAttributes ); } } _SCH_SYMBOL_DESC; diff --git a/eeschema/sch_symbol.h b/eeschema/sch_symbol.h index a544543705..65848beb5d 100644 --- a/eeschema/sch_symbol.h +++ b/eeschema/sch_symbol.h @@ -413,7 +413,11 @@ public: const SCH_FIELD* GetField( FIELD_T aFieldNdx ) const; /** - * Return a field in this symbol. Both versions return nullptr if the field is not found. + * Return a field in this symbol. + * + * @param aFieldName is the canonical name of the field. + * + * @return Both non-const and const versions return nullptr if the field is not found. */ SCH_FIELD* GetField( const wxString& aFieldName ); const SCH_FIELD* GetField( const wxString& aFieldName ) const; @@ -667,6 +671,50 @@ public: /// Set the selected unit of this symbol for all sheets. void SetUnitSelection( int aUnitSelection ); + virtual void SetDNP( bool aEnable, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override; + virtual bool GetDNP( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override; + void SetDNP( bool aEnable, const SCH_SHEET_PATH& aInstance, const std::vector& aVariantNames ); + + bool GetDNPProp() const { return GetDNP( nullptr, Schematic()->GetCurrentVariant() ); } + + void SetDNPProp( bool aEnable ) { SetDNP( aEnable, nullptr, Schematic()->GetCurrentVariant() ); } + + void SetExcludedFromBOM( bool aEnable, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override; + bool GetExcludedFromBOM( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override; + void SetExcludedFromBOM( bool aEnable, const SCH_SHEET_PATH& aInstance, + const std::vector& aVariantNames ); + + bool GetExcludedFromBOMProp() const + { + return GetExcludedFromBOM( nullptr, Schematic()->GetCurrentVariant() ); + } + + void SetExcludedFromBOMProp( bool aEnable ) + { + SetExcludedFromBOM( aEnable, nullptr, Schematic()->GetCurrentVariant() ); + } + + void SetExcludedFromSim( bool aEnable, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override; + bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override; + void SetExcludedFromSim( bool aEnable, const SCH_SHEET_PATH& aInstance, + const std::vector& aVariantNames ); + + bool GetExcludedFromSimProp() const + { + return GetExcludedFromSim( nullptr, Schematic()->GetCurrentVariant() ); + } + + void SetExcludedFromSimProp( bool aEnable ) + { + SetExcludedFromSim( aEnable, nullptr, Schematic()->GetCurrentVariant() ); + } + /** * SCH_SYMBOLs don't currently support embedded files, but their LIB_SYMBOL counterparts * do. @@ -826,6 +874,10 @@ public: /// Return the component classes this symbol belongs in. std::unordered_set GetComponentClassNames( const SCH_SHEET_PATH* aPath ) const; + std::optional GetVariant( const SCH_SHEET_PATH& aInstance, const wxString& aVariantName ) const; + void AddVariant( const SCH_SHEET_PATH& aInstance, const SCH_SYMBOL_VARIANT& aVariant ); + void DeleteVariant( const SCH_SHEET_PATH& aInstance, const wxString& aVariantName ); + bool operator==( const SCH_ITEM& aOther ) const override; protected: diff --git a/eeschema/sch_text.h b/eeschema/sch_text.h index f64f8f1614..ee3fb7ad84 100644 --- a/eeschema/sch_text.h +++ b/eeschema/sch_text.h @@ -84,8 +84,17 @@ public: void DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const override; - void SetExcludedFromSim( bool aExclude ) override { m_excludedFromSim = aExclude; } - bool GetExcludedFromSim() const override { return m_excludedFromSim; } + void SetExcludedFromSim( bool aExclude, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromSim = aExclude; + } + + bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromSim; + } /** * This offset depends on the orientation, the type of text, and the area required to diff --git a/eeschema/sch_textbox.h b/eeschema/sch_textbox.h index 4e60172c4d..92e466c3c8 100644 --- a/eeschema/sch_textbox.h +++ b/eeschema/sch_textbox.h @@ -88,8 +88,17 @@ public: bool IsHypertext() const override; void DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const override; - void SetExcludedFromSim( bool aExclude ) override { m_excludedFromSim = aExclude; } - bool GetExcludedFromSim() const override { return m_excludedFromSim; } + void SetExcludedFromSim( bool aExclude, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromSim = aExclude; + } + + bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromSim; + } bool operator<( const SCH_ITEM& aItem ) const override; diff --git a/eeschema/schematic.cpp b/eeschema/schematic.cpp index 21bfcdb8ad..063ec680d8 100644 --- a/eeschema/schematic.cpp +++ b/eeschema/schematic.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include @@ -240,6 +241,9 @@ void SCHEMATIC::SetRoot( SCH_SHEET* aRootSheet ) m_hierarchy = BuildSheetListSortedByPageNumbers(); m_connectionGraph->Reset(); + SCH_SCREENS screens( aRootSheet ); + + m_variantNames = screens.GetVariantNames(); } @@ -1607,22 +1611,54 @@ void SCHEMATIC::CreateDefaultScreens() Reset(); SCH_SHEET* rootSheet = new SCH_SHEET( this ); - SetRoot( rootSheet ); - SCH_SCREEN* rootScreen = new SCH_SCREEN( this ); - const_cast( rootSheet->m_Uuid ) = rootScreen->GetUuid(); - Root().SetScreen( rootScreen ); - - RootScreen()->SetFileName( wxEmptyString ); + const_cast( rootSheet->m_Uuid ) = rootScreen->GetUuid(); + rootSheet->SetScreen( rootScreen ); + rootScreen->SetFileName( wxEmptyString ); + rootScreen->SetPageNumber( wxT( "1" ) ); // Don't leave root page number empty SCH_SHEET_PATH rootSheetPath; rootSheetPath.push_back( rootSheet ); - RootScreen()->SetPageNumber( wxT( "1" ) ); rootSheetPath.SetPageNumber( wxT( "1" ) ); + SetRoot( rootSheet ); + // Rehash sheetpaths in hierarchy since we changed the uuid. RefreshHierarchy(); -} \ No newline at end of file +} + + +wxArrayString SCHEMATIC::GetVariantNamesForUI() const +{ + wxArrayString variantNames; + + // There is no default variant name. This is just a place holder for UI controls. + variantNames.Add( GetDefaultVariantName() ); + + for( const wxString& name : m_variantNames ) + variantNames.Add( name ); + + return variantNames; +} + + +wxString SCHEMATIC::GetCurrentVariant() const +{ + if( m_currentVariant.IsEmpty() || ( m_currentVariant == GetDefaultVariantName() ) ) + return wxEmptyString; + + return m_currentVariant; +} + + +void SCHEMATIC::DeleteVariant( const wxString& aVariantName ) +{ + wxCHECK( m_rootSheet, /* void */ ); + + SCH_SCREENS allScreens( m_rootSheet ); + + allScreens.DeleteVariant( aVariantName ); +} diff --git a/eeschema/schematic.h b/eeschema/schematic.h index f11bc6d9cf..a62fe2da6e 100644 --- a/eeschema/schematic.h +++ b/eeschema/schematic.h @@ -421,6 +421,32 @@ public: void CreateDefaultScreens(); + /** + * Return an array of variant names for using in wxWidgets UI controls. + * + * Most wxWidgets UI controls that support multiple entries take a wxArrayString as an argument. This + * is a convenience method so it's not necessary to convert a std::set to a wxArrayString at + * the point of use. + * + * @note This also adds the pseudo "" entry to the beginning of the array followed by all other + * variant names sorted using the #StrNumCmp function. + */ + wxArrayString GetVariantNamesForUI() const; + + /** + * Return the current variant being edited. + * + * @return the name of the current variant being edited. An empty string is the default variant. + */ + wxString GetCurrentVariant() const; + + /** + * Delete all information for @a aVariantName. + * + * @param aVariantName is the name of the variant to remove. + */ + void DeleteVariant( const wxString& aVariantName ); + /** * True if a SCHEMATIC exists, false if not */ @@ -496,6 +522,10 @@ private: void updateProjectBusAliases(); std::vector> m_busAliases; + + wxString m_currentVariant; + + std::set m_variantNames; }; #endif diff --git a/eeschema/schematic.keywords b/eeschema/schematic.keywords index ae02ba927b..adafb62041 100644 --- a/eeschema/schematic.keywords +++ b/eeschema/schematic.keywords @@ -51,6 +51,7 @@ extends external exclude_from_sim face +field fields_autoplaced file fill @@ -171,6 +172,7 @@ unit_name unspecified uuid value +variant version width wire diff --git a/eeschema/symbol.h b/eeschema/symbol.h index 59cf8bb8cf..fa29f81ba1 100644 --- a/eeschema/symbol.h +++ b/eeschema/symbol.h @@ -172,18 +172,32 @@ public: /** * Set or clear the exclude from simulation flag. */ - void SetExcludedFromSim( bool aExcludeFromSim ) override + virtual void SetExcludedFromSim( bool aExcludeFromSim, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override { m_excludedFromSim = aExcludeFromSim; } - bool GetExcludedFromSim() const override { return m_excludedFromSim; } + virtual bool GetExcludedFromSim( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromSim; + } /** * Set or clear the exclude from schematic bill of materials flag. */ - void SetExcludedFromBOM( bool aExcludeFromBOM ) override { m_excludedFromBOM = aExcludeFromBOM; } - bool GetExcludedFromBOM() const override { return m_excludedFromBOM; } + virtual void SetExcludedFromBOM( bool aExcludeFromBOM, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override + { + m_excludedFromBOM = aExcludeFromBOM; + } + + virtual bool GetExcludedFromBOM( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override + { + return m_excludedFromBOM; + } /** * Set or clear exclude from board netlist flag. @@ -194,8 +208,10 @@ public: /** * Set or clear the 'Do Not Populate' flag. */ - bool GetDNP() const override { return m_DNP; } - void SetDNP( bool aDNP ) override { m_DNP = aDNP; } + virtual bool GetDNP( const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) const override { return m_DNP; } + virtual void SetDNP( bool aDNP, const SCH_SHEET_PATH* aInstance = nullptr, + const wxString& aVariantName = wxEmptyString ) override { m_DNP = aDNP; } virtual int GetOrientation() const { return SYM_NORMAL; } diff --git a/eeschema/toolbars_sch_editor.cpp b/eeschema/toolbars_sch_editor.cpp index 15c2a9a0a7..fc4352c04d 100644 --- a/eeschema/toolbars_sch_editor.cpp +++ b/eeschema/toolbars_sch_editor.cpp @@ -45,6 +45,12 @@ #include +ACTION_TOOLBAR_CONTROL SCH_ACTION_TOOLBAR_CONTROLS::currentVariant( + "control.currentVariant", + _( "Current Variant" ), + _( "Control to select the current schematic varaint" ) ); + + std::optional SCH_EDIT_TOOLBAR_SETTINGS::DefaultToolbarConfig( TOOLBAR_LOC aToolbar ) { TOOLBAR_CONFIGURATION config; @@ -196,6 +202,9 @@ std::optional SCH_EDIT_TOOLBAR_SETTINGS::DefaultToolbarCo config.AppendSeparator() .AppendAction( SCH_ACTIONS::showPcbNew ); + if( ADVANCED_CFG::GetCfg().m_EnableVariantsUI ) + config.AppendControl( SCH_ACTION_TOOLBAR_CONTROLS::currentVariant ); + // Insert all the IPC plugins here on the toolbar // TODO (ISM): Move this to individual actions for each script config.AppendControl( ACTION_TOOLBAR_CONTROLS::ipcScripting ); @@ -212,6 +221,27 @@ void SCH_EDIT_FRAME::configureToolbars() { SCH_BASE_FRAME::configureToolbars(); + if( ADVANCED_CFG::GetCfg().m_EnableVariantsUI ) + { + // Variant selection drop down control on main tool bar. + auto variantSelectionCtrlFactory = + [this]( ACTION_TOOLBAR* aToolbar ) + { + std::optional currentVariantName = Schematic().GetCurrentVariant(); + wxString tmp = currentVariantName ? *currentVariantName : GetDefaultVariantName(); + + m_currentVariantCtrl = new wxChoice( aToolbar, ID_TOOLBAR_SCH_SELECT_VARAIANT, wxDefaultPosition, + wxDefaultSize, Schematic().GetVariantNamesForUI(), 0, + wxDefaultValidator, tmp ); + + m_currentVariantCtrl->SetToolTip( _( "Select the current variant to display and edit." ) ); + aToolbar->Add( m_currentVariantCtrl ); + UpdateVariantSelectionCtrl( Schematic().GetVariantNamesForUI() ); + }; + + RegisterCustomToolbarControlFactory( SCH_ACTION_TOOLBAR_CONTROLS::currentVariant, variantSelectionCtrlFactory ); + } + // IPC/Scripting plugin control // TODO (ISM): Clean this up to make IPC actions just normal tool actions to get rid of this entire // control @@ -239,3 +269,27 @@ void SCH_EDIT_FRAME::configureToolbars() RegisterCustomToolbarControlFactory( ACTION_TOOLBAR_CONTROLS::ipcScripting, pluginControlFactory ); } + + +void SCH_EDIT_FRAME::UpdateVariantSelectionCtrl( const wxArrayString& aVariantNames ) +{ + if( !m_currentVariantCtrl ) + return; + + // Fall back to the default if nothing is currently selected. + wxString currentSelection = GetDefaultVariantName(); + int selectionIndex = m_currentVariantCtrl->GetSelection(); + + if( selectionIndex != wxNOT_FOUND ) + currentSelection = m_currentVariantCtrl->GetString( selectionIndex ); + + m_currentVariantCtrl->Set( aVariantNames ); + + selectionIndex = m_currentVariantCtrl->FindString( currentSelection ); + + if( ( selectionIndex == wxNOT_FOUND ) && ( m_currentVariantCtrl->GetCount() != 0 ) ) + selectionIndex = 0; + + m_currentVariantCtrl->SetSelection( selectionIndex ); +} + diff --git a/eeschema/toolbars_sch_editor.h b/eeschema/toolbars_sch_editor.h index 7b926381d0..da514e5d7e 100644 --- a/eeschema/toolbars_sch_editor.h +++ b/eeschema/toolbars_sch_editor.h @@ -20,8 +20,19 @@ #ifndef TOOLBARS_SCH_EDITOR_H_ #define TOOLBARS_SCH_EDITOR_H_ +#include #include +/** + * Custom toolbar controls for the schematic editor frame. + */ +class SCH_ACTION_TOOLBAR_CONTROLS : public ACTION_TOOLBAR_CONTROLS +{ +public: + static ACTION_TOOLBAR_CONTROL currentVariant; +}; + + /** * Toolbar configuration for the schematic editor frame. */ diff --git a/eeschema/tools/sch_actions.cpp b/eeschema/tools/sch_actions.cpp index 84e60050e5..60d097d212 100644 --- a/eeschema/tools/sch_actions.cpp +++ b/eeschema/tools/sch_actions.cpp @@ -1677,4 +1677,16 @@ TOOL_ACTION SCH_ACTIONS::showNetlist( TOOL_ACTION_ARGS() .FriendlyName( _( "Show SPICE Netlist" ) ) .Icon( BITMAPS::netlist ) ); +TOOL_ACTION SCH_ACTIONS::addVariant( TOOL_ACTION_ARGS() + .Name( "eeschema.EditorControl.addVariant" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Add Variant..." ) ) + .Tooltip( _( "Add new variant to the schematic." ) ) ); + +TOOL_ACTION SCH_ACTIONS::removeVariant( TOOL_ACTION_ARGS() + .Name( "eeschema.EditorControl.removeVariant" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Remove Variant..." ) ) + .Tooltip( _( "Remove an existing variant from the schematic." ) ) ); + // clang-format on diff --git a/eeschema/tools/sch_actions.h b/eeschema/tools/sch_actions.h index cda15eadb0..ef596cc63e 100644 --- a/eeschema/tools/sch_actions.h +++ b/eeschema/tools/sch_actions.h @@ -182,6 +182,10 @@ public: static TOOL_ACTION addSymbolToSchematic; static TOOL_ACTION exportSymbolsToLibrary; + // Variant operations + static TOOL_ACTION addVariant; + static TOOL_ACTION removeVariant; + // Attribute Toggles static TOOL_ACTION setExcludeFromBOM; static TOOL_ACTION setExcludeFromSimulation; diff --git a/include/advanced_config.h b/include/advanced_config.h index 30c641b7e4..4e095ac2e6 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -872,6 +872,15 @@ public: */ int m_ScreenDPI; + /** + * Enable access to the variants user interface. + * + * Setting name: "EnableVariantsUI" + * Valid values: 0 or 1 + * Default value: 0 + */ + bool m_EnableVariantsUI; + wxString m_traceMasks; ///< Trace masks for wxLogTrace, loaded from the config file. ///@} diff --git a/include/string_utils.h b/include/string_utils.h index 07c4facd0e..55f6d9a7fe 100644 --- a/include/string_utils.h +++ b/include/string_utils.h @@ -495,4 +495,8 @@ KICOMMON_API std::vector ExpandStackedPinNotation( const wxString& aPi bool* aValid = nullptr ); +KICOMMON_API wxString GetDefaultVariantName(); + +KICOMMON_API int SortVariantNames( const wxString& aLhs, const wxString& aRhs ); + #endif // STRING_UTILS_H diff --git a/kicad/cli/command.cpp b/kicad/cli/command.cpp index ff7ecbdc46..8e6bc6507a 100644 --- a/kicad/cli/command.cpp +++ b/kicad/cli/command.cpp @@ -34,7 +34,8 @@ CLI::COMMAND::COMMAND( const std::string& aName ) : m_hasOutputArg( false ), m_hasDrawingSheetArg( false ), m_hasDefineArg( false ), - m_outputArgExpectsDir( false ) + m_outputArgExpectsDir( false ), + m_hasVariantsArg( false ) { m_argParser.add_argument( ARG_HELP_SHORT, ARG_HELP ) @@ -77,7 +78,6 @@ int CLI::COMMAND::Perform( KIWAY& aKiway ) m_argDrawingSheet = From_UTF8( m_argParser.get( ARG_DRAWING_SHEET ).c_str() ); } - if( m_hasDefineArg ) { auto defines = m_argParser.get>( ARG_DEFINE_VAR_LONG ); @@ -99,6 +99,17 @@ int CLI::COMMAND::Perform( KIWAY& aKiway ) } } + if( m_hasVariantsArg ) + { + auto variantNames = m_argParser.get>( ARG_VARIANTS ); + + for( const std::string& variantName : variantNames ) + m_argVariantNames.emplace( variantName ); + + if( m_argVariantNames.empty() ) + return EXIT_CODES::ERR_ARGS; + } + return doPerform( aKiway ); } @@ -178,3 +189,17 @@ void CLI::COMMAND::addDefineArg() "\nUse in the format of '--define-var key=value' or '-D key=value'" ) ) ) .metavar( "KEY=VALUE" ); } + + +void CLI::COMMAND::addVariantsArg() +{ + m_hasVariantsArg = true; + + m_argParser.add_argument( ARG_VARIANTS ) + .default_value( std::set() ) + .append() + .help( UTF8STDSTR( + _( "List of variant names to output.\n" + "When no --variants arguement is provided the default variant is output.\n" + "To output all variants use '--variants=all'" ) ) ); +} diff --git a/kicad/cli/command.h b/kicad/cli/command.h index 002afcdd34..d4c838de6b 100644 --- a/kicad/cli/command.h +++ b/kicad/cli/command.h @@ -35,6 +35,7 @@ #define ARG_DRAWING_SHEET "--drawing-sheet" #define ARG_DEFINE_VAR_SHORT "-D" #define ARG_DEFINE_VAR_LONG "--define-var" +#define ARG_VARIANTS "--variants" namespace CLI { @@ -84,6 +85,11 @@ protected: */ void addDefineArg(); + /** + * Set up the list of variants to output arguement. + */ + void addVariantsArg(); + /** * The internal handler that should be overloaded to implement command specific * processing and work. @@ -143,6 +149,18 @@ protected: * Value of the drawing sheet arg if configured */ std::map m_argDefineVars; + + /** + * Whether or not the input arguement for variant names was added for parsing. + */ + bool m_hasVariantsArg; + + /** + * A set of variant names to output. + * + * An empty set indicates the default variant only. + */ + std::set m_argVariantNames; }; } diff --git a/qa/tests/common/test_kicad_string.cpp b/qa/tests/common/test_kicad_string.cpp index ed25a25884..73942e2814 100644 --- a/qa/tests/common/test_kicad_string.cpp +++ b/qa/tests/common/test_kicad_string.cpp @@ -168,4 +168,22 @@ BOOST_AUTO_TEST_CASE( HTMLEscape ) } } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file + +/** + * Test #SortVariantNames(). + */ +BOOST_AUTO_TEST_CASE( VariantNameSort ) +{ + std::vector variantNames; + + // Verify default variant name is always sorted to the beginning of the list. + variantNames.emplace_back( wxS( "Variant1" ) ); + variantNames.emplace_back( GetDefaultVariantName() ); + std::sort( variantNames.begin(), variantNames.end(), SortVariantNames ); + + BOOST_CHECK_EQUAL( variantNames[0], GetDefaultVariantName() ); + BOOST_CHECK_EQUAL( variantNames[1], wxS( "Variant1" ) ); +} + + +BOOST_AUTO_TEST_SUITE_END()