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