KiCad PCB EDA Suite
Loading...
Searching...
No Matches
board_netlist_updater.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 (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2015 CERN
6 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
7 * Copyright (C) 2011 Wayne Stambaugh <[email protected]>
8 *
9 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25
26#include <common.h> // for PAGE_INFO
27
28#include <base_units.h>
29#include <board.h>
34#include <netinfo.h>
35#include <footprint.h>
37#include <pad.h>
38#include <pcb_group.h>
39#include <pcb_track.h>
40#include <zone.h>
41#include <string_utils.h>
42#include <limits>
43#include <pcb_edit_frame.h>
44#include <pcbnew_settings.h>
47#include <reporter.h>
49#include <tool/tool_manager.h>
50#include <wx/log.h>
51
53
54
56 m_frame( aFrame ),
58 m_commit( aFrame->GetToolManager() ),
59 m_board( aBoard ),
60 m_reporter( &NULL_REPORTER::GetInstance() )
61{
62}
63
64
66 m_frame( nullptr ),
68 m_commit( aToolManager ),
69 m_board( aBoard ),
70 m_reporter( &NULL_REPORTER::GetInstance() )
71{
72}
73
74
78
79
81 REPORTER* aReporter, bool aDryRun )
82{
83 for( NETINFO_ITEM* net : aBoard->GetNetInfo() )
84 {
85 const wxString previous = net->GetNetChain();
86 wxString next = aNetlist.GetNetChainFor( net->GetNetname() );
87
88 if( !previous.IsEmpty() && next.IsEmpty() && aReporter && !aDryRun )
89 {
90 aReporter->Report(
91 wxString::Format(
92 _( "Net chain assignment '%s' on net '%s' cleared by netlist "
93 "update." ),
94 previous, net->GetNetname() ),
96 }
97
98 if( !aDryRun )
99 {
100 net->SetNetChain( next );
101
102 if( previous != next )
103 {
104 for( int i = 0; i < 2; ++i )
105 net->ClearTerminalPad( i );
106 }
107 }
108 }
109}
110
111
113{
114 const std::shared_ptr<NET_SETTINGS>& netSettings = aBoard->GetDesignSettings().m_NetSettings;
115
116 if( !netSettings )
117 return;
118
119 netSettings->ClearNetChainClasses();
120 netSettings->ClearNetChainNetClasses();
121
122 for( const auto& [chain, className] : aNetlist.GetSignalChainClasses() )
123 netSettings->SetNetChainClass( chain, className );
124
125 for( const auto& [chain, netclass] : aNetlist.GetNetChainNetClasses() )
126 netSettings->SetNetChainNetClass( chain, netclass );
127
128 // Chain membership on the board has just been refreshed from the netlist, so assignments
129 // derived from the previous membership are stale. The caller's
130 // SynchronizeNetsAndNetClasses() rebuilds them from the maps set above.
131 netSettings->ClearChainPatternAssignments( NET_CHAIN_SOURCE::BOARD );
132}
133
134
135// These functions allow inspection of pad nets during dry runs by keeping a cache of
136// current pad netnames indexed by pad.
137
138void BOARD_NETLIST_UPDATER::cacheNetname( PAD* aPad, const wxString& aNetname )
139{
140 m_padNets[ aPad ] = aNetname;
141}
142
143
145{
146 if( m_isDryRun && m_padNets.count( aPad ) )
147 return m_padNets[ aPad ];
148 else
149 return aPad->GetNetname();
150}
151
152
153void BOARD_NETLIST_UPDATER::cachePinFunction( PAD* aPad, const wxString& aPinFunction )
154{
155 m_padPinFunctions[ aPad ] = aPinFunction;
156}
157
158
160{
161 if( m_isDryRun && m_padPinFunctions.count( aPad ) )
162 return m_padPinFunctions[ aPad ];
163 else
164 return aPad->GetPinFunction();
165}
166
167
169{
170 VECTOR2I bestPosition;
171
172 if( !m_board->IsEmpty() )
173 {
174 // Position new components below any existing board features.
175 BOX2I bbox = m_board->GetBoardEdgesBoundingBox();
176
177 if( bbox.GetWidth() || bbox.GetHeight() )
178 {
179 bestPosition.x = bbox.Centre().x;
180 bestPosition.y = bbox.GetBottom() + pcbIUScale.mmToIU( 10 );
181 }
182 }
183 else
184 {
185 // Position new components in the center of the page when the board is empty.
186 VECTOR2I pageSize = m_board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
187
188 bestPosition.x = pageSize.x / 2;
189 bestPosition.y = pageSize.y / 2;
190 }
191
192 return bestPosition;
193}
194
195
197{
198 return addNewFootprint( aComponent, aComponent->GetFPID() );
199}
200
201
203{
204 wxString msg;
205
206 if( aFootprintId.empty() )
207 {
208 msg.Printf( _( "Cannot add %s (no footprint assigned)." ),
209 aComponent->GetReference() );
210 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
211 ++m_errorCount;
212 return nullptr;
213 }
214
215 FOOTPRINT* footprint = LoadFootprintFromProject( m_board, aFootprintId );
216
217 if( footprint == nullptr )
218 {
219 msg.Printf( _( "Cannot add %s (footprint '%s' not found)." ),
220 aComponent->GetReference(),
221 EscapeHTML( aFootprintId.Format().wx_str() ) );
222 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
223 ++m_errorCount;
224 return nullptr;
225 }
226
227 footprint->SetStaticComponentClass(
228 m_board->GetComponentClassManager().GetNoneComponentClass() );
229
230 if( m_isDryRun )
231 {
232 msg.Printf( _( "Add %s (footprint '%s')." ),
233 aComponent->GetReference(),
234 EscapeHTML( aFootprintId.Format().wx_str() ) );
235
236 delete footprint;
237 footprint = nullptr;
238 }
239 else
240 {
241 for( PAD* pad : footprint->Pads() )
242 {
243 bool showRatsnest = true;
244
245 if( m_settings )
246 showRatsnest = m_settings->m_Display.m_ShowGlobalRatsnest;
247
248 pad->SetLocalRatsnestVisible( showRatsnest );
249
250 // Pads in the library all have orphaned nets. Replace with Default.
251 pad->SetNetCode( 0 );
252 }
253
254 footprint->SetParent( m_board );
256
257 // This flag is used to prevent connectivity from considering the footprint during its
258 // initial build after the footprint is committed, because we're going to immediately start
259 // a move operation on the footprint and don't want its pads to drive nets onto vias/tracks
260 // it happens to land on at the initial position.
261 footprint->SetAttributes( footprint->GetAttributes() | FP_JUST_ADDED );
262
263 m_addedFootprints.push_back( footprint );
264 m_commit.Add( footprint );
265
266 msg.Printf( _( "Added %s (footprint '%s')." ),
267 aComponent->GetReference(),
268 EscapeHTML( aFootprintId.Format().wx_str() ) );
269 }
270
271 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
273 return footprint;
274}
275
276
278{
279 wxString curClassName, newClassName;
280 COMPONENT_CLASS* newClass = nullptr;
281
282 if( const COMPONENT_CLASS* curClass = aFootprint->GetStaticComponentClass() )
283 curClassName = curClass->GetName();
284
285 // Calculate the new component class
286 if( m_isDryRun )
287 {
289 aNewComponent->GetComponentClassNames() );
290 }
291 else
292 {
293 newClass = m_board->GetComponentClassManager().GetEffectiveStaticComponentClass(
294 aNewComponent->GetComponentClassNames() );
295 newClassName = newClass->GetName();
296 }
297
298 if( curClassName == newClassName )
299 return false;
300
301 // Create a copy for undo if the footprint has not been added during this update
302 FOOTPRINT* copy = nullptr;
303
304 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
305 {
306 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
307 copy->SetParentGroup( nullptr );
308 }
309
310 wxString msg;
311
312 if( m_isDryRun )
313 {
314 if( curClassName == wxEmptyString && newClassName != wxEmptyString )
315 {
316 msg.Printf( _( "Change %s component class to '%s'." ),
317 aFootprint->GetReference(),
318 EscapeHTML( newClassName ) );
319 }
320 else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
321 {
322 msg.Printf( _( "Remove %s component class (currently '%s')." ),
323 aFootprint->GetReference(),
324 EscapeHTML( curClassName ) );
325 }
326 else
327 {
328 msg.Printf( _( "Change %s component class from '%s' to '%s'." ),
329 aFootprint->GetReference(),
330 EscapeHTML( curClassName ),
331 EscapeHTML( newClassName ) );
332 }
333 }
334 else
335 {
336 wxASSERT_MSG( newClass != nullptr, "Component class should not be nullptr" );
337
338 aFootprint->SetStaticComponentClass( newClass );
339
340 if( curClassName == wxEmptyString && newClassName != wxEmptyString )
341 {
342 msg.Printf( _( "Changed %s component class to '%s'." ),
343 aFootprint->GetReference(),
344 EscapeHTML( newClassName ) );
345 }
346 else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
347 {
348 msg.Printf( _( "Removed %s component class (was '%s')." ),
349 aFootprint->GetReference(),
350 EscapeHTML( curClassName ) );
351 }
352 else
353 {
354 msg.Printf( _( "Changed %s component class from '%s' to '%s'." ),
355 aFootprint->GetReference(),
356 EscapeHTML( curClassName ),
357 EscapeHTML( newClassName ) );
358 }
359 }
360
361 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
362
363 if( copy )
364 m_commit.Modified( aFootprint, copy );
365
366 return true;
367}
368
369
371 COMPONENT* aNewComponent )
372{
373 wxString msg;
374
375 if( aNewComponent->GetFPID().empty() )
376 {
377 msg.Printf( _( "Cannot update %s (no footprint assigned)." ),
378 aNewComponent->GetReference() );
379 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
380 ++m_errorCount;
381 return nullptr;
382 }
383
384 FOOTPRINT* newFootprint = LoadFootprintFromProject( m_board, aNewComponent->GetFPID() );
385
386 if( newFootprint == nullptr )
387 {
388 msg.Printf( _( "Cannot update %s (footprint '%s' not found)." ),
389 aNewComponent->GetReference(),
390 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
391 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
392 ++m_errorCount;
393 return nullptr;
394 }
395
396 if( m_isDryRun )
397 {
398 if( aFootprint->IsLocked() && !m_overrideLocks )
399 {
400 msg.Printf( _( "Cannot change %s footprint from '%s' to '%s' (footprint is locked)."),
401 aFootprint->GetReference(),
402 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
403 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
404 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
406 delete newFootprint;
407 return nullptr;
408 }
409 else
410 {
411 msg.Printf( _( "Change %s footprint from '%s' to '%s'."),
412 aFootprint->GetReference(),
413 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
414 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
415 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
417 delete newFootprint;
418 return nullptr;
419 }
420 }
421 else
422 {
423 if( aFootprint->IsLocked() && !m_overrideLocks )
424 {
425 msg.Printf( _( "Could not change %s footprint from '%s' to '%s' (footprint is locked)."),
426 aFootprint->GetReference(),
427 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
428 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
429 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
431 delete newFootprint;
432 return nullptr;
433 }
434 else
435 {
436 // Expand the footprint pad layers
437 newFootprint->FixUpPadsForBoard( m_board );
438
439 m_board->ExchangeFootprint( aFootprint, newFootprint, m_commit, true );
440
441 msg.Printf( _( "Changed %s footprint from '%s' to '%s'."),
442 aFootprint->GetReference(),
443 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
444 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
445 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
447 return newFootprint;
448 }
449 }
450 }
451
452
454{
455 wxString msg;
456
457 const COMPONENT_VARIANT* firstAssociatedVariant = nullptr;
458
459 if( aFootprint->GetFPID() != aNetlistComponent->GetFPID() )
460 {
461 for( const auto& [_, test] : aNetlistComponent->GetVariants() )
462 {
463 if( test.m_fields.count( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) )
464 && aFootprint->GetFPIDAsString() == test.m_fields.at( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) ) )
465 {
466 firstAssociatedVariant = &test;
467 break;
468 }
469 }
470 }
471
472 // Create a copy only if the footprint has not been added during this update
473 FOOTPRINT* copy = nullptr;
474
475 if( !m_commit.GetStatus( aFootprint ) )
476 {
477 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
478 copy->SetParentGroup( nullptr );
479 }
480
481 bool changed = false;
482
483 // Test for reference designator field change.
484 if( aFootprint->GetReference() != aNetlistComponent->GetReference() )
485 {
486 if( m_isDryRun )
487 {
488 msg.Printf( _( "Change %s reference designator to %s." ),
489 aFootprint->GetReference(),
490 aNetlistComponent->GetReference() );
491 }
492 else
493 {
494 msg.Printf( _( "Changed %s reference designator to %s." ),
495 aFootprint->GetReference(),
496 aNetlistComponent->GetReference() );
497
498 changed = true;
499 aFootprint->SetReference( aNetlistComponent->GetReference() );
500 }
501
502 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
503 }
504
505 // Test for value field change.
506 wxString netlistValue = aNetlistComponent->GetValue();
507
508 if( firstAssociatedVariant != nullptr
509 && firstAssociatedVariant->m_fields.count( GetCanonicalFieldName( FIELD_T::VALUE ) ) )
510 {
511 netlistValue = firstAssociatedVariant->m_fields.at( GetCanonicalFieldName( FIELD_T::VALUE ) );
512 }
513
514 if( aFootprint->GetValue() != netlistValue )
515 {
516 if( m_isDryRun )
517 {
518 msg.Printf( _( "Change %s value from %s to %s." ),
519 aFootprint->GetReference(),
520 EscapeHTML( aFootprint->GetValue() ),
521 EscapeHTML( netlistValue ) );
522 }
523 else
524 {
525 msg.Printf( _( "Changed %s value from %s to %s." ),
526 aFootprint->GetReference(),
527 EscapeHTML( aFootprint->GetValue() ),
528 EscapeHTML( netlistValue ) );
529
530 changed = true;
531 aFootprint->SetValue( netlistValue );
532 }
533
534 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
535 }
536
537 // Test for time stamp change.
538 KIID_PATH new_path = aNetlistComponent->GetPath();
539
540 if( !aNetlistComponent->GetKIIDs().empty() )
541 new_path.push_back( aNetlistComponent->GetKIIDs().front() );
542
543 if( aFootprint->GetPath() != new_path )
544 {
545 if( m_isDryRun )
546 {
547 msg.Printf( _( "Update %s symbol association from %s to %s." ),
548 aFootprint->GetReference(),
549 EscapeHTML( aFootprint->GetPath().AsString() ),
550 EscapeHTML( new_path.AsString() ) );
551 }
552 else
553 {
554 msg.Printf( _( "Updated %s symbol association from %s to %s." ),
555 aFootprint->GetReference(),
556 EscapeHTML( aFootprint->GetPath().AsString() ),
557 EscapeHTML( new_path.AsString() ) );
558
559 changed = true;
560 aFootprint->SetPath( new_path );
561 }
562
563 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
564 }
565
566 nlohmann::ordered_map<wxString, wxString> fpFieldsAsMap;
567
568 for( PCB_FIELD* field : aFootprint->GetFields() )
569 {
570 // These fields are individually checked above
571 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
572 {
573 continue;
574 }
575
576 fpFieldsAsMap[field->GetName()] = field->GetText();
577 }
578
579 // Remove the ref/value/footprint fields that are individually handled
580 nlohmann::ordered_map<wxString, wxString> compFields = aNetlistComponent->GetFields();
581 compFields.erase( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
582 compFields.erase( GetCanonicalFieldName( FIELD_T::VALUE ) );
583 compFields.erase( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) );
584
585 // Remove any component class fields - these are not editable in the pcb editor
586 compFields.erase( wxT( "Component Class" ) );
587
588 if( firstAssociatedVariant != nullptr )
589 {
590 for( const auto& [name, value] : firstAssociatedVariant->m_fields )
591 compFields[name] = value;
592 }
593
594 // Fields are stored as an ordered map, but we don't (yet) support reordering the footprint fields to
595 // match the symbol, so we manually check the fields in the order they are stored in the symbol.
596 bool same = true;
597 bool remove_only = true;
598
599 for( const auto& [name, value] : compFields )
600 {
601 if( fpFieldsAsMap.count( name ) == 0 || fpFieldsAsMap[name] != value )
602 {
603 same = false;
604 remove_only = false;
605 break;
606 }
607 }
608
609 for( const auto& [name, value] : fpFieldsAsMap )
610 {
611 if( compFields.count( name ) == 0 )
612 {
613 same = false;
614 break;
615 }
616 }
617
618 if( !same )
619 {
620 if( m_isDryRun )
621 {
622 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
623 {
624 msg.Printf( _( "Update %s fields." ), aFootprint->GetReference() );
625 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
626 }
627
628 // Remove fields that aren't present in the symbol
629 for( PCB_FIELD* field : aFootprint->GetFields() )
630 {
631 if( field->IsMandatory() )
632 continue;
633
634 if( compFields.count( field->GetName() ) == 0 )
635 {
637 {
638 msg.Printf( _( "Remove %s footprint fields not in symbol." ), aFootprint->GetReference() );
639 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
640 }
641
642 break;
643 }
644 }
645 }
646 else
647 {
648 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
649 {
650 msg.Printf( _( "Updated %s fields." ), aFootprint->GetReference() );
651 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
652
653 changed = true;
654
655 // Add or change field value
656 for( auto& [name, value] : compFields )
657 {
658 if( aFootprint->HasField( name ) )
659 {
660 aFootprint->GetField( name )->SetText( value );
661 }
662 else
663 {
664 PCB_FIELD* newField = new PCB_FIELD( aFootprint, FIELD_T::USER );
665 aFootprint->Add( newField );
666
667 newField->SetName( name );
668 newField->SetText( value );
669 newField->SetVisible( false );
670 newField->SetLayer( aFootprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
671
672 // Give the relative position (0,0) in footprint
673 newField->SetPosition( aFootprint->GetPosition() );
674 // Give the footprint orientation
675 newField->Rotate( aFootprint->GetPosition(), aFootprint->GetOrientation() );
676
677 newField->StyleFromSettings( m_board->GetDesignSettings(), true );
678 }
679 }
680 }
681
683 {
684 bool warned = false;
685
686 std::vector<PCB_FIELD*> fieldList;
687 aFootprint->GetFields( fieldList, false );
688
689 for( PCB_FIELD* field : fieldList )
690 {
691 if( field->IsMandatory() )
692 continue;
693
694 if( compFields.count( field->GetName() ) == 0 )
695 {
696 if( !warned )
697 {
698 warned = true;
699 msg.Printf( _( "Removed %s footprint fields not in symbol." ), aFootprint->GetReference() );
700 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
701 }
702
703 aFootprint->Remove( field );
704
705 if( m_frame )
706 m_frame->GetCanvas()->GetView()->Remove( field );
707
708 delete field;
709 }
710 }
711 }
712 }
713 }
714
715 wxString sheetname;
716 wxString sheetfile;
717 wxString fpFilters;
718
719 wxString humanSheetPath = aNetlistComponent->GetHumanReadablePath();
720
721 if( !humanSheetPath.empty() )
722 sheetname = humanSheetPath;
723 else if( aNetlistComponent->GetProperties().count( wxT( "Sheetname" ) ) > 0 )
724 sheetname = aNetlistComponent->GetProperties().at( wxT( "Sheetname" ) );
725
726 if( aNetlistComponent->GetProperties().count( wxT( "Sheetfile" ) ) > 0 )
727 sheetfile = aNetlistComponent->GetProperties().at( wxT( "Sheetfile" ) );
728
729 if( aNetlistComponent->GetProperties().count( wxT( "ki_fp_filters" ) ) > 0 )
730 fpFilters = aNetlistComponent->GetProperties().at( wxT( "ki_fp_filters" ) );
731
732 if( sheetname != aFootprint->GetSheetname() )
733 {
734 if( m_isDryRun )
735 {
736 msg.Printf( _( "Update %s sheetname to '%s'." ),
737 aFootprint->GetReference(),
738 EscapeHTML( sheetname ) );
739 }
740 else
741 {
742 aFootprint->SetSheetname( sheetname );
743 msg.Printf( _( "Updated %s sheetname to '%s'." ),
744 aFootprint->GetReference(),
745 EscapeHTML( sheetname ) );
746 }
747
748 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
749 }
750
751 if( sheetfile != aFootprint->GetSheetfile() )
752 {
753 if( m_isDryRun )
754 {
755 msg.Printf( _( "Update %s sheetfile to '%s'." ),
756 aFootprint->GetReference(),
757 EscapeHTML( sheetfile ) );
758 }
759 else
760 {
761 aFootprint->SetSheetfile( sheetfile );
762 msg.Printf( _( "Updated %s sheetfile to '%s'." ),
763 aFootprint->GetReference(),
764 EscapeHTML( sheetfile ) );
765 }
766
767 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
768 }
769
770 if( fpFilters != aFootprint->GetFilters() )
771 {
772 if( m_isDryRun )
773 {
774 msg.Printf( _( "Update %s footprint filters to '%s'." ),
775 aFootprint->GetReference(),
776 EscapeHTML( fpFilters ) );
777 }
778 else
779 {
780 aFootprint->SetFilters( fpFilters );
781 msg.Printf( _( "Updated %s footprint filters to '%s'." ),
782 aFootprint->GetReference(),
783 EscapeHTML( fpFilters ) );
784 }
785
786 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
787 }
788
789 bool netlistExcludeFromBOM = aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) > 0;
790
791 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromBOM )
792 netlistExcludeFromBOM = firstAssociatedVariant->m_excludedFromBOM;
793
794 if( m_updateFields && netlistExcludeFromBOM != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
795 {
796 if( m_isDryRun )
797 {
798 if( netlistExcludeFromBOM )
799 msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
800 else
801 msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
802 }
803 else
804 {
805 int attributes = aFootprint->GetAttributes();
806
807 if( netlistExcludeFromBOM )
808 {
809 attributes |= FP_EXCLUDE_FROM_BOM;
810 msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
811 }
812 else
813 {
814 attributes &= ~FP_EXCLUDE_FROM_BOM;
815 msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
816 }
817
818 changed = true;
819 aFootprint->SetAttributes( attributes );
820 }
821
822 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
823 }
824
825 bool netlistDNP = aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) > 0;
826
827 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasDnp )
828 netlistDNP = firstAssociatedVariant->m_dnp;
829
830 if( m_updateFields && netlistDNP != ( ( aFootprint->GetAttributes() & FP_DNP ) > 0 ) )
831 {
832 if( m_isDryRun )
833 {
834 if( netlistDNP )
835 msg.Printf( _( "Add %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
836 else
837 msg.Printf( _( "Remove %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
838 }
839 else
840 {
841 int attributes = aFootprint->GetAttributes();
842
843 if( netlistDNP )
844 {
845 attributes |= FP_DNP;
846 msg.Printf( _( "Added %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
847 }
848 else
849 {
850 attributes &= ~FP_DNP;
851 msg.Printf( _( "Removed %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
852 }
853
854 changed = true;
855 aFootprint->SetAttributes( attributes );
856 }
857
858 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
859 }
860
861 bool netlistExcludeFromPosFiles = aNetlistComponent->GetProperties().count( wxT( "exclude_from_pos_files" ) ) > 0;
862
863 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromPosFiles )
864 netlistExcludeFromPosFiles = firstAssociatedVariant->m_excludedFromPosFiles;
865
867 && netlistExcludeFromPosFiles != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES ) > 0 ) )
868 {
869 if( m_isDryRun )
870 {
871 if( netlistExcludeFromPosFiles )
872 {
873 msg.Printf( _( "Add %s 'exclude from position files' fabrication attribute." ),
874 aFootprint->GetReference() );
875 }
876 else
877 {
878 msg.Printf( _( "Remove %s 'exclude from position files' fabrication attribute." ),
879 aFootprint->GetReference() );
880 }
881 }
882 else
883 {
884 int attributes = aFootprint->GetAttributes();
885
886 if( netlistExcludeFromPosFiles )
887 {
888 attributes |= FP_EXCLUDE_FROM_POS_FILES;
889 msg.Printf( _( "Added %s 'exclude from position files' fabrication attribute." ),
890 aFootprint->GetReference() );
891 }
892 else
893 {
894 attributes &= ~FP_EXCLUDE_FROM_POS_FILES;
895 msg.Printf( _( "Removed %s 'exclude from position files' fabrication attribute." ),
896 aFootprint->GetReference() );
897 }
898
899 changed = true;
900 aFootprint->SetAttributes( attributes );
901 }
902
903 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
904 }
905
907 && aNetlistComponent->GetDuplicatePadNumbersAreJumpers() != aFootprint->GetDuplicatePadNumbersAreJumpers() )
908 {
909 bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
910
911 if( !m_isDryRun )
912 {
913 changed = true;
914 aFootprint->SetDuplicatePadNumbersAreJumpers( value );
915
916 if( value )
917 {
918 msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
919 aFootprint->GetReference() );
920 }
921 else
922 {
923 msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
924 aFootprint->GetReference() );
925 }
926 }
927 else
928 {
929 if( value )
930 {
931 msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
932 aFootprint->GetReference() );
933 }
934 else
935 {
936 msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
937 aFootprint->GetReference() );
938 }
939 }
940
941 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
942 }
943
944 if( m_updateFields && aNetlistComponent->JumperPadGroups() != aFootprint->JumperPadGroups() )
945 {
946 if( !m_isDryRun )
947 {
948 changed = true;
949 aFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
950 msg.Printf( _( "Updated %s jumper pad groups" ), aFootprint->GetReference() );
951 }
952 else
953 {
954 msg.Printf( _( "Update %s jumper pad groups" ), aFootprint->GetReference() );
955 }
956
957 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
958 }
959
960 if( changed && copy )
961 m_commit.Modified( aFootprint, copy );
962 else
963 delete copy;
964
965 return true;
966}
967
968
970 COMPONENT* aNetlistComponent )
971{
972 if( !m_transferGroups )
973 return false;
974
975 wxString msg;
976
977 // Create a copy only if the footprint has not been added during this update
978 FOOTPRINT* copy = nullptr;
979
980 if( !m_commit.GetStatus( aPcbFootprint ) )
981 {
982 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
983 copy->SetParentGroup( nullptr );
984 }
985
986 bool changed = false;
987
988 // These hold the info for group and group KIID coming from the netlist
989 // newGroup may point to an existing group on the board if we find an
990 // incoming group UUID that matches an existing group
991 PCB_GROUP* newGroup = nullptr;
992 KIID newGroupKIID = aNetlistComponent->GetGroup() ? aNetlistComponent->GetGroup()->uuid : 0;
993
994 PCB_GROUP* existingGroup = static_cast<PCB_GROUP*>( aPcbFootprint->GetParentGroup() );
995 KIID existingGroupKIID = existingGroup ? existingGroup->m_Uuid : 0;
996
997 // Find existing group based on matching UUIDs
998 auto it = std::find_if( m_board->Groups().begin(), m_board->Groups().end(),
999 [&](PCB_GROUP* group) {
1000 return group->m_Uuid == newGroupKIID;
1001 });
1002
1003 // If we find a group with the same UUID, use it
1004 if( it != m_board->Groups().end() )
1005 newGroup = *it;
1006
1007 // No changes, nothing to do
1008 if( newGroupKIID == existingGroupKIID )
1009 return changed;
1010
1011 // Remove from existing group
1012 if( existingGroupKIID != 0 )
1013 {
1014 if( m_isDryRun )
1015 {
1016 msg.Printf( _( "Remove %s from group '%s'." ),
1017 aPcbFootprint->GetReference(),
1018 EscapeHTML( existingGroup->GetName() ) );
1019 }
1020 else
1021 {
1022 msg.Printf( _( "Removed %s from group '%s'." ),
1023 aPcbFootprint->GetReference(),
1024 EscapeHTML( existingGroup->GetName() ) );
1025
1026 changed = true;
1027 m_commit.Modify( existingGroup, nullptr, RECURSE_MODE::NO_RECURSE );
1028 existingGroup->RemoveItem( aPcbFootprint );
1029
1030 if( existingGroup->GetItems().size() < 2 )
1031 {
1032 existingGroup->RemoveAll();
1033 m_commit.Remove( existingGroup );
1034 }
1035 }
1036
1037 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1038 }
1039
1040 // Add to new group
1041 if( newGroupKIID != 0 )
1042 {
1043 if( m_isDryRun )
1044 {
1045 msg.Printf( _( "Add %s to group '%s'." ),
1046 aPcbFootprint->GetReference(),
1047 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
1048 }
1049 else
1050 {
1051 msg.Printf( _( "Added %s to group '%s'." ),
1052 aPcbFootprint->GetReference(),
1053 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
1054
1055 changed = true;
1056
1057 if( newGroup == nullptr )
1058 {
1059 newGroup = new PCB_GROUP( m_board );
1060 newGroup->SetUuid( newGroupKIID );
1061 newGroup->SetName( aNetlistComponent->GetGroup()->name );
1062
1063 // Add the group to the board manually so we can find it by checking
1064 // board groups for later footprints that are checking for existing groups
1065 m_board->Add( newGroup );
1066 m_commit.Added( newGroup );
1067 m_addedGroups.push_back( newGroup );
1068 }
1069 else
1070 {
1071 m_commit.Modify( newGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1072 }
1073
1074 newGroup->AddItem( aPcbFootprint );
1075 }
1076
1077 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1078 }
1079
1080 if( changed && copy )
1081 m_commit.Modified( aPcbFootprint, copy );
1082 else if( copy )
1083 delete copy;
1084
1085 return changed;
1086}
1087
1088
1090 COMPONENT* aNewComponent )
1091{
1092 wxString msg;
1093
1094 // Create a copy only if the footprint has not been added during this update
1095 FOOTPRINT* copy = nullptr;
1096
1097 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
1098 {
1099 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1100 copy->SetParentGroup( nullptr );
1101 }
1102
1103 bool changed = false;
1104
1105 // At this point, the component footprint is updated. Now update the nets.
1106 std::deque<PAD*> pads = aFootprint->Pads();
1107 std::set<wxString> padNetnames;
1108
1109 std::sort( pads.begin(), pads.end(),
1110 []( PAD* a, PAD* b )
1111 {
1112 return a->m_Uuid < b->m_Uuid;
1113 } );
1114
1115 for( PAD* pad : pads )
1116 {
1117 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
1118
1119 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1120 wxT( "Processing pad %s of component %s" ),
1121 pad->GetNumber(),
1122 aNewComponent->GetReference() );
1123
1124 wxString pinFunction;
1125 wxString pinType;
1126
1127 if( net.IsValid() ) // i.e. the pad has a name
1128 {
1129 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1130 wxT( " Found valid net: %s" ),
1131 net.GetNetName() );
1132 pinFunction = net.GetPinFunction();
1133 pinType = net.GetPinType();
1134 }
1135 else
1136 {
1137 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1138 wxT( " No net found for pad %s" ),
1139 pad->GetNumber() );
1140 }
1141
1142 if( !m_isDryRun )
1143 {
1144 if( pad->GetPinFunction() != pinFunction )
1145 {
1146 changed = true;
1147 pad->SetPinFunction( pinFunction );
1148 }
1149
1150 if( pad->GetPinType() != pinType )
1151 {
1152 changed = true;
1153 pad->SetPinType( pinType );
1154 }
1155 }
1156 else
1157 {
1158 cachePinFunction( pad, pinFunction );
1159 }
1160
1161 // Test if new footprint pad has no net (pads not on copper layers have no net).
1162 if( !net.IsValid() || !pad->IsOnCopperLayer() )
1163 {
1164 if( !pad->GetNetname().IsEmpty() )
1165 {
1166 if( m_isDryRun )
1167 {
1168 msg.Printf( _( "Disconnect %s pin %s." ),
1169 aFootprint->GetReference(),
1170 EscapeHTML( pad->GetNumber() ) );
1171 }
1172 else
1173 {
1174 msg.Printf( _( "Disconnected %s pin %s." ),
1175 aFootprint->GetReference(),
1176 EscapeHTML( pad->GetNumber() ) );
1177 }
1178
1179 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1180 }
1181 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
1182 {
1183 // pad is connectable but has no net found in netlist
1184 msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
1185 aFootprint->GetReference(),
1186 EscapeHTML( pad->GetNumber() ),
1187 EscapeHTML( pad->GetNumber() ) );
1188 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
1190 }
1191
1192 if( !m_isDryRun )
1193 {
1194 changed = true;
1195 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
1196
1197 // If the pad has no net from netlist (i.e. not in netlist
1198 // it cannot have a pin function
1199 if( pad->GetNetname().IsEmpty() )
1200 pad->SetPinFunction( wxEmptyString );
1201
1202 }
1203 else
1204 {
1205 cacheNetname( pad, wxEmptyString );
1206 }
1207 }
1208 else // New footprint pad has a net.
1209 {
1210 wxString netName = net.GetNetName();
1211
1212 if( pad->IsNoConnectPad() )
1213 {
1214 netName = wxString::Format( wxS( "%s" ), net.GetNetName() );
1215
1216 for( int jj = 1; !padNetnames.insert( netName ).second
1217 || ( netName != net.GetNetName() && m_schematicNetNames.count( netName ) );
1218 jj++ )
1219 {
1220 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1221 }
1222 }
1223
1224 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1225
1226 if( netinfo && !m_isDryRun )
1227 netinfo->SetIsCurrent( true );
1228
1229 if( pad->GetNetname() != netName )
1230 {
1231
1232 if( netinfo == nullptr )
1233 {
1234 // It might be a new net that has not been added to the board yet
1235 if( m_addedNets.count( netName ) )
1236 netinfo = m_addedNets[ netName ];
1237 }
1238
1239 if( netinfo == nullptr )
1240 {
1241 netinfo = new NETINFO_ITEM( m_board, netName );
1242
1243 // It is a new net, we have to add it
1244 if( !m_isDryRun )
1245 {
1246 changed = true;
1247 m_commit.Add( netinfo );
1248 }
1249
1250 m_addedNets[netName] = netinfo;
1251 msg.Printf( _( "Add net %s." ),
1252 EscapeHTML( UnescapeString( netName ) ) );
1253 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1254 }
1255
1256 if( !pad->GetNetname().IsEmpty() )
1257 {
1258 m_oldToNewNets[ pad->GetNetname() ] = netName;
1259
1260 if( m_isDryRun )
1261 {
1262 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1263 aFootprint->GetReference(),
1264 EscapeHTML( pad->GetNumber() ),
1265 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1266 EscapeHTML( UnescapeString( netName ) ) );
1267 }
1268 else
1269 {
1270 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1271 aFootprint->GetReference(),
1272 EscapeHTML( pad->GetNumber() ),
1273 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1274 EscapeHTML( UnescapeString( netName ) ) );
1275 }
1276 }
1277 else
1278 {
1279 if( m_isDryRun )
1280 {
1281 msg.Printf( _( "Connect %s pin %s to %s."),
1282 aFootprint->GetReference(),
1283 EscapeHTML( pad->GetNumber() ),
1284 EscapeHTML( UnescapeString( netName ) ) );
1285 }
1286 else
1287 {
1288 msg.Printf( _( "Connected %s pin %s to %s."),
1289 aFootprint->GetReference(),
1290 EscapeHTML( pad->GetNumber() ),
1291 EscapeHTML( UnescapeString( netName ) ) );
1292 }
1293 }
1294
1295 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1296
1297 if( !m_isDryRun )
1298 {
1299 changed = true;
1300 pad->SetNet( netinfo );
1301 }
1302 else
1303 {
1304 cacheNetname( pad, netName );
1305 }
1306 }
1307 }
1308 }
1309
1310 if( changed && copy )
1311 m_commit.Modified( aFootprint, copy );
1312 else if( copy )
1313 delete copy;
1314
1315 return true;
1316}
1317
1318
1320{
1321 // Build the footprint-side representation from the netlist component
1322 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1323
1324 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1325 newUnits.push_back( { u.m_unitName, u.m_pins } );
1326
1327 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1328
1329 auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
1330 const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
1331 {
1332 if( a.size() != b.size() )
1333 return false;
1334
1335 for( size_t i = 0; i < a.size(); ++i )
1336 {
1337 if( a[i].m_unitName != b[i].m_unitName )
1338 return false;
1339
1340 if( a[i].m_pins != b[i].m_pins )
1341 return false;
1342 }
1343
1344 return true;
1345 };
1346
1347 if( unitsEqual( curUnits, newUnits ) )
1348 return false;
1349
1350 wxString msg;
1351
1352 if( m_isDryRun )
1353 {
1354 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1355 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1356 return false; // no actual change on board during dry run
1357 }
1358
1359 // Create a copy only if the footprint has not been added during this update
1360 FOOTPRINT* copy = nullptr;
1361
1362 if( !m_commit.GetStatus( aFootprint ) )
1363 {
1364 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1365 copy->SetParentGroup( nullptr );
1366 }
1367
1368 aFootprint->SetUnitInfo( newUnits );
1369
1370 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1371 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1372
1373 if( copy )
1374 m_commit.Modified( aFootprint, copy );
1375
1376 return true;
1377}
1378
1379
1380bool BOARD_NETLIST_UPDATER::fpidsEquivalent( const LIB_ID& aBoardFpid, const LIB_ID& aSchematicFpid )
1381{
1382 if( aSchematicFpid.IsLegacy() )
1383 return aBoardFpid.GetLibItemName() == aSchematicFpid.GetLibItemName();
1384
1385 return aBoardFpid == aSchematicFpid;
1386}
1387
1388
1390 const std::vector<FOOTPRINT*>& aFootprints,
1391 const LIB_ID& aBaseFpid )
1392{
1393 wxString msg;
1394 const auto& variants = aComponent->GetVariants();
1395
1396 if( aBaseFpid.empty() )
1397 return;
1398
1399 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
1400
1401 struct VARIANT_INFO
1402 {
1403 wxString name;
1404 const COMPONENT_VARIANT* variant;
1405 LIB_ID variantFPID;
1406 };
1407
1408 std::vector<VARIANT_INFO> variantInfo;
1409 variantInfo.reserve( variants.size() );
1410
1411 for( const auto& [variantName, variant] : variants )
1412 {
1413 LIB_ID variantFPID = aBaseFpid;
1414
1415 auto fieldIt = variant.m_fields.find( footprintFieldName );
1416
1417 if( fieldIt != variant.m_fields.end() && !fieldIt->second.IsEmpty() )
1418 {
1419 LIB_ID parsedId;
1420
1421 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
1422 {
1423 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
1424 EscapeHTML( fieldIt->second ),
1425 variantName,
1426 aComponent->GetReference() );
1427 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1428 ++m_errorCount;
1429 }
1430 else
1431 {
1432 variantFPID = parsedId;
1433 }
1434 }
1435
1436 variantInfo.push_back( { variantName, &variant, variantFPID } );
1437 }
1438
1439 for( FOOTPRINT* footprint : aFootprints )
1440 {
1441 if( !footprint )
1442 continue;
1443
1444 FOOTPRINT* copy = nullptr;
1445
1446 if( !m_isDryRun && !m_commit.GetStatus( footprint ) )
1447 {
1448 copy = static_cast<FOOTPRINT*>( footprint->Clone() );
1449 copy->SetParentGroup( nullptr );
1450 }
1451
1452 bool changed = false;
1453
1454 auto printAttributeMessage =
1455 [&]( bool add, const wxString& attrName, const wxString& variantName )
1456 {
1457 if( m_isDryRun )
1458 {
1459 if( aFootprints.size() > 1 )
1460 {
1461 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s (footprint %s)." )
1462 : _( "Remove %s '%s' attribute from variant %s (footprint %s)." ),
1463 footprint->GetReference(),
1464 attrName,
1465 variantName,
1466 footprint->GetFPIDAsString() );
1467 }
1468 else
1469 {
1470 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s." )
1471 : _( "Remove %s '%s' attribute from variant %s." ),
1472 footprint->GetReference(),
1473 attrName,
1474 variantName );
1475 }
1476 }
1477 else
1478 {
1479 if( aFootprints.size() > 1 )
1480 {
1481 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s (footprint %s)." )
1482 : _( "Removed %s '%s' attribute from variant %s (footprint %s)." ),
1483 footprint->GetReference(),
1484 attrName,
1485 variantName,
1486 footprint->GetFPIDAsString() );
1487 }
1488 else
1489 {
1490 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s." )
1491 : _( "Removed %s '%s' attribute from variant %s." ),
1492 footprint->GetReference(),
1493 attrName,
1494 variantName );
1495 }
1496 }
1497 };
1498
1499 bool isBaseFootprint = fpidsEquivalent( footprint->GetFPID(), aBaseFpid );
1500
1501 // The footprint's own DNP flag before this pass forces the default-variant hiding below.
1502 // The per-variant target for a footprint that IS the active choice must fall back to this
1503 // original flag, not the forced one, so the active footprint stays populated.
1504 const bool baseFootprintDnp = footprint->IsDNP();
1505 bool effectiveFootprintDnp = baseFootprintDnp;
1506
1507 // A footprint that is not the component's base footprint is DNP by default (it stands in
1508 // only for the variants that select it). This runs before the per-variant loop so the loop
1509 // sees the correct effective DNP when deciding whether an explicit per-variant override is
1510 // needed; otherwise a footprint kept populated for its own variant would not converge until
1511 // a second netlist update.
1512 if( !isBaseFootprint && !effectiveFootprintDnp )
1513 {
1514 msg.Printf( m_isDryRun ? _( "Add %s 'Do not place' fabrication attribute." )
1515 : _( "Added %s 'Do not place' fabrication attribute." ),
1516 footprint->GetReference() );
1517
1518 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1519
1520 if( !m_isDryRun )
1521 footprint->SetDNP( true );
1522
1523 // Track the forced DNP locally so the per-variant loop below sees the correct effective
1524 // state even in dry run, where SetDNP() is intentionally not applied.
1525 effectiveFootprintDnp = true;
1526 changed = true;
1527 }
1528
1529 std::set<wxString> excessVariants;
1530
1531 for( const auto& [variantName, _] : footprint->GetVariants() )
1532 excessVariants.insert( variantName );
1533
1534 for( const VARIANT_INFO& info : variantInfo )
1535 {
1536 const COMPONENT_VARIANT& variant = *info.variant;
1537
1538 // During dry run, just read current state. During actual run, create variant if needed.
1539 const FOOTPRINT_VARIANT* currentVariant = footprint->GetVariant( info.name );
1540
1541 // Check if this footprint is the active one for this variant
1542 bool isAssociatedFootprint = fpidsEquivalent( footprint->GetFPID(), info.variantFPID );
1543
1544 // When multiple footprints share a RefDes (one per variant), a footprint that is not
1545 // the active choice for this variant must be DNP for it so the 3D viewer and other
1546 // consumers hide it. The base footprint carries no global DNP flag, so it needs an
1547 // explicit per-variant override; non-base footprints are already globally DNP above.
1548 if( !isAssociatedFootprint )
1549 {
1550 if( aFootprints.size() > 1 )
1551 {
1552 excessVariants.erase( info.name );
1553 bool currentDnp = currentVariant ? currentVariant->GetDNP() : effectiveFootprintDnp;
1554
1555 if( !currentDnp )
1556 {
1557 printAttributeMessage( true, _( "Do not place" ), info.name );
1558
1559 if( !m_isDryRun )
1560 {
1561 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1562 fpVariant->SetDNP( true );
1563 }
1564
1565 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1566 changed = true;
1567 }
1568 }
1569
1570 continue;
1571 }
1572
1573 excessVariants.erase( info.name );
1574 bool targetDnp = variant.m_hasDnp ? variant.m_dnp : baseFootprintDnp;
1575 bool currentDnp = currentVariant ? currentVariant->GetDNP() : effectiveFootprintDnp;
1576
1577 if( currentDnp != targetDnp )
1578 {
1579 printAttributeMessage( targetDnp, _( "Do not place" ), info.name );
1580
1581 if( !m_isDryRun )
1582 {
1583 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1584 fpVariant->SetDNP( targetDnp );
1585 }
1586
1587 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1588 changed = true;
1589 }
1590
1591 bool targetExcludedFromBOM = variant.m_hasExcludedFromBOM ? variant.m_excludedFromBOM
1592 : footprint->IsExcludedFromBOM();
1593 bool currentExcludedFromBOM = currentVariant ? currentVariant->GetExcludedFromBOM()
1594 : footprint->IsExcludedFromBOM();
1595
1596 if( currentExcludedFromBOM != targetExcludedFromBOM )
1597 {
1598 printAttributeMessage( targetExcludedFromBOM, _( "exclude from BOM" ), info.name );
1599
1600 if( !m_isDryRun )
1601 {
1602 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1603 fpVariant->SetExcludedFromBOM( targetExcludedFromBOM );
1604 }
1605
1606 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1607 changed = true;
1608 }
1609
1610 bool targetExcludedFromPosFiles = variant.m_hasExcludedFromPosFiles ? variant.m_excludedFromPosFiles
1611 : footprint->IsExcludedFromPosFiles();
1612 bool currentExcludedFromPosFiles = currentVariant ? currentVariant->GetExcludedFromPosFiles()
1613 : footprint->IsExcludedFromPosFiles();
1614
1615 if( currentExcludedFromPosFiles != targetExcludedFromPosFiles )
1616 {
1617 printAttributeMessage( targetExcludedFromPosFiles, _( "exclude from position files" ), info.name );
1618
1619 if( !m_isDryRun )
1620 {
1621 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1622 fpVariant->SetExcludedFromPosFiles( targetExcludedFromPosFiles );
1623 }
1624
1625 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1626 changed = true;
1627 }
1628
1629 for( const auto& [fieldName, fieldValue] : variant.m_fields )
1630 {
1631 if( fieldName.CmpNoCase( footprintFieldName ) == 0 )
1632 continue;
1633
1634 bool hasCurrentValue = currentVariant && currentVariant->HasFieldValue( fieldName );
1635 wxString currentValue = hasCurrentValue ? currentVariant->GetFieldValue( fieldName ) : wxString();
1636
1637 if( currentValue != fieldValue )
1638 {
1639 if( m_isDryRun )
1640 {
1641 if( aFootprints.size() > 1 )
1642 {
1643 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s (footprint %s)." ),
1644 footprint->GetReference(),
1645 fieldName,
1646 fieldValue,
1647 info.name,
1648 footprint->GetFPIDAsString() );
1649 }
1650 else
1651 {
1652 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s." ),
1653 footprint->GetReference(),
1654 fieldName,
1655 fieldValue,
1656 info.name );
1657 }
1658 }
1659 else
1660 {
1661 if( aFootprints.size() > 1 )
1662 {
1663 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s (footprint %s)." ),
1664 footprint->GetReference(),
1665 fieldName,
1666 fieldValue,
1667 info.name,
1668 footprint->GetFPIDAsString() );
1669 }
1670 else
1671 {
1672 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s." ),
1673 footprint->GetReference(),
1674 fieldName,
1675 fieldValue,
1676 info.name );
1677 }
1678
1679 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1680 fpVariant->SetFieldValue( fieldName, fieldValue );
1681 }
1682
1683 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1684 changed = true;
1685 }
1686 }
1687 }
1688
1689 for( const wxString& excess : excessVariants )
1690 {
1691 if( m_isDryRun )
1692 {
1693 msg.Printf( _( "Remove variant %s:%s no longer associated with footprint %s." ),
1694 footprint->GetReference(),
1695 excess,
1696 footprint->GetFPIDAsString() );
1697 }
1698 else
1699 {
1700 msg.Printf( _( "Removed variant %s:%s no longer associated with footprint %s." ),
1701 footprint->GetReference(),
1702 excess,
1703 footprint->GetFPIDAsString() );
1704
1705 footprint->DeleteVariant( excess );
1706 }
1707
1708 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1709 changed = true;
1710 }
1711
1712 if( !m_isDryRun && changed && copy )
1713 m_commit.Modified( footprint, copy );
1714 else
1715 delete copy;
1716 }
1717}
1718
1719
1721{
1722 for( ZONE* zone : m_board->Zones() )
1723 {
1724 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1725 continue;
1726
1727 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1728 }
1729}
1730
1731
1733{
1734 wxString msg;
1735 std::set<wxString> netlistNetnames;
1736
1737 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1738 {
1739 const COMPONENT* component = aNetlist.GetComponent( ii );
1740
1741 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1742 {
1743 const COMPONENT_NET& net = component->GetNet( jj );
1744 netlistNetnames.insert( net.GetNetName() );
1745 }
1746 }
1747
1748 for( PCB_TRACK* via : m_board->Tracks() )
1749 {
1750 if( via->Type() != PCB_VIA_T )
1751 continue;
1752
1753 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1754 {
1755 wxString updatedNetname = wxEmptyString;
1756
1757 // Take via name from name change map if it didn't match to a new pad
1758 // (this is useful for stitching vias that don't connect to tracks)
1759 if( m_oldToNewNets.count( via->GetNetname() ) )
1760 {
1761 updatedNetname = m_oldToNewNets[via->GetNetname()];
1762 }
1763
1764 if( !updatedNetname.IsEmpty() )
1765 {
1766 if( m_isDryRun )
1767 {
1768 wxString originalNetname = via->GetNetname();
1769
1770 msg.Printf( _( "Reconnect via from %s to %s." ),
1771 EscapeHTML( UnescapeString( originalNetname ) ),
1772 EscapeHTML( UnescapeString( updatedNetname ) ) );
1773
1774 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1775 }
1776 else
1777 {
1778 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1779
1780 if( !netinfo )
1781 netinfo = m_addedNets[updatedNetname];
1782
1783 if( netinfo )
1784 {
1785 wxString originalNetname = via->GetNetname();
1786
1787 m_commit.Modify( via );
1788 via->SetNet( netinfo );
1789
1790 msg.Printf( _( "Reconnected via from %s to %s." ),
1791 EscapeHTML( UnescapeString( originalNetname ) ),
1792 EscapeHTML( UnescapeString( updatedNetname ) ) );
1793
1794 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1795 }
1796 }
1797 }
1798 else
1799 {
1800 msg.Printf( _( "Via connected to unknown net (%s)." ),
1801 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1802 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1804 }
1805 }
1806 }
1807
1808 // Board connectivity net names are not the same as schematic connectivity net names.
1809 // Footprints that contain multiple overlapping pads with the same number are suffixed
1810 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1811 // exposed in the zone net name list.
1812 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1813 {
1814 if( netlistNetnames.count( aNetName ) )
1815 return true;
1816
1817 // If the zone net name is a pseudo net name, check if the root net name is in the net
1818 // list. If so, then this is a valid net.
1819 for( const wxString& netName : netlistNetnames )
1820 {
1821 if( aNetName.StartsWith( netName ) )
1822 return true;
1823 }
1824
1825 return false;
1826 };
1827
1828 // Test copper zones to detect "dead" nets (nets without any pad):
1829 for( ZONE* zone : m_board->Zones() )
1830 {
1831 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1832 continue;
1833
1834 if( !isInNetlist( zone->GetNetname() ) )
1835 {
1836 // Look for a pad in the zone's connected-pad-cache which has been updated to
1837 // a new net and use that. While this won't always be the right net, the dead
1838 // net is guaranteed to be wrong.
1839 wxString updatedNetname = wxEmptyString;
1840
1841 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1842 {
1843 if( getNetname( pad ) != zone->GetNetname() )
1844 {
1845 updatedNetname = getNetname( pad );
1846 break;
1847 }
1848 }
1849
1850 // Take zone name from name change map if it didn't match to a new pad
1851 // (this is useful for zones on internal layers)
1852 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1853 {
1854 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1855 }
1856
1857 if( !updatedNetname.IsEmpty() )
1858 {
1859 if( m_isDryRun )
1860 {
1861 wxString originalNetname = zone->GetNetname();
1862
1863 if( !zone->GetZoneName().IsEmpty() )
1864 {
1865 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1866 zone->GetZoneName(),
1867 EscapeHTML( UnescapeString( originalNetname ) ),
1868 EscapeHTML( UnescapeString( updatedNetname ) ) );
1869 }
1870 else
1871 {
1872 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1873 EscapeHTML( UnescapeString( originalNetname ) ),
1874 EscapeHTML( UnescapeString( updatedNetname ) ) );
1875 }
1876
1877 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1878 }
1879 else
1880 {
1881 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1882
1883 if( !netinfo )
1884 netinfo = m_addedNets[ updatedNetname ];
1885
1886 if( netinfo )
1887 {
1888 wxString originalNetname = zone->GetNetname();
1889
1890 m_commit.Modify( zone );
1891 zone->SetNet( netinfo );
1892
1893 if( !zone->GetZoneName().IsEmpty() )
1894 {
1895 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1896 EscapeHTML( zone->GetZoneName() ),
1897 EscapeHTML( UnescapeString( originalNetname ) ),
1898 EscapeHTML( UnescapeString( updatedNetname ) ) );
1899 }
1900 else
1901 {
1902 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
1903 EscapeHTML( UnescapeString( originalNetname ) ),
1904 EscapeHTML( UnescapeString( updatedNetname ) ) );
1905 }
1906
1907 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1908 }
1909 }
1910 }
1911 else
1912 {
1913 if( !zone->GetZoneName().IsEmpty() )
1914 {
1915 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
1916 EscapeHTML( zone->GetZoneName() ) );
1917 }
1918 else
1919 {
1920 wxString layerNames = zone->LayerMaskDescribe();
1921 VECTOR2I pt = zone->GetPosition();
1922
1923 if( m_settings )
1924 {
1925 if( m_settings->m_Display.m_DisplayInvertXAxis )
1926 pt.x *= -1;
1927
1928 if( m_settings->m_Display.m_DisplayInvertYAxis )
1929 pt.y *= -1;
1930 }
1931
1932 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
1933 EscapeHTML( layerNames ),
1934 m_frame ? m_frame->MessageTextFromValue( pt.x )
1937 pt.x ),
1938 m_frame ? m_frame->MessageTextFromValue( pt.y )
1941 pt.y ),
1942 zone->GetNetname() );
1943 }
1944
1945 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1947 }
1948 }
1949 }
1950
1951 return true;
1952}
1953
1954
1956{
1957 if( !m_transferGroups )
1958 return false;
1959
1960 wxString msg;
1961
1962 for( PCB_GROUP* pcbGroup : m_board->Groups() )
1963 {
1964 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
1965
1966 if( netlistGroup == nullptr )
1967 continue;
1968
1969 if( netlistGroup->name != pcbGroup->GetName() )
1970 {
1971 if( m_isDryRun )
1972 {
1973 msg.Printf( _( "Change group name from '%s' to '%s'." ),
1974 EscapeHTML( pcbGroup->GetName() ),
1975 EscapeHTML( netlistGroup->name ) );
1976 }
1977 else
1978 {
1979 msg.Printf( _( "Changed group name from '%s' to '%s'." ),
1980 EscapeHTML( pcbGroup->GetName() ),
1981 EscapeHTML( netlistGroup->name ) );
1982 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1983 pcbGroup->SetName( netlistGroup->name );
1984 }
1985
1986 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1987 }
1988
1989 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
1990 {
1991 if( m_isDryRun )
1992 {
1993 msg.Printf( _( "Change group library link from '%s' to '%s'." ),
1994 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1995 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1996 }
1997 else
1998 {
1999 msg.Printf( _( "Changed group library link from '%s' to '%s'." ),
2000 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
2001 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
2002 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2003 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
2004 }
2005
2006 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2007 }
2008
2009 // A group member may be another group's uuid (a nested group). Restore that
2010 // parent/child relationship on the board.
2011 for( const KIID_PATH& member : netlistGroup->members )
2012 {
2013 if( member.empty() )
2014 continue;
2015
2016 KIID memberGroupUuid =
2017 member.size() == 1 ? member.front() : KIID::FromName( std::string( member.AsString().ToUTF8() ) );
2018
2019 PCB_GROUP* childGroup = nullptr;
2020
2021 for( PCB_GROUP* candidate : m_board->Groups() )
2022 {
2023 if( candidate->m_Uuid == memberGroupUuid )
2024 {
2025 childGroup = candidate;
2026 break;
2027 }
2028 }
2029
2030 if( !childGroup || childGroup == pcbGroup || childGroup->GetParentGroup() == pcbGroup )
2031 {
2032 continue;
2033 }
2034
2035 if( m_isDryRun )
2036 {
2037 msg.Printf( _( "Add group '%s' to group '%s'." ), EscapeHTML( childGroup->GetName() ),
2038 EscapeHTML( pcbGroup->GetName() ) );
2039 }
2040 else
2041 {
2042 msg.Printf( _( "Added group '%s' to group '%s'." ), EscapeHTML( childGroup->GetName() ),
2043 EscapeHTML( pcbGroup->GetName() ) );
2044 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2045 m_commit.Modify( childGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2046 pcbGroup->AddItem( childGroup );
2047 }
2048
2049 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2050 }
2051 }
2052
2053 return true;
2054}
2055
2056
2058 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
2059{
2060 // Verify that board contains all pads in netlist: if it doesn't then footprints are
2061 // wrong or missing.
2062
2063 wxString msg;
2064 wxString padNumber;
2065
2066 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
2067 {
2068 COMPONENT* component = aNetlist.GetComponent( i );
2069 FOOTPRINT* footprint = aFootprintMap[component];
2070
2071 if( !footprint ) // It can be missing in partial designs
2072 continue;
2073
2074 // Explore all pins/pads in component
2075 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
2076 {
2077 padNumber = component->GetNet( jj ).GetPinName();
2078
2079 if( padNumber.IsEmpty() )
2080 {
2081 // bad symbol, report error
2082 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
2083 "to pads in %s." ),
2084 component->GetReference(),
2085 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
2086 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2087 ++m_errorCount;
2088 }
2089 else if( !footprint->FindPadByNumber( padNumber ) )
2090 {
2091 // not found: bad footprint, report error
2092 msg.Printf( _( "%s pad %s not found in %s." ),
2093 component->GetReference(),
2094 EscapeHTML( padNumber ),
2095 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
2096 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2097 ++m_errorCount;
2098 }
2099 }
2100 }
2101
2102 return true;
2103}
2104
2105
2107{
2108 FOOTPRINT* lastPreexistingFootprint = nullptr;
2109 COMPONENT* component = nullptr;
2110 wxString msg;
2111 std::unordered_set<wxString> sheetPaths;
2112 std::unordered_set<FOOTPRINT*> usedFootprints;
2113
2114 m_errorCount = 0;
2115 m_warningCount = 0;
2117
2118 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
2119
2120 if( !m_board->Footprints().empty() )
2121 lastPreexistingFootprint = m_board->Footprints().back();
2122
2124
2125 // First mark all nets (except <no net>) as stale; we'll update those which are current
2126 // in the following two loops. Also prepare the component class manager for updates.
2127 //
2128 if( !m_isDryRun )
2129 {
2130 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2131 net->SetIsCurrent( net->GetNetCode() == 0 );
2132
2133 m_board->GetComponentClassManager().InitNetlistUpdate();
2134 }
2135
2136 // Collect all schematic net names so NC pad deduplication can avoid collisions
2137 for( unsigned ii = 0; ii < aNetlist.GetCount(); ii++ )
2138 {
2139 COMPONENT* comp = aNetlist.GetComponent( ii );
2140
2141 for( unsigned jj = 0; jj < comp->GetNetCount(); jj++ )
2142 m_schematicNetNames.insert( comp->GetNet( jj ).GetNetName() );
2143 }
2144
2145 // Next go through the netlist updating all board footprints which have matching component
2146 // entries and adding new footprints for those that don't.
2147 //
2148 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
2149 {
2150 component = aNetlist.GetComponent( i );
2151
2152 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
2153 continue;
2154
2155 msg.Printf( _( "Processing symbol '%s:%s'." ),
2156 component->GetReference(),
2157 EscapeHTML( component->GetFPID().Format().wx_str() ) );
2158 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2159
2160 const LIB_ID& baseFpid = component->GetFPID();
2161 const bool hasBaseFpid = !baseFpid.empty();
2162
2163 if( baseFpid.IsLegacy() )
2164 {
2165 msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. "
2166 "Use the full 'Library:Footprint' format to avoid repeated update "
2167 "notifications." ),
2168 component->GetReference(),
2169 EscapeHTML( baseFpid.Format().wx_str() ) );
2170 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2172 }
2173
2174 std::vector<FOOTPRINT*> matchingFootprints;
2175
2176 for( FOOTPRINT* footprint : m_board->Footprints() )
2177 {
2178 bool match = false;
2179
2181 {
2182 for( const KIID& uuid : component->GetKIIDs() )
2183 {
2184 KIID_PATH base = component->GetPath();
2185 base.push_back( uuid );
2186
2187 if( footprint->GetPath() == base )
2188 {
2189 match = true;
2190 break;
2191 }
2192 }
2193 }
2194 else
2195 {
2196 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
2197 }
2198
2199 if( match )
2200 matchingFootprints.push_back( footprint );
2201
2202 if( footprint == lastPreexistingFootprint )
2203 {
2204 // No sense going through the newly-created footprints: end of loop
2205 break;
2206 }
2207 }
2208
2209 std::vector<LIB_ID> expectedFpids;
2210 std::unordered_set<wxString> expectedFpidKeys;
2211
2212 auto addExpectedFpid =
2213 [&]( const LIB_ID& aFpid )
2214 {
2215 if( aFpid.empty() )
2216 return;
2217
2218 wxString key = aFpid.Format();
2219
2220 if( expectedFpidKeys.insert( key ).second )
2221 expectedFpids.push_back( aFpid );
2222 };
2223
2224 addExpectedFpid( baseFpid );
2225
2226 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
2227
2228 for( const auto& [variantName, variant] : component->GetVariants() )
2229 {
2230 auto fieldIt = variant.m_fields.find( footprintFieldName );
2231
2232 if( fieldIt == variant.m_fields.end() || fieldIt->second.IsEmpty() )
2233 continue;
2234
2235 LIB_ID parsedId;
2236
2237 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
2238 {
2239 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
2240 EscapeHTML( fieldIt->second ),
2241 variantName,
2242 component->GetReference() );
2243 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2244 ++m_errorCount;
2245 continue;
2246 }
2247
2248 addExpectedFpid( parsedId );
2249 }
2250
2251 auto isExpectedFpid =
2252 [&]( const LIB_ID& aFpid ) -> bool
2253 {
2254 if( aFpid.empty() )
2255 return false;
2256
2257 if( expectedFpidKeys.count( aFpid.Format() ) > 0 )
2258 return true;
2259
2260 for( const LIB_ID& expected : expectedFpids )
2261 {
2262 if( fpidsEquivalent( aFpid, expected ) )
2263 return true;
2264 }
2265
2266 return false;
2267 };
2268
2269 auto takeMatchingFootprint =
2270 [&]( const LIB_ID& aFpid ) -> FOOTPRINT*
2271 {
2272 for( FOOTPRINT* footprint : matchingFootprints )
2273 {
2274 if( usedFootprints.count( footprint ) )
2275 continue;
2276
2277 if( fpidsEquivalent( footprint->GetFPID(), aFpid ) )
2278 return footprint;
2279 }
2280
2281 return nullptr;
2282 };
2283
2284 std::vector<FOOTPRINT*> componentFootprints;
2285 componentFootprints.reserve( expectedFpids.size() );
2286 FOOTPRINT* baseFootprint = nullptr;
2287
2288 if( hasBaseFpid )
2289 baseFootprint = takeMatchingFootprint( baseFpid );
2290 else if( !matchingFootprints.empty() )
2291 baseFootprint = matchingFootprints.front();
2292
2293 if( !baseFootprint && m_replaceFootprints && !matchingFootprints.empty() )
2294 {
2295 FOOTPRINT* replaceCandidate = nullptr;
2296
2297 for( FOOTPRINT* footprint : matchingFootprints )
2298 {
2299 if( usedFootprints.count( footprint ) )
2300 continue;
2301
2302 if( isExpectedFpid( footprint->GetFPID() ) )
2303 continue;
2304
2305 replaceCandidate = footprint;
2306 break;
2307 }
2308
2309 if( replaceCandidate )
2310 {
2311 FOOTPRINT* replaced = replaceFootprint( aNetlist, replaceCandidate, component );
2312
2313 if( replaced )
2314 baseFootprint = replaced;
2315 else
2316 baseFootprint = replaceCandidate;
2317 }
2318 }
2319
2320 if( !baseFootprint && !m_replaceFootprints )
2321 {
2322 for( FOOTPRINT* footprint : matchingFootprints )
2323 {
2324 if( usedFootprints.count( footprint ) )
2325 continue;
2326
2327 if( isExpectedFpid( footprint->GetFPID() ) )
2328 continue;
2329
2330 baseFootprint = footprint;
2331 break;
2332 }
2333 }
2334
2335 if( !baseFootprint && ( hasBaseFpid || expectedFpids.empty() ) )
2336 baseFootprint = addNewFootprint( component, baseFpid );
2337
2338 if( baseFootprint )
2339 {
2340 componentFootprints.push_back( baseFootprint );
2341 usedFootprints.insert( baseFootprint );
2342 footprintMap[ component ] = baseFootprint;
2343 }
2344
2345 for( const LIB_ID& fpid : expectedFpids )
2346 {
2347 // Both IDs are schematic-derived, so either side may be legacy; compare in both
2348 // directions so a bare base name and a qualified variant name for the same
2349 // footprint are not split into a duplicate.
2350 if( fpidsEquivalent( fpid, baseFpid ) || fpidsEquivalent( baseFpid, fpid ) )
2351 continue;
2352
2353 FOOTPRINT* footprint = takeMatchingFootprint( fpid );
2354
2355 if( !footprint )
2356 footprint = addNewFootprint( component, fpid );
2357
2358 if( footprint )
2359 {
2360 componentFootprints.push_back( footprint );
2361 usedFootprints.insert( footprint );
2362 }
2363 }
2364
2365 for( FOOTPRINT* footprint : componentFootprints )
2366 {
2367 if( !footprint )
2368 continue;
2369
2370 updateFootprintParameters( footprint, component );
2371 updateFootprintGroup( footprint, component );
2372 updateComponentPadConnections( footprint, component );
2373 updateComponentClass( footprint, component );
2374 updateComponentUnits( footprint, component );
2375
2376 sheetPaths.insert( footprint->GetSheetname() );
2377 }
2378
2379 if( !componentFootprints.empty() )
2380 applyComponentVariants( component, componentFootprints, baseFpid );
2381 }
2382
2383 updateCopperZoneNets( aNetlist );
2384 updateGroups( aNetlist );
2385
2386 // Finally go through the board footprints and update all those that *don't* have matching
2387 // component entries.
2388 //
2389 for( FOOTPRINT* footprint : m_board->Footprints() )
2390 {
2391 bool matched = false;
2392 bool doDelete = m_deleteUnusedFootprints;
2393
2394 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
2395 doDelete = false;
2396
2397 bool isStaleVariantFootprint = false;
2398
2399 if( usedFootprints.count( footprint ) )
2400 {
2401 matched = true;
2402 }
2403 else
2404 {
2406 component = aNetlist.GetComponentByPath( footprint->GetPath() );
2407 else
2408 component = aNetlist.GetComponentByReference( footprint->GetReference() );
2409
2410 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
2411 {
2412 // When replace footprints is enabled and a component has variant footprints,
2413 // footprints matching by reference but not in usedFootprints are stale variant
2414 // footprints that should be replaced/removed.
2415 if( m_replaceFootprints && !component->GetVariants().empty() )
2416 {
2417 matched = false;
2418 isStaleVariantFootprint = true;
2419 }
2420 else
2421 {
2422 matched = true;
2423 }
2424 }
2425 }
2426
2427 // Stale variant footprints should be deleted when m_replaceFootprints is enabled,
2428 // regardless of m_deleteUnusedFootprints setting.
2429 if( isStaleVariantFootprint )
2430 doDelete = true;
2431
2432 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
2433 {
2434 if( m_isDryRun )
2435 {
2436 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
2437 footprint->GetReference() );
2438 }
2439 else
2440 {
2441 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
2442 footprint->GetReference() );
2443 }
2444
2445 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2447 doDelete = false;
2448 }
2449
2450 if( doDelete && !matched )
2451 {
2452 if( m_isDryRun )
2453 {
2454 msg.Printf( _( "Remove unused footprint %s." ),
2455 footprint->GetReference() );
2456 }
2457 else
2458 {
2459 m_commit.Remove( footprint );
2460 msg.Printf( _( "Removed unused footprint %s." ),
2461 footprint->GetReference() );
2462 }
2463
2464 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2465 }
2466 else if( !m_isDryRun )
2467 {
2468 if( !matched )
2469 footprint->SetPath( KIID_PATH() );
2470
2471 for( PAD* pad : footprint->Pads() )
2472 {
2473 if( pad->GetNet() )
2474 pad->GetNet()->SetIsCurrent( true );
2475 }
2476 }
2477 }
2478
2479 if( !m_isDryRun )
2480 {
2481 // Finalise the component class manager
2482 m_board->GetComponentClassManager().FinishNetlistUpdate();
2483 m_board->SynchronizeComponentClasses( sheetPaths );
2484
2485 m_board->BuildConnectivity();
2486 testConnectivity( aNetlist, footprintMap );
2487
2488 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2489 {
2490 if( !net->IsCurrent() )
2491 {
2492 msg.Printf( _( "Removed unused net %s." ),
2493 EscapeHTML( net->GetNetname() ) );
2494 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2495 }
2496 }
2497
2498 m_board->RemoveUnusedNets( &m_commit );
2499
2500 // Update board variant registry from netlist
2501 const std::vector<wxString>& netlistVariants = aNetlist.GetVariantNames();
2502
2503 if( !netlistVariants.empty() || !m_board->GetVariantNames().empty() )
2504 {
2505 m_reporter->Report( _( "Updating design variants..." ), RPT_SEVERITY_INFO );
2506
2507 auto findBoardVariantName =
2508 [&]( const wxString& aVariantName ) -> wxString
2509 {
2510 for( const wxString& name : m_board->GetVariantNames() )
2511 {
2512 if( name.CmpNoCase( aVariantName ) == 0 )
2513 return name;
2514 }
2515
2516 return wxEmptyString;
2517 };
2518
2519 std::vector<wxString> updatedVariantNames;
2520 updatedVariantNames.reserve( netlistVariants.size() );
2521
2522 for( const wxString& variantName : netlistVariants )
2523 {
2524 wxString actualName = findBoardVariantName( variantName );
2525
2526 if( actualName.IsEmpty() )
2527 {
2528 m_board->AddVariant( variantName );
2529 actualName = findBoardVariantName( variantName );
2530
2531 if( !actualName.IsEmpty() )
2532 {
2533 msg.Printf( _( "Added variant '%s'." ), actualName );
2534 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2535 }
2536 }
2537
2538 if( actualName.IsEmpty() )
2539 continue;
2540
2541 // Update description if changed
2542 wxString newDescription = aNetlist.GetVariantDescription( variantName );
2543 wxString oldDescription = m_board->GetVariantDescription( actualName );
2544
2545 if( newDescription != oldDescription )
2546 {
2547 m_board->SetVariantDescription( actualName, newDescription );
2548 msg.Printf( _( "Updated description for variant '%s'." ), actualName );
2549 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2550 }
2551
2552 updatedVariantNames.push_back( actualName );
2553 }
2554
2555 std::vector<wxString> variantsToRemove;
2556
2557 for( const wxString& existingName : m_board->GetVariantNames() )
2558 {
2559 bool found = false;
2560
2561 for( const wxString& variantName : netlistVariants )
2562 {
2563 if( existingName.CmpNoCase( variantName ) == 0 )
2564 {
2565 found = true;
2566 break;
2567 }
2568 }
2569
2570 if( !found )
2571 variantsToRemove.push_back( existingName );
2572 }
2573
2574 for( const wxString& variantName : variantsToRemove )
2575 {
2576 m_board->DeleteVariant( variantName );
2577 msg.Printf( _( "Removed variant '%s'." ), variantName );
2578 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2579 }
2580
2581 if( !updatedVariantNames.empty() )
2582 m_board->SetVariantNames( updatedVariantNames );
2583 }
2584
2585 // When new footprints are added, the automatic zone refill is disabled because:
2586 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
2587 // (the auto refills rebuild the connectivity with incomplete data)
2588 // * it is useless because zones will be refilled after placing new footprints
2589 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
2590
2591 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
2592
2593 // Netlist is authoritative for chain assignment, so the terminal-pin reapplication
2594 // below starts from a clean slate.
2596
2597 // Net chains may specify a display colour override; lift that into the
2598 // board-side lookup so the PCB painter can use it when highlighting.
2599 for( const auto& [chain, colorStr] : aNetlist.GetNetChainColors() )
2600 {
2601 if( !colorStr.IsEmpty() )
2602 {
2603 KIGFX::COLOR4D color;
2604
2605 if( color.SetFromHexString( colorStr ) )
2606 m_board->SetNetChainColor( chain, color );
2607 }
2608 }
2609
2610 ApplyChainNetclasses( m_board, aNetlist );
2611
2612 // Always resync after chain cleanup so existing NETINFO_ITEM effective-netclass
2613 // pointers pick up cleared/changed chain entries even when no chain carries a netclass.
2614 m_board->SynchronizeNetsAndNetClasses( true );
2615
2616 for( const auto& sig : aNetlist.GetNetChainTerminalPins() )
2617 {
2618 PAD* pads[2] = { nullptr, nullptr };
2619
2620 for( size_t i = 0; i < sig.second.size() && i < 2; ++i )
2621 {
2622 const wxString& ref = sig.second[i].first;
2623 const wxString& pin = sig.second[i].second;
2624 FOOTPRINT* fp = m_board->FindFootprintByReference( ref );
2625
2626 if( !fp )
2627 continue;
2628
2629 PAD* candidate = nullptr;
2630 PAD* best = nullptr;
2631 int bestDist = std::numeric_limits<int>::max();
2632 BOX2I bbox = fp->GetBoundingBox();
2633
2634 while( ( candidate = fp->FindPadByNumber( pin, candidate ) ) )
2635 {
2636 VECTOR2I pos = candidate->GetPosition();
2637 int dist = std::min( { pos.x - bbox.GetLeft(), bbox.GetRight() - pos.x,
2638 pos.y - bbox.GetTop(), bbox.GetBottom() - pos.y } );
2639
2640 if( !best || dist < bestDist || ( dist == bestDist && candidate->m_Uuid < best->m_Uuid ) )
2641 {
2642 best = candidate;
2643 bestDist = dist;
2644 }
2645 }
2646
2647 pads[i] = best;
2648 }
2649
2650 for( int i = 0; i < 2; ++i )
2651 {
2652 if( !pads[i] )
2653 continue;
2654
2655 NETINFO_ITEM* termNet = pads[i]->GetNet();
2656
2657 if( !termNet || termNet->GetNetChain() != sig.first )
2658 continue;
2659
2660 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2661 {
2662 if( net != termNet && net->GetNetChain() == sig.first
2663 && net->GetTerminalPad( i ) )
2664 {
2665 net->ClearTerminalPad( i );
2666 }
2667 }
2668
2669 termNet->SetTerminal( i, pads[i] );
2670 }
2671 }
2672
2673 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
2674 // sure any modification to netclasses gets persisted to project settings through a save.
2675 if( m_frame )
2676 m_frame->OnModify();
2677 }
2678
2679 if( m_isDryRun )
2680 {
2681 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
2682 delete addedNet.second;
2683
2684 m_addedNets.clear();
2685 }
2686
2687 // Update the ratsnest
2688 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2689 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2690
2691 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
2692 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
2693
2694 return true;
2695}
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
#define ZONE_FILL_OP
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetUuid(const KIID &aUuid)
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:343
std::map< PAD *, wxString > m_padNets
void cacheNetname(PAD *aPad, const wxString &aNetname)
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
wxString getPinFunction(PAD *aPad)
std::vector< FOOTPRINT * > m_addedFootprints
bool updateFootprintParameters(FOOTPRINT *aFootprint, COMPONENT *aNetlistComponent)
std::map< wxString, wxString > m_oldToNewNets
static void ApplyChainAssignments(BOARD *aBoard, const NETLIST &aNetlist, REPORTER *aReporter, bool aDryRun)
Apply the netlist's chain assignments to every NETINFO_ITEM on the board.
static void ApplyChainNetclasses(BOARD *aBoard, const NETLIST &aNetlist)
Mirror the netlist's chain-to-class and chain-to-netclass maps into the project's NET_SETTINGS.
bool updateFootprintGroup(FOOTPRINT *aPcbFootprint, COMPONENT *aNetlistComponent)
static bool fpidsEquivalent(const LIB_ID &aBoardFpid, const LIB_ID &aSchematicFpid)
Compare a board footprint ID against a schematic-derived footprint ID, ignoring the library nickname ...
std::map< wxString, NETINFO_ITEM * > m_addedNets
std::vector< PCB_GROUP * > m_addedGroups
bool testConnectivity(NETLIST &aNetlist, std::map< COMPONENT *, FOOTPRINT * > &aFootprintMap)
bool updateCopperZoneNets(NETLIST &aNetlist)
void cachePinFunction(PAD *aPad, const wxString &aPinFunction)
bool updateGroups(NETLIST &aNetlist)
BOARD_NETLIST_UPDATER(PCB_EDIT_FRAME *aFrame, BOARD *aBoard)
Construct an updater for interactive use from the board editor.
bool updateComponentClass(FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
bool updateComponentPadConnections(FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
std::map< PAD *, wxString > m_padPinFunctions
void applyComponentVariants(COMPONENT *aComponent, const std::vector< FOOTPRINT * > &aFootprints, const LIB_ID &aBaseFpid)
std::map< ZONE *, std::vector< PAD * > > m_zoneConnectionsCache
FOOTPRINT * replaceFootprint(NETLIST &aNetlist, FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
bool updateComponentUnits(FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
FOOTPRINT * addNewFootprint(COMPONENT *aComponent)
std::set< wxString > m_schematicNetNames
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:373
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1098
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1158
constexpr size_type GetWidth() const
Definition box2.h:210
constexpr Vec Centre() const
Definition box2.h:93
constexpr size_type GetHeight() const
Definition box2.h:211
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
static wxString GetFullClassNameForConstituents(const std::unordered_set< wxString > &classNames)
Gets the full effective class name for the given set of constituent classes.
A lightweight representation of a component class.
const wxString & GetName() const
Fetches the full name of this component class.
Used to store the component pin name to net name (and pin function) associations stored in a netlist.
const wxString & GetNetName() const
const wxString & GetPinFunction() const
const wxString & GetPinName() const
const wxString & GetPinType() const
Store all of the related component information found in a netlist.
const std::vector< UNIT_INFO > & GetUnitInfo() const
const wxString & GetHumanReadablePath() const
const COMPONENT_NET & GetNet(unsigned aIndex) const
const KIID_PATH & GetPath() const
const wxString & GetReference() const
const wxString & GetValue() const
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
const std::map< wxString, wxString > & GetProperties() const
const CASE_INSENSITIVE_MAP< COMPONENT_VARIANT > & GetVariants() const
NETLIST_GROUP * GetGroup() const
const std::vector< KIID > & GetKIIDs() const
bool GetDuplicatePadNumbersAreJumpers() const
const LIB_ID & GetFPID() const
unsigned GetNetCount() const
std::unordered_set< wxString > & GetComponentClassNames()
std::vector< std::set< wxString > > & JumperPadGroups()
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:50
wxString GetName() const
Definition eda_group.h:47
void RemoveAll()
Definition eda_group.cpp:86
void RemoveItem(EDA_ITEM *aItem)
Remove item from group.
Definition eda_group.cpp:77
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
void SetName(const wxString &aName)
Definition eda_group.h:48
const KIID m_Uuid
Definition eda_item.h:531
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:114
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:89
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:265
Variant information for a footprint.
Definition footprint.h:215
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:262
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:242
bool GetExcludedFromBOM() const
Definition footprint.h:231
bool GetExcludedFromPosFiles() const
Definition footprint.h:234
bool GetDNP() const
Definition footprint.h:228
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:1165
void SetPosition(const VECTOR2I &aPos) override
EDA_ANGLE GetOrientation() const
Definition footprint.h:409
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
wxString GetSheetname() const
Definition footprint.h:470
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:468
void SetFilters(const wxString &aFilters)
Definition footprint.h:477
void SetStaticComponentClass(const COMPONENT_CLASS *aClass) const
Sets the component class object pointer for this footprint.
const std::vector< FP_UNIT_INFO > & GetUnitInfo() const
Definition footprint.h:956
void SetAttributes(int aAttributes)
Definition footprint.h:511
void SetSheetfile(const wxString &aSheetfile)
Definition footprint.h:474
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::vector< std::set< wxString > > & JumperPadGroups()
Each jumper pad group is a set of pad numbers that should be treated as internally connected.
Definition footprint.h:1172
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1166
bool HasField(const wxString &aFieldName) const
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
std::deque< PAD * > & Pads()
Definition footprint.h:375
int GetAttributes() const
Definition footprint.h:510
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:420
wxString GetFPIDAsString() const
Definition footprint.h:450
wxString GetSheetfile() const
Definition footprint.h:473
const LIB_ID & GetFPID() const
Definition footprint.h:444
void SetReference(const wxString &aReference)
Definition footprint.h:863
bool IsLocked() const override
Definition footprint.h:637
void SetValue(const wxString &aValue)
Definition footprint.h:884
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetUnitInfo(const std::vector< FP_UNIT_INFO > &aUnits)
Definition footprint.h:955
wxString GetFilters() const
Definition footprint.h:476
void SetSheetname(const wxString &aSheetname)
Definition footprint.h:471
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:879
const COMPONENT_CLASS * GetStaticComponentClass() const
Returns the component class for this footprint.
void FixUpPadsForBoard(BOARD *aBoard)
Used post-loading of a footprint to adjust the layers on pads to match board inner layers.
const wxString & GetReference() const
Definition footprint.h:857
const KIID_PATH & GetPath() const
Definition footprint.h:467
VECTOR2I GetPosition() const override
Definition footprint.h:406
PAD * FindPadByNumber(const wxString &aPadNumber, PAD *aSearchAfterMe=nullptr) const
Return a PAD with a matching number.
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
bool SetFromHexString(const wxString &aColorString)
Definition color4d.cpp:176
wxString AsString() const
Definition kiid.cpp:423
Definition kiid.h:46
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool empty() const
Definition lib_id.h:189
wxString GetUniStringLibId() const
Definition lib_id.h:144
UTF8 Format() const
Definition lib_id.cpp:132
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
bool IsLegacy() const
Definition lib_id.h:176
Handle the data for a net.
Definition netinfo.h:46
const wxString & GetNetChain() const
Definition netinfo.h:112
void SetTerminal(int aIndex, PAD *aPad)
Set the terminal-pad pointer and the persisted UUID at aIndex from a single pad, keeping the two view...
void SetIsCurrent(bool isCurrent)
Definition netinfo.h:150
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:256
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
const std::map< wxString, std::vector< std::pair< wxString, wxString > > > & GetNetChainTerminalPins() const
const std::vector< wxString > & GetVariantNames() const
unsigned GetCount() const
COMPONENT * GetComponentByPath(const KIID_PATH &aPath)
Return a COMPONENT by aPath.
COMPONENT * GetComponentByReference(const wxString &aReference)
Return a COMPONENT by aReference.
const std::map< wxString, wxString > & GetNetChainNetClasses() const
const std::map< wxString, wxString > & GetSignalChainClasses() const
NETLIST_GROUP * GetGroupByUuid(const KIID &aUuid)
Return a NETLIST_GROUP by aUuid.
const std::map< wxString, wxString > & GetNetChainColors() const
COMPONENT * GetComponent(unsigned aIndex)
Return the COMPONENT at aIndex.
wxString GetNetChainFor(const wxString &aNet) const
wxString GetVariantDescription(const wxString &aVariantName) const
A singleton reporter that reports to nowhere.
Definition reporter.h:250
Definition pad.h:61
const wxString & GetPinFunction() const
Definition pad.h:154
VECTOR2I GetPosition() const override
Definition pad.cpp:245
The main frame for Pcbnew.
void SetName(const wxString &aName)
Definition pcb_field.h:108
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
EDA_ITEM * AsEdaItem() override
Definition pcb_group.h:58
void StyleFromSettings(const BOARD_DESIGN_SETTINGS &settings, bool aCheckSide) override
Definition pcb_text.cpp:355
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:95
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:565
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:101
Master controller class:
wxString wx_str() const
Definition utf8.cpp:41
Handle a list of polygons defining a copper zone.
Definition zone.h:70
The common library.
#define _(s)
@ NO_RECURSE
Definition eda_item.h:50
@ FP_DNP
Definition footprint.h:89
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:85
@ FP_BOARD_ONLY
Definition footprint.h:87
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:86
@ FP_JUST_ADDED
Definition footprint.h:88
@ F_Fab
Definition layer_ids.h:115
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
KICOMMON_API wxString MessageTextFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A helper to convert the double length aValue to a string in inches, millimeters, or unscaled units.
Class to handle a set of BOARD_ITEMs.
FOOTPRINT * LoadFootprintFromProject(BOARD *aBoard, const LIB_ID &aFootprintId, bool aKeepUuid)
Load a footprint from the project library table and apply board default settings.
CITER next(CITER it)
Definition ptree.cpp:120
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
T * GetAppSettings(const char *aFilename)
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
wxString UnescapeString(const wxString &aSource)
bool m_hasExcludedFromPosFiles
nlohmann::ordered_map< wxString, wxString > m_fields
std::vector< KIID > members
@ USER
The field ID hasn't been set yet; field is invalid.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
wxString GetCanonicalFieldName(FIELD_T aFieldType)
KIBIS_COMPONENT * comp
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
const SHAPE_LINE_CHAIN chain
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683