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 {
464 && aFootprint->GetFPIDAsString()
466 {
467 firstAssociatedVariant = &test;
468 break;
469 }
470 }
471 }
472
473 // Create a copy only if the footprint has not been added during this update
474 FOOTPRINT* copy = nullptr;
475
476 if( !m_commit.GetStatus( aFootprint ) )
477 {
478 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
479 copy->SetParentGroup( nullptr );
480 }
481
482 bool changed = false;
483
484 // Test for reference designator field change.
485 if( aFootprint->GetReference() != aNetlistComponent->GetReference() )
486 {
487 if( m_isDryRun )
488 {
489 msg.Printf( _( "Change %s reference designator to %s." ),
490 aFootprint->GetReference(),
491 aNetlistComponent->GetReference() );
492 }
493 else
494 {
495 msg.Printf( _( "Changed %s reference designator to %s." ),
496 aFootprint->GetReference(),
497 aNetlistComponent->GetReference() );
498
499 changed = true;
500 aFootprint->SetReference( aNetlistComponent->GetReference() );
501 }
502
503 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
504 }
505
506 // Test for value field change.
507 wxString netlistValue = aNetlistComponent->GetValue();
508
509 if( firstAssociatedVariant != nullptr
510 && firstAssociatedVariant->m_fields.count( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) ) )
511 {
512 netlistValue = firstAssociatedVariant->m_fields.at( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) );
513 }
514
515 if( aFootprint->GetValue() != netlistValue )
516 {
517 if( m_isDryRun )
518 {
519 msg.Printf( _( "Change %s value from %s to %s." ),
520 aFootprint->GetReference(),
521 EscapeHTML( aFootprint->GetValue() ),
522 EscapeHTML( netlistValue ) );
523 }
524 else
525 {
526 msg.Printf( _( "Changed %s value from %s to %s." ),
527 aFootprint->GetReference(),
528 EscapeHTML( aFootprint->GetValue() ),
529 EscapeHTML( netlistValue ) );
530
531 changed = true;
532 aFootprint->SetValue( netlistValue );
533 }
534
535 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
536 }
537
538 // Test for time stamp change.
539 KIID_PATH new_path = aNetlistComponent->GetPath();
540
541 if( !aNetlistComponent->GetKIIDs().empty() )
542 new_path.push_back( aNetlistComponent->GetKIIDs().front() );
543
544 if( aFootprint->GetPath() != new_path )
545 {
546 if( m_isDryRun )
547 {
548 msg.Printf( _( "Update %s symbol association from %s to %s." ),
549 aFootprint->GetReference(),
550 EscapeHTML( aFootprint->GetPath().AsString() ),
551 EscapeHTML( new_path.AsString() ) );
552 }
553 else
554 {
555 msg.Printf( _( "Updated %s symbol association from %s to %s." ),
556 aFootprint->GetReference(),
557 EscapeHTML( aFootprint->GetPath().AsString() ),
558 EscapeHTML( new_path.AsString() ) );
559
560 changed = true;
561 aFootprint->SetPath( new_path );
562 }
563
564 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
565 }
566
567 nlohmann::ordered_map<wxString, wxString> fpFieldsAsMap;
568
569 for( PCB_FIELD* field : aFootprint->GetFields() )
570 {
571 // These fields are individually checked above
572 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
573 {
574 continue;
575 }
576
577 fpFieldsAsMap[field->GetName()] = field->GetText();
578 }
579
580 // Remove the ref/value/footprint fields that are individually handled
581 nlohmann::ordered_map<wxString, wxString> compFields = aNetlistComponent->GetFields();
583 compFields.erase( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) );
585
586 // Remove any component class fields - these are not editable in the pcb editor
587 compFields.erase( wxT( "Component Class" ) );
588
589 if( firstAssociatedVariant != nullptr )
590 {
591 for( const auto& [name, value] : firstAssociatedVariant->m_fields )
592 compFields[name] = value;
593 }
594
595 // Fields are stored as an ordered map, but we don't (yet) support reordering the footprint fields to
596 // match the symbol, so we manually check the fields in the order they are stored in the symbol.
597 bool same = true;
598 bool remove_only = true;
599
600 for( const auto& [name, value] : compFields )
601 {
602 if( fpFieldsAsMap.count( name ) == 0 || fpFieldsAsMap[name] != value )
603 {
604 same = false;
605 remove_only = false;
606 break;
607 }
608 }
609
610 for( const auto& [name, value] : fpFieldsAsMap )
611 {
612 if( compFields.count( name ) == 0 )
613 {
614 same = false;
615 break;
616 }
617 }
618
619 if( !same )
620 {
621 if( m_isDryRun )
622 {
623 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
624 {
625 msg.Printf( _( "Update %s fields." ), aFootprint->GetReference() );
626 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
627 }
628
629 // Remove fields that aren't present in the symbol
630 for( PCB_FIELD* field : aFootprint->GetFields() )
631 {
632 if( field->IsMandatory() )
633 continue;
634
635 if( compFields.count( field->GetName() ) == 0 )
636 {
638 {
639 msg.Printf( _( "Remove %s footprint fields not in symbol." ), aFootprint->GetReference() );
640 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
641 }
642
643 break;
644 }
645 }
646 }
647 else
648 {
649 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
650 {
651 msg.Printf( _( "Updated %s fields." ), aFootprint->GetReference() );
652 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
653
654 changed = true;
655
656 // Add or change field value
657 for( auto& [name, value] : compFields )
658 {
659 if( aFootprint->HasField( name ) )
660 {
661 aFootprint->GetField( name )->SetText( value );
662 }
663 else
664 {
665 PCB_FIELD* newField = new PCB_FIELD( aFootprint, FIELD_T::USER );
666 aFootprint->Add( newField );
667
668 newField->SetName( name );
669 newField->SetText( value );
670 newField->SetVisible( false );
671 newField->SetLayer( aFootprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
672
673 // Give the relative position (0,0) in footprint
674 newField->SetPosition( aFootprint->GetPosition() );
675 // Give the footprint orientation
676 newField->Rotate( aFootprint->GetPosition(), aFootprint->GetOrientation() );
677
678 newField->StyleFromSettings( m_board->GetDesignSettings(), true );
679 }
680 }
681 }
682
684 {
685 bool warned = false;
686
687 std::vector<PCB_FIELD*> fieldList;
688 aFootprint->GetFields( fieldList, false );
689
690 for( PCB_FIELD* field : fieldList )
691 {
692 if( field->IsMandatory() )
693 continue;
694
695 if( compFields.count( field->GetName() ) == 0 )
696 {
697 if( !warned )
698 {
699 warned = true;
700 msg.Printf( _( "Removed %s footprint fields not in symbol." ), aFootprint->GetReference() );
701 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
702 }
703
704 aFootprint->Remove( field );
705
706 if( m_frame )
707 m_frame->GetCanvas()->GetView()->Remove( field );
708
709 delete field;
710 }
711 }
712 }
713 }
714 }
715
716 wxString sheetname;
717 wxString sheetfile;
718 wxString fpFilters;
719
720 wxString humanSheetPath = aNetlistComponent->GetHumanReadablePath();
721
722 if( !humanSheetPath.empty() )
723 sheetname = humanSheetPath;
724 else if( aNetlistComponent->GetProperties().count( wxT( "Sheetname" ) ) > 0 )
725 sheetname = aNetlistComponent->GetProperties().at( wxT( "Sheetname" ) );
726
727 if( aNetlistComponent->GetProperties().count( wxT( "Sheetfile" ) ) > 0 )
728 sheetfile = aNetlistComponent->GetProperties().at( wxT( "Sheetfile" ) );
729
730 if( aNetlistComponent->GetProperties().count( wxT( "ki_fp_filters" ) ) > 0 )
731 fpFilters = aNetlistComponent->GetProperties().at( wxT( "ki_fp_filters" ) );
732
733 if( sheetname != aFootprint->GetSheetname() )
734 {
735 if( m_isDryRun )
736 {
737 msg.Printf( _( "Update %s sheetname to '%s'." ),
738 aFootprint->GetReference(),
739 EscapeHTML( sheetname ) );
740 }
741 else
742 {
743 aFootprint->SetSheetname( sheetname );
744 msg.Printf( _( "Updated %s sheetname to '%s'." ),
745 aFootprint->GetReference(),
746 EscapeHTML( sheetname ) );
747 }
748
749 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
750 }
751
752 if( sheetfile != aFootprint->GetSheetfile() )
753 {
754 if( m_isDryRun )
755 {
756 msg.Printf( _( "Update %s sheetfile to '%s'." ),
757 aFootprint->GetReference(),
758 EscapeHTML( sheetfile ) );
759 }
760 else
761 {
762 aFootprint->SetSheetfile( sheetfile );
763 msg.Printf( _( "Updated %s sheetfile to '%s'." ),
764 aFootprint->GetReference(),
765 EscapeHTML( sheetfile ) );
766 }
767
768 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
769 }
770
771 if( fpFilters != aFootprint->GetFilters() )
772 {
773 if( m_isDryRun )
774 {
775 msg.Printf( _( "Update %s footprint filters to '%s'." ),
776 aFootprint->GetReference(),
777 EscapeHTML( fpFilters ) );
778 }
779 else
780 {
781 aFootprint->SetFilters( fpFilters );
782 msg.Printf( _( "Updated %s footprint filters to '%s'." ),
783 aFootprint->GetReference(),
784 EscapeHTML( fpFilters ) );
785 }
786
787 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
788 }
789
790 bool netlistExcludeFromBOM = aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) > 0;
791
792 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromBOM )
793 netlistExcludeFromBOM = firstAssociatedVariant->m_excludedFromBOM;
794
795 if( m_updateFields && netlistExcludeFromBOM != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
796 {
797 if( m_isDryRun )
798 {
799 if( netlistExcludeFromBOM )
800 msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
801 else
802 msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
803 }
804 else
805 {
806 int attributes = aFootprint->GetAttributes();
807
808 if( netlistExcludeFromBOM )
809 {
810 attributes |= FP_EXCLUDE_FROM_BOM;
811 msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
812 }
813 else
814 {
815 attributes &= ~FP_EXCLUDE_FROM_BOM;
816 msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
817 }
818
819 changed = true;
820 aFootprint->SetAttributes( attributes );
821 }
822
823 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
824 }
825
826 bool netlistExcludeFromSim = aNetlistComponent->GetProperties().count( wxT( "exclude_from_sim" ) ) > 0;
827
828 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromSim )
829 netlistExcludeFromSim = firstAssociatedVariant->m_excludedFromSim;
830
832 && netlistExcludeFromSim != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_SIM ) > 0 ) )
833 {
834 if( m_isDryRun )
835 {
836 if( netlistExcludeFromSim )
837 msg.Printf( _( "Add %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
838 else
839 msg.Printf( _( "Remove %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
840 }
841 else
842 {
843 int attributes = aFootprint->GetAttributes();
844
845 if( netlistExcludeFromSim )
846 {
847 attributes |= FP_EXCLUDE_FROM_SIM;
848 msg.Printf( _( "Added %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
849 }
850 else
851 {
852 attributes &= ~FP_EXCLUDE_FROM_SIM;
853 msg.Printf( _( "Removed %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
854 }
855
856 changed = true;
857 aFootprint->SetAttributes( attributes );
858 }
859
860 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
861 }
862
863 bool netlistDNP = aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) > 0;
864
865 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasDnp )
866 netlistDNP = firstAssociatedVariant->m_dnp;
867
868 if( m_updateFields && netlistDNP != ( ( aFootprint->GetAttributes() & FP_DNP ) > 0 ) )
869 {
870 if( m_isDryRun )
871 {
872 if( netlistDNP )
873 msg.Printf( _( "Add %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
874 else
875 msg.Printf( _( "Remove %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
876 }
877 else
878 {
879 int attributes = aFootprint->GetAttributes();
880
881 if( netlistDNP )
882 {
883 attributes |= FP_DNP;
884 msg.Printf( _( "Added %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
885 }
886 else
887 {
888 attributes &= ~FP_DNP;
889 msg.Printf( _( "Removed %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
890 }
891
892 changed = true;
893 aFootprint->SetAttributes( attributes );
894 }
895
896 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
897 }
898
899 bool netlistExcludeFromPosFiles = aNetlistComponent->GetProperties().count( wxT( "exclude_from_pos_files" ) ) > 0;
900
901 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromPosFiles )
902 netlistExcludeFromPosFiles = firstAssociatedVariant->m_excludedFromPosFiles;
903
905 && netlistExcludeFromPosFiles != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES ) > 0 ) )
906 {
907 if( m_isDryRun )
908 {
909 if( netlistExcludeFromPosFiles )
910 {
911 msg.Printf( _( "Add %s 'exclude from position files' fabrication attribute." ),
912 aFootprint->GetReference() );
913 }
914 else
915 {
916 msg.Printf( _( "Remove %s 'exclude from position files' fabrication attribute." ),
917 aFootprint->GetReference() );
918 }
919 }
920 else
921 {
922 int attributes = aFootprint->GetAttributes();
923
924 if( netlistExcludeFromPosFiles )
925 {
926 attributes |= FP_EXCLUDE_FROM_POS_FILES;
927 msg.Printf( _( "Added %s 'exclude from position files' fabrication attribute." ),
928 aFootprint->GetReference() );
929 }
930 else
931 {
932 attributes &= ~FP_EXCLUDE_FROM_POS_FILES;
933 msg.Printf( _( "Removed %s 'exclude from position files' fabrication attribute." ),
934 aFootprint->GetReference() );
935 }
936
937 changed = true;
938 aFootprint->SetAttributes( attributes );
939 }
940
941 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
942 }
943
945 && aNetlistComponent->GetDuplicatePadNumbersAreJumpers() != aFootprint->GetDuplicatePadNumbersAreJumpers() )
946 {
947 bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
948
949 if( !m_isDryRun )
950 {
951 changed = true;
952 aFootprint->SetDuplicatePadNumbersAreJumpers( value );
953
954 if( value )
955 {
956 msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
957 aFootprint->GetReference() );
958 }
959 else
960 {
961 msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
962 aFootprint->GetReference() );
963 }
964 }
965 else
966 {
967 if( value )
968 {
969 msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
970 aFootprint->GetReference() );
971 }
972 else
973 {
974 msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
975 aFootprint->GetReference() );
976 }
977 }
978
979 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
980 }
981
982 if( m_updateFields && aNetlistComponent->JumperPadGroups() != aFootprint->JumperPadGroups() )
983 {
984 if( !m_isDryRun )
985 {
986 changed = true;
987 aFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
988 msg.Printf( _( "Updated %s jumper pad groups" ), aFootprint->GetReference() );
989 }
990 else
991 {
992 msg.Printf( _( "Update %s jumper pad groups" ), aFootprint->GetReference() );
993 }
994
995 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
996 }
997
998 if( changed && copy )
999 m_commit.Modified( aFootprint, copy );
1000 else
1001 delete copy;
1002
1003 return true;
1004}
1005
1006
1008 COMPONENT* aNetlistComponent )
1009{
1010 if( !m_transferGroups )
1011 return false;
1012
1013 wxString msg;
1014
1015 // Create a copy only if the footprint has not been added during this update
1016 FOOTPRINT* copy = nullptr;
1017
1018 if( !m_commit.GetStatus( aPcbFootprint ) )
1019 {
1020 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
1021 copy->SetParentGroup( nullptr );
1022 }
1023
1024 bool changed = false;
1025
1026 // These hold the info for group and group KIID coming from the netlist
1027 // newGroup may point to an existing group on the board if we find an
1028 // incoming group UUID that matches an existing group
1029 PCB_GROUP* newGroup = nullptr;
1030 KIID newGroupKIID = aNetlistComponent->GetGroup() ? aNetlistComponent->GetGroup()->uuid : 0;
1031
1032 PCB_GROUP* existingGroup = static_cast<PCB_GROUP*>( aPcbFootprint->GetParentGroup() );
1033 KIID existingGroupKIID = existingGroup ? existingGroup->m_Uuid : 0;
1034
1035 // Find existing group based on matching UUIDs
1036 auto it = std::find_if( m_board->Groups().begin(), m_board->Groups().end(),
1037 [&](PCB_GROUP* group) {
1038 return group->m_Uuid == newGroupKIID;
1039 });
1040
1041 // If we find a group with the same UUID, use it
1042 if( it != m_board->Groups().end() )
1043 newGroup = *it;
1044
1045 // No changes, nothing to do
1046 if( newGroupKIID == existingGroupKIID )
1047 return changed;
1048
1049 // Remove from existing group
1050 if( existingGroupKIID != 0 )
1051 {
1052 if( m_isDryRun )
1053 {
1054 msg.Printf( _( "Remove %s from group '%s'." ),
1055 aPcbFootprint->GetReference(),
1056 EscapeHTML( existingGroup->GetName() ) );
1057 }
1058 else
1059 {
1060 msg.Printf( _( "Removed %s from group '%s'." ),
1061 aPcbFootprint->GetReference(),
1062 EscapeHTML( existingGroup->GetName() ) );
1063
1064 changed = true;
1065 m_commit.Modify( existingGroup, nullptr, RECURSE_MODE::NO_RECURSE );
1066 existingGroup->RemoveItem( aPcbFootprint );
1067
1068 if( existingGroup->GetItems().size() < 2 )
1069 {
1070 existingGroup->RemoveAll();
1071 m_commit.Remove( existingGroup );
1072 }
1073 }
1074
1075 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1076 }
1077
1078 // Add to new group
1079 if( newGroupKIID != 0 )
1080 {
1081 if( m_isDryRun )
1082 {
1083 msg.Printf( _( "Add %s to group '%s'." ),
1084 aPcbFootprint->GetReference(),
1085 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
1086 }
1087 else
1088 {
1089 msg.Printf( _( "Added %s to group '%s'." ),
1090 aPcbFootprint->GetReference(),
1091 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
1092
1093 changed = true;
1094
1095 if( newGroup == nullptr )
1096 {
1097 newGroup = new PCB_GROUP( m_board );
1098 newGroup->SetUuid( newGroupKIID );
1099 newGroup->SetName( aNetlistComponent->GetGroup()->name );
1100
1101 // Add the group to the board manually so we can find it by checking
1102 // board groups for later footprints that are checking for existing groups
1103 m_board->Add( newGroup );
1104 m_commit.Added( newGroup );
1105 m_addedGroups.push_back( newGroup );
1106 }
1107 else
1108 {
1109 m_commit.Modify( newGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1110 }
1111
1112 newGroup->AddItem( aPcbFootprint );
1113 }
1114
1115 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1116 }
1117
1118 if( changed && copy )
1119 m_commit.Modified( aPcbFootprint, copy );
1120 else if( copy )
1121 delete copy;
1122
1123 return changed;
1124}
1125
1126
1128 COMPONENT* aNewComponent )
1129{
1130 wxString msg;
1131
1132 // Create a copy only if the footprint has not been added during this update
1133 FOOTPRINT* copy = nullptr;
1134
1135 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
1136 {
1137 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1138 copy->SetParentGroup( nullptr );
1139 }
1140
1141 bool changed = false;
1142
1143 // At this point, the component footprint is updated. Now update the nets.
1144 std::deque<PAD*> pads = aFootprint->Pads();
1145 std::set<wxString> padNetnames;
1146
1147 std::sort( pads.begin(), pads.end(),
1148 []( PAD* a, PAD* b )
1149 {
1150 return a->m_Uuid < b->m_Uuid;
1151 } );
1152
1153 for( PAD* pad : pads )
1154 {
1155 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
1156
1157 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1158 wxT( "Processing pad %s of component %s" ),
1159 pad->GetNumber(),
1160 aNewComponent->GetReference() );
1161
1162 wxString pinFunction;
1163 wxString pinType;
1164
1165 if( net.IsValid() ) // i.e. the pad has a name
1166 {
1167 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1168 wxT( " Found valid net: %s" ),
1169 net.GetNetName() );
1170 pinFunction = net.GetPinFunction();
1171 pinType = net.GetPinType();
1172 }
1173 else
1174 {
1175 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1176 wxT( " No net found for pad %s" ),
1177 pad->GetNumber() );
1178 }
1179
1180 if( !m_isDryRun )
1181 {
1182 if( pad->GetPinFunction() != pinFunction )
1183 {
1184 changed = true;
1185 pad->SetPinFunction( pinFunction );
1186 }
1187
1188 if( pad->GetPinType() != pinType )
1189 {
1190 changed = true;
1191 pad->SetPinType( pinType );
1192 }
1193 }
1194 else
1195 {
1196 cachePinFunction( pad, pinFunction );
1197 }
1198
1199 // Test if new footprint pad has no net (pads not on copper layers have no net).
1200 if( !net.IsValid() || !pad->IsOnCopperLayer() )
1201 {
1202 if( !pad->GetNetname().IsEmpty() )
1203 {
1204 if( m_isDryRun )
1205 {
1206 msg.Printf( _( "Disconnect %s pin %s." ),
1207 aFootprint->GetReference(),
1208 EscapeHTML( pad->GetNumber() ) );
1209 }
1210 else
1211 {
1212 msg.Printf( _( "Disconnected %s pin %s." ),
1213 aFootprint->GetReference(),
1214 EscapeHTML( pad->GetNumber() ) );
1215 }
1216
1217 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1218 }
1219 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
1220 {
1221 // pad is connectable but has no net found in netlist
1222 msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
1223 aFootprint->GetReference(),
1224 EscapeHTML( pad->GetNumber() ),
1225 EscapeHTML( pad->GetNumber() ) );
1226 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
1228 }
1229
1230 if( !m_isDryRun )
1231 {
1232 changed = true;
1233 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
1234
1235 // If the pad has no net from netlist (i.e. not in netlist
1236 // it cannot have a pin function
1237 if( pad->GetNetname().IsEmpty() )
1238 pad->SetPinFunction( wxEmptyString );
1239
1240 }
1241 else
1242 {
1243 cacheNetname( pad, wxEmptyString );
1244 }
1245 }
1246 else // New footprint pad has a net.
1247 {
1248 wxString netName = net.GetNetName();
1249
1250 if( pad->IsNoConnectPad() )
1251 {
1252 netName = wxString::Format( wxS( "%s" ), net.GetNetName() );
1253
1254 for( int jj = 1; !padNetnames.insert( netName ).second
1255 || ( netName != net.GetNetName() && m_schematicNetNames.count( netName ) );
1256 jj++ )
1257 {
1258 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1259 }
1260 }
1261
1262 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1263
1264 if( netinfo && !m_isDryRun )
1265 netinfo->SetIsCurrent( true );
1266
1267 if( pad->GetNetname() != netName )
1268 {
1269
1270 if( netinfo == nullptr )
1271 {
1272 // It might be a new net that has not been added to the board yet
1273 if( m_addedNets.count( netName ) )
1274 netinfo = m_addedNets[ netName ];
1275 }
1276
1277 if( netinfo == nullptr )
1278 {
1279 netinfo = new NETINFO_ITEM( m_board, netName );
1280
1281 // It is a new net, we have to add it
1282 if( !m_isDryRun )
1283 {
1284 changed = true;
1285 m_commit.Add( netinfo );
1286 }
1287
1288 m_addedNets[netName] = netinfo;
1289 msg.Printf( _( "Add net %s." ),
1290 EscapeHTML( UnescapeString( netName ) ) );
1291 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1292 }
1293
1294 if( !pad->GetNetname().IsEmpty() )
1295 {
1296 m_oldToNewNets[ pad->GetNetname() ] = netName;
1297
1298 if( m_isDryRun )
1299 {
1300 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1301 aFootprint->GetReference(),
1302 EscapeHTML( pad->GetNumber() ),
1303 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1304 EscapeHTML( UnescapeString( netName ) ) );
1305 }
1306 else
1307 {
1308 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1309 aFootprint->GetReference(),
1310 EscapeHTML( pad->GetNumber() ),
1311 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1312 EscapeHTML( UnescapeString( netName ) ) );
1313 }
1314 }
1315 else
1316 {
1317 if( m_isDryRun )
1318 {
1319 msg.Printf( _( "Connect %s pin %s to %s."),
1320 aFootprint->GetReference(),
1321 EscapeHTML( pad->GetNumber() ),
1322 EscapeHTML( UnescapeString( netName ) ) );
1323 }
1324 else
1325 {
1326 msg.Printf( _( "Connected %s pin %s to %s."),
1327 aFootprint->GetReference(),
1328 EscapeHTML( pad->GetNumber() ),
1329 EscapeHTML( UnescapeString( netName ) ) );
1330 }
1331 }
1332
1333 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1334
1335 if( !m_isDryRun )
1336 {
1337 changed = true;
1338 pad->SetNet( netinfo );
1339 }
1340 else
1341 {
1342 cacheNetname( pad, netName );
1343 }
1344 }
1345 }
1346 }
1347
1348 if( changed && copy )
1349 m_commit.Modified( aFootprint, copy );
1350 else if( copy )
1351 delete copy;
1352
1353 return true;
1354}
1355
1356
1358{
1359 // Build the footprint-side representation from the netlist component
1360 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1361
1362 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1363 newUnits.push_back( { u.m_unitName, u.m_pins } );
1364
1365 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1366
1367 auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
1368 const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
1369 {
1370 if( a.size() != b.size() )
1371 return false;
1372
1373 for( size_t i = 0; i < a.size(); ++i )
1374 {
1375 if( a[i].m_unitName != b[i].m_unitName )
1376 return false;
1377
1378 if( a[i].m_pins != b[i].m_pins )
1379 return false;
1380 }
1381
1382 return true;
1383 };
1384
1385 if( unitsEqual( curUnits, newUnits ) )
1386 return false;
1387
1388 wxString msg;
1389
1390 if( m_isDryRun )
1391 {
1392 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1393 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1394 return false; // no actual change on board during dry run
1395 }
1396
1397 // Create a copy only if the footprint has not been added during this update
1398 FOOTPRINT* copy = nullptr;
1399
1400 if( !m_commit.GetStatus( aFootprint ) )
1401 {
1402 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1403 copy->SetParentGroup( nullptr );
1404 }
1405
1406 aFootprint->SetUnitInfo( newUnits );
1407
1408 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1409 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1410
1411 if( copy )
1412 m_commit.Modified( aFootprint, copy );
1413
1414 return true;
1415}
1416
1417
1418bool BOARD_NETLIST_UPDATER::fpidsEquivalent( const LIB_ID& aBoardFpid, const LIB_ID& aSchematicFpid )
1419{
1420 if( aSchematicFpid.IsLegacy() )
1421 return aBoardFpid.GetLibItemName() == aSchematicFpid.GetLibItemName();
1422
1423 return aBoardFpid == aSchematicFpid;
1424}
1425
1426
1428 const std::vector<FOOTPRINT*>& aFootprints,
1429 const LIB_ID& aBaseFpid )
1430{
1431 wxString msg;
1432 const auto& variants = aComponent->GetVariants();
1433
1434 if( aBaseFpid.empty() )
1435 return;
1436
1437 const wxString footprintFieldName = GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED );
1438
1439 struct VARIANT_INFO
1440 {
1441 wxString name;
1442 const COMPONENT_VARIANT* variant;
1443 LIB_ID variantFPID;
1444 };
1445
1446 std::vector<VARIANT_INFO> variantInfo;
1447 variantInfo.reserve( variants.size() );
1448
1449 for( const auto& [variantName, variant] : variants )
1450 {
1451 LIB_ID variantFPID = aBaseFpid;
1452
1453 auto fieldIt = variant.m_fields.find( footprintFieldName );
1454
1455 if( fieldIt != variant.m_fields.end() && !fieldIt->second.IsEmpty() )
1456 {
1457 LIB_ID parsedId;
1458
1459 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
1460 {
1461 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
1462 EscapeHTML( fieldIt->second ),
1463 variantName,
1464 aComponent->GetReference() );
1465 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1466 ++m_errorCount;
1467 }
1468 else
1469 {
1470 variantFPID = parsedId;
1471 }
1472 }
1473
1474 variantInfo.push_back( { variantName, &variant, variantFPID } );
1475 }
1476
1477 for( FOOTPRINT* footprint : aFootprints )
1478 {
1479 if( !footprint )
1480 continue;
1481
1482 FOOTPRINT* copy = nullptr;
1483
1484 if( !m_isDryRun && !m_commit.GetStatus( footprint ) )
1485 {
1486 copy = static_cast<FOOTPRINT*>( footprint->Clone() );
1487 copy->SetParentGroup( nullptr );
1488 }
1489
1490 bool changed = false;
1491
1492 auto printAttributeMessage =
1493 [&]( bool add, const wxString& attrName, const wxString& variantName )
1494 {
1495 if( m_isDryRun )
1496 {
1497 if( aFootprints.size() > 1 )
1498 {
1499 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s (footprint %s)." )
1500 : _( "Remove %s '%s' attribute from variant %s (footprint %s)." ),
1501 footprint->GetReference(),
1502 attrName,
1503 variantName,
1504 footprint->GetFPIDAsString() );
1505 }
1506 else
1507 {
1508 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s." )
1509 : _( "Remove %s '%s' attribute from variant %s." ),
1510 footprint->GetReference(),
1511 attrName,
1512 variantName );
1513 }
1514 }
1515 else
1516 {
1517 if( aFootprints.size() > 1 )
1518 {
1519 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s (footprint %s)." )
1520 : _( "Removed %s '%s' attribute from variant %s (footprint %s)." ),
1521 footprint->GetReference(),
1522 attrName,
1523 variantName,
1524 footprint->GetFPIDAsString() );
1525 }
1526 else
1527 {
1528 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s." )
1529 : _( "Removed %s '%s' attribute from variant %s." ),
1530 footprint->GetReference(),
1531 attrName,
1532 variantName );
1533 }
1534 }
1535 };
1536
1537 bool isBaseFootprint = fpidsEquivalent( footprint->GetFPID(), aBaseFpid );
1538
1539 // The footprint's own DNP flag before this pass forces the default-variant hiding below.
1540 // The per-variant target for a footprint that IS the active choice must fall back to this
1541 // original flag, not the forced one, so the active footprint stays populated.
1542 const bool baseFootprintDnp = footprint->IsDNP();
1543 bool effectiveFootprintDnp = baseFootprintDnp;
1544
1545 // A footprint that is not the component's base footprint is DNP by default (it stands in
1546 // only for the variants that select it). This runs before the per-variant loop so the loop
1547 // sees the correct effective DNP when deciding whether an explicit per-variant override is
1548 // needed; otherwise a footprint kept populated for its own variant would not converge until
1549 // a second netlist update.
1550 if( !isBaseFootprint && !effectiveFootprintDnp )
1551 {
1552 msg.Printf( m_isDryRun ? _( "Add %s 'Do not place' fabrication attribute." )
1553 : _( "Added %s 'Do not place' fabrication attribute." ),
1554 footprint->GetReference() );
1555
1556 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1557
1558 if( !m_isDryRun )
1559 footprint->SetDNP( true );
1560
1561 // Track the forced DNP locally so the per-variant loop below sees the correct effective
1562 // state even in dry run, where SetDNP() is intentionally not applied.
1563 effectiveFootprintDnp = true;
1564 changed = true;
1565 }
1566
1567 std::set<wxString> excessVariants;
1568
1569 for( const auto& [variantName, _] : footprint->GetVariants() )
1570 excessVariants.insert( variantName );
1571
1572 for( const VARIANT_INFO& info : variantInfo )
1573 {
1574 const COMPONENT_VARIANT& variant = *info.variant;
1575
1576 // During dry run, just read current state. During actual run, create variant if needed.
1577 const FOOTPRINT_VARIANT* currentVariant = footprint->GetVariant( info.name );
1578
1579 // Check if this footprint is the active one for this variant
1580 bool isAssociatedFootprint = fpidsEquivalent( footprint->GetFPID(), info.variantFPID );
1581
1582 // When multiple footprints share a RefDes (one per variant), a footprint that is not
1583 // the active choice for this variant must be DNP for it so the 3D viewer and other
1584 // consumers hide it. The base footprint carries no global DNP flag, so it needs an
1585 // explicit per-variant override; non-base footprints are already globally DNP above.
1586 if( !isAssociatedFootprint )
1587 {
1588 if( aFootprints.size() > 1 )
1589 {
1590 excessVariants.erase( info.name );
1591 bool currentDnp = currentVariant ? currentVariant->GetDNP() : effectiveFootprintDnp;
1592
1593 if( !currentDnp )
1594 {
1595 printAttributeMessage( true, _( "Do not place" ), info.name );
1596
1597 if( !m_isDryRun )
1598 {
1599 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1600 fpVariant->SetDNP( true );
1601 }
1602
1603 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1604 changed = true;
1605 }
1606 }
1607
1608 continue;
1609 }
1610
1611 excessVariants.erase( info.name );
1612 bool targetDnp = variant.m_hasDnp ? variant.m_dnp : baseFootprintDnp;
1613 bool currentDnp = currentVariant ? currentVariant->GetDNP() : effectiveFootprintDnp;
1614
1615 if( currentDnp != targetDnp )
1616 {
1617 printAttributeMessage( targetDnp, _( "Do not place" ), info.name );
1618
1619 if( !m_isDryRun )
1620 {
1621 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1622 fpVariant->SetDNP( targetDnp );
1623 }
1624
1625 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1626 changed = true;
1627 }
1628
1629 bool targetExcludedFromBOM = variant.m_hasExcludedFromBOM ? variant.m_excludedFromBOM
1630 : footprint->IsExcludedFromBOM();
1631 bool currentExcludedFromBOM = currentVariant ? currentVariant->GetExcludedFromBOM()
1632 : footprint->IsExcludedFromBOM();
1633
1634 if( currentExcludedFromBOM != targetExcludedFromBOM )
1635 {
1636 printAttributeMessage( targetExcludedFromBOM, _( "exclude from BOM" ), info.name );
1637
1638 if( !m_isDryRun )
1639 {
1640 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1641 fpVariant->SetExcludedFromBOM( targetExcludedFromBOM );
1642 }
1643
1644 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1645 changed = true;
1646 }
1647
1648 bool targetExcludedFromSim = variant.m_hasExcludedFromSim ? variant.m_excludedFromSim
1649 : footprint->IsExcludedFromSim();
1650 bool currentExcludedFromSim = currentVariant ? currentVariant->GetExcludedFromSim()
1651 : footprint->IsExcludedFromSim();
1652
1653 if( currentExcludedFromSim != targetExcludedFromSim )
1654 {
1655 printAttributeMessage( targetExcludedFromSim, _( "exclude from simulation" ), info.name );
1656
1657 if( !m_isDryRun )
1658 {
1659 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1660 fpVariant->SetExcludedFromSim( targetExcludedFromSim );
1661 }
1662
1663 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1664 changed = true;
1665 }
1666
1667 bool targetExcludedFromPosFiles = variant.m_hasExcludedFromPosFiles ? variant.m_excludedFromPosFiles
1668 : footprint->IsExcludedFromPosFiles();
1669 bool currentExcludedFromPosFiles = currentVariant ? currentVariant->GetExcludedFromPosFiles()
1670 : footprint->IsExcludedFromPosFiles();
1671
1672 if( currentExcludedFromPosFiles != targetExcludedFromPosFiles )
1673 {
1674 printAttributeMessage( targetExcludedFromPosFiles, _( "exclude from position files" ), info.name );
1675
1676 if( !m_isDryRun )
1677 {
1678 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1679 fpVariant->SetExcludedFromPosFiles( targetExcludedFromPosFiles );
1680 }
1681
1682 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1683 changed = true;
1684 }
1685
1686 for( const auto& [fieldName, fieldValue] : variant.m_fields )
1687 {
1688 if( fieldName.CmpNoCase( footprintFieldName ) == 0 )
1689 continue;
1690
1691 bool hasCurrentValue = currentVariant && currentVariant->HasFieldValue( fieldName );
1692 wxString currentValue = hasCurrentValue ? currentVariant->GetFieldValue( fieldName ) : wxString();
1693
1694 if( currentValue != fieldValue )
1695 {
1696 if( m_isDryRun )
1697 {
1698 if( aFootprints.size() > 1 )
1699 {
1700 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s (footprint %s)." ),
1701 footprint->GetReference(),
1702 fieldName,
1703 fieldValue,
1704 info.name,
1705 footprint->GetFPIDAsString() );
1706 }
1707 else
1708 {
1709 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s." ),
1710 footprint->GetReference(),
1711 fieldName,
1712 fieldValue,
1713 info.name );
1714 }
1715 }
1716 else
1717 {
1718 if( aFootprints.size() > 1 )
1719 {
1720 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s (footprint %s)." ),
1721 footprint->GetReference(),
1722 fieldName,
1723 fieldValue,
1724 info.name,
1725 footprint->GetFPIDAsString() );
1726 }
1727 else
1728 {
1729 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s." ),
1730 footprint->GetReference(),
1731 fieldName,
1732 fieldValue,
1733 info.name );
1734 }
1735
1736 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1737 fpVariant->SetFieldValue( fieldName, fieldValue );
1738 }
1739
1740 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1741 changed = true;
1742 }
1743 }
1744 }
1745
1746 for( const wxString& excess : excessVariants )
1747 {
1748 if( m_isDryRun )
1749 {
1750 msg.Printf( _( "Remove variant %s:%s no longer associated with footprint %s." ),
1751 footprint->GetReference(),
1752 excess,
1753 footprint->GetFPIDAsString() );
1754 }
1755 else
1756 {
1757 msg.Printf( _( "Removed variant %s:%s no longer associated with footprint %s." ),
1758 footprint->GetReference(),
1759 excess,
1760 footprint->GetFPIDAsString() );
1761
1762 footprint->DeleteVariant( excess );
1763 }
1764
1765 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1766 changed = true;
1767 }
1768
1769 if( !m_isDryRun && changed && copy )
1770 m_commit.Modified( footprint, copy );
1771 else
1772 delete copy;
1773 }
1774}
1775
1776
1778{
1779 for( ZONE* zone : m_board->Zones() )
1780 {
1781 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1782 continue;
1783
1784 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1785 }
1786}
1787
1788
1790{
1791 wxString msg;
1792 std::set<wxString> netlistNetnames;
1793
1794 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1795 {
1796 const COMPONENT* component = aNetlist.GetComponent( ii );
1797
1798 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1799 {
1800 const COMPONENT_NET& net = component->GetNet( jj );
1801 netlistNetnames.insert( net.GetNetName() );
1802 }
1803 }
1804
1805 for( PCB_TRACK* via : m_board->Tracks() )
1806 {
1807 if( via->Type() != PCB_VIA_T )
1808 continue;
1809
1810 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1811 {
1812 wxString updatedNetname = wxEmptyString;
1813
1814 // Take via name from name change map if it didn't match to a new pad
1815 // (this is useful for stitching vias that don't connect to tracks)
1816 if( m_oldToNewNets.count( via->GetNetname() ) )
1817 {
1818 updatedNetname = m_oldToNewNets[via->GetNetname()];
1819 }
1820
1821 if( !updatedNetname.IsEmpty() )
1822 {
1823 if( m_isDryRun )
1824 {
1825 wxString originalNetname = via->GetNetname();
1826
1827 msg.Printf( _( "Reconnect via from %s to %s." ),
1828 EscapeHTML( UnescapeString( originalNetname ) ),
1829 EscapeHTML( UnescapeString( updatedNetname ) ) );
1830
1831 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1832 }
1833 else
1834 {
1835 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1836
1837 if( !netinfo )
1838 netinfo = m_addedNets[updatedNetname];
1839
1840 if( netinfo )
1841 {
1842 wxString originalNetname = via->GetNetname();
1843
1844 m_commit.Modify( via );
1845 via->SetNet( netinfo );
1846
1847 msg.Printf( _( "Reconnected via from %s to %s." ),
1848 EscapeHTML( UnescapeString( originalNetname ) ),
1849 EscapeHTML( UnescapeString( updatedNetname ) ) );
1850
1851 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1852 }
1853 }
1854 }
1855 else
1856 {
1857 msg.Printf( _( "Via connected to unknown net (%s)." ),
1858 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1859 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1861 }
1862 }
1863 }
1864
1865 // Board connectivity net names are not the same as schematic connectivity net names.
1866 // Footprints that contain multiple overlapping pads with the same number are suffixed
1867 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1868 // exposed in the zone net name list.
1869 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1870 {
1871 if( netlistNetnames.count( aNetName ) )
1872 return true;
1873
1874 // If the zone net name is a pseudo net name, check if the root net name is in the net
1875 // list. If so, then this is a valid net.
1876 for( const wxString& netName : netlistNetnames )
1877 {
1878 if( aNetName.StartsWith( netName ) )
1879 return true;
1880 }
1881
1882 return false;
1883 };
1884
1885 // Test copper zones to detect "dead" nets (nets without any pad):
1886 for( ZONE* zone : m_board->Zones() )
1887 {
1888 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1889 continue;
1890
1891 if( !isInNetlist( zone->GetNetname() ) )
1892 {
1893 // Look for a pad in the zone's connected-pad-cache which has been updated to
1894 // a new net and use that. While this won't always be the right net, the dead
1895 // net is guaranteed to be wrong.
1896 wxString updatedNetname = wxEmptyString;
1897
1898 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1899 {
1900 if( getNetname( pad ) != zone->GetNetname() )
1901 {
1902 updatedNetname = getNetname( pad );
1903 break;
1904 }
1905 }
1906
1907 // Take zone name from name change map if it didn't match to a new pad
1908 // (this is useful for zones on internal layers)
1909 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1910 {
1911 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1912 }
1913
1914 if( !updatedNetname.IsEmpty() )
1915 {
1916 if( m_isDryRun )
1917 {
1918 wxString originalNetname = zone->GetNetname();
1919
1920 if( !zone->GetZoneName().IsEmpty() )
1921 {
1922 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1923 zone->GetZoneName(),
1924 EscapeHTML( UnescapeString( originalNetname ) ),
1925 EscapeHTML( UnescapeString( updatedNetname ) ) );
1926 }
1927 else
1928 {
1929 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1930 EscapeHTML( UnescapeString( originalNetname ) ),
1931 EscapeHTML( UnescapeString( updatedNetname ) ) );
1932 }
1933
1934 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1935 }
1936 else
1937 {
1938 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1939
1940 if( !netinfo )
1941 netinfo = m_addedNets[ updatedNetname ];
1942
1943 if( netinfo )
1944 {
1945 wxString originalNetname = zone->GetNetname();
1946
1947 m_commit.Modify( zone );
1948 zone->SetNet( netinfo );
1949
1950 if( !zone->GetZoneName().IsEmpty() )
1951 {
1952 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1953 EscapeHTML( zone->GetZoneName() ),
1954 EscapeHTML( UnescapeString( originalNetname ) ),
1955 EscapeHTML( UnescapeString( updatedNetname ) ) );
1956 }
1957 else
1958 {
1959 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
1960 EscapeHTML( UnescapeString( originalNetname ) ),
1961 EscapeHTML( UnescapeString( updatedNetname ) ) );
1962 }
1963
1964 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1965 }
1966 }
1967 }
1968 else
1969 {
1970 if( !zone->GetZoneName().IsEmpty() )
1971 {
1972 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
1973 EscapeHTML( zone->GetZoneName() ) );
1974 }
1975 else
1976 {
1977 wxString layerNames = zone->LayerMaskDescribe();
1978 VECTOR2I pt = zone->GetPosition();
1979
1980 if( m_settings )
1981 {
1982 if( m_settings->m_Display.m_DisplayInvertXAxis )
1983 pt.x *= -1;
1984
1985 if( m_settings->m_Display.m_DisplayInvertYAxis )
1986 pt.y *= -1;
1987 }
1988
1989 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
1990 EscapeHTML( layerNames ),
1991 m_frame ? m_frame->MessageTextFromValue( pt.x )
1994 pt.x ),
1995 m_frame ? m_frame->MessageTextFromValue( pt.y )
1998 pt.y ),
1999 zone->GetNetname() );
2000 }
2001
2002 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2004 }
2005 }
2006 }
2007
2008 return true;
2009}
2010
2011
2013{
2014 if( !m_transferGroups )
2015 return false;
2016
2017 wxString msg;
2018
2019 for( PCB_GROUP* pcbGroup : m_board->Groups() )
2020 {
2021 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
2022
2023 if( netlistGroup == nullptr )
2024 continue;
2025
2026 if( netlistGroup->name != pcbGroup->GetName() )
2027 {
2028 if( m_isDryRun )
2029 {
2030 msg.Printf( _( "Change group name from '%s' to '%s'." ),
2031 EscapeHTML( pcbGroup->GetName() ),
2032 EscapeHTML( netlistGroup->name ) );
2033 }
2034 else
2035 {
2036 msg.Printf( _( "Changed group name from '%s' to '%s'." ),
2037 EscapeHTML( pcbGroup->GetName() ),
2038 EscapeHTML( netlistGroup->name ) );
2039 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2040 pcbGroup->SetName( netlistGroup->name );
2041 }
2042
2043 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2044 }
2045
2046 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
2047 {
2048 if( m_isDryRun )
2049 {
2050 msg.Printf( _( "Change group library link from '%s' to '%s'." ),
2051 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
2052 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
2053 }
2054 else
2055 {
2056 msg.Printf( _( "Changed group library link from '%s' to '%s'." ),
2057 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
2058 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
2059 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2060 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
2061 }
2062
2063 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2064 }
2065
2066 // A group member may be another group's uuid (a nested group). Restore that
2067 // parent/child relationship on the board.
2068 for( const KIID_PATH& member : netlistGroup->members )
2069 {
2070 if( member.empty() )
2071 continue;
2072
2073 KIID memberGroupUuid =
2074 member.size() == 1 ? member.front() : KIID::FromName( std::string( member.AsString().ToUTF8() ) );
2075
2076 PCB_GROUP* childGroup = nullptr;
2077
2078 for( PCB_GROUP* candidate : m_board->Groups() )
2079 {
2080 if( candidate->m_Uuid == memberGroupUuid )
2081 {
2082 childGroup = candidate;
2083 break;
2084 }
2085 }
2086
2087 if( !childGroup || childGroup == pcbGroup || childGroup->GetParentGroup() == pcbGroup )
2088 {
2089 continue;
2090 }
2091
2092 if( m_isDryRun )
2093 {
2094 msg.Printf( _( "Add group '%s' to group '%s'." ), EscapeHTML( childGroup->GetName() ),
2095 EscapeHTML( pcbGroup->GetName() ) );
2096 }
2097 else
2098 {
2099 msg.Printf( _( "Added group '%s' to group '%s'." ), EscapeHTML( childGroup->GetName() ),
2100 EscapeHTML( pcbGroup->GetName() ) );
2101 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2102 m_commit.Modify( childGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2103 pcbGroup->AddItem( childGroup );
2104 }
2105
2106 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2107 }
2108 }
2109
2110 return true;
2111}
2112
2113
2115 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
2116{
2117 // Verify that board contains all pads in netlist: if it doesn't then footprints are
2118 // wrong or missing.
2119
2120 wxString msg;
2121 wxString padNumber;
2122
2123 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
2124 {
2125 COMPONENT* component = aNetlist.GetComponent( i );
2126 FOOTPRINT* footprint = aFootprintMap[component];
2127
2128 if( !footprint ) // It can be missing in partial designs
2129 continue;
2130
2131 // Explore all pins/pads in component
2132 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
2133 {
2134 padNumber = component->GetNet( jj ).GetPinName();
2135
2136 if( padNumber.IsEmpty() )
2137 {
2138 // bad symbol, report error
2139 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
2140 "to pads in %s." ),
2141 component->GetReference(),
2142 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
2143 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2144 ++m_errorCount;
2145 }
2146 else if( !footprint->FindPadByNumber( padNumber ) )
2147 {
2148 // not found: bad footprint, report error
2149 msg.Printf( _( "%s pad %s not found in %s." ),
2150 component->GetReference(),
2151 EscapeHTML( padNumber ),
2152 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
2153 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2154 ++m_errorCount;
2155 }
2156 }
2157 }
2158
2159 return true;
2160}
2161
2162
2164{
2165 FOOTPRINT* lastPreexistingFootprint = nullptr;
2166 COMPONENT* component = nullptr;
2167 wxString msg;
2168 std::unordered_set<wxString> sheetPaths;
2169 std::unordered_set<FOOTPRINT*> usedFootprints;
2170
2171 m_errorCount = 0;
2172 m_warningCount = 0;
2174
2175 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
2176
2177 if( !m_board->Footprints().empty() )
2178 lastPreexistingFootprint = m_board->Footprints().back();
2179
2181
2182 // First mark all nets (except <no net>) as stale; we'll update those which are current
2183 // in the following two loops. Also prepare the component class manager for updates.
2184 //
2185 if( !m_isDryRun )
2186 {
2187 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2188 net->SetIsCurrent( net->GetNetCode() == 0 );
2189
2190 m_board->GetComponentClassManager().InitNetlistUpdate();
2191 }
2192
2193 // Collect all schematic net names so NC pad deduplication can avoid collisions
2194 for( unsigned ii = 0; ii < aNetlist.GetCount(); ii++ )
2195 {
2196 COMPONENT* comp = aNetlist.GetComponent( ii );
2197
2198 for( unsigned jj = 0; jj < comp->GetNetCount(); jj++ )
2199 m_schematicNetNames.insert( comp->GetNet( jj ).GetNetName() );
2200 }
2201
2202 // Next go through the netlist updating all board footprints which have matching component
2203 // entries and adding new footprints for those that don't.
2204 //
2205 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
2206 {
2207 component = aNetlist.GetComponent( i );
2208
2209 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
2210 continue;
2211
2212 msg.Printf( _( "Processing symbol '%s:%s'." ),
2213 component->GetReference(),
2214 EscapeHTML( component->GetFPID().Format().wx_str() ) );
2215 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2216
2217 const LIB_ID& baseFpid = component->GetFPID();
2218 const bool hasBaseFpid = !baseFpid.empty();
2219
2220 if( baseFpid.IsLegacy() )
2221 {
2222 msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. "
2223 "Use the full 'Library:Footprint' format to avoid repeated update "
2224 "notifications." ),
2225 component->GetReference(),
2226 EscapeHTML( baseFpid.Format().wx_str() ) );
2227 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2229 }
2230
2231 std::vector<FOOTPRINT*> matchingFootprints;
2232
2233 for( FOOTPRINT* footprint : m_board->Footprints() )
2234 {
2235 bool match = false;
2236
2238 {
2239 for( const KIID& uuid : component->GetKIIDs() )
2240 {
2241 KIID_PATH base = component->GetPath();
2242 base.push_back( uuid );
2243
2244 if( footprint->GetPath() == base )
2245 {
2246 match = true;
2247 break;
2248 }
2249 }
2250 }
2251 else
2252 {
2253 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
2254 }
2255
2256 if( match )
2257 matchingFootprints.push_back( footprint );
2258
2259 if( footprint == lastPreexistingFootprint )
2260 {
2261 // No sense going through the newly-created footprints: end of loop
2262 break;
2263 }
2264 }
2265
2266 std::vector<LIB_ID> expectedFpids;
2267 std::unordered_set<wxString> expectedFpidKeys;
2268
2269 auto addExpectedFpid =
2270 [&]( const LIB_ID& aFpid )
2271 {
2272 if( aFpid.empty() )
2273 return;
2274
2275 wxString key = aFpid.Format();
2276
2277 if( expectedFpidKeys.insert( key ).second )
2278 expectedFpids.push_back( aFpid );
2279 };
2280
2281 addExpectedFpid( baseFpid );
2282
2283 const wxString footprintFieldName = GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED );
2284
2285 for( const auto& [variantName, variant] : component->GetVariants() )
2286 {
2287 auto fieldIt = variant.m_fields.find( footprintFieldName );
2288
2289 if( fieldIt == variant.m_fields.end() || fieldIt->second.IsEmpty() )
2290 continue;
2291
2292 LIB_ID parsedId;
2293
2294 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
2295 {
2296 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
2297 EscapeHTML( fieldIt->second ),
2298 variantName,
2299 component->GetReference() );
2300 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2301 ++m_errorCount;
2302 continue;
2303 }
2304
2305 addExpectedFpid( parsedId );
2306 }
2307
2308 auto isExpectedFpid =
2309 [&]( const LIB_ID& aFpid ) -> bool
2310 {
2311 if( aFpid.empty() )
2312 return false;
2313
2314 if( expectedFpidKeys.count( aFpid.Format() ) > 0 )
2315 return true;
2316
2317 for( const LIB_ID& expected : expectedFpids )
2318 {
2319 if( fpidsEquivalent( aFpid, expected ) )
2320 return true;
2321 }
2322
2323 return false;
2324 };
2325
2326 auto takeMatchingFootprint =
2327 [&]( const LIB_ID& aFpid ) -> FOOTPRINT*
2328 {
2329 for( FOOTPRINT* footprint : matchingFootprints )
2330 {
2331 if( usedFootprints.count( footprint ) )
2332 continue;
2333
2334 if( fpidsEquivalent( footprint->GetFPID(), aFpid ) )
2335 return footprint;
2336 }
2337
2338 return nullptr;
2339 };
2340
2341 std::vector<FOOTPRINT*> componentFootprints;
2342 componentFootprints.reserve( expectedFpids.size() );
2343 FOOTPRINT* baseFootprint = nullptr;
2344
2345 if( hasBaseFpid )
2346 baseFootprint = takeMatchingFootprint( baseFpid );
2347 else if( !matchingFootprints.empty() )
2348 baseFootprint = matchingFootprints.front();
2349
2350 if( !baseFootprint && m_replaceFootprints && !matchingFootprints.empty() )
2351 {
2352 FOOTPRINT* replaceCandidate = nullptr;
2353
2354 for( FOOTPRINT* footprint : matchingFootprints )
2355 {
2356 if( usedFootprints.count( footprint ) )
2357 continue;
2358
2359 if( isExpectedFpid( footprint->GetFPID() ) )
2360 continue;
2361
2362 replaceCandidate = footprint;
2363 break;
2364 }
2365
2366 if( replaceCandidate )
2367 {
2368 FOOTPRINT* replaced = replaceFootprint( aNetlist, replaceCandidate, component );
2369
2370 if( replaced )
2371 baseFootprint = replaced;
2372 else
2373 baseFootprint = replaceCandidate;
2374 }
2375 }
2376
2377 if( !baseFootprint && !m_replaceFootprints )
2378 {
2379 for( FOOTPRINT* footprint : matchingFootprints )
2380 {
2381 if( usedFootprints.count( footprint ) )
2382 continue;
2383
2384 if( isExpectedFpid( footprint->GetFPID() ) )
2385 continue;
2386
2387 baseFootprint = footprint;
2388 break;
2389 }
2390 }
2391
2392 if( !baseFootprint && ( hasBaseFpid || expectedFpids.empty() ) )
2393 baseFootprint = addNewFootprint( component, baseFpid );
2394
2395 if( baseFootprint )
2396 {
2397 componentFootprints.push_back( baseFootprint );
2398 usedFootprints.insert( baseFootprint );
2399 footprintMap[ component ] = baseFootprint;
2400 }
2401
2402 for( const LIB_ID& fpid : expectedFpids )
2403 {
2404 // Both IDs are schematic-derived, so either side may be legacy; compare in both
2405 // directions so a bare base name and a qualified variant name for the same
2406 // footprint are not split into a duplicate.
2407 if( fpidsEquivalent( fpid, baseFpid ) || fpidsEquivalent( baseFpid, fpid ) )
2408 continue;
2409
2410 FOOTPRINT* footprint = takeMatchingFootprint( fpid );
2411
2412 if( !footprint )
2413 footprint = addNewFootprint( component, fpid );
2414
2415 if( footprint )
2416 {
2417 componentFootprints.push_back( footprint );
2418 usedFootprints.insert( footprint );
2419 }
2420 }
2421
2422 for( FOOTPRINT* footprint : componentFootprints )
2423 {
2424 if( !footprint )
2425 continue;
2426
2427 updateFootprintParameters( footprint, component );
2428 updateFootprintGroup( footprint, component );
2429 updateComponentPadConnections( footprint, component );
2430 updateComponentClass( footprint, component );
2431 updateComponentUnits( footprint, component );
2432
2433 sheetPaths.insert( footprint->GetSheetname() );
2434 }
2435
2436 if( !componentFootprints.empty() )
2437 applyComponentVariants( component, componentFootprints, baseFpid );
2438 }
2439
2440 updateCopperZoneNets( aNetlist );
2441 updateGroups( aNetlist );
2442
2443 // Finally go through the board footprints and update all those that *don't* have matching
2444 // component entries.
2445 //
2446 for( FOOTPRINT* footprint : m_board->Footprints() )
2447 {
2448 bool matched = false;
2449 bool doDelete = m_deleteUnusedFootprints;
2450
2451 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
2452 doDelete = false;
2453
2454 bool isStaleVariantFootprint = false;
2455
2456 if( usedFootprints.count( footprint ) )
2457 {
2458 matched = true;
2459 }
2460 else
2461 {
2463 component = aNetlist.GetComponentByPath( footprint->GetPath() );
2464 else
2465 component = aNetlist.GetComponentByReference( footprint->GetReference() );
2466
2467 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
2468 {
2469 // When replace footprints is enabled and a component has variant footprints,
2470 // footprints matching by reference but not in usedFootprints are stale variant
2471 // footprints that should be replaced/removed.
2472 if( m_replaceFootprints && !component->GetVariants().empty() )
2473 {
2474 matched = false;
2475 isStaleVariantFootprint = true;
2476 }
2477 else
2478 {
2479 matched = true;
2480 }
2481 }
2482 }
2483
2484 // Stale variant footprints should be deleted when m_replaceFootprints is enabled,
2485 // regardless of m_deleteUnusedFootprints setting.
2486 if( isStaleVariantFootprint )
2487 doDelete = true;
2488
2489 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
2490 {
2491 if( m_isDryRun )
2492 {
2493 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
2494 footprint->GetReference() );
2495 }
2496 else
2497 {
2498 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
2499 footprint->GetReference() );
2500 }
2501
2502 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2504 doDelete = false;
2505 }
2506
2507 if( doDelete && !matched )
2508 {
2509 if( m_isDryRun )
2510 {
2511 msg.Printf( _( "Remove unused footprint %s." ),
2512 footprint->GetReference() );
2513 }
2514 else
2515 {
2516 m_commit.Remove( footprint );
2517 msg.Printf( _( "Removed unused footprint %s." ),
2518 footprint->GetReference() );
2519 }
2520
2521 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2522 }
2523 else if( !m_isDryRun )
2524 {
2525 if( !matched )
2526 footprint->SetPath( KIID_PATH() );
2527
2528 for( PAD* pad : footprint->Pads() )
2529 {
2530 if( pad->GetNet() )
2531 pad->GetNet()->SetIsCurrent( true );
2532 }
2533 }
2534 }
2535
2536 if( !m_isDryRun )
2537 {
2538 // Finalise the component class manager
2539 m_board->GetComponentClassManager().FinishNetlistUpdate();
2540 m_board->SynchronizeComponentClasses( sheetPaths );
2541
2542 m_board->BuildConnectivity();
2543 testConnectivity( aNetlist, footprintMap );
2544
2545 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2546 {
2547 if( !net->IsCurrent() )
2548 {
2549 msg.Printf( _( "Removed unused net %s." ),
2550 EscapeHTML( net->GetNetname() ) );
2551 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2552 }
2553 }
2554
2555 m_board->RemoveUnusedNets( &m_commit );
2556
2557 // Update board variant registry from netlist
2558 const std::vector<wxString>& netlistVariants = aNetlist.GetVariantNames();
2559
2560 if( !netlistVariants.empty() || !m_board->GetVariantNames().empty() )
2561 {
2562 m_reporter->Report( _( "Updating design variants..." ), RPT_SEVERITY_INFO );
2563
2564 auto findBoardVariantName =
2565 [&]( const wxString& aVariantName ) -> wxString
2566 {
2567 for( const wxString& name : m_board->GetVariantNames() )
2568 {
2569 if( name.CmpNoCase( aVariantName ) == 0 )
2570 return name;
2571 }
2572
2573 return wxEmptyString;
2574 };
2575
2576 std::vector<wxString> updatedVariantNames;
2577 updatedVariantNames.reserve( netlistVariants.size() );
2578
2579 for( const wxString& variantName : netlistVariants )
2580 {
2581 wxString actualName = findBoardVariantName( variantName );
2582
2583 if( actualName.IsEmpty() )
2584 {
2585 m_board->AddVariant( variantName );
2586 actualName = findBoardVariantName( variantName );
2587
2588 if( !actualName.IsEmpty() )
2589 {
2590 msg.Printf( _( "Added variant '%s'." ), actualName );
2591 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2592 }
2593 }
2594
2595 if( actualName.IsEmpty() )
2596 continue;
2597
2598 // Update description if changed
2599 wxString newDescription = aNetlist.GetVariantDescription( variantName );
2600 wxString oldDescription = m_board->GetVariantDescription( actualName );
2601
2602 if( newDescription != oldDescription )
2603 {
2604 m_board->SetVariantDescription( actualName, newDescription );
2605 msg.Printf( _( "Updated description for variant '%s'." ), actualName );
2606 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2607 }
2608
2609 updatedVariantNames.push_back( actualName );
2610 }
2611
2612 std::vector<wxString> variantsToRemove;
2613
2614 for( const wxString& existingName : m_board->GetVariantNames() )
2615 {
2616 bool found = false;
2617
2618 for( const wxString& variantName : netlistVariants )
2619 {
2620 if( existingName.CmpNoCase( variantName ) == 0 )
2621 {
2622 found = true;
2623 break;
2624 }
2625 }
2626
2627 if( !found )
2628 variantsToRemove.push_back( existingName );
2629 }
2630
2631 for( const wxString& variantName : variantsToRemove )
2632 {
2633 m_board->DeleteVariant( variantName );
2634 msg.Printf( _( "Removed variant '%s'." ), variantName );
2635 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2636 }
2637
2638 if( !updatedVariantNames.empty() )
2639 m_board->SetVariantNames( updatedVariantNames );
2640 }
2641
2642 // When new footprints are added, the automatic zone refill is disabled because:
2643 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
2644 // (the auto refills rebuild the connectivity with incomplete data)
2645 // * it is useless because zones will be refilled after placing new footprints
2646 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
2647
2648 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
2649
2650 // Netlist is authoritative for chain assignment, so the terminal-pin reapplication
2651 // below starts from a clean slate.
2653
2654 // Net chains may specify a display colour override; lift that into the
2655 // board-side lookup so the PCB painter can use it when highlighting.
2656 for( const auto& [chain, colorStr] : aNetlist.GetNetChainColors() )
2657 {
2658 if( !colorStr.IsEmpty() )
2659 {
2660 KIGFX::COLOR4D color;
2661
2662 if( color.SetFromHexString( colorStr ) )
2663 m_board->SetNetChainColor( chain, color );
2664 }
2665 }
2666
2667 ApplyChainNetclasses( m_board, aNetlist );
2668
2669 // Always resync after chain cleanup so existing NETINFO_ITEM effective-netclass
2670 // pointers pick up cleared/changed chain entries even when no chain carries a netclass.
2671 m_board->SynchronizeNetsAndNetClasses( true );
2672
2673 for( const auto& sig : aNetlist.GetNetChainTerminalPins() )
2674 {
2675 PAD* pads[2] = { nullptr, nullptr };
2676
2677 for( size_t i = 0; i < sig.second.size() && i < 2; ++i )
2678 {
2679 const wxString& ref = sig.second[i].first;
2680 const wxString& pin = sig.second[i].second;
2681 FOOTPRINT* fp = m_board->FindFootprintByReference( ref );
2682
2683 if( !fp )
2684 continue;
2685
2686 PAD* candidate = nullptr;
2687 PAD* best = nullptr;
2688 int bestDist = std::numeric_limits<int>::max();
2689 BOX2I bbox = fp->GetBoundingBox();
2690
2691 while( ( candidate = fp->FindPadByNumber( pin, candidate ) ) )
2692 {
2693 VECTOR2I pos = candidate->GetPosition();
2694 int dist = std::min( { pos.x - bbox.GetLeft(), bbox.GetRight() - pos.x,
2695 pos.y - bbox.GetTop(), bbox.GetBottom() - pos.y } );
2696
2697 if( !best || dist < bestDist || ( dist == bestDist && candidate->m_Uuid < best->m_Uuid ) )
2698 {
2699 best = candidate;
2700 bestDist = dist;
2701 }
2702 }
2703
2704 pads[i] = best;
2705 }
2706
2707 for( int i = 0; i < 2; ++i )
2708 {
2709 if( !pads[i] )
2710 continue;
2711
2712 NETINFO_ITEM* termNet = pads[i]->GetNet();
2713
2714 if( !termNet || termNet->GetNetChain() != sig.first )
2715 continue;
2716
2717 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2718 {
2719 if( net != termNet && net->GetNetChain() == sig.first
2720 && net->GetTerminalPad( i ) )
2721 {
2722 net->ClearTerminalPad( i );
2723 }
2724 }
2725
2726 termNet->SetTerminal( i, pads[i] );
2727 }
2728 }
2729
2730 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
2731 // sure any modification to netclasses gets persisted to project settings through a save.
2732 if( m_frame )
2733 m_frame->OnModify();
2734 }
2735
2736 if( m_isDryRun )
2737 {
2738 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
2739 delete addedNet.second;
2740
2741 m_addedNets.clear();
2742 }
2743
2744 // Update the ratsnest
2745 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2746 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2747
2748 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
2749 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
2750
2751 return true;
2752}
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
#define ZONE_FILL_OP
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
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:374
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:409
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1207
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
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:64
wxString GetName() const
Definition eda_group.h:61
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:62
const KIID m_Uuid
Definition eda_item.h:597
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:342
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:231
Variant information for a footprint.
Definition footprint.h:227
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:286
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:258
bool GetExcludedFromSim() const
Definition footprint.h:247
bool GetExcludedFromBOM() const
Definition footprint.h:244
bool GetExcludedFromPosFiles() const
Definition footprint.h:250
bool GetDNP() const
Definition footprint.h:241
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:1232
void SetPosition(const VECTOR2I &aPos) override
EDA_ANGLE GetOrientation() const
Definition footprint.h:438
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
wxString GetSheetname() const
Definition footprint.h:510
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:497
void SetFilters(const wxString &aFilters)
Definition footprint.h:517
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:1017
void SetAttributes(int aAttributes)
Definition footprint.h:551
void SetSheetfile(const wxString &aSheetfile)
Definition footprint.h:514
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:1239
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1233
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:404
int GetAttributes() const
Definition footprint.h:550
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:449
wxString GetFPIDAsString() const
Definition footprint.h:479
wxString GetSheetfile() const
Definition footprint.h:513
const LIB_ID & GetFPID() const
Definition footprint.h:473
void SetReference(const wxString &aReference)
Definition footprint.h:907
bool IsLocked() const override
Definition footprint.h:680
void SetValue(const wxString &aValue)
Definition footprint.h:930
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:1016
wxString GetFilters() const
Definition footprint.h:516
void SetSheetname(const wxString &aSheetname)
Definition footprint.h:511
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:925
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:901
const KIID_PATH & GetPath() const
Definition footprint.h:496
VECTOR2I GetPosition() const override
Definition footprint.h:435
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:50
const wxString & GetNetChain() const
Definition netinfo.h:122
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:160
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:280
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:267
Definition pad.h:61
const wxString & GetPinFunction() const
Definition pad.h:154
VECTOR2I GetPosition() const override
Definition pad.cpp:246
The main frame for Pcbnew.
void SetName(const wxString &aName)
Definition pcb_field.h:119
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
EDA_ITEM * AsEdaItem() override
Definition pcb_group.h:59
void StyleFromSettings(const BOARD_DESIGN_SETTINGS &settings, bool aCheckSide) override
Definition pcb_text.cpp:371
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:102
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:581
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Master controller class:
wxString wx_str() const
Definition utf8.cpp:41
Handle a list of polygons defining a copper zone.
Definition zone.h:70
#define _(s)
@ NO_RECURSE
Definition eda_item.h:52
@ FP_DNP
Definition footprint.h:91
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:87
@ FP_BOARD_ONLY
Definition footprint.h:89
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:88
@ FP_JUST_ADDED
Definition footprint.h:90
@ FP_EXCLUDE_FROM_SIM
Definition footprint.h:92
@ 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
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ 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".
@ UNTRANSLATED
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:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683