diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index a3483eaab6..763b42f138 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -113,6 +113,7 @@ set( KICOMMON_SRCS
settings/settings_manager.cpp
project/board_project_settings.cpp
+ project/component_class_settings.cpp
project/net_settings.cpp
project/project_archiver.cpp
project/project_file.cpp
@@ -775,7 +776,10 @@ set( PCB_COMMON_SRCS
${CMAKE_SOURCE_DIR}/pcbnew/teardrop/teardrop_parameters.cpp #needed by board_design_settings.cpp
${CMAKE_SOURCE_DIR}/pcbnew/router/pns_meander.cpp #needed by board_design_settings.cpp
${CMAKE_SOURCE_DIR}/pcbnew/board.cpp
- ${CMAKE_SOURCE_DIR}/pcbnew/component_class_manager.cpp #needed by board.cpp
+ ${CMAKE_SOURCE_DIR}/pcbnew/component_classes/component_class.cpp
+ ${CMAKE_SOURCE_DIR}/pcbnew/component_classes/component_class_assignment_rule.cpp
+ ${CMAKE_SOURCE_DIR}/pcbnew/component_classes/component_class_cache_proxy.cpp
+ ${CMAKE_SOURCE_DIR}/pcbnew/component_classes/component_class_manager.cpp
${CMAKE_SOURCE_DIR}/pcbnew/board_item.cpp
${CMAKE_SOURCE_DIR}/pcbnew/pcb_dimension.cpp
${CMAKE_SOURCE_DIR}/pcbnew/pcb_shape.cpp
diff --git a/common/drc_rules.keywords b/common/drc_rules.keywords
index a976e6e4db..884d21fd19 100644
--- a/common/drc_rules.keywords
+++ b/common/drc_rules.keywords
@@ -1,5 +1,6 @@
annular_width
assertion
+assign_component_class
board_edge
buried_via
clearance
diff --git a/common/project/component_class_settings.cpp b/common/project/component_class_settings.cpp
new file mode 100644
index 0000000000..53f7ad0a8a
--- /dev/null
+++ b/common/project/component_class_settings.cpp
@@ -0,0 +1,393 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+
+#include
+#include
+
+constexpr int componentClassSettingsSchemaVersion = 0;
+
+
+COMPONENT_CLASS_SETTINGS::COMPONENT_CLASS_SETTINGS( JSON_SETTINGS* aParent,
+ const std::string& aPath ) :
+ NESTED_SETTINGS( "component_class_settings", componentClassSettingsSchemaVersion, aParent,
+ aPath, false ),
+ m_enableSheetComponentClasses( false )
+{
+ m_params.emplace_back( new PARAM_LAMBDA(
+ "sheet_component_classes",
+ [&]() -> nlohmann::json
+ {
+ nlohmann::json ret = {};
+
+ ret["enabled"] = m_enableSheetComponentClasses;
+
+ return ret;
+ },
+ [&]( const nlohmann::json& aJson )
+ {
+ if( !aJson.is_object() )
+ return;
+
+ if( !aJson.contains( "enabled" ) )
+ return;
+
+ m_enableSheetComponentClasses = aJson["enabled"].get();
+ },
+ {} ) );
+
+ m_params.emplace_back( new PARAM_LAMBDA(
+ "assignments",
+ [&]() -> nlohmann::json
+ {
+ nlohmann::json ret = nlohmann::json::array();
+
+ for( const COMPONENT_CLASS_ASSIGNMENT_DATA& assignment :
+ m_componentClassAssignments )
+ {
+ ret.push_back( saveAssignment( assignment ) );
+ }
+
+ return ret;
+ },
+ [&]( const nlohmann::json& aJson )
+ {
+ if( !aJson.is_array() )
+ return;
+
+ ClearComponentClassAssignments();
+
+ for( const auto& assignmentJson : aJson )
+ {
+ COMPONENT_CLASS_ASSIGNMENT_DATA assignment = loadAssignment( assignmentJson );
+ m_componentClassAssignments.push_back( assignment );
+ }
+ },
+ {} ) );
+}
+
+
+COMPONENT_CLASS_SETTINGS::~COMPONENT_CLASS_SETTINGS()
+{
+ // Release early before destroying members
+ if( m_parent )
+ {
+ m_parent->ReleaseNestedSettings( this );
+ m_parent = nullptr;
+ }
+}
+
+
+nlohmann::json
+COMPONENT_CLASS_SETTINGS::saveAssignment( const COMPONENT_CLASS_ASSIGNMENT_DATA& aAssignment )
+{
+ nlohmann::json ret;
+
+ const wxString matchOperator =
+ aAssignment.GetConditionsOperator()
+ == COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR::ALL
+ ? wxT( "ALL" )
+ : wxT( "ANY" );
+
+ ret["component_class"] = aAssignment.GetComponentClass().ToUTF8();
+ ret["conditions_operator"] = matchOperator.ToUTF8();
+
+ nlohmann::json conditionsJson;
+
+ for( const auto& [conditionType, conditionData] : aAssignment.GetConditions() )
+ {
+ nlohmann::json conditionJson;
+
+ if( !conditionData.first.empty() )
+ conditionJson["primary"] = conditionData.first;
+
+ if( !conditionData.second.empty() )
+ conditionJson["secondary"] = conditionData.second;
+
+ const wxString conditionName =
+ COMPONENT_CLASS_ASSIGNMENT_DATA::GetConditionName( conditionType );
+ conditionsJson[conditionName] = conditionJson;
+ }
+
+ ret["conditions"] = conditionsJson;
+
+ return ret;
+}
+
+
+COMPONENT_CLASS_ASSIGNMENT_DATA
+COMPONENT_CLASS_SETTINGS::loadAssignment( const nlohmann::json& aJson )
+{
+ COMPONENT_CLASS_ASSIGNMENT_DATA assignment;
+
+ assignment.SetComponentClass(
+ wxString( aJson["component_class"].get().c_str(), wxConvUTF8 ) );
+
+ const wxString matchOperator( aJson["conditions_operator"].get().c_str(),
+ wxConvUTF8 );
+
+ if( matchOperator == wxT( "ALL" ) )
+ {
+ assignment.SetConditionsOperation(
+ COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR::ALL );
+ }
+ else
+ {
+ assignment.SetConditionsOperation(
+ COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR::ANY );
+ }
+
+ for( const auto& [conditionTypeStr, conditionData] : aJson["conditions"].items() )
+ {
+ wxString primary, secondary;
+ COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE conditionType =
+ COMPONENT_CLASS_ASSIGNMENT_DATA::GetConditionType( conditionTypeStr );
+
+ if( conditionData.contains( "primary" ) )
+ primary = wxString( conditionData["primary"].get().c_str(), wxConvUTF8 );
+
+ if( conditionData.contains( "secondary" ) )
+ secondary =
+ wxString( conditionData["secondary"].get().c_str(), wxConvUTF8 );
+
+ assignment.SetCondition( conditionType, primary, secondary );
+ }
+
+ return assignment;
+}
+
+
+bool COMPONENT_CLASS_SETTINGS::operator==( const COMPONENT_CLASS_SETTINGS& aOther ) const
+{
+ // TODO: Implement this
+ throw;
+ //return true;
+}
+
+
+/**************************************************************************************************
+ *
+ * COMPONENT_CLASS_ASSIGNMENT_DATA implementation
+ *
+ *************************************************************************************************/
+
+wxString COMPONENT_CLASS_ASSIGNMENT_DATA::GetConditionName( const CONDITION_TYPE aCondition )
+{
+ switch( aCondition )
+ {
+ case CONDITION_TYPE::REFERENCE: return wxT( "REFERENCE" );
+ case CONDITION_TYPE::FOOTPRINT: return wxT( "FOOTPRINT" );
+ case CONDITION_TYPE::SIDE: return wxT( "SIDE" );
+ case CONDITION_TYPE::ROTATION: return wxT( "ROTATION" );
+ case CONDITION_TYPE::FOOTPRINT_FIELD: return wxT( "FOOTPRINT_FIELD" );
+ case CONDITION_TYPE::CUSTOM: return wxT( "CUSTOM" );
+ case CONDITION_TYPE::SHEET_NAME: return wxT( "SHEET_NAME" );
+ }
+
+ wxASSERT_MSG( false, "Invalid condition type" );
+
+ return wxEmptyString;
+}
+
+
+COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE
+COMPONENT_CLASS_ASSIGNMENT_DATA::GetConditionType( const wxString& aCondition )
+{
+ if( aCondition == wxT( "REFERENCE" ) )
+ return CONDITION_TYPE::REFERENCE;
+ if( aCondition == wxT( "FOOTPRINT" ) )
+ return CONDITION_TYPE::FOOTPRINT;
+ if( aCondition == wxT( "SIDE" ) )
+ return CONDITION_TYPE::SIDE;
+ if( aCondition == wxT( "ROTATION" ) )
+ return CONDITION_TYPE::ROTATION;
+ if( aCondition == wxT( "FOOTPRINT_FIELD" ) )
+ return CONDITION_TYPE::FOOTPRINT_FIELD;
+ if( aCondition == wxT( "CUSTOM" ) )
+ return CONDITION_TYPE::CUSTOM;
+ if( aCondition == wxT( "SHEET_NAME" ) )
+ return CONDITION_TYPE::SHEET_NAME;
+
+ wxASSERT_MSG( false, "Invalid condition type" );
+
+ return CONDITION_TYPE::REFERENCE;
+}
+
+
+wxString COMPONENT_CLASS_ASSIGNMENT_DATA::GetAssignmentInDRCLanguage() const
+{
+ if( m_componentClass.empty() )
+ return wxEmptyString;
+
+ if( m_conditions.empty() )
+ {
+ // A condition which always applies the netclass
+ return wxString::Format( wxT( "(version 1) (assign_component_class \"%s\")" ),
+ m_componentClass );
+ }
+
+ // Lambda to format a comma-separated list of references in to a DRC expression
+ auto getRefExpr = []( wxString aRefs ) -> wxString
+ {
+ aRefs.Trim( true ).Trim( false );
+
+ wxArrayString refs = wxSplit( aRefs, ',' );
+
+ if( refs.empty() )
+ return wxEmptyString;
+
+ std::ranges::transform( refs, refs.begin(),
+ []( const wxString& aRef )
+ {
+ return wxString::Format( wxT( "A.Reference == '%s'" ), aRef );
+ } );
+
+ wxString refsExpr = refs[0];
+
+ if( refs.size() > 1 )
+ {
+ for( auto itr = refs.begin() + 1; itr != refs.end(); ++itr )
+ refsExpr = refsExpr + wxT( " || " ) + *itr;
+ }
+
+ return wxString::Format( wxT( "( %s )" ), refsExpr );
+ };
+
+ // Lambda to format a footprint match DRC expression
+ auto getFootprintExpr = []( wxString aFootprint ) -> wxString
+ {
+ aFootprint.Trim( true ).Trim( false );
+
+ if( aFootprint.empty() )
+ return wxEmptyString;
+
+ return wxString::Format( wxT( "( A.Library_Link == '%s' )" ), aFootprint );
+ };
+
+ // Lambda to format a layer side DRC expression
+ auto getSideExpr = []( const wxString& aSide ) -> wxString
+ {
+ if( aSide == wxT( "Any" ) )
+ return wxEmptyString;
+
+ return wxString::Format( wxT( "( A.Layer == '%s' )" ),
+ aSide == wxT( "Front" ) ? wxT( "F.Cu" ) : wxT( "B.Cu" ) );
+ };
+
+ // Lambda to format a rotation DRC expression
+ auto getRotationExpr = []( wxString aRotation ) -> wxString
+ {
+ aRotation.Trim( true ).Trim( false );
+
+ int dummy;
+
+ if( aRotation.empty() || aRotation == wxT( "Any" ) || !aRotation.ToInt( &dummy ) )
+ return wxEmptyString;
+
+ return wxString::Format( wxT( "( A.Orientation == %s deg )" ), aRotation );
+ };
+
+
+ // Lambda to format a footprint field DRC expression
+ auto getFootprintFieldExpr = []( wxString aFieldName, wxString aFieldMatch ) -> wxString
+ {
+ aFieldName.Trim( true ).Trim( false );
+ aFieldMatch.Trim( true ).Trim( false );
+
+ if( aFieldName.empty() || aFieldMatch.empty() )
+ return wxEmptyString;
+
+ return wxString::Format( wxT( "( A.getField('%s') == '%s' )" ), aFieldName, aFieldMatch );
+ };
+
+ // Lambda to format a custom DRC expression
+ auto getCustomFieldExpr = []( wxString aExpr ) -> wxString
+ {
+ aExpr.Trim( true ).Trim( false );
+
+ if( aExpr.empty() )
+ return wxEmptyString;
+
+ return wxString::Format( wxT( "( %s )" ), aExpr );
+ };
+
+ // Lambda to format a sheet name expression
+ auto getSheetNameExpr = []( wxString aSheetName ) -> wxString
+ {
+ aSheetName.Trim( true ).Trim( false );
+
+ if( aSheetName.empty() )
+ return wxEmptyString;
+
+ return wxString::Format( wxT( "( A.memberOfSheet('%s') )" ), aSheetName );
+ };
+
+ std::vector conditionsExprs;
+
+ for( auto& [conditionType, conditionData] : m_conditions )
+ {
+ wxString conditionExpr;
+
+ switch( conditionType )
+ {
+ case CONDITION_TYPE::REFERENCE: conditionExpr = getRefExpr( conditionData.first ); break;
+ case CONDITION_TYPE::FOOTPRINT:
+ conditionExpr = getFootprintExpr( conditionData.first );
+ break;
+ case CONDITION_TYPE::SIDE: conditionExpr = getSideExpr( conditionData.first ); break;
+ case CONDITION_TYPE::ROTATION:
+ conditionExpr = getRotationExpr( conditionData.first );
+ break;
+ case CONDITION_TYPE::FOOTPRINT_FIELD:
+ conditionExpr = getFootprintFieldExpr( conditionData.first, conditionData.second );
+ break;
+ case CONDITION_TYPE::CUSTOM:
+ conditionExpr = getCustomFieldExpr( conditionData.first );
+ break;
+ case CONDITION_TYPE::SHEET_NAME:
+ conditionExpr = getSheetNameExpr( conditionData.first );
+ break;
+ }
+
+ if( !conditionExpr.empty() )
+ conditionsExprs.push_back( conditionExpr );
+ }
+
+ if( conditionsExprs.empty() )
+ return wxString::Format( wxT( "(version 1) (assign_component_class \"%s\")" ),
+ m_componentClass );
+
+ wxString allConditionsExpr = conditionsExprs[0];
+
+ if( conditionsExprs.size() > 1 )
+ {
+ wxString operatorExpr =
+ m_conditionsOperator == CONDITIONS_OPERATOR::ALL ? wxT( " && " ) : wxT( " || " );
+
+ for( auto itr = conditionsExprs.begin() + 1; itr != conditionsExprs.end(); ++itr )
+ {
+ allConditionsExpr = allConditionsExpr + operatorExpr + *itr;
+ }
+ }
+
+ return wxString::Format(
+ wxT( "(version 1) (assign_component_class \"%s\" (condition \"%s\" ) )" ),
+ m_componentClass, allConditionsExpr );
+}
diff --git a/common/project/project_file.cpp b/common/project/project_file.cpp
index 766448ab77..2a4d85adee 100644
--- a/common/project/project_file.cpp
+++ b/common/project/project_file.cpp
@@ -20,10 +20,10 @@
*/
#include
+#include
#include
#include
#include
-#include
#include
#include
#include
@@ -36,13 +36,8 @@ const int projectFileSchemaVersion = 3;
PROJECT_FILE::PROJECT_FILE( const wxString& aFullPath ) :
JSON_SETTINGS( aFullPath, SETTINGS_LOC::PROJECT, projectFileSchemaVersion ),
- m_ErcSettings( nullptr ),
- m_SchematicSettings( nullptr ),
- m_BoardSettings(),
- m_sheets(),
- m_boards(),
- m_project( nullptr ),
- m_wasMigrated( false )
+ m_ErcSettings( nullptr ), m_SchematicSettings( nullptr ), m_BoardSettings(),
+ m_project( nullptr ), m_wasMigrated( false )
{
// Keep old files around
m_deleteLegacyAfterMigration = false;
@@ -119,6 +114,9 @@ PROJECT_FILE::PROJECT_FILE( const wxString& aFullPath ) :
m_NetSettings = std::make_shared( this, "net_settings" );
+ m_ComponentClassSettings =
+ std::make_shared( this, "component_class_settings" );
+
m_params.emplace_back( new PARAM_LAYER_PRESET( "board.layer_presets", &m_LayerPresets ) );
m_params.emplace_back( new PARAM_VIEWPORT( "board.viewports", &m_Viewports ) );
diff --git a/include/dialogs/panel_assign_component_classes.h b/include/dialogs/panel_assign_component_classes.h
new file mode 100644
index 0000000000..0fc7ff95fb
--- /dev/null
+++ b/include/dialogs/panel_assign_component_classes.h
@@ -0,0 +1,434 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+
+#ifndef PANEL_ASSIGN_COMPONENT_CLASSES_H
+#define PANEL_ASSIGN_COMPONENT_CLASSES_H
+
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+
+/**************************************************************************************************
+ *
+ * PANEL_ASSIGN_COMPONENT_CLASSES
+ *
+ *************************************************************************************************/
+
+/**
+ * Top-level panel for dynamic component class assignment configuration. Nests a
+ * PANEL_COMPONENT_CLASS_ASSIGNMENT panel for each set of component class assignment conditions
+ */
+class PANEL_COMPONENT_CLASS_ASSIGNMENT;
+
+class PANEL_ASSIGN_COMPONENT_CLASSES : public PANEL_ASSIGN_COMPONENT_CLASSES_BASE
+{
+public:
+ PANEL_ASSIGN_COMPONENT_CLASSES( wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame,
+ std::shared_ptr aSettings,
+ DIALOG_SHIM* aDlg );
+
+ ~PANEL_ASSIGN_COMPONENT_CLASSES() override;
+
+ /// Loads current component class assignments from the board settings
+ bool TransferDataToWindow() override;
+
+ /// Saves the component class assignments to the board settings
+ bool TransferDataFromWindow() override;
+
+ /// Loads component class assignments from the given settings object
+ void ImportSettingsFrom( const std::shared_ptr& aOtherSettings );
+
+ /// Adds a new component class assignment rule
+ void OnAddAssignmentClick( wxCommandEvent& event ) override;
+
+ /// Removes a given component class assignment rule
+ void RemoveAssignment( PANEL_COMPONENT_CLASS_ASSIGNMENT* aPanel );
+
+ /// Returns references for all currently selected footprints
+ const std::vector& GetSelectionRefs() const { return m_selectionRefs; }
+
+ /// Returns names of all fields present in footprints
+ const std::vector& GetFieldNames() const { return m_fieldNames; }
+
+ /// Returns names of all sheets present in footprints
+ const std::vector& GetSheetNames() const { return m_sheetNames; }
+
+ /// Gets the active edit frame
+ PCB_EDIT_FRAME* GetFrame() const { return m_frame; }
+
+ /// Validates that all assignment rules can compile successfully
+ bool Validate() override;
+
+private:
+ /// Adds a new component class assignment rule
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* addAssignment();
+
+ /// Scrolls the panel to specified assignment rule
+ void scrollToAssignment( const PANEL_COMPONENT_CLASS_ASSIGNMENT* aAssignment ) const;
+
+ /// The parent dialog
+ DIALOG_SHIM* m_dlg;
+
+ /// The active edit frame
+ PCB_EDIT_FRAME* m_frame;
+
+ /// Vector of all currently displayed assignment rules
+ std::vector m_assignments;
+
+ /// The active settings object
+ std::shared_ptr m_componentClassSettings;
+
+ /// All currently selected footprint references
+ std::vector m_selectionRefs;
+
+ /// All footprint fields names present on the board
+ std::vector m_fieldNames;
+
+ /// All sheet names present on the board
+ std::vector m_sheetNames;
+
+ /// The list of all currently present component class assignments
+ wxBoxSizer* m_assignmentsList;
+};
+
+
+/**************************************************************************************************
+ *
+ * CONDITION_DATA
+ *
+ *************************************************************************************************/
+
+/**
+ * Class used to provide a unified interface from condition panels
+ * Handles determining the type of condition in use, and getting and settings the field data
+ */
+class CONDITION_DATA
+{
+public:
+ CONDITION_DATA( const COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE aCondition,
+ wxTextEntry* aPrimary, wxTextEntry* aSecondary = nullptr ) :
+ m_conditionType( aCondition ), m_primaryCtrl( aPrimary ), m_secondaryCtrl( aSecondary )
+ {
+ }
+
+ virtual ~CONDITION_DATA() {};
+
+ /// Gets the type of condition (e.g. Reference, Side) this data represents
+ virtual COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE GetConditionType() const
+ {
+ return m_conditionType;
+ }
+
+ /// Gets the primary data member for the condition (e.g. Reference, Side)
+ virtual wxString GetPrimaryField() const;
+
+ /// Sets the primary data member for the condition (e.g. Reference, Side)
+ virtual void SetPrimaryField( const wxString& aVal );
+
+ /// Gets the primary data member for the condition (e.g. FOOTPRITNT field value)
+ virtual wxString GetSecondaryField() const;
+
+ /// Sets the primary data member for the condition (e.g. FOOTPRITNT field
+ /// value)
+ virtual void SetSecondaryField( const wxString& aVal );
+
+private:
+ /// The type of condition being referenced
+ COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE m_conditionType;
+
+ /// The primary data field in the condition panel
+ wxTextEntry* m_primaryCtrl;
+
+ /// The Secondary data field in the condition panel
+ wxTextEntry* m_secondaryCtrl;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_ASSIGNMENT
+ *
+ *************************************************************************************************/
+
+/**
+* Panel which configures a set of conditions for a component class assignment rule
+*/
+class PANEL_COMPONENT_CLASS_ASSIGNMENT : public PANEL_COMPONENT_CLASS_ASSIGNMENT_BASE
+{
+public:
+ /// IDs for match type popup menu
+ enum ADD_MATCH_POPUP
+ {
+ ID_REFERENCE = wxID_HIGHEST + 1,
+ ID_FOOTPRINT,
+ ID_SIDE,
+ ID_ROTATION,
+ ID_FOOTPRINT_FIELD,
+ ID_SHEET_NAME,
+ ID_CUSTOM
+ };
+
+ PANEL_COMPONENT_CLASS_ASSIGNMENT( wxWindow* aParent,
+ PANEL_ASSIGN_COMPONENT_CLASSES* aPanelParent,
+ DIALOG_SHIM* aDlg );
+ ~PANEL_COMPONENT_CLASS_ASSIGNMENT();
+
+ /// Deletes this component class assignment rule
+ void OnDeleteAssignmentClick( wxCommandEvent& event ) override;
+
+ /// Adds a match condition to this component class assignment rule
+ void OnAddConditionClick( wxCommandEvent& event ) override;
+
+ /// Highlights footprints matching this set of conditions
+ void OnHighlightItemsClick( wxCommandEvent& event ) override;
+
+ /// Adds a condition to this component class assignment rule
+ CONDITION_DATA* AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE aCondition );
+
+ /// Removes a given condition from this component class assignment rule (note: called from the child
+ /// condition panel)
+ void RemoveCondition( wxPanel* aMatch );
+
+ const std::vector& GetConditions() const { return m_matches; }
+
+ /// Sets the resulting component class for this assignment
+ void SetComponentClass( const wxString& aComponentClass ) const;
+
+ /// Gets the resulting component class for this assignment
+ const wxString GetComponentClass() const;
+
+ /// Sets the boolean operator applied to all assignment conditions
+ void
+ SetConditionsOperator( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR aCondition ) const;
+
+ /// Gets the boolean operator applied to all assignment conditions
+ COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR GetConditionsOperator() const;
+
+ /// Converts the UI representation in to the internal assignment data representation
+ COMPONENT_CLASS_ASSIGNMENT_DATA GenerateAssignmentData() const;
+
+protected:
+ /// Handles add match condition popup menu selections
+ void onMenu( wxCommandEvent& aEvent );
+
+ /// The top-level configuration panel which owns this assignment rule
+ PANEL_ASSIGN_COMPONENT_CLASSES* m_parentPanel;
+
+ /// The sizer containing match condition panels
+ wxStaticBoxSizer* m_matchesList;
+
+ /// Set containing all currently configured match condition types
+ std::unordered_set m_conditionTypes;
+
+ /// The parent configuration dialog
+ DIALOG_SHIM* m_dlg;
+
+ /// All match conditions for this component class assignment rule
+ std::vector m_matches;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_REFERENCE
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on footprint reference
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_REFERENCE
+ : public PANEL_COMPONENT_CLASS_CONDITION_REFERENCE_BASE,
+ public CONDITION_DATA
+{
+public:
+ /// IDs for match type popup menu
+ enum IMPORT_POPUP_IDS
+ {
+ ID_IMPORT_REFS = wxID_HIGHEST + 1
+ };
+
+ explicit PANEL_COMPONENT_CLASS_CONDITION_REFERENCE( wxWindow* aParent );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+
+ void OnImportRefsClick( wxCommandEvent& event ) override;
+ void SetSelectionRefs( const std::vector& aRefs ) { m_selectionRefs = aRefs; }
+
+protected:
+ /// Handles import references popup menu selections
+ void onMenu( wxCommandEvent& aEvent );
+
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+ std::vector m_selectionRefs;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on footprint library identifier
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT
+ : public PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT_BASE,
+ public CONDITION_DATA
+{
+public:
+ explicit PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT( wxWindow* aParent, DIALOG_SHIM* aDlg );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+ void OnShowLibraryClick( wxCommandEvent& event ) override;
+
+protected:
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+ DIALOG_SHIM* m_dlg;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_SIDE
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on which side of the board a footprint is on
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_SIDE : public PANEL_COMPONENT_CLASS_CONDITION_SIDE_BASE,
+ public CONDITION_DATA
+{
+public:
+ explicit PANEL_COMPONENT_CLASS_CONDITION_SIDE( wxWindow* aParent );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+
+protected:
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_ROTATION
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on footprint rotation
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_ROTATION
+ : public PANEL_COMPONENT_CLASS_CONDITION_ROTATION_BASE,
+ public CONDITION_DATA
+{
+public:
+ explicit PANEL_COMPONENT_CLASS_CONDITION_ROTATION( wxWindow* aParent );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+
+protected:
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_FIELD
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on footprint field contents
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_FIELD : public PANEL_COMPONENT_CLASS_CONDITION_FIELD_BASE,
+ public CONDITION_DATA
+{
+public:
+ explicit PANEL_COMPONENT_CLASS_CONDITION_FIELD( wxWindow* aParent );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+
+ void SetFieldsList( const std::vector& aFields );
+
+protected:
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_CUSTOM
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on a custom DRC expression
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_CUSTOM : public PANEL_COMPONENT_CLASS_CONDITION_CUSTOM_BASE,
+ public CONDITION_DATA
+{
+public:
+ explicit PANEL_COMPONENT_CLASS_CONDITION_CUSTOM( wxWindow* aParent );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+
+protected:
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+};
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_SHEET
+ *
+ *************************************************************************************************/
+
+/**
+ * Configures matching based on a custom DRC expression
+ */
+class PANEL_COMPONENT_CLASS_CONDITION_SHEET : public PANEL_COMPONENT_CLASS_CONDITION_SHEET_BASE,
+ public CONDITION_DATA
+{
+public:
+ explicit PANEL_COMPONENT_CLASS_CONDITION_SHEET( wxWindow* aParent );
+
+ void OnDeleteConditionClick( wxCommandEvent& event ) override;
+
+ void SetSheetsList( const std::vector& aSheets );
+
+protected:
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
+};
+
+#endif //PANEL_ASSIGN_COMPONENT_CLASSES_H
diff --git a/include/dialogs/panel_generate_component_classes.h b/include/dialogs/panel_generate_component_classes.h
new file mode 100644
index 0000000000..57e876f629
--- /dev/null
+++ b/include/dialogs/panel_generate_component_classes.h
@@ -0,0 +1,56 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+
+#ifndef PANEL_GENERATE_COMPONENT_CLASSES_H
+#define PANEL_GENERATE_COMPONENT_CLASSES_H
+
+#include
+
+#include
+#include
+#include
+
+
+class PANEL_GENERATE_COMPONENT_CLASSES : public PANEL_GENERATE_COMPONENT_CLASSES_BASE
+{
+public:
+ PANEL_GENERATE_COMPONENT_CLASSES( wxWindow* aParentWindow, EDA_DRAW_FRAME* aFrame,
+ std::shared_ptr aSettings,
+ std::vector& aSelectionRefs );
+
+ bool TransferDataToWindow() override;
+ bool TransferDataFromWindow() override;
+
+ bool Validate() override;
+
+ //void ImportSettingsFrom( const std::shared_ptr& aNetSettings );
+
+protected:
+ EDA_DRAW_FRAME* m_frame;
+ std::shared_ptr m_componentClassSettings;
+
+ std::vector m_selectionRefs;
+};
+
+#endif //PANEL_GENERATE_COMPONENT_CLASSES
diff --git a/include/project/component_class_settings.h b/include/project/component_class_settings.h
new file mode 100644
index 0000000000..88d265a97d
--- /dev/null
+++ b/include/project/component_class_settings.h
@@ -0,0 +1,160 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#ifndef PROJECT_COMPONENT_CLASS_SETTINGS_H
+#define PROJECT_COMPONENT_CLASS_SETTINGS_H
+
+#include
+#include
+
+#include
+
+
+class KICOMMON_API COMPONENT_CLASS_ASSIGNMENT_DATA
+{
+public:
+ /// A condition match type
+ enum class CONDITION_TYPE
+ {
+ REFERENCE,
+ FOOTPRINT,
+ SIDE,
+ ROTATION,
+ FOOTPRINT_FIELD,
+ CUSTOM,
+ SHEET_NAME
+ };
+
+ /// Whether conditions are applied with OR or AND logic
+ enum class CONDITIONS_OPERATOR
+ {
+ ALL,
+ ANY
+ };
+
+ /// Sets the given condition type with the assocated match data
+ void SetCondition( const CONDITION_TYPE aCondition, const wxString& aPrimaryData,
+ const wxString& aSecondaryData )
+ {
+ m_conditions[aCondition] = { aPrimaryData, aSecondaryData };
+ }
+
+ /// Gets all conditions
+ const std::unordered_map>& GetConditions() const
+ {
+ return m_conditions;
+ }
+
+ /// Sets the resulting component class for matching footprints
+ void SetComponentClass( const wxString& aComponentClass )
+ {
+ m_componentClass = aComponentClass;
+ }
+
+ /// Gets the resulting component class for matching footprints
+ const wxString& GetComponentClass() const { return m_componentClass; }
+
+ /// Sets the boolean operation in use for all conditions
+ void SetConditionsOperation( const CONDITIONS_OPERATOR aOperator )
+ {
+ m_conditionsOperator = aOperator;
+ }
+
+ /// Gets the boolean operation in use for all conditions
+ CONDITIONS_OPERATOR GetConditionsOperator() const { return m_conditionsOperator; }
+
+ /// Maps a CONDITION_TYPE to a descriptive string
+ static wxString GetConditionName( const CONDITION_TYPE aCondition );
+
+ /// Maps a descriptive string to a CONDITION_TYPE
+ static CONDITION_TYPE GetConditionType( const wxString& aCondition );
+
+ /// Returns the DRC rules language for this component class assignment
+ wxString GetAssignmentInDRCLanguage() const;
+
+protected:
+ /// The name of the component class for this assignment rule
+ wxString m_componentClass;
+
+ /// Map of condition types to primary and secondary data fields for the condition
+ std::unordered_map> m_conditions;
+
+ /// Whether conditions are applied with AND or OR logic
+ /// Defaults to ALL
+ CONDITIONS_OPERATOR m_conditionsOperator{ CONDITIONS_OPERATOR::ALL };
+};
+
+
+/**
+ * COMPONENT_CLASS_SETTINGS stores data for component classes, including rules for automatic
+ * generation of component classes.
+ */
+class KICOMMON_API COMPONENT_CLASS_SETTINGS : public NESTED_SETTINGS
+{
+public:
+ COMPONENT_CLASS_SETTINGS( JSON_SETTINGS* aParent, const std::string& aPath );
+
+ virtual ~COMPONENT_CLASS_SETTINGS();
+
+ /// Sets whether component classes should be generated for components in hierarchical sheets
+ void SetEnableSheetComponentClasses( bool aEnabled )
+ {
+ m_enableSheetComponentClasses = aEnabled;
+ }
+
+ /// Gets whether component classes should be generated for components in hierarchical sheets
+ bool GetEnableSheetComponentClasses() const { return m_enableSheetComponentClasses; }
+
+ /// Clear all dynamic component class assignments
+ void ClearComponentClassAssignments() { m_componentClassAssignments.clear(); }
+
+ /// Gets all dynamic component class assignments
+ const std::vector& GetComponentClassAssignments() const
+ {
+ return m_componentClassAssignments;
+ }
+
+ // Adds a dynamic component class assignment
+ void AddComponentClassAssignment( const COMPONENT_CLASS_ASSIGNMENT_DATA& aAssignment )
+ {
+ m_componentClassAssignments.push_back( aAssignment );
+ }
+
+ bool operator==( const COMPONENT_CLASS_SETTINGS& aOther ) const;
+
+ bool operator!=( const COMPONENT_CLASS_SETTINGS& aOther ) const
+ {
+ return !operator==( aOther );
+ }
+
+private:
+ /// Toggle generation of component classes for hierarchical sheets
+ bool m_enableSheetComponentClasses;
+
+ /// All dynamic component class assignment rules
+ std::vector m_componentClassAssignments;
+
+ /// Saves a dynamic component class assignment to JSON
+ static nlohmann::json saveAssignment( const COMPONENT_CLASS_ASSIGNMENT_DATA& aAssignment );
+
+ /// Loads a dynamic component class assignment from JSON
+ static COMPONENT_CLASS_ASSIGNMENT_DATA loadAssignment( const nlohmann::json& aJson );
+};
+
+#endif // PROJECT_COMPONENT_CLASS_SETTINGS_H
diff --git a/include/project/project_file.h b/include/project/project_file.h
index ba55a34292..9ea2303ed0 100644
--- a/include/project/project_file.h
+++ b/include/project/project_file.h
@@ -31,6 +31,7 @@
class BOARD_DESIGN_SETTINGS;
class ERC_SETTINGS;
class NET_SETTINGS;
+class COMPONENT_CLASS_SETTINGS;
class LAYER_PAIR_SETTINGS;
class SCHEMATIC_SETTINGS;
class TEMPLATES;
@@ -105,6 +106,11 @@ public:
return m_NetSettings;
}
+ std::shared_ptr& ComponentClassSettings()
+ {
+ return m_ComponentClassSettings;
+ }
+
/**
* @return true if it should be safe to auto-save this file without user action
*/
@@ -182,6 +188,10 @@ public:
*/
std::shared_ptr m_NetSettings;
+ /**
+ * Component class settings for the project (owned here)
+ */
+ std::shared_ptr m_ComponentClassSettings;
std::vector m_LayerPresets; /// List of stored layer presets
std::vector m_Viewports; /// List of stored viewports (pos + zoom)
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index bbc6f67a0e..fd553b4bf8 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -181,6 +181,8 @@ set( PCBNEW_DIALOGS
dialogs/panel_pcbnew_display_origin_base.cpp
dialogs/panel_rule_area_properties_keepout_base.cpp
dialogs/panel_rule_area_properties_placement_base.cpp
+ dialogs/panel_assign_component_classes.cpp
+ dialogs/panel_assign_component_classes_base.cpp
dialogs/panel_setup_constraints.cpp
dialogs/panel_setup_constraints_base.cpp
dialogs/panel_setup_dimensions.cpp
@@ -329,7 +331,6 @@ set( PCBNEW_CLASS_SRCS
array_pad_number_provider.cpp
build_BOM_from_board.cpp
cleanup_item.cpp
- component_class_manager.cpp
convert_shape_list_to_polygon.cpp
cross-probing.cpp
edit.cpp
@@ -428,6 +429,11 @@ set( PCBNEW_CLASS_SRCS
widgets/net_inspector_panel.cpp
widgets/pcb_net_inspector_panel.cpp
+ component_classes/component_class.cpp
+ component_classes/component_class_cache_proxy.cpp
+ component_classes/component_class_assignment_rule.cpp
+ component_classes/component_class_manager.cpp
+
)
set( PCBNEW_GIT_SRCS
diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp
index bfb94e281f..9a92f535a7 100644
--- a/pcbnew/board.cpp
+++ b/pcbnew/board.cpp
@@ -55,6 +55,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -73,18 +74,13 @@ VECTOR2I BOARD_ITEM::ZeroOffset( 0, 0 );
BOARD::BOARD() :
- BOARD_ITEM_CONTAINER( (BOARD_ITEM*) nullptr, PCB_T ),
- m_LegacyDesignSettingsLoaded( false ),
- m_LegacyCopperEdgeClearanceLoaded( false ),
- m_LegacyNetclassesLoaded( false ),
- m_boardUse( BOARD_USE::NORMAL ),
- m_timeStamp( 1 ),
- m_paper( PAGE_INFO::A4 ),
- m_project( nullptr ),
- m_userUnits( EDA_UNITS::MM ),
+ BOARD_ITEM_CONTAINER( (BOARD_ITEM*) nullptr, PCB_T ), m_LegacyDesignSettingsLoaded( false ),
+ m_LegacyCopperEdgeClearanceLoaded( false ), m_LegacyNetclassesLoaded( false ),
+ m_boardUse( BOARD_USE::NORMAL ), m_timeStamp( 1 ), m_paper( PAGE_INFO::A4 ),
+ m_project( nullptr ), m_userUnits( EDA_UNITS::MM ),
m_designSettings( new BOARD_DESIGN_SETTINGS( nullptr, "board.design_settings" ) ),
- m_NetInfo( this ),
- m_embedFonts( false )
+ m_NetInfo( this ), m_embedFonts( false ),
+ m_componentClassManager( std::make_unique( this ) )
{
// A too small value do not allow connecting 2 shapes (i.e. segments) not exactly connected
// A too large value do not allow safely connecting 2 shapes like very short segments.
@@ -2162,6 +2158,17 @@ void BOARD::SynchronizeNetsAndNetClasses( bool aResetTrackAndViaSizes )
}
+bool BOARD::SynchronizeComponentClasses( const std::unordered_set& aNewSheetPaths ) const
+{
+ std::shared_ptr settings =
+ GetProject()->GetProjectFile().ComponentClassSettings();
+
+ return m_componentClassManager->SyncDynamicComponentClassAssignments(
+ settings->GetComponentClassAssignments(), settings->GetEnableSheetComponentClasses(),
+ aNewSheetPaths );
+}
+
+
int BOARD::SetAreasNetCodesFromNetNames()
{
int error_count = 0;
diff --git a/pcbnew/board.h b/pcbnew/board.h
index 2ca3ef930d..b910fa5326 100644
--- a/pcbnew/board.h
+++ b/pcbnew/board.h
@@ -27,7 +27,7 @@
#include
#include
-#include
+#include
#include
#include // Needed for stl hash extensions
#include // for OUTLINE_ERROR_HANDLER
@@ -1004,6 +1004,11 @@ public:
*/
void SynchronizeNetsAndNetClasses( bool aResetTrackAndViaSizes );
+ /**
+ * Copy component class / component class generator information from the project settings
+ */
+ bool SynchronizeComponentClasses( const std::unordered_set& aNewSheetPaths ) const;
+
/**
* Copy the current project's text variables into the boards property cache.
*/
@@ -1302,7 +1307,7 @@ public:
/**
* Gets the component class manager
*/
- COMPONENT_CLASS_MANAGER& GetComponentClassManager() { return m_componentClassManager; }
+ COMPONENT_CLASS_MANAGER& GetComponentClassManager() { return *m_componentClassManager; }
PROJECT::ELEM ProjectElementType() override { return PROJECT::ELEM::BOARD; }
@@ -1422,7 +1427,7 @@ private:
bool m_embedFonts;
- COMPONENT_CLASS_MANAGER m_componentClassManager;
+ std::unique_ptr m_componentClassManager;
};
diff --git a/pcbnew/board_commit.cpp b/pcbnew/board_commit.cpp
index 17e6ee95c7..ac09ebde17 100644
--- a/pcbnew/board_commit.cpp
+++ b/pcbnew/board_commit.cpp
@@ -42,6 +42,7 @@
#include
#include
+#include
using namespace std::placeholders;
@@ -317,6 +318,15 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags )
if( !staleTeardropPadsAndVias.empty() || !staleTeardropTracks.empty() )
teardropMgr.RemoveTeardrops( *this, &staleTeardropPadsAndVias, &staleTeardropTracks );
+ auto updateComponentClasses = [this]( BOARD_ITEM* boardItem )
+ {
+ if( boardItem->Type() != PCB_FOOTPRINT_T )
+ return;
+
+ FOOTPRINT* footprint = static_cast( boardItem );
+ GetBoard()->GetComponentClassManager().RebuildRequiredCaches( footprint );
+ };
+
for( COMMIT_LINE& ent : m_changes )
{
if( !ent.m_item || !ent.m_item->IsBOARD_ITEM() )
@@ -367,6 +377,8 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags )
if( view && boardItem->Type() != PCB_NETINFO_T )
view->Add( boardItem );
+ updateComponentClasses( boardItem );
+
break;
case CHT_REMOVE:
@@ -510,6 +522,8 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags )
propagateDamage( boardItem, staleZones, &staleHatchedShapes ); // after
}
+ updateComponentClasses( boardItem );
+
if( view )
view->Update( boardItem );
@@ -533,7 +547,10 @@ void BOARD_COMMIT::Push( const wxString& aMessage, int aCommitFlags )
{
item->ClearEditFlags();
} );
- }
+ } // ... and regenerate them.
+
+ // Invalidate component classes
+ board->GetComponentClassManager().InvalidateComponentClasses();
if( m_isBoardEditor )
{
@@ -703,6 +720,15 @@ void BOARD_COMMIT::Revert()
board->IncrementTimeStamp(); // clear caches
+ auto updateComponentClasses = [this]( BOARD_ITEM* boardItem )
+ {
+ if( boardItem->Type() != PCB_FOOTPRINT_T )
+ return;
+
+ FOOTPRINT* footprint = static_cast( boardItem );
+ GetBoard()->GetComponentClassManager().RebuildRequiredCaches( footprint );
+ };
+
std::vector bulkAddedItems;
std::vector bulkRemovedItems;
std::vector itemsChanged;
@@ -767,6 +793,8 @@ void BOARD_COMMIT::Revert()
bulkAddedItems.push_back( boardItem );
}
+ updateComponentClasses( boardItem );
+
break;
}
@@ -792,6 +820,8 @@ void BOARD_COMMIT::Revert()
connectivity->Add( boardItem );
itemsChanged.push_back( boardItem );
+ updateComponentClasses( boardItem );
+
delete entry.m_copy;
break;
}
@@ -804,6 +834,9 @@ void BOARD_COMMIT::Revert()
boardItem->ClearEditFlags();
}
+ // Invalidate component classes
+ board->GetComponentClassManager().InvalidateComponentClasses();
+
if( bulkAddedItems.size() > 0 || bulkRemovedItems.size() > 0 || itemsChanged.size() > 0 )
board->OnItemsCompositeUpdate( bulkAddedItems, bulkRemovedItems, itemsChanged );
@@ -822,4 +855,3 @@ void BOARD_COMMIT::Revert()
clear();
}
-
diff --git a/pcbnew/component_class_manager.cpp b/pcbnew/component_class_manager.cpp
deleted file mode 100644
index b902970712..0000000000
--- a/pcbnew/component_class_manager.cpp
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or (at your
- * option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see .
- */
-
-#include
-
-#include
-
-
-void COMPONENT_CLASS::AddConstituentClass( COMPONENT_CLASS* componentClass )
-{
- m_constituentClasses.push_back( componentClass );
-}
-
-
-bool COMPONENT_CLASS::ContainsClassName( const wxString& className ) const
-{
- if( m_constituentClasses.size() == 0 )
- return false;
-
- if( m_constituentClasses.size() == 1 )
- return m_name == className;
-
- return std::any_of( m_constituentClasses.begin(), m_constituentClasses.end(),
- [&className]( const COMPONENT_CLASS* testClass )
- {
- return testClass->GetFullName() == className;
- } );
-}
-
-
-wxString COMPONENT_CLASS::GetName() const
-{
- if( m_constituentClasses.size() == 0 )
- return wxT( "" );
-
- if( m_constituentClasses.size() == 1 )
- return m_name;
-
- wxASSERT( m_constituentClasses.size() >= 2 );
-
- wxString name;
-
- if( m_constituentClasses.size() == 2 )
- {
- name.Printf( _( "%s and %s" ), m_constituentClasses[0]->GetName(),
- m_constituentClasses[1]->GetName() );
- }
- else if( m_constituentClasses.size() == 3 )
- {
- name.Printf( _( "%s, %s and %s" ), m_constituentClasses[0]->GetName(),
- m_constituentClasses[1]->GetName(), m_constituentClasses[2]->GetName() );
- }
- else if( m_constituentClasses.size() > 3 )
- {
- name.Printf( _( "%s, %s and %d more" ), m_constituentClasses[0]->GetName(),
- m_constituentClasses[1]->GetName(),
- static_cast( m_constituentClasses.size() - 2 ) );
- }
-
- return name;
-}
-
-
-bool COMPONENT_CLASS::IsEmpty() const
-{
- return m_constituentClasses.size() == 0;
-}
-
-
-COMPONENT_CLASS_MANAGER::COMPONENT_CLASS_MANAGER()
-{
- m_noneClass = std::make_unique( wxEmptyString );
-}
-
-
-COMPONENT_CLASS* COMPONENT_CLASS_MANAGER::GetEffectiveComponentClass(
- const std::unordered_set& classNames )
-{
- if( classNames.size() == 0 )
- return m_noneClass.get();
-
- // Lambda to handle finding constituent component classes. This first checks the cache,
- // and if found moves the class to the primary classes map. If not found, it either returns
- // an existing class in the primary list or creates a new class.
- auto getOrCreateClass = [this]( const wxString& className )
- {
- if( m_classesCache.count( className ) )
- {
- auto existingClass = m_classesCache.extract( className );
- m_classes.insert( std::move( existingClass ) );
- }
- else if( !m_classes.count( className ) )
- {
- std::unique_ptr newClass =
- std::make_unique( className );
- newClass->AddConstituentClass( newClass.get() );
- m_classes[className] = std::move( newClass );
- }
-
- return m_classes[className].get();
- };
-
- // Handle single-assignment component classes
- if( classNames.size() == 1 )
- return getOrCreateClass( *classNames.begin() );
-
- // Handle composite component classes
- std::vector sortedClassNames( classNames.begin(), classNames.end() );
-
- std::sort( sortedClassNames.begin(), sortedClassNames.end(),
- []( const wxString& str1, const wxString& str2 )
- {
- return str1.Cmp( str2 ) < 0;
- } );
-
- wxString fullName = GetFullClassNameForConstituents( sortedClassNames );
-
- if( m_effectiveClassesCache.count( fullName ) )
- {
- // The effective class was previously constructed - copy it across to the new live map
- auto existingClass = m_effectiveClassesCache.extract( fullName );
- COMPONENT_CLASS* effClass = existingClass.mapped().get();
- m_effectiveClasses.insert( std::move( existingClass ) );
-
- // Ensure that all constituent component classes are copied to the live map
- for( COMPONENT_CLASS* constClass : effClass->GetConstituentClasses() )
- {
- if( m_classesCache.count( constClass->GetFullName() ) )
- {
- auto constClassNode = m_classesCache.extract( constClass->GetFullName() );
- m_classes.insert( std::move( constClassNode ) );
- }
- }
- }
- else if( !m_effectiveClasses.count( fullName ) )
- {
- // The effective class was not previously constructed
- std::unique_ptr effClass = std::make_unique( fullName );
-
- for( const wxString& className : sortedClassNames )
- effClass->AddConstituentClass( getOrCreateClass( className ) );
-
- m_effectiveClasses[fullName] = std::move( effClass );
- }
-
- return m_effectiveClasses[fullName].get();
-}
-
-
-void COMPONENT_CLASS_MANAGER::InitNetlistUpdate()
-{
- m_classesCache = std::move( m_classes );
- m_effectiveClassesCache = std::move( m_effectiveClasses );
-}
-
-
-void COMPONENT_CLASS_MANAGER::FinishNetlistUpdate()
-{
- m_classesCache.clear();
- m_effectiveClassesCache.clear();
-}
-
-
-wxString COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents(
- const std::unordered_set& classNames )
-{
- std::vector sortedClassNames( classNames.begin(), classNames.end() );
-
- std::sort( sortedClassNames.begin(), sortedClassNames.end(),
- []( const wxString& str1, const wxString& str2 )
- {
- return str1.Cmp( str2 ) < 0;
- } );
-
- return GetFullClassNameForConstituents( sortedClassNames );
-}
-
-
-wxString
-COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents( const std::vector& classNames )
-{
- if( classNames.size() == 0 )
- return wxEmptyString;
-
- wxString fullName = classNames[0];
-
- for( std::size_t i = 1; i < classNames.size(); ++i )
- {
- fullName += ",";
- fullName += classNames[i];
- }
-
- return fullName;
-}
diff --git a/pcbnew/component_class_manager.h b/pcbnew/component_class_manager.h
deleted file mode 100644
index 327c0528c3..0000000000
--- a/pcbnew/component_class_manager.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * This program source code file is part of KiCad, a free EDA CAD application.
- *
- * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation, either version 3 of the License, or (at your
- * option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see .
- */
-
-#ifndef PCBNEW_COMPONENT_CLASS_MANAGER_H
-#define PCBNEW_COMPONENT_CLASS_MANAGER_H
-
-#include
-#include
-#include
-#include
-
-#include
-
-/*
- * A lightweight representation of a component class. The membership within
- * m_consituentClasses allows determination of the type of class this is:
- *
- * m_constituentClasses.size() == 0: This is a null class (no assigment).
- * m_name is empty.
- * m_constituentClasses.size() == 1: This is an atomic class. The constituent class
- * pointer refers to itself. m_name contains the name of the atomic class
- * m_constituentClasses.size() > 1: This is a composite class. The constituent class
- * pointers refer to all atomic members. m_name contains a comma-delimited list of
- * all atomic member class names.
- */
-class COMPONENT_CLASS
-{
-public:
- COMPONENT_CLASS( const wxString& name ) : m_name( name ) {}
-
- /// Fetches the display name of this component class
- wxString GetName() const;
-
- /// Fetches the full name of this component class
- const wxString& GetFullName() const { return m_name; }
-
- /// Adds a constituent component class to an effective component class
- void AddConstituentClass( COMPONENT_CLASS* componentClass );
-
- /// Determines if this (effective) component class contains a specific sub-class
- bool ContainsClassName( const wxString& className ) const;
-
- /// Determines if this (effective) component class is empty (i.e. no classes defined)
- bool IsEmpty() const;
-
- /// Fetches a vector of the constituent classes for this (effective) class
- const std::vector& GetConstituentClasses() const
- {
- return m_constituentClasses;
- }
-
-private:
- /// The full name of the component class
- wxString m_name;
-
- /// The COMPONENT_CLASS objects contributing to this complete component class
- std::vector m_constituentClasses;
-};
-
-/*
- * A class to manage Component Classes in a board context
- *
- * This manager owns generated COMPONENT_CLASS objects, and guarantees that pointers to managed
- * objects are valid for the duration of the board lifetime. Note that, in order to maintain this
- * guarantee, there are two methods that must be called when updating the board from the netlist
- * (InitNetlistUpdate and FinishNetlistUpdate).
- */
-class COMPONENT_CLASS_MANAGER
-{
-public:
- COMPONENT_CLASS_MANAGER();
-
- /// @brief Gets the full effective class name for the given set of constituent classes
- static wxString
- GetFullClassNameForConstituents( const std::unordered_set& classNames );
-
- /// @brief Gets the full effective class name for the given set of constituent classes
- /// @param classNames a sorted vector of consituent class names
- static wxString GetFullClassNameForConstituents( const std::vector& classNames );
-
- /// @brief Gets an effective component class for the given constituent class names
- /// @param classes The names of the constituent component classes
- /// @return Effective COMPONENT_CLASS object
- COMPONENT_CLASS* GetEffectiveComponentClass( const std::unordered_set& classNames );
-
- /// Returns the unassigned component class
- const COMPONENT_CLASS* GetNoneComponentClass() const { return m_noneClass.get(); }
-
- /// Prepare the manager for a board update
- /// Must be called prior to updating the PCB from the netlist
- void InitNetlistUpdate();
-
- /// Cleans up the manager after a board update
- /// Must be called after updating the PCB from the netlist
- void FinishNetlistUpdate();
-
- /// Resets the contents of the manager
- // All pointers to COMPONENT_CLASS objects will being invalid
- void Reset();
-
- /// @brief Fetches a read-only map of the fundamental component classes
- const std::unordered_map>& GetClasses() const
- {
- return m_classes;
- }
-
-protected:
- /// All individual component classes
- std::unordered_map> m_classes;
-
- /// Generated effective component classes
- std::unordered_map> m_effectiveClasses;
-
- /// Cache of all individual component classes (for netlist updating)
- std::unordered_map> m_classesCache;
-
- /// Cache of all generated effective component classes (for netlist updating)
- std::unordered_map> m_effectiveClassesCache;
-
- /// The class to represent an unassigned component class
- std::unique_ptr m_noneClass;
-};
-
-#endif
diff --git a/pcbnew/component_classes/component_class.cpp b/pcbnew/component_classes/component_class.cpp
new file mode 100644
index 0000000000..5c21052c95
--- /dev/null
+++ b/pcbnew/component_classes/component_class.cpp
@@ -0,0 +1,100 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+
+#include
+#include
+
+
+void COMPONENT_CLASS::AddConstituentClass( COMPONENT_CLASS* componentClass )
+{
+ m_constituentClasses.push_back( componentClass );
+}
+
+
+const COMPONENT_CLASS* COMPONENT_CLASS::GetConstituentClass( const wxString& className ) const
+{
+ const auto itr = std::ranges::find_if( m_constituentClasses,
+ [&className]( const COMPONENT_CLASS* testClass )
+ {
+ return testClass->GetName() == className;
+ } );
+
+ if( itr != m_constituentClasses.end() )
+ return *itr;
+
+ return nullptr;
+}
+
+
+bool COMPONENT_CLASS::ContainsClassName( const wxString& className ) const
+{
+ return GetConstituentClass( className ) != nullptr;
+}
+
+
+wxString COMPONENT_CLASS::GetHumanReadableName() const
+{
+ if( m_constituentClasses.size() == 0 )
+ return wxT( "" );
+
+ if( m_constituentClasses.size() == 1 )
+ return m_name;
+
+ wxASSERT( m_constituentClasses.size() >= 2 );
+
+ wxString name;
+
+ if( m_constituentClasses.size() == 2 )
+ {
+ name.Printf( _( "%s and %s" ), m_constituentClasses[0]->GetName(),
+ m_constituentClasses[1]->GetName() );
+ }
+ else if( m_constituentClasses.size() == 3 )
+ {
+ name.Printf( _( "%s, %s and %s" ), m_constituentClasses[0]->GetName(),
+ m_constituentClasses[1]->GetName(), m_constituentClasses[2]->GetName() );
+ }
+ else if( m_constituentClasses.size() > 3 )
+ {
+ name.Printf( _( "%s, %s and %d more" ), m_constituentClasses[0]->GetName(),
+ m_constituentClasses[1]->GetName(),
+ static_cast( m_constituentClasses.size() - 2 ) );
+ }
+
+ return name;
+}
+
+
+bool COMPONENT_CLASS::IsEmpty() const
+{
+ return m_constituentClasses.empty();
+}
+
+
+bool COMPONENT_CLASS::operator==( const COMPONENT_CLASS& aComponent ) const
+{
+ return GetName() == aComponent.GetName();
+}
+
+
+bool COMPONENT_CLASS::operator!=( const COMPONENT_CLASS& aComponent ) const
+{
+ return !( *this == aComponent );
+}
\ No newline at end of file
diff --git a/pcbnew/component_classes/component_class.h b/pcbnew/component_classes/component_class.h
new file mode 100644
index 0000000000..13d3ba6bd6
--- /dev/null
+++ b/pcbnew/component_classes/component_class.h
@@ -0,0 +1,106 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+
+#ifndef PCBNEW_COMPONENT_CLASS_H
+#define PCBNEW_COMPONENT_CLASS_H
+
+#include
+#include
+
+/**
+ * A lightweight representation of a component class. The membership within
+ * m_consituentClasses allows determination of the type of class this is:
+ *
+ * m_constituentClasses.size() == 0: This is a null class (no assigment).
+ * m_name is empty.
+ * m_constituentClasses.size() == 1: This is an atomic class. The constituent class
+ * pointer refers to itself. m_name contains the name of the atomic class
+ * m_constituentClasses.size() > 1: This is a composite class. The constituent class
+ * pointers refer to all atomic members. m_name contains a comma-delimited list of
+ * all atomic member class names.
+ */
+class COMPONENT_CLASS
+{
+public:
+ /// The assignment context in which this component class is used
+ enum class USAGE
+ {
+ STATIC,
+ DYNAMIC,
+ STATIC_AND_DYNAMIC,
+ EFFECTIVE
+ };
+
+ /// Construct a new component class
+ explicit COMPONENT_CLASS( const wxString& name, const USAGE aUsageContext ) :
+ m_name( name ), m_usageContext( aUsageContext )
+ {
+ }
+
+ /// @brief Gets the consolidated name of this component class (which may be an aggregate). This is
+ /// intended for display to users (e.g. in infobars or messages). WARNING: Do not use this
+ /// to compare equivalence, or to export to other tools)
+ wxString GetHumanReadableName() const;
+
+ /// Fetches the full name of this component class
+ const wxString& GetName() const { return m_name; }
+
+ /// Adds a constituent component class to an effective component class
+ void AddConstituentClass( COMPONENT_CLASS* componentClass );
+
+ /// Returns a named constituent class of this component class, or nullptr if not found
+ const COMPONENT_CLASS* GetConstituentClass( const wxString& className ) const;
+
+ /// Determines if this (effective) component class contains a specific constituent class
+ bool ContainsClassName( const wxString& className ) const;
+
+ /// Determines if this (effective) component class is empty (i.e. no classes defined)
+ bool IsEmpty() const;
+
+ /// Fetches a vector of the constituent classes for this (effective) class
+ const std::vector& GetConstituentClasses() const
+ {
+ return m_constituentClasses;
+ }
+
+ /// Gets the assignment context in which this component class is being used
+ USAGE GetUsageContext() const { return m_usageContext; }
+
+ /// Sets the assignment context in which this component class is being used
+ void SetUsageContext( const USAGE aUsageContext ) { m_usageContext = aUsageContext; }
+
+ /// Tests two component classes for equality based on full class name
+ bool operator==( const COMPONENT_CLASS& aComponent ) const;
+
+ /// Tests two component classes for inequality based on full class name
+ bool operator!=( const COMPONENT_CLASS& aComponent ) const;
+
+private:
+ /// The full name of the component class
+ wxString m_name;
+
+ /// The COMPONENT_CLASS objects contributing to this complete component class
+ std::vector m_constituentClasses;
+
+ /// The assignment context in which this component class is being used
+ USAGE m_usageContext;
+};
+
+#endif //PCBNEW_COMPONENT_CLASS_H
diff --git a/pcbnew/component_classes/component_class_assignment_rule.cpp b/pcbnew/component_classes/component_class_assignment_rule.cpp
new file mode 100644
index 0000000000..14c44c082a
--- /dev/null
+++ b/pcbnew/component_classes/component_class_assignment_rule.cpp
@@ -0,0 +1,38 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+
+#include
+#include
+
+
+COMPONENT_CLASS_ASSIGNMENT_RULE::COMPONENT_CLASS_ASSIGNMENT_RULE(
+ const wxString& aComponentClass, std::shared_ptr&& aCondition ) :
+ m_componentClass( aComponentClass ), m_condition( std::move( aCondition ) )
+{
+}
+
+
+bool COMPONENT_CLASS_ASSIGNMENT_RULE::Matches( const FOOTPRINT* aFootprint ) const
+{
+ if( !m_condition )
+ return true;
+
+ return m_condition->EvaluateFor( aFootprint, nullptr, 0, aFootprint->GetSide(), nullptr );
+}
diff --git a/pcbnew/component_classes/component_class_assignment_rule.h b/pcbnew/component_classes/component_class_assignment_rule.h
new file mode 100644
index 0000000000..3f18373559
--- /dev/null
+++ b/pcbnew/component_classes/component_class_assignment_rule.h
@@ -0,0 +1,60 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#ifndef COMPONENT_CLASS_ASSIGNMENT_RULE_H
+#define COMPONENT_CLASS_ASSIGNMENT_RULE_H
+
+#include
+#include
+#include
+
+#include
+
+class FOOTPRINT;
+
+/**
+ * Class which represents a component class assignment rule. These are used to dynamically assign component classes
+ * based on FOOTPRINT parameters which are exposed through the DRC language.
+ */
+class COMPONENT_CLASS_ASSIGNMENT_RULE
+{
+public:
+ /// Construct a component class assignment rule
+ explicit COMPONENT_CLASS_ASSIGNMENT_RULE( const wxString& aComponentClass,
+ std::shared_ptr&& aCondition );
+
+ /// The component class to assign to matching footprints
+ wxString GetComponentClass() const { return m_componentClass; }
+ void SetComponentClass( const wxString& aComponentClass )
+ {
+ m_componentClass = aComponentClass;
+ }
+
+ /// Tests whether this rules matches the given footprint
+ bool Matches( const FOOTPRINT* aFootprint ) const;
+
+protected:
+ /// The component class to assign to matching footprints
+ wxString m_componentClass;
+
+ /// The DRC condition which specifies footprint matches for this component class
+ std::shared_ptr m_condition;
+};
+
+#endif // COMPONENT_CLASS_ASSIGNMENT_RULE_H
diff --git a/pcbnew/component_classes/component_class_cache_proxy.cpp b/pcbnew/component_classes/component_class_cache_proxy.cpp
new file mode 100644
index 0000000000..e445b01c0f
--- /dev/null
+++ b/pcbnew/component_classes/component_class_cache_proxy.cpp
@@ -0,0 +1,48 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+
+#include
+
+#include
+#include
+
+const COMPONENT_CLASS* COMPONENT_CLASS_CACHE_PROXY::GetComponentClass() const
+{
+ COMPONENT_CLASS_MANAGER& mgr = m_footprint->GetBoard()->GetComponentClassManager();
+
+ if( mgr.GetTicker() > m_lastTickerValue )
+ {
+ RecomputeComponentClass( &mgr );
+ }
+
+ return m_finalComponentClass;
+}
+
+
+void COMPONENT_CLASS_CACHE_PROXY::RecomputeComponentClass( COMPONENT_CLASS_MANAGER* manager ) const
+{
+ if( !manager )
+ manager = &m_footprint->GetBoard()->GetComponentClassManager();
+
+ m_dynamicComponentClass = manager->GetDynamicComponentClassesForFootprint( m_footprint );
+ m_finalComponentClass =
+ manager->GetCombinedComponentClass( m_staticComponentClass, m_dynamicComponentClass );
+ m_lastTickerValue = manager->GetTicker();
+}
diff --git a/pcbnew/component_classes/component_class_cache_proxy.h b/pcbnew/component_classes/component_class_cache_proxy.h
new file mode 100644
index 0000000000..fd330892af
--- /dev/null
+++ b/pcbnew/component_classes/component_class_cache_proxy.h
@@ -0,0 +1,70 @@
+/*
+* This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+
+#ifndef PCBNEW_COMPONENT_CLASS_CACHE_PROXY_H
+#define PCBNEW_COMPONENT_CLASS_CACHE_PROXY_H
+
+#include
+#include
+
+/*
+ * A class which acts as a cache-aware proxy for a FOOTPRINT's component class.
+ * Creating dynamic component classes (from component class generators) is an
+ * expensive operation, so we want to cache the results. This class is a cache
+ * proxy which tracks the validity of the cached component class with respect
+ * to loaded static and dynamic component class rules
+ */
+class COMPONENT_CLASS_CACHE_PROXY
+{
+public:
+ explicit COMPONENT_CLASS_CACHE_PROXY( FOOTPRINT* footprint ) : m_footprint( footprint ) {}
+
+ /// Sets the static component class
+ /// Static component classes are assigned in the schematic, and are transferred through the
+ /// netlist
+ void SetStaticComponentClass( const COMPONENT_CLASS* compClass )
+ {
+ m_staticComponentClass = compClass;
+ }
+
+ /// Gets the static component class
+ const COMPONENT_CLASS* GetStaticComponentClass() const { return m_staticComponentClass; }
+
+ /// Gets the full component class (static + dynamic resultant component class)
+ const COMPONENT_CLASS* GetComponentClass() const;
+
+ /// Forces recomputation of the component class
+ void RecomputeComponentClass( COMPONENT_CLASS_MANAGER* manager = nullptr ) const;
+
+ /// Invalidates the cache
+ /// The component class will be recalculated on the next access
+ void InvalidateCache() { m_lastTickerValue = -1; }
+
+protected:
+ FOOTPRINT* m_footprint;
+
+ const COMPONENT_CLASS* m_staticComponentClass{ nullptr };
+ mutable const COMPONENT_CLASS* m_dynamicComponentClass{ nullptr };
+ mutable const COMPONENT_CLASS* m_finalComponentClass{ nullptr };
+
+ mutable long long int m_lastTickerValue{ -1 };
+};
+
+#endif //PCBNEW_COMPONENT_CLASS_CACHE_PROXY_H
diff --git a/pcbnew/component_classes/component_class_manager.cpp b/pcbnew/component_classes/component_class_manager.cpp
new file mode 100644
index 0000000000..b30cabba8b
--- /dev/null
+++ b/pcbnew/component_classes/component_class_manager.cpp
@@ -0,0 +1,507 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+COMPONENT_CLASS_MANAGER::COMPONENT_CLASS_MANAGER( BOARD* board ) :
+ m_board( board ), m_hasCustomAssignmentConditions( false )
+{
+ m_noneClass =
+ std::make_shared( wxEmptyString, COMPONENT_CLASS::USAGE::STATIC );
+}
+
+
+/**
+ * Computes and returns an effective component class for a (possibly empty) set of constituent
+ * class names. This is called by the netlist updater to set static component classes on footprints.
+ *
+ * Where constituent or effective component classes already exist, they are re-used. This allows
+ * efficient comparison of (effective) component classes by pointer in DRC checks.
+ *
+ * Preconditions: InitNetlistUpdate() must be called before invoking this method.
+ * @param classNames The constitent component class names
+ * @return A pointer to an effective COMPONENT_CLASS representing all constituent component classes
+ */
+COMPONENT_CLASS* COMPONENT_CLASS_MANAGER::GetEffectiveStaticComponentClass(
+ const std::unordered_set& classNames )
+{
+ // Handle no component class condition
+ if( classNames.size() == 0 )
+ return m_noneClass.get();
+
+ // Handle single-assignment component classes
+ if( classNames.size() == 1 )
+ {
+ const wxString& className = *classNames.begin();
+ m_staticClassNamesCache.erase( className );
+ return getOrCreateConstituentClass( className, COMPONENT_CLASS::USAGE::STATIC );
+ }
+
+ // Handle composite component classes
+ const std::vector sortedClassNames = sortClassNames( classNames );
+ wxString fullName = GetFullClassNameForConstituents( sortedClassNames );
+
+ COMPONENT_CLASS* effectiveClass =
+ getOrCreateEffectiveClass( sortedClassNames, COMPONENT_CLASS::USAGE::STATIC );
+
+ for( const COMPONENT_CLASS* constituentClass : effectiveClass->GetConstituentClasses() )
+ m_staticClassNamesCache.erase( constituentClass->GetName() );
+
+ return effectiveClass;
+}
+
+
+void COMPONENT_CLASS_MANAGER::InitNetlistUpdate()
+{
+ for( const auto& [className, classPtr] : m_constituentClasses )
+ {
+ if( classPtr->GetUsageContext() == COMPONENT_CLASS::USAGE::STATIC
+ || classPtr->GetUsageContext() == COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC )
+ {
+ m_staticClassNamesCache.insert( className );
+ }
+ }
+
+ ++m_ticker;
+}
+
+
+void COMPONENT_CLASS_MANAGER::FinishNetlistUpdate()
+{
+ // m_staticClassesCache now contains any static component classes that are unused from the
+ // netlist update. Delete any effective component classes which refer to them in a static-only
+ // context, or update their usage context.
+ for( const wxString& className : m_staticClassNamesCache )
+ {
+ COMPONENT_CLASS* staticClass = m_constituentClasses[className].get();
+
+ if( staticClass->GetUsageContext() == COMPONENT_CLASS::USAGE::STATIC )
+ {
+ // Any static-only classes can be deleted, along with effective classes which refer to them
+ std::unordered_set classesToDelete;
+
+ for( const auto& [combinedFullName, combinedCompClass] : m_effectiveClasses )
+ {
+ if( combinedCompClass->ContainsClassName( className ) )
+ classesToDelete.insert( combinedFullName );
+ }
+
+ for( const wxString& classNameToDelete : classesToDelete )
+ m_effectiveClasses.erase( classNameToDelete );
+
+ m_constituentClasses.erase( className );
+ }
+ else
+ {
+ // Set the component class to dynamic-only scope
+ wxASSERT( staticClass->GetUsageContext()
+ == COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC );
+ staticClass->SetUsageContext( COMPONENT_CLASS::USAGE::DYNAMIC );
+ }
+ }
+
+ // Clear the caches
+ m_staticClassNamesCache.clear();
+}
+
+
+wxString COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents(
+ const std::unordered_set& classNames )
+{
+ const std::vector sortedClassNames = sortClassNames( classNames );
+
+ return GetFullClassNameForConstituents( sortedClassNames );
+}
+
+
+wxString
+COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents( const std::vector& classNames )
+{
+ if( classNames.size() == 0 )
+ return wxEmptyString;
+
+ wxString fullName = classNames[0];
+
+ for( std::size_t i = 1; i < classNames.size(); ++i )
+ {
+ fullName += ",";
+ fullName += classNames[i];
+ }
+
+ return fullName;
+}
+
+
+bool COMPONENT_CLASS_MANAGER::SyncDynamicComponentClassAssignments(
+ const std::vector& aAssignments,
+ bool aGenerateSheetClasses, const std::unordered_set& aNewSheetPaths )
+{
+ m_hasCustomAssignmentConditions = false;
+ bool success = true;
+
+ // Invalidate component class cache entries
+ ++m_ticker;
+
+ // Save previous dynamically assigned component class names
+ std::unordered_set prevClassNames;
+
+ for( const auto& rule : m_assignmentRules )
+ prevClassNames.insert( rule->GetComponentClass() );
+
+ m_assignmentRules.clear();
+
+ // Parse all assignment rules
+ std::vector> rules;
+
+ for( const COMPONENT_CLASS_ASSIGNMENT_DATA& assignment : aAssignments )
+ {
+ if( assignment.GetConditions().contains(
+ COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM ) )
+ {
+ m_hasCustomAssignmentConditions = true;
+ }
+
+ std::shared_ptr rule = CompileAssignmentRule( assignment );
+
+ if( rule )
+ rules.emplace_back( std::move( rule ) );
+ else
+ success = false;
+ }
+
+ // Generate sheet classes if required
+ if( aGenerateSheetClasses )
+ {
+ std::unordered_set sheetNames = aNewSheetPaths;
+
+ for( const FOOTPRINT* footprint : m_board->Footprints() )
+ sheetNames.insert( footprint->GetSheetname() );
+
+ for( wxString sheetName : sheetNames )
+ {
+ // Don't generate a class for empty sheets (e.g. manually placed footprints) or the root
+ // sheet
+ if( sheetName.empty() || sheetName == wxT( "/" ) )
+ continue;
+
+ sheetName.Replace( wxT( "\"" ), wxT( "" ) );
+ sheetName.Replace( wxT( "'" ), wxT( "" ) );
+
+ COMPONENT_CLASS_ASSIGNMENT_DATA assignment;
+ assignment.SetComponentClass( sheetName );
+ assignment.SetCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SHEET_NAME,
+ sheetName, wxEmptyString );
+
+ std::shared_ptr rule =
+ CompileAssignmentRule( assignment );
+
+ if( rule )
+ rules.emplace_back( std::move( rule ) );
+ else
+ success = false;
+ }
+ }
+
+ // Set the assignment rules
+ if( success )
+ m_assignmentRules = std::move( rules );
+
+ // Re-use or create component classes which may be output by assignment rules
+ for( const auto& rule : m_assignmentRules )
+ {
+ wxString className = rule->GetComponentClass();
+ prevClassNames.erase( className );
+ getOrCreateConstituentClass( className, COMPONENT_CLASS::USAGE::DYNAMIC );
+ }
+
+ // prevClassNames now contains all dynamic component class names no longer in use. Remove any
+ // effective component classes no longer in use.
+ for( const wxString& className : prevClassNames )
+ {
+ COMPONENT_CLASS* dynamicClass = m_constituentClasses[className].get();
+
+ if( dynamicClass->GetUsageContext() == COMPONENT_CLASS::USAGE::DYNAMIC )
+ {
+ // Any dynamic-only classes can be deleted, along with effective classes which refer to them
+ std::unordered_set classesToDelete;
+
+ for( const auto& [combinedFullName, combinedCompClass] : m_effectiveClasses )
+ {
+ if( combinedCompClass->ContainsClassName( className ) )
+ classesToDelete.insert( combinedFullName );
+ }
+
+ for( const wxString& classNameToDelete : classesToDelete )
+ m_effectiveClasses.erase( classNameToDelete );
+
+ m_constituentClasses.erase( className );
+ }
+ else
+ {
+ // Set the component class to dynamic-only scope
+ wxASSERT( dynamicClass->GetUsageContext()
+ == COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC );
+ dynamicClass->SetUsageContext( COMPONENT_CLASS::USAGE::STATIC );
+ }
+ }
+
+ return success;
+}
+
+
+std::shared_ptr
+COMPONENT_CLASS_MANAGER::CompileAssignmentRule( const COMPONENT_CLASS_ASSIGNMENT_DATA& aAssignment )
+{
+ const wxString ruleSource = aAssignment.GetAssignmentInDRCLanguage();
+
+ // Ignore incomplete rules (e.g. no component class name specified)
+ if( ruleSource.empty() )
+ return nullptr;
+
+ DRC_RULES_PARSER parser( ruleSource, wxT( "Component class assignment rule" ) );
+
+ try
+ {
+ std::vector> parsed;
+
+ WX_STRING_REPORTER reporter;
+ parser.ParseComponentClassAssignmentRules( parsed, &reporter );
+
+ if( reporter.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
+ return nullptr;
+
+ if( parsed.size() != 1 )
+ return nullptr;
+
+ return parsed[0];
+ }
+ catch( PARSE_ERROR& )
+ {
+ return nullptr;
+ }
+}
+
+
+const COMPONENT_CLASS*
+COMPONENT_CLASS_MANAGER::GetCombinedComponentClass( const COMPONENT_CLASS* staticClass,
+ const COMPONENT_CLASS* dynamicClass )
+{
+ std::unordered_set classNames;
+
+ if( staticClass )
+ {
+ for( const COMPONENT_CLASS* compClass : staticClass->GetConstituentClasses() )
+ classNames.insert( compClass->GetName() );
+ }
+
+ if( dynamicClass )
+ {
+ for( const COMPONENT_CLASS* compClass : dynamicClass->GetConstituentClasses() )
+ classNames.insert( compClass->GetName() );
+ }
+
+ if( classNames.empty() )
+ return GetNoneComponentClass();
+
+ if( classNames.size() == 1 )
+ {
+ wxASSERT( m_constituentClasses.contains( *classNames.begin() ) );
+ return m_constituentClasses[*classNames.begin()].get();
+ }
+
+ const std::vector sortedClassNames = sortClassNames( classNames );
+
+ wxString fullCombinedName = GetFullClassNameForConstituents( sortedClassNames );
+
+ if( !m_effectiveClasses.contains( fullCombinedName ) )
+ {
+ std::unique_ptr combinedClass = std::make_unique(
+ fullCombinedName, COMPONENT_CLASS::USAGE::EFFECTIVE );
+
+ for( const wxString& className : sortedClassNames )
+ {
+ wxASSERT( m_constituentClasses.contains( className ) );
+ combinedClass->AddConstituentClass( m_constituentClasses[className].get() );
+ }
+
+ m_effectiveClasses[fullCombinedName] = std::move( combinedClass );
+ }
+
+ return m_effectiveClasses[fullCombinedName].get();
+}
+
+
+void COMPONENT_CLASS_MANAGER::InvalidateComponentClasses()
+{
+ ++m_ticker;
+}
+
+
+void COMPONENT_CLASS_MANAGER::ForceComponentClassRecalculation() const
+{
+ for( const auto& footprint : m_board->Footprints() )
+ {
+ footprint->RecomputeComponentClass();
+ }
+}
+
+
+const COMPONENT_CLASS*
+COMPONENT_CLASS_MANAGER::GetDynamicComponentClassesForFootprint( const FOOTPRINT* footprint )
+{
+ std::unordered_set classNames;
+
+ // Assemble matching component class names
+ for( const auto& rule : m_assignmentRules )
+ {
+ if( rule->Matches( footprint ) )
+ classNames.insert( rule->GetComponentClass() );
+ }
+
+ // Handle composite component classes
+ const std::vector sortedClassNames = sortClassNames( classNames );
+ const wxString fullName = GetFullClassNameForConstituents( sortedClassNames );
+
+ // No matching component classes
+ if( classNames.empty() )
+ return nullptr;
+
+ // One matching component class
+ if( classNames.size() == 1 )
+ return getOrCreateConstituentClass( *classNames.begin(), COMPONENT_CLASS::USAGE::DYNAMIC );
+
+ // Multiple matching component classes
+ return getOrCreateEffectiveClass( sortedClassNames, COMPONENT_CLASS::USAGE::DYNAMIC );
+}
+
+
+std::vector
+COMPONENT_CLASS_MANAGER::sortClassNames( const std::unordered_set& classNames )
+{
+ std::vector sortedClassNames( classNames.begin(), classNames.end() );
+
+ std::ranges::sort( sortedClassNames,
+ []( const wxString& str1, const wxString& str2 )
+ {
+ return str1.Cmp( str2 ) < 0;
+ } );
+
+ return sortedClassNames;
+}
+
+
+COMPONENT_CLASS*
+COMPONENT_CLASS_MANAGER::getOrCreateConstituentClass( const wxString& aClassName,
+ COMPONENT_CLASS::USAGE aContext )
+{
+ if( aContext == COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC
+ || aContext == COMPONENT_CLASS::USAGE::EFFECTIVE )
+ {
+ wxFAIL_MSG( "Can't create a STATIC_AND_DYNAMIC or EFFECTIVE constituent component class" );
+ return m_noneClass.get();
+ }
+
+ if( m_constituentClasses.contains( aClassName ) )
+ {
+ COMPONENT_CLASS* compClass = m_constituentClasses[aClassName].get();
+
+ if( aContext != compClass->GetUsageContext() )
+ compClass->SetUsageContext( COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC );
+
+ return compClass;
+ }
+
+ std::unique_ptr newClass =
+ std::make_unique( aClassName, aContext );
+ newClass->AddConstituentClass( newClass.get() );
+
+ m_constituentClasses[aClassName] = std::move( newClass );
+
+ return m_constituentClasses[aClassName].get();
+}
+
+
+COMPONENT_CLASS*
+COMPONENT_CLASS_MANAGER::getOrCreateEffectiveClass( const std::vector& aClassNames,
+ COMPONENT_CLASS::USAGE aContext )
+{
+ wxString fullClassName = GetFullClassNameForConstituents( aClassNames );
+
+ if( m_effectiveClasses.contains( fullClassName ) )
+ {
+ COMPONENT_CLASS* compClass = m_effectiveClasses[fullClassName].get();
+
+ for( COMPONENT_CLASS* constituentClass : compClass->GetConstituentClasses() )
+ {
+ if( constituentClass->GetUsageContext() != aContext )
+ constituentClass->SetUsageContext( COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC );
+ }
+ }
+ else
+ {
+ std::unique_ptr effectiveClass = std::make_unique(
+ fullClassName, COMPONENT_CLASS::USAGE::EFFECTIVE );
+
+ for( const wxString& className : aClassNames )
+ {
+ COMPONENT_CLASS* constituentClass = getOrCreateConstituentClass( className, aContext );
+ effectiveClass->AddConstituentClass( constituentClass );
+ }
+
+ m_effectiveClasses[fullClassName] = std::move( effectiveClass );
+ }
+
+ return m_effectiveClasses[fullClassName].get();
+}
+
+
+std::unordered_set COMPONENT_CLASS_MANAGER::GetClassNames() const
+{
+ std::unordered_set classNames;
+
+ for( const auto& className : m_constituentClasses | std::views::keys )
+ classNames.insert( className );
+
+ return classNames;
+}
+
+
+void COMPONENT_CLASS_MANAGER::RebuildRequiredCaches( FOOTPRINT* aFootprint ) const
+{
+ if( aFootprint )
+ {
+ aFootprint->BuildCourtyardCaches();
+ }
+ else
+ {
+ for( FOOTPRINT* fp : m_board->Footprints() )
+ fp->BuildCourtyardCaches();
+ }
+}
diff --git a/pcbnew/component_classes/component_class_manager.h b/pcbnew/component_classes/component_class_manager.h
new file mode 100644
index 0000000000..cf2eeb6cab
--- /dev/null
+++ b/pcbnew/component_classes/component_class_manager.h
@@ -0,0 +1,153 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#ifndef PCBNEW_COMPONENT_CLASS_MANAGER_H
+#define PCBNEW_COMPONENT_CLASS_MANAGER_H
+
+#include
+#include
+#include
+
+#include
+#include
+
+
+class BOARD;
+class COMPONENT_CLASS_ASSIGNMENT_RULE;
+class DRC_TOOL;
+class FOOTPRINT;
+
+/**
+ * A class to manage Component Classes in a board context
+ *
+ * This manager owns generated COMPONENT_CLASS objects, and guarantees that pointers to managed
+ * objects are valid for the duration of the board lifetime. Note that, in order to maintain this
+ * guarantee, there are two methods that must be called when updating the board from the netlist
+ * (InitNetlistUpdate and FinishNetlistUpdate).
+ */
+class COMPONENT_CLASS_MANAGER
+{
+public:
+ explicit COMPONENT_CLASS_MANAGER( BOARD* board );
+
+ /// @brief Gets the full effective class name for the given set of constituent classes
+ static wxString
+ GetFullClassNameForConstituents( const std::unordered_set& classNames );
+
+ /// @brief Gets the full effective class name for the given set of constituent classes
+ /// @param classNames a sorted vector of consituent class names
+ static wxString GetFullClassNameForConstituents( const std::vector& classNames );
+
+ /// @brief Gets an effective component class for the given constituent class names
+ /// @param classNames The names of the constituent component classes
+ /// @return Effective COMPONENT_CLASS object
+ COMPONENT_CLASS*
+ GetEffectiveStaticComponentClass( const std::unordered_set& classNames );
+
+ /// Returns the unassigned component class
+ const COMPONENT_CLASS* GetNoneComponentClass() const { return m_noneClass.get(); }
+
+ /// Prepare the manager for a board update
+ /// Must be called prior to updating the PCB from the netlist
+ void InitNetlistUpdate();
+
+ /// Cleans up the manager after a board update
+ /// Must be called after updating the PCB from the netlist
+ void FinishNetlistUpdate();
+
+ /// Fetches a read-only map of the fundamental component classes
+ std::unordered_set GetClassNames() const;
+
+ /// Synchronises all dynamic component class assignment rules
+ /// @returns false if rules fail to parse, true if successful
+ bool SyncDynamicComponentClassAssignments(
+ const std::vector& aAssignments,
+ bool aGenerateSheetClasses, const std::unordered_set& aNewSheetPaths );
+
+ /// Gets the dynamic component classes which match the given footprint
+ const COMPONENT_CLASS* GetDynamicComponentClassesForFootprint( const FOOTPRINT* footprint );
+
+ /// Gets the combined component class with the given static and dynamic constituent component classes
+ const COMPONENT_CLASS* GetCombinedComponentClass( const COMPONENT_CLASS* staticClass,
+ const COMPONENT_CLASS* dynamicClass );
+
+ /// Forces the component class for all footprints to be recalculated. This should be called before running DRC as
+ /// checking for valid component class cache entries is threadsafe, but computing them is not. Blocking during this
+ /// check would be a negative performance impact for DRC computation, so we force recalculation instead.
+ void ForceComponentClassRecalculation() const;
+
+ /// Gets the component class validity ticker
+ /// Used to check validity of cached component classes
+ long long int GetTicker() const { return m_ticker; }
+
+ /// Invalidates any caches component classes and recomputes caches if required. This will force
+ /// recomputation of component classes on next access
+ void InvalidateComponentClasses();
+
+ /// Rebuilds any caches that may be required by custom assignment rules
+ /// @param fp the footprint to rebuild. If null, rebuilds all footprint caches
+ void RebuildRequiredCaches( FOOTPRINT* aFootprint = nullptr ) const;
+
+ /// Determines whether any custom dynamic rules have a custom assignment condition
+ bool HasCustomAssignmentConditions() const { return m_hasCustomAssignmentConditions; }
+
+ static std::shared_ptr
+ CompileAssignmentRule( const COMPONENT_CLASS_ASSIGNMENT_DATA& aAssignment );
+
+protected:
+ /// Sorts the given class names in to canonical order
+ static std::vector sortClassNames( const std::unordered_set& classNames );
+
+ /// Returns a constituent component class, re-using an existing instantiation where possible
+ COMPONENT_CLASS* getOrCreateConstituentClass( const wxString& aClassName,
+ COMPONENT_CLASS::USAGE aContext );
+
+ /// Returns an effective component class for the given set of constituent class names
+ /// Precondition: aClassNames is sorted by sortClassNames
+ COMPONENT_CLASS* getOrCreateEffectiveClass( const std::vector& aClassNames,
+ COMPONENT_CLASS::USAGE aContext );
+
+ /// The board these component classes are assigned to / from
+ BOARD* m_board;
+
+ /// The class to represent an unassigned component class
+ std::shared_ptr m_noneClass;
+
+ /// All individual component classes from static assignments
+ std::unordered_map> m_constituentClasses;
+
+ /// Generated effective (composite) static component classes
+ std::unordered_map> m_effectiveClasses;
+
+ /// Cache of in-use static component class names
+ /// Used for cleanup following netlist updates
+ std::unordered_set m_staticClassNamesCache;
+
+
+ /// Active component class assignment rules
+ std::vector> m_assignmentRules;
+
+ /// Quick lookup of presence of custom dynamic assignment conditions
+ bool m_hasCustomAssignmentConditions;
+
+ /// Monotonically increasing ticker to test cached component class validity
+ long long int m_ticker{ 0 };
+};
+
+#endif
diff --git a/pcbnew/dialogs/dialog_board_setup.cpp b/pcbnew/dialogs/dialog_board_setup.cpp
index 7849576e0c..5e41a12b7a 100644
--- a/pcbnew/dialogs/dialog_board_setup.cpp
+++ b/pcbnew/dialogs/dialog_board_setup.cpp
@@ -37,6 +37,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -47,6 +48,8 @@
#include "dialog_board_setup.h"
+#include
+
#define RESOLVE_PAGE( T, pageIndex ) static_cast( m_treebook->ResolvePage( pageIndex ) )
@@ -185,6 +188,17 @@ DIALOG_BOARD_SETUP::DIALOG_BOARD_SETUP( PCB_EDIT_FRAME* aFrame ) :
false );
}, _( "Net Classes" ) );
+ m_componentClassesPage = m_treebook->GetPageCount();
+ m_treebook->AddLazySubPage(
+ [this]( wxWindow* aParent ) -> wxWindow*
+ {
+ // Construct the panel
+ return new PANEL_ASSIGN_COMPONENT_CLASSES(
+ aParent, m_frame, m_frame->Prj().GetProjectFile().ComponentClassSettings(),
+ this );
+ },
+ _( "Component Classes" ) );
+
m_customRulesPage = m_treebook->GetPageCount();
m_treebook->AddLazySubPage(
[this]( wxWindow* aParent ) -> wxWindow*
@@ -377,6 +391,14 @@ void DIALOG_BOARD_SETUP::onAuxiliaryAction( wxCommandEvent& aEvent )
m_netclassesPage )->ImportSettingsFrom( otherProjectFile.m_NetSettings );
}
+ if( importDlg.m_ComponentClassesOpt->GetValue() )
+ {
+ PROJECT_FILE& otherProjectFile = otherPrj->GetProjectFile();
+
+ RESOLVE_PAGE( PANEL_ASSIGN_COMPONENT_CLASSES, m_componentClassesPage )
+ ->ImportSettingsFrom( otherProjectFile.m_ComponentClassSettings );
+ }
+
if( importDlg.m_TracksAndViasOpt->GetValue() )
{
RESOLVE_PAGE( PANEL_SETUP_TRACKS_AND_VIAS,
diff --git a/pcbnew/dialogs/dialog_board_setup.h b/pcbnew/dialogs/dialog_board_setup.h
index 8a5ea3d027..182634f45d 100644
--- a/pcbnew/dialogs/dialog_board_setup.h
+++ b/pcbnew/dialogs/dialog_board_setup.h
@@ -67,6 +67,7 @@ private:
size_t m_teardropsPage;
size_t m_tuningPatternsPage;
size_t m_netclassesPage;
+ size_t m_componentClassesPage;
size_t m_customRulesPage;
size_t m_severitiesPage;
size_t m_embeddedFilesPage;
diff --git a/pcbnew/dialogs/dialog_import_settings.cpp b/pcbnew/dialogs/dialog_import_settings.cpp
index 10e8343d16..6e5b9a323a 100644
--- a/pcbnew/dialogs/dialog_import_settings.cpp
+++ b/pcbnew/dialogs/dialog_import_settings.cpp
@@ -83,7 +83,8 @@ bool DIALOG_IMPORT_SETTINGS::UpdateImportSettingsButton()
|| m_ConstraintsOpt->IsChecked() || m_NetclassesOpt->IsChecked()
|| m_SeveritiesOpt->IsChecked() || m_TextAndGraphicsOpt->IsChecked()
|| m_FormattingOpt->IsChecked() || m_TracksAndViasOpt->IsChecked()
- || m_TuningPatternsOpt->IsChecked() || m_CustomRulesOpt->IsChecked() );
+ || m_TuningPatternsOpt->IsChecked() || m_CustomRulesOpt->IsChecked()
+ || m_ComponentClassesOpt->IsChecked() );
m_sdbSizer1OK->Enable( buttonEnableState );
diff --git a/pcbnew/dialogs/dialog_import_settings_base.cpp b/pcbnew/dialogs/dialog_import_settings_base.cpp
index 6f9bbcb0f8..b35ec938c8 100644
--- a/pcbnew/dialogs/dialog_import_settings_base.cpp
+++ b/pcbnew/dialogs/dialog_import_settings_base.cpp
@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
+// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@@ -75,6 +75,9 @@ DIALOG_IMPORT_SETTINGS_BASE::DIALOG_IMPORT_SETTINGS_BASE( wxWindow* parent, wxWi
m_NetclassesOpt = new wxCheckBox( this, wxID_ANY, _("Net classes"), wxDefaultPosition, wxDefaultSize, 0 );
bLeftCol->Add( m_NetclassesOpt, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+ m_ComponentClassesOpt = new wxCheckBox( this, wxID_ANY, _("Component classes"), wxDefaultPosition, wxDefaultSize, 0 );
+ bLeftCol->Add( m_ComponentClassesOpt, 0, wxBOTTOM|wxLEFT|wxRIGHT, 5 );
+
m_CustomRulesOpt = new wxCheckBox( this, wxID_ANY, _("Custom rules"), wxDefaultPosition, wxDefaultSize, 0 );
bLeftCol->Add( m_CustomRulesOpt, 0, wxBOTTOM|wxRIGHT|wxLEFT, 5 );
@@ -120,6 +123,7 @@ DIALOG_IMPORT_SETTINGS_BASE::DIALOG_IMPORT_SETTINGS_BASE( wxWindow* parent, wxWi
m_TracksAndViasOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_TuningPatternsOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_NetclassesOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
+ m_ComponentClassesOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_CustomRulesOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_SeveritiesOpt->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_selectAllButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnSelectAll ), NULL, this );
@@ -136,6 +140,7 @@ DIALOG_IMPORT_SETTINGS_BASE::~DIALOG_IMPORT_SETTINGS_BASE()
m_TracksAndViasOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_TuningPatternsOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_NetclassesOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
+ m_ComponentClassesOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_CustomRulesOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_SeveritiesOpt->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnCheckboxClicked ), NULL, this );
m_selectAllButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_IMPORT_SETTINGS_BASE::OnSelectAll ), NULL, this );
diff --git a/pcbnew/dialogs/dialog_import_settings_base.fbp b/pcbnew/dialogs/dialog_import_settings_base.fbp
index 3d4a758985..3e5eb8efa7 100644
--- a/pcbnew/dialogs/dialog_import_settings_base.fbp
+++ b/pcbnew/dialogs/dialog_import_settings_base.fbp
@@ -1,1176 +1,1261 @@
-
+
-
-
diff --git a/pcbnew/dialogs/dialog_import_settings_base.h b/pcbnew/dialogs/dialog_import_settings_base.h
index 30d539d9de..0ca977ed7a 100644
--- a/pcbnew/dialogs/dialog_import_settings_base.h
+++ b/pcbnew/dialogs/dialog_import_settings_base.h
@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
+// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
@@ -64,6 +64,7 @@ class DIALOG_IMPORT_SETTINGS_BASE : public DIALOG_SHIM
wxCheckBox* m_TeardropsOpt;
wxCheckBox* m_TuningPatternsOpt;
wxCheckBox* m_NetclassesOpt;
+ wxCheckBox* m_ComponentClassesOpt;
wxCheckBox* m_CustomRulesOpt;
wxCheckBox* m_SeveritiesOpt;
diff --git a/pcbnew/dialogs/dialog_rule_area_properties.cpp b/pcbnew/dialogs/dialog_rule_area_properties.cpp
index 90d5f22483..51694451ab 100644
--- a/pcbnew/dialogs/dialog_rule_area_properties.cpp
+++ b/pcbnew/dialogs/dialog_rule_area_properties.cpp
@@ -250,8 +250,8 @@ bool DIALOG_RULE_AREA_PROPERTIES::TransferDataToWindow()
// Fetch component classes
std::set classNames;
- for( const auto& [k, v] : m_board->GetComponentClassManager().GetClasses() )
- classNames.insert( k );
+ for( const wxString& className : m_board->GetComponentClassManager().GetClassNames() )
+ classNames.insert( className );
for( const wxString& sourceName : classNames )
m_placementProperties->m_componentClassCombo->Append( sourceName );
diff --git a/pcbnew/dialogs/panel_assign_component_classes.cpp b/pcbnew/dialogs/panel_assign_component_classes.cpp
new file mode 100644
index 0000000000..38521f2b46
--- /dev/null
+++ b/pcbnew/dialogs/panel_assign_component_classes.cpp
@@ -0,0 +1,788 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+/**************************************************************************************************
+ *
+ * PANEL_ASSIGN_COMPONENT_CLASSES implementation
+ * This is the top-level panel for component class configuration
+ *
+ *************************************************************************************************/
+
+PANEL_ASSIGN_COMPONENT_CLASSES::PANEL_ASSIGN_COMPONENT_CLASSES(
+ wxWindow* aParentWindow, PCB_EDIT_FRAME* aFrame,
+ std::shared_ptr aSettings, DIALOG_SHIM* aDlg ) :
+ PANEL_ASSIGN_COMPONENT_CLASSES_BASE( aParentWindow ), m_dlg( aDlg ), m_frame( aFrame ),
+ m_componentClassSettings( std::move( aSettings ) ),
+ m_assignmentsList( static_cast( m_assignmentsScrollWindow->GetSizer() ) )
+{
+ // Load footprint fields and sheet names
+ const BOARD* board = dynamic_cast( m_frame->GetModel() );
+ std::set fieldsSet;
+ std::set sheetsSet;
+
+ for( const FOOTPRINT* fp : board->Footprints() )
+ {
+ wxString sheetName = fp->GetSheetname();
+
+ if( !sheetName.empty() )
+ {
+ sheetName.Replace( wxT( "\"" ), wxT( "" ) );
+ sheetName.Replace( wxT( "'" ), wxT( "" ) );
+ sheetsSet.insert( fp->GetSheetname() );
+ }
+
+ for( const PCB_FIELD* field : fp->GetFields() )
+ fieldsSet.insert( field->GetName() );
+ }
+
+ // Sort field names
+ std::vector fieldNames( fieldsSet.begin(), fieldsSet.end() );
+ std::ranges::sort( fieldNames,
+ []( const wxString& a, const wxString& b )
+ {
+ return a.Cmp( b ) < 0;
+ } );
+
+ m_fieldNames = std::move( fieldNames );
+
+ // Sort sheet names
+ std::vector sheetNames( sheetsSet.begin(), sheetsSet.end() );
+ std::ranges::sort( sheetNames,
+ []( const wxString& a, const wxString& b )
+ {
+ return a.Cmp( b ) < 0;
+ } );
+
+ m_sheetNames = std::move( sheetNames );
+
+ // Get references of currently selected items
+ std::set refsSet;
+
+ for( const EDA_ITEM* item : m_frame->GetCurrentSelection() )
+ {
+ if( item->Type() != PCB_FOOTPRINT_T )
+ continue;
+
+ const FOOTPRINT* fp = static_cast( item );
+ wxString ref = fp->GetReferenceAsString();
+ refsSet.insert( ref );
+ }
+
+ std::vector refs( refsSet.begin(), refsSet.end() );
+ std::ranges::sort( refs,
+ []( const wxString& a, const wxString& b )
+ {
+ return a.Cmp( b ) < 0;
+ } );
+
+ m_selectionRefs = std::move( refs );
+}
+
+
+PANEL_ASSIGN_COMPONENT_CLASSES::~PANEL_ASSIGN_COMPONENT_CLASSES()
+{
+ BOARD* board = dynamic_cast( m_frame->GetModel() );
+ PCB_SELECTION_TOOL* selTool = m_frame->GetToolManager()->GetTool();
+
+ for( FOOTPRINT* fp : board->Footprints() )
+ selTool->UnbrightenItem( fp );
+
+ m_frame->GetCanvas()->Refresh();
+}
+
+
+bool PANEL_ASSIGN_COMPONENT_CLASSES::TransferDataToWindow()
+{
+ // Load sheet-level settings
+ m_assignSheetClasses->SetValue( m_componentClassSettings->GetEnableSheetComponentClasses() );
+
+ // Load dynamic component class assignments
+ for( const COMPONENT_CLASS_ASSIGNMENT_DATA& assignmentData :
+ m_componentClassSettings->GetComponentClassAssignments() )
+ {
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* assignment = addAssignment();
+ assignment->SetComponentClass( assignmentData.GetComponentClass() );
+ assignment->SetConditionsOperator( assignmentData.GetConditionsOperator() );
+
+ for( const auto& [conditionType, conditionData] : assignmentData.GetConditions() )
+ {
+ CONDITION_DATA* match = assignment->AddCondition( conditionType );
+ match->SetPrimaryField( conditionData.first );
+ match->SetSecondaryField( conditionData.second );
+ }
+ }
+
+ return true;
+}
+
+
+bool PANEL_ASSIGN_COMPONENT_CLASSES::TransferDataFromWindow()
+{
+ if( !Validate() )
+ return false;
+
+ // Save sheet-level settings
+ m_componentClassSettings->SetEnableSheetComponentClasses( m_assignSheetClasses->GetValue() );
+
+ // Save dynamic component class assignments
+ m_componentClassSettings->ClearComponentClassAssignments();
+
+ for( const auto assignment : m_assignments )
+ {
+ COMPONENT_CLASS_ASSIGNMENT_DATA assignmentData = assignment->GenerateAssignmentData();
+ m_componentClassSettings->AddComponentClassAssignment( assignmentData );
+ }
+
+ return true;
+}
+
+
+bool PANEL_ASSIGN_COMPONENT_CLASSES::Validate()
+{
+ PCB_EDIT_FRAME* frame = GetFrame();
+ BOARD* board = dynamic_cast( frame->GetModel() );
+
+ frame->GetCanvas()->Refresh();
+
+ for( PANEL_COMPONENT_CLASS_ASSIGNMENT* assignment : m_assignments )
+ {
+ const COMPONENT_CLASS_ASSIGNMENT_DATA assignmentData = assignment->GenerateAssignmentData();
+ const std::shared_ptr rule =
+ board->GetComponentClassManager().CompileAssignmentRule( assignmentData );
+
+ if( !rule )
+ {
+ const wxString msg = wxString::Format(
+ _( "Error with conditions for component class assignment %s" ),
+ assignment->GetComponentClass() );
+ PAGED_DIALOG::GetDialog( this )->SetError( msg, this, assignment );
+ scrollToAssignment( assignment );
+ assignment->SetFocus();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+void PANEL_ASSIGN_COMPONENT_CLASSES::OnAddAssignmentClick( wxCommandEvent& event )
+{
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* assignment = addAssignment();
+ scrollToAssignment( assignment );
+}
+
+
+PANEL_COMPONENT_CLASS_ASSIGNMENT* PANEL_ASSIGN_COMPONENT_CLASSES::addAssignment()
+{
+ PANEL_COMPONENT_CLASS_ASSIGNMENT* assignmentPanel =
+ new PANEL_COMPONENT_CLASS_ASSIGNMENT( m_assignmentsScrollWindow, this, m_dlg );
+
+#if __OSX__
+ m_assignmentsList->Add( assignmentPanel, 0, wxEXPAND, 5 );
+#else
+ m_assignmentsList->Add( assignmentPanel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
+#endif
+
+ Layout();
+
+ m_assignments.push_back( assignmentPanel );
+
+ return assignmentPanel;
+}
+
+
+void PANEL_ASSIGN_COMPONENT_CLASSES::scrollToAssignment(
+ const PANEL_COMPONENT_CLASS_ASSIGNMENT* aAssignment ) const
+{
+ const wxPoint viewStart = m_assignmentsScrollWindow->GetViewStart();
+ const wxPoint panelPosition = aAssignment->GetPosition();
+ const wxSize panelSize = aAssignment->GetClientSize();
+
+ m_assignmentsScrollWindow->Scroll( viewStart.x, panelPosition.y + panelSize.y );
+}
+
+
+void PANEL_ASSIGN_COMPONENT_CLASSES::RemoveAssignment( PANEL_COMPONENT_CLASS_ASSIGNMENT* aPanel )
+{
+ m_assignments.erase( std::ranges::find( m_assignments, aPanel ) );
+ m_assignmentsList->Detach( aPanel );
+ aPanel->Destroy();
+ Layout();
+}
+
+
+void PANEL_ASSIGN_COMPONENT_CLASSES::ImportSettingsFrom(
+ const std::shared_ptr& aOtherSettings )
+{
+ std::shared_ptr savedSettings = m_componentClassSettings;
+
+ m_componentClassSettings = aOtherSettings;
+ TransferDataToWindow();
+
+ m_componentClassSettings = std::move( savedSettings );
+}
+
+
+/**************************************************************************************************
+ *
+ * CONDITION_DATA implementation
+ * Provides a common interface for all condition panel types
+ *
+ *************************************************************************************************/
+
+wxString CONDITION_DATA::GetPrimaryField() const
+{
+ wxString data = m_primaryCtrl->GetValue();
+ data.Trim( true );
+ data.Trim( false );
+
+ if( m_conditionType != COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM )
+ {
+ data.Replace( wxT( "\"" ), wxT( "" ) );
+ data.Replace( wxT( "'" ), wxT( "" ) );
+ }
+
+ return data;
+}
+
+
+void CONDITION_DATA::SetPrimaryField( const wxString& aVal )
+{
+ m_primaryCtrl->SetValue( aVal );
+}
+
+
+wxString CONDITION_DATA::GetSecondaryField() const
+{
+ if( !m_secondaryCtrl )
+ return wxEmptyString;
+
+ wxString data = m_secondaryCtrl->GetValue();
+ data.Trim( true );
+ data.Trim( false );
+
+ if( m_conditionType != COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM )
+ {
+ data.Replace( wxT( "\"" ), wxT( "" ) );
+ data.Replace( wxT( "'" ), wxT( "" ) );
+ }
+
+ return data;
+}
+
+
+void CONDITION_DATA::SetSecondaryField( const wxString& aVal )
+{
+ if( m_secondaryCtrl )
+ m_secondaryCtrl->SetValue( aVal );
+};
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_ASSIGNMENT implementation
+ * This is the dynamically-added panel for each component class assignment rule set
+ *
+ *************************************************************************************************/
+
+PANEL_COMPONENT_CLASS_ASSIGNMENT::PANEL_COMPONENT_CLASS_ASSIGNMENT(
+ wxWindow* aParent, PANEL_ASSIGN_COMPONENT_CLASSES* aPanelParent, DIALOG_SHIM* aDlg ) :
+ PANEL_COMPONENT_CLASS_ASSIGNMENT_BASE( aParent ), m_parentPanel( aPanelParent ),
+ m_matchesList( static_cast( GetSizer() ) ), m_dlg( aDlg )
+{
+ m_buttonAddCondition->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
+ m_buttonDeleteAssignment->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
+ m_buttonHighlightItems->SetBitmap( KiBitmapBundle( BITMAPS::net_highlight ) );
+
+ Connect( wxEVT_MENU, wxCommandEventHandler( PANEL_COMPONENT_CLASS_ASSIGNMENT::onMenu ), nullptr,
+ this );
+}
+
+
+PANEL_COMPONENT_CLASS_ASSIGNMENT::~PANEL_COMPONENT_CLASS_ASSIGNMENT()
+{
+ Disconnect( wxEVT_MENU, wxCommandEventHandler( PANEL_COMPONENT_CLASS_ASSIGNMENT::onMenu ),
+ nullptr, this );
+}
+
+
+COMPONENT_CLASS_ASSIGNMENT_DATA PANEL_COMPONENT_CLASS_ASSIGNMENT::GenerateAssignmentData() const
+{
+ COMPONENT_CLASS_ASSIGNMENT_DATA assignmentData;
+ assignmentData.SetComponentClass( GetComponentClass() );
+ assignmentData.SetConditionsOperation( GetConditionsOperator() );
+
+ for( const auto& condition : GetConditions() )
+ {
+ assignmentData.SetCondition( condition->GetConditionType(), condition->GetPrimaryField(),
+ condition->GetSecondaryField() );
+ }
+
+ return assignmentData;
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::OnAddConditionClick( wxCommandEvent& event )
+{
+ auto hasCondition = [this]( const COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE aCondition )
+ {
+ return m_conditionTypes.contains( aCondition );
+ };
+
+ wxMenu menu;
+ menu.Append( ID_REFERENCE, _( "Reference..." ) );
+ menu.Append( ID_FOOTPRINT, _( "Footprint..." ) );
+ menu.Append( ID_SIDE, _( "Side..." ) );
+ menu.Append( ID_ROTATION, _( "Rotation..." ) );
+ menu.Append( ID_FOOTPRINT_FIELD, _( "Footprint Field..." ) );
+ menu.Append( ID_SHEET_NAME, _( "Sheet Name..." ) );
+ menu.Append( ID_CUSTOM, _( "Custom..." ) );
+ menu.Enable( ID_REFERENCE,
+ !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::REFERENCE ) );
+ menu.Enable( ID_FOOTPRINT,
+ !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT ) );
+ menu.Enable( ID_SIDE, !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SIDE ) );
+ menu.Enable( ID_ROTATION,
+ !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::ROTATION ) );
+ menu.Enable(
+ ID_FOOTPRINT_FIELD,
+ !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT_FIELD ) );
+ menu.Enable( ID_CUSTOM,
+ !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM ) );
+ menu.Enable( ID_SHEET_NAME,
+ !hasCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SHEET_NAME ) );
+ PopupMenu( &menu );
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::OnDeleteAssignmentClick( wxCommandEvent& event )
+{
+ m_parentPanel->RemoveAssignment( this );
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::SetComponentClass( const wxString& aComponentClass ) const
+{
+ m_componentClass->SetValue( aComponentClass );
+}
+
+
+const wxString PANEL_COMPONENT_CLASS_ASSIGNMENT::GetComponentClass() const
+{
+ return m_componentClass->GetValue();
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::onMenu( wxCommandEvent& aEvent )
+{
+ switch( aEvent.GetId() )
+ {
+ case ID_REFERENCE:
+ AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::REFERENCE );
+ break;
+ case ID_FOOTPRINT:
+ AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT );
+ break;
+ case ID_SIDE: AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SIDE ); break;
+ case ID_ROTATION:
+ AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::ROTATION );
+ break;
+ case ID_FOOTPRINT_FIELD:
+ AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT_FIELD );
+ break;
+ case ID_CUSTOM: AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM ); break;
+ case ID_SHEET_NAME:
+ AddCondition( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SHEET_NAME );
+ break;
+
+ default: wxFAIL_MSG( wxT( "Unknown ID in context menu event" ) );
+ }
+}
+
+
+CONDITION_DATA* PANEL_COMPONENT_CLASS_ASSIGNMENT::AddCondition(
+ const COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE aCondition )
+{
+ wxASSERT_MSG( !m_conditionTypes.contains( aCondition ), "Condition type already exists" );
+
+ wxPanel* panelToAdd = nullptr;
+
+ switch( aCondition )
+ {
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::REFERENCE:
+ {
+ PANEL_COMPONENT_CLASS_CONDITION_REFERENCE* refsPanel =
+ new PANEL_COMPONENT_CLASS_CONDITION_REFERENCE( this );
+ refsPanel->SetSelectionRefs( m_parentPanel->GetSelectionRefs() );
+ panelToAdd = refsPanel;
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::REFERENCE );
+ break;
+ }
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT:
+ {
+ panelToAdd = new PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT( this, m_dlg );
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT );
+ break;
+ }
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SIDE:
+ {
+ panelToAdd = new PANEL_COMPONENT_CLASS_CONDITION_SIDE( this );
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SIDE );
+ break;
+ }
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::ROTATION:
+ {
+ panelToAdd = new PANEL_COMPONENT_CLASS_CONDITION_ROTATION( this );
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::ROTATION );
+ break;
+ }
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT_FIELD:
+ {
+ PANEL_COMPONENT_CLASS_CONDITION_FIELD* fieldPanel =
+ new PANEL_COMPONENT_CLASS_CONDITION_FIELD( this );
+ fieldPanel->SetFieldsList( m_parentPanel->GetFieldNames() );
+ panelToAdd = fieldPanel;
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT_FIELD );
+ break;
+ }
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM:
+ {
+ panelToAdd = new PANEL_COMPONENT_CLASS_CONDITION_CUSTOM( this );
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::CUSTOM );
+ break;
+ }
+ case COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SHEET_NAME:
+ {
+ PANEL_COMPONENT_CLASS_CONDITION_SHEET* sheetPanel =
+ new PANEL_COMPONENT_CLASS_CONDITION_SHEET( this );
+ sheetPanel->SetSheetsList( m_parentPanel->GetSheetNames() );
+ panelToAdd = sheetPanel;
+ m_conditionTypes.insert( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::SHEET_NAME );
+ break;
+ }
+ }
+
+ const size_t numItems = m_matchesList->GetItemCount();
+ m_matchesList->Insert( numItems - 1, panelToAdd, 0, wxEXPAND | wxTOP, 5 );
+ Layout();
+ GetParent()->Layout();
+ m_parentPanel->Layout();
+
+ CONDITION_DATA* conditionIface = dynamic_cast( panelToAdd );
+ m_matches.push_back( conditionIface );
+
+ return conditionIface;
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::RemoveCondition( wxPanel* aMatch )
+{
+ if( CONDITION_DATA* matchData = dynamic_cast( aMatch ) )
+ {
+ m_conditionTypes.erase( matchData->GetConditionType() );
+ m_matches.erase( std::ranges::find( m_matches, matchData ) );
+ }
+
+ m_matchesList->Detach( aMatch );
+ aMatch->Destroy();
+ Layout();
+ m_parentPanel->Layout();
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::SetConditionsOperator(
+ const COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR aCondition ) const
+{
+ if( aCondition == COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR::ALL )
+ {
+ m_radioAll->SetValue( true );
+ m_radioAny->SetValue( false );
+ }
+ else
+ {
+ m_radioAll->SetValue( false );
+ m_radioAny->SetValue( true );
+ }
+}
+
+
+COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR
+PANEL_COMPONENT_CLASS_ASSIGNMENT::GetConditionsOperator() const
+{
+ if( m_radioAll->GetValue() == true )
+ return COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR::ALL;
+
+ return COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITIONS_OPERATOR::ANY;
+}
+
+
+void PANEL_COMPONENT_CLASS_ASSIGNMENT::OnHighlightItemsClick( wxCommandEvent& event )
+{
+ PCB_EDIT_FRAME* frame = m_parentPanel->GetFrame();
+ BOARD* board = dynamic_cast( frame->GetModel() );
+ PCB_SELECTION_TOOL* selTool = frame->GetToolManager()->GetTool();
+
+ const COMPONENT_CLASS_ASSIGNMENT_DATA assignment = GenerateAssignmentData();
+ const std::shared_ptr rule =
+ board->GetComponentClassManager().CompileAssignmentRule( assignment );
+
+ if( !rule )
+ return;
+
+ for( FOOTPRINT* fp : board->Footprints() )
+ {
+ if( rule->Matches( fp ) )
+ selTool->BrightenItem( fp );
+ else
+ selTool->UnbrightenItem( fp );
+ }
+
+ frame->GetCanvas()->Refresh();
+}
+
+
+/**************************************************************************************************
+ *
+ * PANEL_COMPONENT_CLASS_CONDITION_* implementations
+ * These are the dynamically-added panels for each condition / match type
+ *
+ *************************************************************************************************/
+
+PANEL_COMPONENT_CLASS_CONDITION_REFERENCE::PANEL_COMPONENT_CLASS_CONDITION_REFERENCE(
+ wxWindow* aParent ) :
+ PANEL_COMPONENT_CLASS_CONDITION_REFERENCE_BASE( aParent ),
+ CONDITION_DATA( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::REFERENCE, m_refs ),
+ m_panelParent( static_cast( aParent ) )
+{
+ m_title->SetMinSize( { GetTextExtent( _( "Footprint Field:" ) ).x, -1 } );
+
+ m_buttonImportRefs->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
+ m_buttonDeleteMatch->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
+
+ Connect( wxEVT_MENU, wxCommandEventHandler( PANEL_COMPONENT_CLASS_CONDITION_REFERENCE::onMenu ),
+ nullptr, this );
+}
+
+
+void PANEL_COMPONENT_CLASS_CONDITION_REFERENCE::OnDeleteConditionClick( wxCommandEvent& event )
+{
+ m_panelParent->RemoveCondition( this );
+
+ Disconnect( wxEVT_MENU,
+ wxCommandEventHandler( PANEL_COMPONENT_CLASS_CONDITION_REFERENCE::onMenu ), nullptr,
+ this );
+}
+
+
+void PANEL_COMPONENT_CLASS_CONDITION_REFERENCE::OnImportRefsClick( wxCommandEvent& event )
+{
+ wxMenu menu;
+ menu.Append( ID_IMPORT_REFS, _( "Import references from selection" ) );
+ menu.Enable( ID_IMPORT_REFS, !m_selectionRefs.empty() );
+ PopupMenu( &menu );
+}
+
+
+void PANEL_COMPONENT_CLASS_CONDITION_REFERENCE::onMenu( wxCommandEvent& aEvent )
+{
+ if( aEvent.GetId() != ID_IMPORT_REFS )
+ wxFAIL_MSG( wxT( "Unknown ID in context menu event" ) );
+
+ if( m_selectionRefs.empty() )
+ return;
+
+ wxString refs = m_selectionRefs[0];
+
+ for( size_t i = 1; i < m_selectionRefs.size(); i++ )
+ {
+ refs += wxT( "," );
+ refs += m_selectionRefs[i];
+ }
+
+ m_refs->SetValue( refs );
+}
+
+
+PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT::PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT(
+ wxWindow* aParent, DIALOG_SHIM* aDlg ) :
+ PANEL_COMPONENT_CLASS_CONDITION_FOOTPRINT_BASE( aParent ),
+ CONDITION_DATA( COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE::FOOTPRINT, m_footprint ),
+ m_panelParent( static_cast