KiCad PCB EDA Suite
Loading...
Searching...
No Matches
component_class_manager.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20
22
23#include <board.h>
27#include <drc/drc_rule_parser.h>
28#include <footprint.h>
30#include <tools/drc_tool.h>
31
32
34 m_board( board )
35{
36 m_noneClass = std::make_shared<COMPONENT_CLASS>( wxEmptyString, COMPONENT_CLASS::USAGE::STATIC );
37}
38
39
41 const std::unordered_set<wxString>& classNames )
42{
43 // Handle no component class condition
44 if( classNames.size() == 0 )
45 return m_noneClass.get();
46
47 // Handle single-assignment component classes
48 if( classNames.size() == 1 )
49 {
50 const wxString& className = *classNames.begin();
51 m_staticClassNamesCache.erase( className );
53 }
54
55 // Handle composite component classes
56 const std::vector<wxString> sortedClassNames = sortClassNames( classNames );
57 wxString fullName = GetFullClassNameForConstituents( sortedClassNames );
58
59 COMPONENT_CLASS* effectiveClass =
61
62 for( const COMPONENT_CLASS* constituentClass : effectiveClass->GetConstituentClasses() )
63 m_staticClassNamesCache.erase( constituentClass->GetName() );
64
65 return effectiveClass;
66}
67
68
70{
71 for( const auto& [className, classPtr] : m_constituentClasses )
72 {
73 if( classPtr->GetUsageContext() == COMPONENT_CLASS::USAGE::STATIC
74 || classPtr->GetUsageContext() == COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC )
75 {
76 m_staticClassNamesCache.insert( className );
77 }
78 }
79
80 ++m_ticker;
81}
82
83
85{
86 // m_staticClassesCache now contains any static component classes that are unused from the
87 // netlist update. Delete any effective component classes which refer to them in a static-only
88 // context, or update their usage context.
89
90 // First, collect all component classes that will be deleted so we can clear footprint pointers
91 // before deletion. This prevents use-after-free when auto-save serializes footprints.
92 std::unordered_set<const COMPONENT_CLASS*> classesToDelete;
93
94 for( const wxString& className : m_staticClassNamesCache )
95 {
96 COMPONENT_CLASS* staticClass = m_constituentClasses[className].get();
97
99 {
100 classesToDelete.insert( staticClass );
101
102 for( const auto& [combinedFullName, combinedCompClass] : m_effectiveClasses )
103 {
104 if( combinedCompClass->ContainsClassName( className ) )
105 classesToDelete.insert( combinedCompClass.get() );
106 }
107 }
108 }
109
110 // Clear footprint static component class pointers that reference classes being deleted
111 if( !classesToDelete.empty() )
112 {
113 for( FOOTPRINT* footprint : m_board->Footprints() )
114 {
115 if( classesToDelete.count( footprint->GetStaticComponentClass() ) )
116 footprint->SetStaticComponentClass( nullptr );
117 }
118 }
119
120 // Now perform the actual deletions
121 for( const wxString& className : m_staticClassNamesCache )
122 {
123 COMPONENT_CLASS* staticClass = m_constituentClasses[className].get();
124
125 if( staticClass->GetUsageContext() == COMPONENT_CLASS::USAGE::STATIC )
126 {
127 // Any static-only classes can be deleted, along with effective classes which refer to them
128 std::unordered_set<wxString> effectiveClassesToDelete;
129
130 for( const auto& [combinedFullName, combinedCompClass] : m_effectiveClasses )
131 {
132 if( combinedCompClass->ContainsClassName( className ) )
133 effectiveClassesToDelete.insert( combinedFullName );
134 }
135
136 for( const wxString& classNameToDelete : effectiveClassesToDelete )
137 m_effectiveClasses.erase( classNameToDelete );
138
139 m_constituentClasses.erase( className );
140 }
141 else
142 {
143 // Set the component class to dynamic-only scope
144 wxASSERT( staticClass->GetUsageContext()
147 }
148 }
149
150 // Clear the caches
152}
153
154
156 const std::unordered_set<wxString>& classNames )
157{
158 const std::vector<wxString> sortedClassNames = sortClassNames( classNames );
159
160 return GetFullClassNameForConstituents( sortedClassNames );
161}
162
163
164wxString
165COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents( const std::vector<wxString>& classNames )
166{
167 if( classNames.size() == 0 )
168 return wxEmptyString;
169
170 wxString fullName = classNames[0];
171
172 for( std::size_t i = 1; i < classNames.size(); ++i )
173 {
174 fullName += ",";
175 fullName += classNames[i];
176 }
177
178 return fullName;
179}
180
181
183 const std::vector<COMPONENT_CLASS_ASSIGNMENT_DATA>& aAssignments,
184 bool aGenerateSheetClasses, const std::unordered_set<wxString>& aNewSheetPaths )
185{
186 bool success = true;
187
188 // Invalidate component class cache entries
189 ++m_ticker;
190
191 // Save previous dynamically assigned component class names
192 std::unordered_set<wxString> prevClassNames;
193
194 for( const auto& rule : m_assignmentRules )
195 prevClassNames.insert( rule->GetComponentClass() );
196
197 m_assignmentRules.clear();
198
199 // Parse all assignment rules
200 std::vector<std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE>> rules;
201
202 for( const COMPONENT_CLASS_ASSIGNMENT_DATA& assignment : aAssignments )
203 {
204 std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE> rule = CompileAssignmentRule( assignment );
205
206 if( rule )
207 rules.emplace_back( std::move( rule ) );
208 else
209 success = false;
210 }
211
212 // Generate sheet classes if required
213 if( aGenerateSheetClasses )
214 {
215 std::unordered_set<wxString> sheetNames = aNewSheetPaths;
216
217 for( const FOOTPRINT* footprint : m_board->Footprints() )
218 sheetNames.insert( footprint->GetSheetname() );
219
220 for( wxString sheetName : sheetNames )
221 {
222 // Don't generate a class for empty sheets (e.g. manually placed footprints) or the root
223 // sheet
224 if( sheetName.empty() || sheetName == wxT( "/" ) )
225 continue;
226
227 sheetName.Replace( wxT( "\"" ), wxT( "" ) );
228 sheetName.Replace( wxT( "'" ), wxT( "" ) );
229
231 assignment.SetComponentClass( sheetName );
233 sheetName, wxEmptyString );
234
235 std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE> rule =
236 CompileAssignmentRule( assignment );
237
238 if( rule )
239 rules.emplace_back( std::move( rule ) );
240 else
241 success = false;
242 }
243 }
244
245 // Set the assignment rules
246 if( success )
247 m_assignmentRules = std::move( rules );
248
249 // Re-use or create component classes which may be output by assignment rules
250 for( const auto& rule : m_assignmentRules )
251 {
252 wxString className = rule->GetComponentClass();
253 prevClassNames.erase( className );
255 }
256
257 // prevClassNames now contains all dynamic component class names no longer in use. Remove any
258 // effective component classes no longer in use.
259 for( const wxString& className : prevClassNames )
260 {
261 COMPONENT_CLASS* dynamicClass = m_constituentClasses[className].get();
262
263 if( dynamicClass->GetUsageContext() == COMPONENT_CLASS::USAGE::DYNAMIC )
264 {
265 // Any dynamic-only classes can be deleted, along with effective classes which refer to them
266 std::unordered_set<wxString> classesToDelete;
267
268 for( const auto& [combinedFullName, combinedCompClass] : m_effectiveClasses )
269 {
270 if( combinedCompClass->ContainsClassName( className ) )
271 classesToDelete.insert( combinedFullName );
272 }
273
274 for( const wxString& classNameToDelete : classesToDelete )
275 m_effectiveClasses.erase( classNameToDelete );
276
277 m_constituentClasses.erase( className );
278 }
279 else
280 {
281 // Set the component class to dynamic-only scope
282 wxASSERT( dynamicClass->GetUsageContext()
285 }
286 }
287
288 return success;
289}
290
291
292std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE>
294{
295 const wxString ruleSource = aAssignment.GetAssignmentInDRCLanguage();
296
297 // Ignore incomplete rules (e.g. no component class name specified)
298 if( ruleSource.empty() )
299 return nullptr;
300
301 DRC_RULES_PARSER parser( ruleSource, wxT( "Component class assignment rule" ) );
302
303 try
304 {
305 std::vector<std::shared_ptr<COMPONENT_CLASS_ASSIGNMENT_RULE>> parsed;
306
309
310 if( reporter.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
311 return nullptr;
312
313 if( parsed.size() != 1 )
314 return nullptr;
315
316 return parsed[0];
317 }
318 catch( PARSE_ERROR& )
319 {
320 return nullptr;
321 }
322}
323
324
325const COMPONENT_CLASS*
327 const COMPONENT_CLASS* dynamicClass )
328{
329 std::unordered_set<wxString> classNames;
330
331 if( staticClass )
332 {
333 for( const COMPONENT_CLASS* compClass : staticClass->GetConstituentClasses() )
334 classNames.insert( compClass->GetName() );
335 }
336
337 if( dynamicClass )
338 {
339 for( const COMPONENT_CLASS* compClass : dynamicClass->GetConstituentClasses() )
340 classNames.insert( compClass->GetName() );
341 }
342
343 if( classNames.empty() )
344 return GetNoneComponentClass();
345
346 if( classNames.size() == 1 )
347 {
348 wxASSERT( m_constituentClasses.contains( *classNames.begin() ) );
349 return m_constituentClasses[*classNames.begin()].get();
350 }
351
352 const std::vector<wxString> sortedClassNames = sortClassNames( classNames );
353
354 wxString fullCombinedName = GetFullClassNameForConstituents( sortedClassNames );
355
356 if( !m_effectiveClasses.contains( fullCombinedName ) )
357 {
358 std::unique_ptr<COMPONENT_CLASS> combinedClass = std::make_unique<COMPONENT_CLASS>(
359 fullCombinedName, COMPONENT_CLASS::USAGE::EFFECTIVE );
360
361 for( const wxString& className : sortedClassNames )
362 {
363 wxASSERT( m_constituentClasses.contains( className ) );
364 combinedClass->AddConstituentClass( m_constituentClasses[className].get() );
365 }
366
367 m_effectiveClasses[fullCombinedName] = std::move( combinedClass );
368 }
369
370 return m_effectiveClasses[fullCombinedName].get();
371}
372
373
378
379
381{
382 for( const auto& footprint : m_board->Footprints() )
383 {
384 footprint->RecomputeComponentClass();
385 }
386}
387
388
389const COMPONENT_CLASS*
391{
392 std::unordered_set<wxString> classNames;
393
394 // Assemble matching component class names
395 for( const auto& rule : m_assignmentRules )
396 {
397 if( rule->Matches( footprint ) )
398 classNames.insert( rule->GetComponentClass() );
399 }
400
401 // Handle composite component classes
402 const std::vector<wxString> sortedClassNames = sortClassNames( classNames );
403 const wxString fullName = GetFullClassNameForConstituents( sortedClassNames );
404
405 // No matching component classes
406 if( classNames.empty() )
407 return nullptr;
408
409 // One matching component class
410 if( classNames.size() == 1 )
412
413 // Multiple matching component classes
415}
416
417
418std::vector<wxString>
419COMPONENT_CLASS_MANAGER::sortClassNames( const std::unordered_set<wxString>& classNames )
420{
421 std::vector<wxString> sortedClassNames( classNames.begin(), classNames.end() );
422
423 std::ranges::sort( sortedClassNames,
424 []( const wxString& str1, const wxString& str2 )
425 {
426 return str1.Cmp( str2 ) < 0;
427 } );
428
429 return sortedClassNames;
430}
431
432
435 COMPONENT_CLASS::USAGE aContext )
436{
438 || aContext == COMPONENT_CLASS::USAGE::EFFECTIVE )
439 {
440 wxFAIL_MSG( "Can't create a STATIC_AND_DYNAMIC or EFFECTIVE constituent component class" );
441 return m_noneClass.get();
442 }
443
444 if( m_constituentClasses.contains( aClassName ) )
445 {
446 COMPONENT_CLASS* compClass = m_constituentClasses[aClassName].get();
447
448 if( aContext != compClass->GetUsageContext() )
450
451 return compClass;
452 }
453
454 std::unique_ptr<COMPONENT_CLASS> newClass =
455 std::make_unique<COMPONENT_CLASS>( aClassName, aContext );
456 newClass->AddConstituentClass( newClass.get() );
457
458 m_constituentClasses[aClassName] = std::move( newClass );
459
460 return m_constituentClasses[aClassName].get();
461}
462
463
465COMPONENT_CLASS_MANAGER::getOrCreateEffectiveClass( const std::vector<wxString>& aClassNames,
466 COMPONENT_CLASS::USAGE aContext )
467{
468 wxString fullClassName = GetFullClassNameForConstituents( aClassNames );
469
470 if( m_effectiveClasses.contains( fullClassName ) )
471 {
472 COMPONENT_CLASS* compClass = m_effectiveClasses[fullClassName].get();
473
474 for( COMPONENT_CLASS* constituentClass : compClass->GetConstituentClasses() )
475 {
476 if( constituentClass->GetUsageContext() != aContext )
477 constituentClass->SetUsageContext( COMPONENT_CLASS::USAGE::STATIC_AND_DYNAMIC );
478 }
479 }
480 else
481 {
482 std::unique_ptr<COMPONENT_CLASS> effectiveClass = std::make_unique<COMPONENT_CLASS>(
483 fullClassName, COMPONENT_CLASS::USAGE::EFFECTIVE );
484
485 for( const wxString& className : aClassNames )
486 {
487 COMPONENT_CLASS* constituentClass = getOrCreateConstituentClass( className, aContext );
488 effectiveClass->AddConstituentClass( constituentClass );
489 }
490
491 m_effectiveClasses[fullClassName] = std::move( effectiveClass );
492 }
493
494 return m_effectiveClasses[fullClassName].get();
495}
496
497
498std::unordered_set<wxString> COMPONENT_CLASS_MANAGER::GetClassNames() const
499{
500 std::unordered_set<wxString> classNames;
501
502 for( const auto& className : m_constituentClasses | std::views::keys )
503 classNames.insert( className );
504
505 return classNames;
506}
507
508
510{
511 if( aFootprint )
512 {
513 aFootprint->BuildCourtyardCaches();
514 }
515 else
516 {
517 for( FOOTPRINT* fp : m_board->Footprints() )
518 fp->BuildCourtyardCaches();
519 }
520}
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
void AddCondition(const CONDITION_TYPE aCondition, const wxString &aPrimaryData, const wxString &aSecondaryData)
Sets the given condition type with the assocated match data.
wxString GetAssignmentInDRCLanguage() const
Returns the DRC rules language for this component class assignment.
void SetComponentClass(const wxString &aComponentClass)
Sets the resulting component class for matching footprints.
std::unordered_set< wxString > GetClassNames() const
Fetches a read-only map of the fundamental component classes.
static std::shared_ptr< COMPONENT_CLASS_ASSIGNMENT_RULE > CompileAssignmentRule(const COMPONENT_CLASS_ASSIGNMENT_DATA &aAssignment)
std::unordered_set< wxString > m_staticClassNamesCache
Cache of in-use static component class names Used for cleanup following netlist updates.
std::unordered_map< wxString, std::unique_ptr< COMPONENT_CLASS > > m_constituentClasses
All individual component classes from static assignments.
COMPONENT_CLASS * GetEffectiveStaticComponentClass(const std::unordered_set< wxString > &classNames)
Computes and returns an effective component class for a (possibly empty) set of constituent class nam...
long long int m_ticker
Monotonically increasing ticker to test cached component class validity.
void ForceComponentClassRecalculation() const
Forces the component class for all footprints to be recalculated.
COMPONENT_CLASS * getOrCreateEffectiveClass(const std::vector< wxString > &aClassNames, COMPONENT_CLASS::USAGE aContext)
Returns an effective component class for the given set of constituent class names Precondition: aClas...
static wxString GetFullClassNameForConstituents(const std::unordered_set< wxString > &classNames)
Gets the full effective class name for the given set of constituent classes.
void InvalidateComponentClasses()
Invalidates any caches component classes and recomputes caches if required.
void FinishNetlistUpdate()
Cleans up the manager after a board update Must be called after updating the PCB from the netlist.
std::vector< std::shared_ptr< COMPONENT_CLASS_ASSIGNMENT_RULE > > m_assignmentRules
Active component class assignment rules.
const COMPONENT_CLASS * GetCombinedComponentClass(const COMPONENT_CLASS *staticClass, const COMPONENT_CLASS *dynamicClass)
Gets the combined component class with the given static and dynamic constituent component classes.
static std::vector< wxString > sortClassNames(const std::unordered_set< wxString > &classNames)
Sorts the given class names in to canonical order.
void RebuildRequiredCaches(FOOTPRINT *aFootprint=nullptr) const
Rebuilds any caches that may be required by custom assignment rules.
COMPONENT_CLASS * getOrCreateConstituentClass(const wxString &aClassName, COMPONENT_CLASS::USAGE aContext)
Returns a constituent component class, re-using an existing instantiation where possible.
BOARD * m_board
The board these component classes are assigned to / from.
const COMPONENT_CLASS * GetNoneComponentClass() const
Returns the unassigned component class.
std::unordered_map< wxString, std::unique_ptr< COMPONENT_CLASS > > m_effectiveClasses
Generated effective (composite) static component classes.
const COMPONENT_CLASS * GetDynamicComponentClassesForFootprint(const FOOTPRINT *footprint)
Gets the dynamic component classes which match the given footprint.
void InitNetlistUpdate()
Prepare the manager for a board update Must be called prior to updating the PCB from the netlist.
std::shared_ptr< COMPONENT_CLASS > m_noneClass
The class to represent an unassigned component class.
bool SyncDynamicComponentClassAssignments(const std::vector< COMPONENT_CLASS_ASSIGNMENT_DATA > &aAssignments, bool aGenerateSheetClasses, const std::unordered_set< wxString > &aNewSheetPaths)
Synchronises all dynamic component class assignment rules.
A lightweight representation of a component class.
void SetUsageContext(const USAGE aUsageContext)
Sets the assignment context in which this component class is being used.
USAGE
The assignment context in which this component class is used.
USAGE GetUsageContext() const
Gets the assignment context in which this component class is being used.
const std::vector< COMPONENT_CLASS * > & GetConstituentClasses() const
Fetches a vector of the constituent classes for this (effective) class.
void ParseComponentClassAssignmentRules(std::vector< std::shared_ptr< COMPONENT_CLASS_ASSIGNMENT_RULE > > &aRules, REPORTER *aReporter)
void BuildCourtyardCaches(OUTLINE_ERROR_HANDLER *aErrorHandler=nullptr)
Build complex polygons of the courtyard areas from graphic items on the courtyard layers.
A wrapper for reporting to a wxString object.
Definition reporter.h:242
@ RPT_SEVERITY_ERROR
A filename or source description, a problem input line, a line number, a byte offset,...
IbisParser parser & reporter