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