7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-03-30 06:26:55 +00:00

Implement dynamic assignment of component classes

This commit is contained in:
JamesJCode 2025-03-09 20:46:34 +00:00
parent bb62444fe9
commit ede5faee72
56 changed files with 14041 additions and 1605 deletions

View File

@ -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

View File

@ -1,5 +1,6 @@
annular_width
assertion
assign_component_class
board_edge
buried_via
clearance

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <nlohmann/json.hpp>
#include <project/component_class_settings.h>
#include <settings/parameters.h>
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<nlohmann::json>(
"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<bool>();
},
{} ) );
m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>(
"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<std::string>().c_str(), wxConvUTF8 ) );
const wxString matchOperator( aJson["conditions_operator"].get<std::string>().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<std::string>().c_str(), wxConvUTF8 );
if( conditionData.contains( "secondary" ) )
secondary =
wxString( conditionData["secondary"].get<std::string>().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<wxString> 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 );
}

View File

@ -20,10 +20,10 @@
*/
#include <project.h>
#include <project/component_class_settings.h>
#include <project/net_settings.h>
#include <settings/json_settings_internals.h>
#include <project/project_file.h>
#include <settings/common_settings.h>
#include <settings/parameters.h>
#include <wildcards_and_files_ext.h>
#include <wx/config.h>
@ -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<NET_SETTINGS>( this, "net_settings" );
m_ComponentClassSettings =
std::make_shared<COMPONENT_CLASS_SETTINGS>( 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 ) );

View File

@ -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 <panel_assign_component_classes_base.h>
#include <unordered_set>
#include <vector>
#include <dialog_shim.h>
#include <pcb_edit_frame.h>
#include <project/component_class_settings.h>
/**************************************************************************************************
*
* 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<COMPONENT_CLASS_SETTINGS> 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<COMPONENT_CLASS_SETTINGS>& 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<wxString>& GetSelectionRefs() const { return m_selectionRefs; }
/// Returns names of all fields present in footprints
const std::vector<wxString>& GetFieldNames() const { return m_fieldNames; }
/// Returns names of all sheets present in footprints
const std::vector<wxString>& 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<PANEL_COMPONENT_CLASS_ASSIGNMENT*> m_assignments;
/// The active settings object
std::shared_ptr<COMPONENT_CLASS_SETTINGS> m_componentClassSettings;
/// All currently selected footprint references
std::vector<wxString> m_selectionRefs;
/// All footprint fields names present on the board
std::vector<wxString> m_fieldNames;
/// All sheet names present on the board
std::vector<wxString> 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<CONDITION_DATA*>& 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<COMPONENT_CLASS_ASSIGNMENT_DATA::CONDITION_TYPE> m_conditionTypes;
/// The parent configuration dialog
DIALOG_SHIM* m_dlg;
/// All match conditions for this component class assignment rule
std::vector<CONDITION_DATA*> 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<wxString>& aRefs ) { m_selectionRefs = aRefs; }
protected:
/// Handles import references popup menu selections
void onMenu( wxCommandEvent& aEvent );
PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
std::vector<wxString> 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<wxString>& 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<wxString>& aSheets );
protected:
PANEL_COMPONENT_CLASS_ASSIGNMENT* m_panelParent;
};
#endif //PANEL_ASSIGN_COMPONENT_CLASSES_H

View File

@ -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 <panel_generate_component_classes_base.h>
#include <eda_draw_frame.h>
#include <project/component_class_settings.h>
#include <vector>
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<COMPONENT_CLASS_SETTINGS> aSettings,
std::vector<wxString>& aSelectionRefs );
bool TransferDataToWindow() override;
bool TransferDataFromWindow() override;
bool Validate() override;
//void ImportSettingsFrom( const std::shared_ptr<NET_SETTINGS>& aNetSettings );
protected:
EDA_DRAW_FRAME* m_frame;
std::shared_ptr<COMPONENT_CLASS_SETTINGS> m_componentClassSettings;
std::vector<wxString> m_selectionRefs;
};
#endif //PANEL_GENERATE_COMPONENT_CLASSES

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PROJECT_COMPONENT_CLASS_SETTINGS_H
#define PROJECT_COMPONENT_CLASS_SETTINGS_H
#include <vector>
#include <unordered_map>
#include <settings/nested_settings.h>
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<CONDITION_TYPE, std::pair<wxString, wxString>>& 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<CONDITION_TYPE, std::pair<wxString, wxString>> 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<COMPONENT_CLASS_ASSIGNMENT_DATA>& 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<COMPONENT_CLASS_ASSIGNMENT_DATA> 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

View File

@ -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<COMPONENT_CLASS_SETTINGS>& 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<NET_SETTINGS> m_NetSettings;
/**
* Component class settings for the project (owned here)
*/
std::shared_ptr<COMPONENT_CLASS_SETTINGS> m_ComponentClassSettings;
std::vector<LAYER_PRESET> m_LayerPresets; /// List of stored layer presets
std::vector<VIEWPORT> m_Viewports; /// List of stored viewports (pos + zoom)

View File

@ -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

View File

@ -55,6 +55,7 @@
#include <pcbnew_settings.h>
#include <progress_reporter.h>
#include <project.h>
#include <project/component_class_settings.h>
#include <project/net_settings.h>
#include <project/project_file.h>
#include <project/project_local_settings.h>
@ -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<COMPONENT_CLASS_MANAGER>( 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<wxString>& aNewSheetPaths ) const
{
std::shared_ptr<COMPONENT_CLASS_SETTINGS> settings =
GetProject()->GetProjectFile().ComponentClassSettings();
return m_componentClassManager->SyncDynamicComponentClassAssignments(
settings->GetComponentClassAssignments(), settings->GetEnableSheetComponentClasses(),
aNewSheetPaths );
}
int BOARD::SetAreasNetCodesFromNetNames()
{
int error_count = 0;

View File

@ -27,7 +27,7 @@
#include <board_item_container.h>
#include <board_stackup_manager/board_stackup.h>
#include <component_class_manager.h>
#include <component_classes/component_class_manager.h>
#include <embedded_files.h>
#include <common.h> // Needed for stl hash extensions
#include <convert_shape_list_to_polygon.h> // 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<wxString>& 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<COMPONENT_CLASS_MANAGER> m_componentClassManager;
};

View File

@ -42,6 +42,7 @@
#include <teardrop/teardrop.h>
#include <functional>
#include <project/project_file.h>
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<FOOTPRINT*>( 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<FOOTPRINT*>( boardItem );
GetBoard()->GetComponentClassManager().RebuildRequiredCaches( footprint );
};
std::vector<BOARD_ITEM*> bulkAddedItems;
std::vector<BOARD_ITEM*> bulkRemovedItems;
std::vector<BOARD_ITEM*> 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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <utility>
#include <component_class_manager.h>
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( "<None>" );
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<int>( 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<COMPONENT_CLASS>( wxEmptyString );
}
COMPONENT_CLASS* COMPONENT_CLASS_MANAGER::GetEffectiveComponentClass(
const std::unordered_set<wxString>& 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<COMPONENT_CLASS> newClass =
std::make_unique<COMPONENT_CLASS>( 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<wxString> 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<COMPONENT_CLASS> effClass = std::make_unique<COMPONENT_CLASS>( 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<wxString>& classNames )
{
std::vector<wxString> 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<wxString>& 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PCBNEW_COMPONENT_CLASS_MANAGER_H
#define PCBNEW_COMPONENT_CLASS_MANAGER_H
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <wx/string.h>
#include <board_item.h>
/*
* 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<COMPONENT_CLASS*>& 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<COMPONENT_CLASS*> 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<wxString>& 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<wxString>& 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<wxString>& 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<wxString, std::unique_ptr<COMPONENT_CLASS>>& GetClasses() const
{
return m_classes;
}
protected:
/// All individual component classes
std::unordered_map<wxString, std::unique_ptr<COMPONENT_CLASS>> m_classes;
/// Generated effective component classes
std::unordered_map<wxString, std::unique_ptr<COMPONENT_CLASS>> m_effectiveClasses;
/// Cache of all individual component classes (for netlist updating)
std::unordered_map<wxString, std::unique_ptr<COMPONENT_CLASS>> m_classesCache;
/// Cache of all generated effective component classes (for netlist updating)
std::unordered_map<wxString, std::unique_ptr<COMPONENT_CLASS>> m_effectiveClassesCache;
/// The class to represent an unassigned component class
std::unique_ptr<COMPONENT_CLASS> m_noneClass;
};
#endif

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <component_classes/component_class.h>
#include <wx/intl.h>
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( "<None>" );
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<int>( 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 );
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PCBNEW_COMPONENT_CLASS_H
#define PCBNEW_COMPONENT_CLASS_H
#include <wx/string.h>
#include <vector>
/**
* 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<COMPONENT_CLASS*>& 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<COMPONENT_CLASS*> m_constituentClasses;
/// The assignment context in which this component class is being used
USAGE m_usageContext;
};
#endif //PCBNEW_COMPONENT_CLASS_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <component_classes/component_class_assignment_rule.h>
#include <footprint.h>
COMPONENT_CLASS_ASSIGNMENT_RULE::COMPONENT_CLASS_ASSIGNMENT_RULE(
const wxString& aComponentClass, std::shared_ptr<DRC_RULE_CONDITION>&& 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 );
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef COMPONENT_CLASS_ASSIGNMENT_RULE_H
#define COMPONENT_CLASS_ASSIGNMENT_RULE_H
#include <memory>
#include <vector>
#include <wx/string.h>
#include <drc/drc_rule_condition.h>
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<DRC_RULE_CONDITION>&& 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<DRC_RULE_CONDITION> m_condition;
};
#endif // COMPONENT_CLASS_ASSIGNMENT_RULE_H

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <component_classes/component_class_cache_proxy.h>
#include <board.h>
#include <footprint.h>
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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PCBNEW_COMPONENT_CLASS_CACHE_PROXY_H
#define PCBNEW_COMPONENT_CLASS_CACHE_PROXY_H
#include <component_classes/component_class.h>
#include <footprint.h>
/*
* 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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <component_classes/component_class_manager.h>
#include <board.h>
#include <component_classes/component_class.h>
#include <component_classes/component_class_assignment_rule.h>
#include <drc/drc_cache_generator.h>
#include <drc/drc_rule_parser.h>
#include <footprint.h>
#include <project/component_class_settings.h>
#include <tools/drc_tool.h>
COMPONENT_CLASS_MANAGER::COMPONENT_CLASS_MANAGER( BOARD* board ) :
m_board( board ), m_hasCustomAssignmentConditions( false )
{
m_noneClass =
std::make_shared<COMPONENT_CLASS>( 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<wxString>& 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<wxString> 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<wxString> 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<wxString>& classNames )
{
const std::vector<wxString> sortedClassNames = sortClassNames( classNames );
return GetFullClassNameForConstituents( sortedClassNames );
}
wxString
COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents( const std::vector<wxString>& 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<COMPONENT_CLASS_ASSIGNMENT_DATA>& aAssignments,
bool aGenerateSheetClasses, const std::unordered_set<wxString>& aNewSheetPaths )
{
m_hasCustomAssignmentConditions = false;
bool success = true;
// Invalidate component class cache entries
++m_ticker;
// Save previous dynamically assigned component class names
std::unordered_set<wxString> prevClassNames;
for( const auto& rule : m_assignmentRules )
prevClassNames.insert( rule->GetComponentClass() );
m_assignmentRules.clear();
// Parse all assignment rules
std::vector<std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE>> 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<COMPONENT_CLASS_ASSIGNMENT_RULE> rule = CompileAssignmentRule( assignment );
if( rule )
rules.emplace_back( std::move( rule ) );
else
success = false;
}
// Generate sheet classes if required
if( aGenerateSheetClasses )
{
std::unordered_set<wxString> 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<COMPONENT_CLASS_ASSIGNMENT_RULE> 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<wxString> 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_ASSIGNMENT_RULE>
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<std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE>> 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<wxString> 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<wxString> sortedClassNames = sortClassNames( classNames );
wxString fullCombinedName = GetFullClassNameForConstituents( sortedClassNames );
if( !m_effectiveClasses.contains( fullCombinedName ) )
{
std::unique_ptr<COMPONENT_CLASS> combinedClass = std::make_unique<COMPONENT_CLASS>(
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<wxString> 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<wxString> 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<wxString>
COMPONENT_CLASS_MANAGER::sortClassNames( const std::unordered_set<wxString>& classNames )
{
std::vector<wxString> 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<COMPONENT_CLASS> newClass =
std::make_unique<COMPONENT_CLASS>( 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<wxString>& 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<COMPONENT_CLASS> effectiveClass = std::make_unique<COMPONENT_CLASS>(
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<wxString> COMPONENT_CLASS_MANAGER::GetClassNames() const
{
std::unordered_set<wxString> 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();
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PCBNEW_COMPONENT_CLASS_MANAGER_H
#define PCBNEW_COMPONENT_CLASS_MANAGER_H
#include <unordered_map>
#include <unordered_set>
#include <wx/string.h>
#include <component_classes/component_class.h>
#include <project/component_class_settings.h>
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<wxString>& 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<wxString>& 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<wxString>& 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<wxString> GetClassNames() const;
/// Synchronises all dynamic component class assignment rules
/// @returns false if rules fail to parse, true if successful
bool SyncDynamicComponentClassAssignments(
const std::vector<COMPONENT_CLASS_ASSIGNMENT_DATA>& aAssignments,
bool aGenerateSheetClasses, const std::unordered_set<wxString>& 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<COMPONENT_CLASS_ASSIGNMENT_RULE>
CompileAssignmentRule( const COMPONENT_CLASS_ASSIGNMENT_DATA& aAssignment );
protected:
/// Sorts the given class names in to canonical order
static std::vector<wxString> sortClassNames( const std::unordered_set<wxString>& 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<wxString>& 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<COMPONENT_CLASS> m_noneClass;
/// All individual component classes from static assignments
std::unordered_map<wxString, std::unique_ptr<COMPONENT_CLASS>> m_constituentClasses;
/// Generated effective (composite) static component classes
std::unordered_map<wxString, std::unique_ptr<COMPONENT_CLASS>> m_effectiveClasses;
/// Cache of in-use static component class names
/// Used for cleanup following netlist updates
std::unordered_set<wxString> m_staticClassNamesCache;
/// Active component class assignment rules
std::vector<std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE>> 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

View File

@ -37,6 +37,7 @@
#include <dialogs/panel_setup_teardrops.h>
#include <dialogs/panel_setup_tuning_patterns.h>
#include <dialogs/panel_setup_netclasses.h>
#include <dialogs/panel_assign_component_classes.h>
#include <panel_text_variables.h>
#include <project.h>
#include <project/project_file.h>
@ -47,6 +48,8 @@
#include "dialog_board_setup.h"
#include <footprint.h>
#define RESOLVE_PAGE( T, pageIndex ) static_cast<T*>( 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,

View File

@ -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;

View File

@ -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 );

Some files were not shown because too many files have changed in this diff Show More