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 );
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 const_cast<KIID&>( newGroup->m_Uuid ) = 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; jj++ )
1158 {
1159 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1160 }
1161 }
1162
1163 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1164
1165 if( netinfo && !m_isDryRun )
1166 netinfo->SetIsCurrent( true );
1167
1168 if( pad->GetNetname() != netName )
1169 {
1170
1171 if( netinfo == nullptr )
1172 {
1173 // It might be a new net that has not been added to the board yet
1174 if( m_addedNets.count( netName ) )
1175 netinfo = m_addedNets[ netName ];
1176 }
1177
1178 if( netinfo == nullptr )
1179 {
1180 netinfo = new NETINFO_ITEM( m_board, netName );
1181
1182 // It is a new net, we have to add it
1183 if( !m_isDryRun )
1184 {
1185 changed = true;
1186 m_commit.Add( netinfo );
1187 }
1188
1189 m_addedNets[netName] = netinfo;
1190 msg.Printf( _( "Add net %s." ),
1191 EscapeHTML( UnescapeString( netName ) ) );
1192 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1193 }
1194
1195 if( !pad->GetNetname().IsEmpty() )
1196 {
1197 m_oldToNewNets[ pad->GetNetname() ] = netName;
1198
1199 if( m_isDryRun )
1200 {
1201 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1202 aFootprint->GetReference(),
1203 EscapeHTML( pad->GetNumber() ),
1204 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1205 EscapeHTML( UnescapeString( netName ) ) );
1206 }
1207 else
1208 {
1209 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1210 aFootprint->GetReference(),
1211 EscapeHTML( pad->GetNumber() ),
1212 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1213 EscapeHTML( UnescapeString( netName ) ) );
1214 }
1215 }
1216 else
1217 {
1218 if( m_isDryRun )
1219 {
1220 msg.Printf( _( "Connect %s pin %s to %s."),
1221 aFootprint->GetReference(),
1222 EscapeHTML( pad->GetNumber() ),
1223 EscapeHTML( UnescapeString( netName ) ) );
1224 }
1225 else
1226 {
1227 msg.Printf( _( "Connected %s pin %s to %s."),
1228 aFootprint->GetReference(),
1229 EscapeHTML( pad->GetNumber() ),
1230 EscapeHTML( UnescapeString( netName ) ) );
1231 }
1232 }
1233
1234 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1235
1236 if( !m_isDryRun )
1237 {
1238 changed = true;
1239 pad->SetNet( netinfo );
1240 }
1241 else
1242 {
1243 cacheNetname( pad, netName );
1244 }
1245 }
1246 }
1247 }
1248
1249 if( changed && copy )
1250 m_commit.Modified( aFootprint, copy );
1251 else if( copy )
1252 delete copy;
1253
1254 return true;
1255}
1256
1257
1259{
1260 // Build the footprint-side representation from the netlist component
1261 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1262
1263 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1264 newUnits.push_back( { u.m_unitName, u.m_pins } );
1265
1266 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1267
1268 auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
1269 const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
1270 {
1271 if( a.size() != b.size() )
1272 return false;
1273
1274 for( size_t i = 0; i < a.size(); ++i )
1275 {
1276 if( a[i].m_unitName != b[i].m_unitName )
1277 return false;
1278
1279 if( a[i].m_pins != b[i].m_pins )
1280 return false;
1281 }
1282
1283 return true;
1284 };
1285
1286 if( unitsEqual( curUnits, newUnits ) )
1287 return false;
1288
1289 wxString msg;
1290
1291 if( m_isDryRun )
1292 {
1293 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1294 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1295 return false; // no actual change on board during dry run
1296 }
1297
1298 // Create a copy only if the footprint has not been added during this update
1299 FOOTPRINT* copy = nullptr;
1300
1301 if( !m_commit.GetStatus( aFootprint ) )
1302 {
1303 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1304 copy->SetParentGroup( nullptr );
1305 }
1306
1307 aFootprint->SetUnitInfo( newUnits );
1308
1309 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1310 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1311
1312 if( copy )
1313 m_commit.Modified( aFootprint, copy );
1314
1315 return true;
1316}
1317
1318
1320 const std::vector<FOOTPRINT*>& aFootprints,
1321 const LIB_ID& aBaseFpid )
1322{
1323 wxString msg;
1324 const auto& variants = aComponent->GetVariants();
1325
1326 if( variants.empty() )
1327 return;
1328
1329 if( aBaseFpid.empty() )
1330 return;
1331
1332 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
1333
1334 struct VARIANT_INFO
1335 {
1336 wxString name;
1337 const COMPONENT_VARIANT* variant;
1338 LIB_ID variantFPID;
1339 };
1340
1341 std::vector<VARIANT_INFO> variantInfo;
1342 variantInfo.reserve( variants.size() );
1343
1344 for( const auto& [variantName, variant] : variants )
1345 {
1346 LIB_ID variantFPID = aBaseFpid;
1347
1348 auto fieldIt = variant.m_fields.find( footprintFieldName );
1349
1350 if( fieldIt != variant.m_fields.end() && !fieldIt->second.IsEmpty() )
1351 {
1352 LIB_ID parsedId;
1353
1354 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
1355 {
1356 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
1357 EscapeHTML( fieldIt->second ),
1358 variantName,
1359 aComponent->GetReference() );
1360 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1361 ++m_errorCount;
1362 }
1363 else
1364 {
1365 variantFPID = parsedId;
1366 }
1367 }
1368
1369 variantInfo.push_back( { variantName, &variant, variantFPID } );
1370 }
1371
1372 for( FOOTPRINT* footprint : aFootprints )
1373 {
1374 if( !footprint )
1375 continue;
1376
1377 FOOTPRINT* copy = nullptr;
1378
1379 if( !m_isDryRun && !m_commit.GetStatus( footprint ) )
1380 {
1381 copy = static_cast<FOOTPRINT*>( footprint->Clone() );
1382 copy->SetParentGroup( nullptr );
1383 }
1384
1385 bool changed = false;
1386
1387 auto printAttributeMessage =
1388 [&]( bool add, const wxString& attrName, const wxString& variantName )
1389 {
1390 if( m_isDryRun )
1391 {
1392 if( aFootprints.size() > 1 )
1393 {
1394 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s (footprint %s)." )
1395 : _( "Remove %s '%s' attribute from variant %s (footprint %s)." ),
1396 footprint->GetReference(),
1397 attrName,
1398 variantName,
1399 footprint->GetFPIDAsString() );
1400 }
1401 else
1402 {
1403 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s." )
1404 : _( "Remove %s '%s' attribute from variant %s." ),
1405 footprint->GetReference(),
1406 attrName,
1407 variantName );
1408 }
1409 }
1410 else
1411 {
1412 if( aFootprints.size() > 1 )
1413 {
1414 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s (footprint %s)." )
1415 : _( "Removed %s '%s' attribute from variant %s (footprint %s)." ),
1416 footprint->GetReference(),
1417 attrName,
1418 variantName,
1419 footprint->GetFPIDAsString() );
1420 }
1421 else
1422 {
1423 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s." )
1424 : _( "Removed %s '%s' attribute from variant %s." ),
1425 footprint->GetReference(),
1426 attrName,
1427 variantName );
1428 }
1429 }
1430 };
1431
1432 std::set<wxString> excessVariants;
1433
1434 for( const auto& [variantName, _] : footprint->GetVariants() )
1435 excessVariants.insert( variantName );
1436
1437 for( const VARIANT_INFO& info : variantInfo )
1438 {
1439 const COMPONENT_VARIANT& variant = *info.variant;
1440
1441 // During dry run, just read current state. During actual run, create variant if needed.
1442 const FOOTPRINT_VARIANT* currentVariant = footprint->GetVariant( info.name );
1443
1444 // Check if this footprint is the active one for this variant
1445 bool isAssociatedFootprint = ( footprint->GetFPID() == info.variantFPID );
1446
1447 // If this footprint is not active for this variant, it doesn't need variant info for it.
1448 // Otherwise, apply explicit overrides from schematic, or reset to base footprint value.
1449
1450 if( !isAssociatedFootprint )
1451 continue;
1452
1453 excessVariants.erase( info.name );
1454 bool targetDnp = variant.m_hasDnp ? variant.m_dnp : footprint->IsDNP();
1455 bool currentDnp = currentVariant ? currentVariant->GetDNP() : footprint->IsDNP();
1456
1457 if( currentDnp != targetDnp )
1458 {
1459 printAttributeMessage( targetDnp, _( "Do not place" ), info.name );
1460
1461 if( !m_isDryRun )
1462 {
1463 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1464 fpVariant->SetDNP( targetDnp );
1465 }
1466
1467 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1468 changed = true;
1469 }
1470
1471 bool targetExcludedFromBOM = variant.m_hasExcludedFromBOM ? variant.m_excludedFromBOM
1472 : footprint->IsExcludedFromBOM();
1473 bool currentExcludedFromBOM = currentVariant ? currentVariant->GetExcludedFromBOM()
1474 : footprint->IsExcludedFromBOM();
1475
1476 if( currentExcludedFromBOM != targetExcludedFromBOM )
1477 {
1478 printAttributeMessage( targetExcludedFromBOM, _( "exclude from BOM" ), info.name );
1479
1480 if( !m_isDryRun )
1481 {
1482 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1483 fpVariant->SetExcludedFromBOM( targetExcludedFromBOM );
1484 }
1485
1486 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1487 changed = true;
1488 }
1489
1490 bool targetExcludedFromPosFiles = variant.m_hasExcludedFromPosFiles ? variant.m_excludedFromPosFiles
1491 : footprint->IsExcludedFromPosFiles();
1492 bool currentExcludedFromPosFiles = currentVariant ? currentVariant->GetExcludedFromPosFiles()
1493 : footprint->IsExcludedFromPosFiles();
1494
1495 if( currentExcludedFromPosFiles != targetExcludedFromPosFiles )
1496 {
1497 printAttributeMessage( targetExcludedFromPosFiles, _( "exclude from position files" ), info.name );
1498
1499 if( !m_isDryRun )
1500 {
1501 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1502 fpVariant->SetExcludedFromPosFiles( targetExcludedFromPosFiles );
1503 }
1504
1505 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1506 changed = true;
1507 }
1508
1509 for( const auto& [fieldName, fieldValue] : variant.m_fields )
1510 {
1511 if( fieldName.CmpNoCase( footprintFieldName ) == 0 )
1512 continue;
1513
1514 bool hasCurrentValue = currentVariant && currentVariant->HasFieldValue( fieldName );
1515 wxString currentValue = hasCurrentValue ? currentVariant->GetFieldValue( fieldName ) : wxString();
1516
1517 if( currentValue != fieldValue )
1518 {
1519 if( m_isDryRun )
1520 {
1521 if( aFootprints.size() > 1 )
1522 {
1523 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s (footprint %s)." ),
1524 footprint->GetReference(),
1525 fieldName,
1526 fieldValue,
1527 info.name,
1528 footprint->GetFPIDAsString() );
1529 }
1530 else
1531 {
1532 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s." ),
1533 footprint->GetReference(),
1534 fieldName,
1535 fieldValue,
1536 info.name );
1537 }
1538 }
1539 else
1540 {
1541 if( aFootprints.size() > 1 )
1542 {
1543 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s (footprint %s)." ),
1544 footprint->GetReference(),
1545 fieldName,
1546 fieldValue,
1547 info.name,
1548 footprint->GetFPIDAsString() );
1549 }
1550 else
1551 {
1552 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s." ),
1553 footprint->GetReference(),
1554 fieldName,
1555 fieldValue,
1556 info.name );
1557 }
1558
1559 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1560 fpVariant->SetFieldValue( fieldName, fieldValue );
1561 }
1562
1563 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1564 changed = true;
1565 }
1566 }
1567 }
1568
1569 for( const wxString& excess : excessVariants )
1570 {
1571 if( m_isDryRun )
1572 {
1573 msg.Printf( _( "Remove variant %s:%s no longer associated with footprint %s." ),
1574 footprint->GetReference(),
1575 excess,
1576 footprint->GetFPIDAsString() );
1577 }
1578 else
1579 {
1580 msg.Printf( _( "Removed variant %s:%s no longer associated with footprint %s." ),
1581 footprint->GetReference(),
1582 excess,
1583 footprint->GetFPIDAsString() );
1584
1585 footprint->DeleteVariant( excess );
1586 }
1587
1588 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1589 changed = true;
1590 }
1591
1592 // For the default variant: if this footprint is not the base footprint
1593 // it should be DNP by default
1594 bool isBaseFootprint = ( footprint->GetFPID() == aBaseFpid );
1595
1596 if( !isBaseFootprint && !footprint->IsDNP() )
1597 {
1598 msg.Printf( m_isDryRun ? _( "Add %s 'Do not place' fabrication attribute." )
1599 : _( "Added %s 'Do not place' fabrication attribute." ),
1600 footprint->GetReference() );
1601
1602 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1603
1604 if( !m_isDryRun )
1605 footprint->SetDNP( true );
1606
1607 changed = true;
1608 }
1609
1610 if( !m_isDryRun && changed && copy )
1611 m_commit.Modified( footprint, copy );
1612 else
1613 delete copy;
1614 }
1615}
1616
1617
1619{
1620 for( ZONE* zone : m_board->Zones() )
1621 {
1622 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1623 continue;
1624
1625 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1626 }
1627}
1628
1629
1631{
1632 wxString msg;
1633 std::set<wxString> netlistNetnames;
1634
1635 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1636 {
1637 const COMPONENT* component = aNetlist.GetComponent( ii );
1638
1639 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1640 {
1641 const COMPONENT_NET& net = component->GetNet( jj );
1642 netlistNetnames.insert( net.GetNetName() );
1643 }
1644 }
1645
1646 for( PCB_TRACK* via : m_board->Tracks() )
1647 {
1648 if( via->Type() != PCB_VIA_T )
1649 continue;
1650
1651 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1652 {
1653 wxString updatedNetname = wxEmptyString;
1654
1655 // Take via name from name change map if it didn't match to a new pad
1656 // (this is useful for stitching vias that don't connect to tracks)
1657 if( m_oldToNewNets.count( via->GetNetname() ) )
1658 {
1659 updatedNetname = m_oldToNewNets[via->GetNetname()];
1660 }
1661
1662 if( !updatedNetname.IsEmpty() )
1663 {
1664 if( m_isDryRun )
1665 {
1666 wxString originalNetname = via->GetNetname();
1667
1668 msg.Printf( _( "Reconnect via from %s to %s." ),
1669 EscapeHTML( UnescapeString( originalNetname ) ),
1670 EscapeHTML( UnescapeString( updatedNetname ) ) );
1671
1672 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1673 }
1674 else
1675 {
1676 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1677
1678 if( !netinfo )
1679 netinfo = m_addedNets[updatedNetname];
1680
1681 if( netinfo )
1682 {
1683 wxString originalNetname = via->GetNetname();
1684
1685 m_commit.Modify( via );
1686 via->SetNet( netinfo );
1687
1688 msg.Printf( _( "Reconnected via from %s to %s." ),
1689 EscapeHTML( UnescapeString( originalNetname ) ),
1690 EscapeHTML( UnescapeString( updatedNetname ) ) );
1691
1692 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1693 }
1694 }
1695 }
1696 else
1697 {
1698 msg.Printf( _( "Via connected to unknown net (%s)." ),
1699 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1700 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1702 }
1703 }
1704 }
1705
1706 // Board connectivity net names are not the same as schematic connectivity net names.
1707 // Footprints that contain multiple overlapping pads with the same number are suffixed
1708 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1709 // exposed in the zone net name list.
1710 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1711 {
1712 if( netlistNetnames.count( aNetName ) )
1713 return true;
1714
1715 // If the zone net name is a pseudo net name, check if the root net name is in the net
1716 // list. If so, then this is a valid net.
1717 for( const wxString& netName : netlistNetnames )
1718 {
1719 if( aNetName.StartsWith( netName ) )
1720 return true;
1721 }
1722
1723 return false;
1724 };
1725
1726 // Test copper zones to detect "dead" nets (nets without any pad):
1727 for( ZONE* zone : m_board->Zones() )
1728 {
1729 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1730 continue;
1731
1732 if( !isInNetlist( zone->GetNetname() ) )
1733 {
1734 // Look for a pad in the zone's connected-pad-cache which has been updated to
1735 // a new net and use that. While this won't always be the right net, the dead
1736 // net is guaranteed to be wrong.
1737 wxString updatedNetname = wxEmptyString;
1738
1739 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1740 {
1741 if( getNetname( pad ) != zone->GetNetname() )
1742 {
1743 updatedNetname = getNetname( pad );
1744 break;
1745 }
1746 }
1747
1748 // Take zone name from name change map if it didn't match to a new pad
1749 // (this is useful for zones on internal layers)
1750 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1751 {
1752 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1753 }
1754
1755 if( !updatedNetname.IsEmpty() )
1756 {
1757 if( m_isDryRun )
1758 {
1759 wxString originalNetname = zone->GetNetname();
1760
1761 if( !zone->GetZoneName().IsEmpty() )
1762 {
1763 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1764 zone->GetZoneName(),
1765 EscapeHTML( UnescapeString( originalNetname ) ),
1766 EscapeHTML( UnescapeString( updatedNetname ) ) );
1767 }
1768 else
1769 {
1770 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1771 EscapeHTML( UnescapeString( originalNetname ) ),
1772 EscapeHTML( UnescapeString( updatedNetname ) ) );
1773 }
1774
1775 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1776 }
1777 else
1778 {
1779 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1780
1781 if( !netinfo )
1782 netinfo = m_addedNets[ updatedNetname ];
1783
1784 if( netinfo )
1785 {
1786 wxString originalNetname = zone->GetNetname();
1787
1788 m_commit.Modify( zone );
1789 zone->SetNet( netinfo );
1790
1791 if( !zone->GetZoneName().IsEmpty() )
1792 {
1793 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1794 EscapeHTML( zone->GetZoneName() ),
1795 EscapeHTML( UnescapeString( originalNetname ) ),
1796 EscapeHTML( UnescapeString( updatedNetname ) ) );
1797 }
1798 else
1799 {
1800 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
1801 EscapeHTML( UnescapeString( originalNetname ) ),
1802 EscapeHTML( UnescapeString( updatedNetname ) ) );
1803 }
1804
1805 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1806 }
1807 }
1808 }
1809 else
1810 {
1811 if( !zone->GetZoneName().IsEmpty() )
1812 {
1813 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
1814 EscapeHTML( zone->GetZoneName() ) );
1815 }
1816 else
1817 {
1818 wxString layerNames = zone->LayerMaskDescribe();
1819 VECTOR2I pt = zone->GetPosition();
1820
1821 if( m_frame && m_frame->GetPcbNewSettings() )
1822 {
1823 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertXAxis )
1824 pt.x *= -1;
1825
1826 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
1827 pt.y *= -1;
1828 }
1829
1830 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
1831 EscapeHTML( layerNames ),
1832 m_frame ? m_frame->MessageTextFromValue( pt.x )
1834 m_frame ? m_frame->MessageTextFromValue( pt.y )
1836 zone->GetNetname() );
1837 }
1838
1839 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1841 }
1842 }
1843 }
1844
1845 return true;
1846}
1847
1848
1850{
1851 if( !m_transferGroups )
1852 return false;
1853
1854 wxString msg;
1855
1856 for( PCB_GROUP* pcbGroup : m_board->Groups() )
1857 {
1858 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
1859
1860 if( netlistGroup == nullptr )
1861 continue;
1862
1863 if( netlistGroup->name != pcbGroup->GetName() )
1864 {
1865 if( m_isDryRun )
1866 {
1867 msg.Printf( _( "Change group name from '%s' to '%s'." ),
1868 EscapeHTML( pcbGroup->GetName() ),
1869 EscapeHTML( netlistGroup->name ) );
1870 }
1871 else
1872 {
1873 msg.Printf( _( "Changed group name from '%s' to '%s'." ),
1874 EscapeHTML( pcbGroup->GetName() ),
1875 EscapeHTML( netlistGroup->name ) );
1876 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1877 pcbGroup->SetName( netlistGroup->name );
1878 }
1879
1880 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1881 }
1882
1883 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
1884 {
1885 if( m_isDryRun )
1886 {
1887 msg.Printf( _( "Change group library link from '%s' to '%s'." ),
1888 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1889 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1890 }
1891 else
1892 {
1893 msg.Printf( _( "Changed group library link from '%s' to '%s'." ),
1894 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1895 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1896 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1897 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
1898 }
1899
1900 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1901 }
1902 }
1903
1904 return true;
1905}
1906
1907
1909 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
1910{
1911 // Verify that board contains all pads in netlist: if it doesn't then footprints are
1912 // wrong or missing.
1913
1914 wxString msg;
1915 wxString padNumber;
1916
1917 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
1918 {
1919 COMPONENT* component = aNetlist.GetComponent( i );
1920 FOOTPRINT* footprint = aFootprintMap[component];
1921
1922 if( !footprint ) // It can be missing in partial designs
1923 continue;
1924
1925 // Explore all pins/pads in component
1926 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1927 {
1928 padNumber = component->GetNet( jj ).GetPinName();
1929
1930 if( padNumber.IsEmpty() )
1931 {
1932 // bad symbol, report error
1933 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
1934 "to pads in %s." ),
1935 component->GetReference(),
1936 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1937 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1938 ++m_errorCount;
1939 }
1940 else if( !footprint->FindPadByNumber( padNumber ) )
1941 {
1942 // not found: bad footprint, report error
1943 msg.Printf( _( "%s pad %s not found in %s." ),
1944 component->GetReference(),
1945 EscapeHTML( padNumber ),
1946 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1947 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1948 ++m_errorCount;
1949 }
1950 }
1951 }
1952
1953 return true;
1954}
1955
1956
1958{
1959 FOOTPRINT* lastPreexistingFootprint = nullptr;
1960 COMPONENT* component = nullptr;
1961 wxString msg;
1962 std::unordered_set<wxString> sheetPaths;
1963 std::unordered_set<FOOTPRINT*> usedFootprints;
1964
1965 m_errorCount = 0;
1966 m_warningCount = 0;
1968
1969 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
1970
1971 if( !m_board->Footprints().empty() )
1972 lastPreexistingFootprint = m_board->Footprints().back();
1973
1975
1976 // First mark all nets (except <no net>) as stale; we'll update those which are current
1977 // in the following two loops. Also prepare the component class manager for updates.
1978 //
1979 if( !m_isDryRun )
1980 {
1981 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1982 net->SetIsCurrent( net->GetNetCode() == 0 );
1983
1984 m_board->GetComponentClassManager().InitNetlistUpdate();
1985 }
1986
1987 // Next go through the netlist updating all board footprints which have matching component
1988 // entries and adding new footprints for those that don't.
1989 //
1990 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
1991 {
1992 component = aNetlist.GetComponent( i );
1993
1994 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
1995 continue;
1996
1997 msg.Printf( _( "Processing symbol '%s:%s'." ),
1998 component->GetReference(),
1999 EscapeHTML( component->GetFPID().Format().wx_str() ) );
2000 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2001
2002 const LIB_ID& baseFpid = component->GetFPID();
2003 const bool hasBaseFpid = !baseFpid.empty();
2004
2005 if( baseFpid.IsLegacy() )
2006 {
2007 msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. "
2008 "Use the full 'Library:Footprint' format to avoid repeated update "
2009 "notifications." ),
2010 component->GetReference(),
2011 EscapeHTML( baseFpid.Format().wx_str() ) );
2012 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2014 }
2015
2016 std::vector<FOOTPRINT*> matchingFootprints;
2017
2018 for( FOOTPRINT* footprint : m_board->Footprints() )
2019 {
2020 bool match = false;
2021
2023 {
2024 for( const KIID& uuid : component->GetKIIDs() )
2025 {
2026 KIID_PATH base = component->GetPath();
2027 base.push_back( uuid );
2028
2029 if( footprint->GetPath() == base )
2030 {
2031 match = true;
2032 break;
2033 }
2034 }
2035 }
2036 else
2037 {
2038 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
2039 }
2040
2041 if( match )
2042 matchingFootprints.push_back( footprint );
2043
2044 if( footprint == lastPreexistingFootprint )
2045 {
2046 // No sense going through the newly-created footprints: end of loop
2047 break;
2048 }
2049 }
2050
2051 std::vector<LIB_ID> expectedFpids;
2052 std::unordered_set<wxString> expectedFpidKeys;
2053
2054 auto addExpectedFpid =
2055 [&]( const LIB_ID& aFpid )
2056 {
2057 if( aFpid.empty() )
2058 return;
2059
2060 wxString key = aFpid.Format();
2061
2062 if( expectedFpidKeys.insert( key ).second )
2063 expectedFpids.push_back( aFpid );
2064 };
2065
2066 addExpectedFpid( baseFpid );
2067
2068 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
2069
2070 for( const auto& [variantName, variant] : component->GetVariants() )
2071 {
2072 auto fieldIt = variant.m_fields.find( footprintFieldName );
2073
2074 if( fieldIt == variant.m_fields.end() || fieldIt->second.IsEmpty() )
2075 continue;
2076
2077 LIB_ID parsedId;
2078
2079 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
2080 {
2081 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
2082 EscapeHTML( fieldIt->second ),
2083 variantName,
2084 component->GetReference() );
2085 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2086 ++m_errorCount;
2087 continue;
2088 }
2089
2090 addExpectedFpid( parsedId );
2091 }
2092
2093 // When the schematic-side FPID has no library nickname (legacy format like
2094 // "DGG56" instead of "Package_SO:DGG56"), matching should compare only the
2095 // footprint item name. Otherwise the board footprint (which always has a library
2096 // nickname) will never match, causing perpetual "change footprint" notifications.
2097 auto fpidMatches =
2098 [&]( const LIB_ID& aBoardFpid, const LIB_ID& aExpectedFpid ) -> bool
2099 {
2100 if( aExpectedFpid.IsLegacy() )
2101 return aBoardFpid.GetLibItemName() == aExpectedFpid.GetLibItemName();
2102
2103 return aBoardFpid == aExpectedFpid;
2104 };
2105
2106 auto isExpectedFpid =
2107 [&]( const LIB_ID& aFpid ) -> bool
2108 {
2109 if( aFpid.empty() )
2110 return false;
2111
2112 if( expectedFpidKeys.count( aFpid.Format() ) > 0 )
2113 return true;
2114
2115 for( const LIB_ID& expected : expectedFpids )
2116 {
2117 if( fpidMatches( aFpid, expected ) )
2118 return true;
2119 }
2120
2121 return false;
2122 };
2123
2124 auto takeMatchingFootprint =
2125 [&]( const LIB_ID& aFpid ) -> FOOTPRINT*
2126 {
2127 for( FOOTPRINT* footprint : matchingFootprints )
2128 {
2129 if( usedFootprints.count( footprint ) )
2130 continue;
2131
2132 if( fpidMatches( footprint->GetFPID(), aFpid ) )
2133 return footprint;
2134 }
2135
2136 return nullptr;
2137 };
2138
2139 std::vector<FOOTPRINT*> componentFootprints;
2140 componentFootprints.reserve( expectedFpids.size() );
2141 FOOTPRINT* baseFootprint = nullptr;
2142
2143 if( hasBaseFpid )
2144 baseFootprint = takeMatchingFootprint( baseFpid );
2145 else if( !matchingFootprints.empty() )
2146 baseFootprint = matchingFootprints.front();
2147
2148 if( !baseFootprint && m_replaceFootprints && !matchingFootprints.empty() )
2149 {
2150 FOOTPRINT* replaceCandidate = nullptr;
2151
2152 for( FOOTPRINT* footprint : matchingFootprints )
2153 {
2154 if( usedFootprints.count( footprint ) )
2155 continue;
2156
2157 if( isExpectedFpid( footprint->GetFPID() ) )
2158 continue;
2159
2160 replaceCandidate = footprint;
2161 break;
2162 }
2163
2164 if( replaceCandidate )
2165 {
2166 FOOTPRINT* replaced = replaceFootprint( aNetlist, replaceCandidate, component );
2167
2168 if( replaced )
2169 baseFootprint = replaced;
2170 else
2171 baseFootprint = replaceCandidate;
2172 }
2173 }
2174
2175 if( !baseFootprint && hasBaseFpid )
2176 baseFootprint = addNewFootprint( component, baseFpid );
2177
2178 if( baseFootprint )
2179 {
2180 componentFootprints.push_back( baseFootprint );
2181 usedFootprints.insert( baseFootprint );
2182 footprintMap[ component ] = baseFootprint;
2183 }
2184
2185 for( const LIB_ID& fpid : expectedFpids )
2186 {
2187 if( fpid == baseFpid )
2188 continue;
2189
2190 FOOTPRINT* footprint = takeMatchingFootprint( fpid );
2191
2192 if( !footprint )
2193 footprint = addNewFootprint( component, fpid );
2194
2195 if( footprint )
2196 {
2197 componentFootprints.push_back( footprint );
2198 usedFootprints.insert( footprint );
2199 }
2200 }
2201
2202 for( FOOTPRINT* footprint : componentFootprints )
2203 {
2204 if( !footprint )
2205 continue;
2206
2207 updateFootprintParameters( footprint, component );
2208 updateFootprintGroup( footprint, component );
2209 updateComponentPadConnections( footprint, component );
2210 updateComponentClass( footprint, component );
2211 updateComponentUnits( footprint, component );
2212
2213 sheetPaths.insert( footprint->GetSheetname() );
2214 }
2215
2216 if( !componentFootprints.empty() )
2217 applyComponentVariants( component, componentFootprints, baseFpid );
2218 }
2219
2220 updateCopperZoneNets( aNetlist );
2221 updateGroups( aNetlist );
2222
2223 // Finally go through the board footprints and update all those that *don't* have matching
2224 // component entries.
2225 //
2226 for( FOOTPRINT* footprint : m_board->Footprints() )
2227 {
2228 bool matched = false;
2229 bool doDelete = m_deleteUnusedFootprints;
2230
2231 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
2232 doDelete = false;
2233
2234 bool isStaleVariantFootprint = false;
2235
2236 if( usedFootprints.count( footprint ) )
2237 {
2238 matched = true;
2239 }
2240 else
2241 {
2243 component = aNetlist.GetComponentByPath( footprint->GetPath() );
2244 else
2245 component = aNetlist.GetComponentByReference( footprint->GetReference() );
2246
2247 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
2248 {
2249 // When replace footprints is enabled and a component has variant footprints,
2250 // footprints matching by reference but not in usedFootprints are stale variant
2251 // footprints that should be replaced/removed.
2252 if( m_replaceFootprints && !component->GetVariants().empty() )
2253 {
2254 matched = false;
2255 isStaleVariantFootprint = true;
2256 }
2257 else
2258 {
2259 matched = true;
2260 }
2261 }
2262 }
2263
2264 // Stale variant footprints should be deleted when m_replaceFootprints is enabled,
2265 // regardless of m_deleteUnusedFootprints setting.
2266 if( isStaleVariantFootprint )
2267 doDelete = true;
2268
2269 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
2270 {
2271 if( m_isDryRun )
2272 {
2273 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
2274 footprint->GetReference() );
2275 }
2276 else
2277 {
2278 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
2279 footprint->GetReference() );
2280 }
2281
2282 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2284 doDelete = false;
2285 }
2286
2287 if( doDelete && !matched )
2288 {
2289 if( m_isDryRun )
2290 {
2291 msg.Printf( _( "Remove unused footprint %s." ),
2292 footprint->GetReference() );
2293 }
2294 else
2295 {
2296 m_commit.Remove( footprint );
2297 msg.Printf( _( "Removed unused footprint %s." ),
2298 footprint->GetReference() );
2299 }
2300
2301 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2302 }
2303 else if( !m_isDryRun )
2304 {
2305 if( !matched )
2306 footprint->SetPath( KIID_PATH() );
2307
2308 for( PAD* pad : footprint->Pads() )
2309 {
2310 if( pad->GetNet() )
2311 pad->GetNet()->SetIsCurrent( true );
2312 }
2313 }
2314 }
2315
2316 if( !m_isDryRun )
2317 {
2318 // Finalise the component class manager
2319 m_board->GetComponentClassManager().FinishNetlistUpdate();
2320 m_board->SynchronizeComponentClasses( sheetPaths );
2321
2322 m_board->BuildConnectivity();
2323 testConnectivity( aNetlist, footprintMap );
2324
2325 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2326 {
2327 if( !net->IsCurrent() )
2328 {
2329 msg.Printf( _( "Removed unused net %s." ),
2330 EscapeHTML( net->GetNetname() ) );
2331 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2332 }
2333 }
2334
2335 m_board->RemoveUnusedNets( &m_commit );
2336
2337 // Update board variant registry from netlist
2338 const std::vector<wxString>& netlistVariants = aNetlist.GetVariantNames();
2339
2340 if( !netlistVariants.empty() || !m_board->GetVariantNames().empty() )
2341 {
2342 m_reporter->Report( _( "Updating design variants..." ), RPT_SEVERITY_INFO );
2343
2344 auto findBoardVariantName =
2345 [&]( const wxString& aVariantName ) -> wxString
2346 {
2347 for( const wxString& name : m_board->GetVariantNames() )
2348 {
2349 if( name.CmpNoCase( aVariantName ) == 0 )
2350 return name;
2351 }
2352
2353 return wxEmptyString;
2354 };
2355
2356 std::vector<wxString> updatedVariantNames;
2357 updatedVariantNames.reserve( netlistVariants.size() );
2358
2359 for( const wxString& variantName : netlistVariants )
2360 {
2361 wxString actualName = findBoardVariantName( variantName );
2362
2363 if( actualName.IsEmpty() )
2364 {
2365 m_board->AddVariant( variantName );
2366 actualName = findBoardVariantName( variantName );
2367
2368 if( !actualName.IsEmpty() )
2369 {
2370 msg.Printf( _( "Added variant '%s'." ), actualName );
2371 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2372 }
2373 }
2374
2375 if( actualName.IsEmpty() )
2376 continue;
2377
2378 // Update description if changed
2379 wxString newDescription = aNetlist.GetVariantDescription( variantName );
2380 wxString oldDescription = m_board->GetVariantDescription( actualName );
2381
2382 if( newDescription != oldDescription )
2383 {
2384 m_board->SetVariantDescription( actualName, newDescription );
2385 msg.Printf( _( "Updated description for variant '%s'." ), actualName );
2386 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2387 }
2388
2389 updatedVariantNames.push_back( actualName );
2390 }
2391
2392 std::vector<wxString> variantsToRemove;
2393
2394 for( const wxString& existingName : m_board->GetVariantNames() )
2395 {
2396 bool found = false;
2397
2398 for( const wxString& variantName : netlistVariants )
2399 {
2400 if( existingName.CmpNoCase( variantName ) == 0 )
2401 {
2402 found = true;
2403 break;
2404 }
2405 }
2406
2407 if( !found )
2408 variantsToRemove.push_back( existingName );
2409 }
2410
2411 for( const wxString& variantName : variantsToRemove )
2412 {
2413 m_board->DeleteVariant( variantName );
2414 msg.Printf( _( "Removed variant '%s'." ), variantName );
2415 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2416 }
2417
2418 if( !updatedVariantNames.empty() )
2419 m_board->SetVariantNames( updatedVariantNames );
2420 }
2421
2422 // When new footprints are added, the automatic zone refill is disabled because:
2423 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
2424 // (the auto refills rebuild the connectivity with incomplete data)
2425 // * it is useless because zones will be refilled after placing new footprints
2426 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
2427
2428 // Update net, netcode and netclass data after commiting the netlist
2429 m_board->SynchronizeNetsAndNetClasses( true );
2430 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
2431
2432 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
2433 // sure any modification to netclasses gets persisted to project settings through a save.
2434 if( m_frame )
2435 m_frame->OnModify();
2436 }
2437
2438 if( m_isDryRun )
2439 {
2440 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
2441 delete addedNet.second;
2442
2443 m_addedNets.clear();
2444 }
2445
2446 // Update the ratsnest
2447 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2448 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2449
2450 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
2451 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
2452
2453 return true;
2454}
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
#define ZONE_FILL_OP
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:285
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)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
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:527
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:117
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:93
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:400
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:284
Variant information for a footprint.
Definition footprint.h:146
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:193
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:173
bool GetExcludedFromBOM() const
Definition footprint.h:162
bool GetExcludedFromPosFiles() const
Definition footprint.h:165
bool GetDNP() const
Definition footprint.h:159
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:1047
void SetPosition(const VECTOR2I &aPos) override
EDA_ANGLE GetOrientation() const
Definition footprint.h:330
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
wxString GetSheetname() const
Definition footprint.h:377
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:375
void SetFilters(const wxString &aFilters)
Definition footprint.h:384
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:850
void SetAttributes(int aAttributes)
Definition footprint.h:418
void SetSheetfile(const wxString &aSheetfile)
Definition footprint.h:381
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:1054
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1048
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:306
int GetAttributes() const
Definition footprint.h:417
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:339
wxString GetFPIDAsString() const
Definition footprint.h:357
wxString GetSheetfile() const
Definition footprint.h:380
const LIB_ID & GetFPID() const
Definition footprint.h:351
void SetReference(const wxString &aReference)
Definition footprint.h:757
bool IsLocked() const override
Definition footprint.h:544
void SetValue(const wxString &aValue)
Definition footprint.h:778
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:849
wxString GetFilters() const
Definition footprint.h:383
void SetSheetname(const wxString &aSheetname)
Definition footprint.h:378
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:773
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:751
const KIID_PATH & GetPath() const
Definition footprint.h:374
VECTOR2I GetPosition() const override
Definition footprint.h:327
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:49
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:54
void SetIsCurrent(bool isCurrent)
Definition netinfo.h:136
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:247
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:97
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:330
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:84
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:420
wxString wx_str() const
Definition utf8.cpp:45
Handle a list of polygons defining a copper zone.
Definition zone.h:73
The common library.
#define _(s)
@ NO_RECURSE
Definition eda_item.h:53
@ FP_DNP
Definition footprint.h:90
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:86
@ FP_BOARD_ONLY
Definition footprint.h:88
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:87
@ FP_JUST_ADDED
Definition footprint.h:89
@ 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)
VECTOR3I expected(15, 30, 45)
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695