/* * 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(); } }