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 COMPONENT* aNetlistComponent )
397{
398 wxString msg;
399
400 // Create a copy only if the footprint has not been added during this update
401 FOOTPRINT* copy = nullptr;
402
403 if( !m_commit.GetStatus( aPcbFootprint ) )
404 {
405 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
406 copy->SetParentGroup( nullptr );
407 }
408
409 bool changed = false;
410
411 // Test for reference designator field change.
412 if( aPcbFootprint->GetReference() != aNetlistComponent->GetReference() )
413 {
414 if( m_isDryRun )
415 {
416 msg.Printf( _( "Change %s reference designator to %s." ),
417 aPcbFootprint->GetReference(),
418 aNetlistComponent->GetReference() );
419 }
420 else
421 {
422 msg.Printf( _( "Changed %s reference designator to %s." ),
423 aPcbFootprint->GetReference(),
424 aNetlistComponent->GetReference() );
425
426 changed = true;
427 aPcbFootprint->SetReference( aNetlistComponent->GetReference() );
428 }
429
430 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
431 }
432
433 // Test for value field change.
434 if( aPcbFootprint->GetValue() != aNetlistComponent->GetValue() )
435 {
436 if( m_isDryRun )
437 {
438 msg.Printf( _( "Change %s value from %s to %s." ),
439 aPcbFootprint->GetReference(),
440 EscapeHTML( aPcbFootprint->GetValue() ),
441 EscapeHTML( aNetlistComponent->GetValue() ) );
442 }
443 else
444 {
445 msg.Printf( _( "Changed %s value from %s to %s." ),
446 aPcbFootprint->GetReference(),
447 EscapeHTML( aPcbFootprint->GetValue() ),
448 EscapeHTML( aNetlistComponent->GetValue() ) );
449
450 changed = true;
451 aPcbFootprint->SetValue( aNetlistComponent->GetValue() );
452 }
453
454 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
455 }
456
457 // Test for time stamp change.
458 KIID_PATH new_path = aNetlistComponent->GetPath();
459
460 if( !aNetlistComponent->GetKIIDs().empty() )
461 new_path.push_back( aNetlistComponent->GetKIIDs().front() );
462
463 if( aPcbFootprint->GetPath() != new_path )
464 {
465 if( m_isDryRun )
466 {
467 msg.Printf( _( "Update %s symbol association from %s to %s." ),
468 aPcbFootprint->GetReference(),
469 EscapeHTML( aPcbFootprint->GetPath().AsString() ),
470 EscapeHTML( new_path.AsString() ) );
471 }
472 else
473 {
474 msg.Printf( _( "Updated %s symbol association from %s to %s." ),
475 aPcbFootprint->GetReference(),
476 EscapeHTML( aPcbFootprint->GetPath().AsString() ),
477 EscapeHTML( new_path.AsString() ) );
478
479 changed = true;
480 aPcbFootprint->SetPath( new_path );
481 }
482
483 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
484 }
485
486 nlohmann::ordered_map<wxString, wxString> fpFieldsAsMap;
487
488 for( PCB_FIELD* field : aPcbFootprint->GetFields() )
489 {
490 // These fields are individually checked above
491 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
492 {
493 continue;
494 }
495
496 fpFieldsAsMap[field->GetName()] = field->GetText();
497 }
498
499 // Remove the ref/value/footprint fields that are individually handled
500 nlohmann::ordered_map<wxString, wxString> compFields = aNetlistComponent->GetFields();
501 compFields.erase( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
502 compFields.erase( GetCanonicalFieldName( FIELD_T::VALUE ) );
503 compFields.erase( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) );
504
505 // Remove any component class fields - these are not editable in the pcb editor
506 compFields.erase( wxT( "Component Class" ) );
507
508 // Fields are stored as an ordered map, but we don't (yet) support reordering
509 // the footprint fields to match the symbol, so we manually check the fields
510 // in the order they are stored in the symbol.
511 bool same = true;
512 bool remove_only = true;
513
514 for( const auto& [name, value] : compFields )
515 {
516 if( fpFieldsAsMap.count( name ) == 0 || fpFieldsAsMap[name] != value )
517 {
518 same = false;
519 remove_only = false;
520 break;
521 }
522 }
523
524 for( const auto& [name, value] : fpFieldsAsMap )
525 {
526 if( compFields.count( name ) == 0 )
527 {
528 same = false;
529 break;
530 }
531 }
532
533 if( !same )
534 {
535 if( m_isDryRun )
536 {
537 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
538 {
539 msg.Printf( _( "Update %s fields." ), aPcbFootprint->GetReference() );
540 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
541 }
542
543 // Remove fields that aren't present in the symbol
544 for( PCB_FIELD* field : aPcbFootprint->GetFields() )
545 {
546 if( field->IsMandatory() )
547 continue;
548
549 if( compFields.count( field->GetName() ) == 0 )
550 {
552 {
553 msg.Printf( _( "Remove %s footprint fields not in symbol." ),
554 aPcbFootprint->GetReference() );
555 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
556 }
557
558 break;
559 }
560 }
561 }
562 else
563 {
564 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
565 {
566 msg.Printf( _( "Updated %s fields." ), aPcbFootprint->GetReference() );
567 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
568
569 changed = true;
570
571 // Add or change field value
572 for( auto& [name, value] : compFields )
573 {
574 if( aPcbFootprint->HasField( name ) )
575 {
576 aPcbFootprint->GetField( name )->SetText( value );
577 }
578 else
579 {
580 PCB_FIELD* newField = new PCB_FIELD( aPcbFootprint, FIELD_T::USER );
581 aPcbFootprint->Add( newField );
582
583 newField->SetName( name );
584 newField->SetText( value );
585 newField->SetVisible( false );
586 newField->SetLayer( aPcbFootprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
587
588 // Give the relative position (0,0) in footprint
589 newField->SetPosition( aPcbFootprint->GetPosition() );
590 // Give the footprint orientation
591 newField->Rotate( aPcbFootprint->GetPosition(), aPcbFootprint->GetOrientation() );
592
593 if( m_frame )
594 newField->StyleFromSettings( m_frame->GetDesignSettings(), true );
595 }
596 }
597 }
598
600 {
601 bool warned = false;
602
603 std::vector<PCB_FIELD*> fieldList;
604 aPcbFootprint->GetFields( fieldList, false );
605
606 for( PCB_FIELD* field : fieldList )
607 {
608 if( field->IsMandatory() )
609 continue;
610
611 if( compFields.count( field->GetName() ) == 0 )
612 {
613 if( !warned )
614 {
615 warned = true;
616 msg.Printf( _( "Removed %s footprint fields not in symbol." ),
617 aPcbFootprint->GetReference() );
618 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
619 }
620
621 aPcbFootprint->Remove( field );
622
623 if( m_frame )
624 m_frame->GetCanvas()->GetView()->Remove( field );
625
626 delete field;
627 }
628 }
629 }
630 }
631 }
632
633 wxString sheetname;
634 wxString sheetfile;
635 wxString fpFilters;
636
637 wxString humanSheetPath = aNetlistComponent->GetHumanReadablePath();
638
639 if( !humanSheetPath.empty() )
640 sheetname = humanSheetPath;
641 else if( aNetlistComponent->GetProperties().count( wxT( "Sheetname" ) ) > 0 )
642 sheetname = aNetlistComponent->GetProperties().at( wxT( "Sheetname" ) );
643
644 if( aNetlistComponent->GetProperties().count( wxT( "Sheetfile" ) ) > 0 )
645 sheetfile = aNetlistComponent->GetProperties().at( wxT( "Sheetfile" ) );
646
647 if( aNetlistComponent->GetProperties().count( wxT( "ki_fp_filters" ) ) > 0 )
648 fpFilters = aNetlistComponent->GetProperties().at( wxT( "ki_fp_filters" ) );
649
650 if( sheetname != aPcbFootprint->GetSheetname() )
651 {
652 if( m_isDryRun )
653 {
654 msg.Printf( _( "Update %s sheetname to '%s'." ),
655 aPcbFootprint->GetReference(),
656 EscapeHTML( sheetname ) );
657 }
658 else
659 {
660 aPcbFootprint->SetSheetname( sheetname );
661 msg.Printf( _( "Updated %s sheetname to '%s'." ),
662 aPcbFootprint->GetReference(),
663 EscapeHTML( sheetname ) );
664 }
665
666 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
667 }
668
669 if( sheetfile != aPcbFootprint->GetSheetfile() )
670 {
671 if( m_isDryRun )
672 {
673 msg.Printf( _( "Update %s sheetfile to '%s'." ),
674 aPcbFootprint->GetReference(),
675 EscapeHTML( sheetfile ) );
676 }
677 else
678 {
679 aPcbFootprint->SetSheetfile( sheetfile );
680 msg.Printf( _( "Updated %s sheetfile to '%s'." ),
681 aPcbFootprint->GetReference(),
682 EscapeHTML( sheetfile ) );
683 }
684
685 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
686 }
687
688 if( fpFilters != aPcbFootprint->GetFilters() )
689 {
690 if( m_isDryRun )
691 {
692 msg.Printf( _( "Update %s footprint filters to '%s'." ),
693 aPcbFootprint->GetReference(),
694 EscapeHTML( fpFilters ) );
695 }
696 else
697 {
698 aPcbFootprint->SetFilters( fpFilters );
699 msg.Printf( _( "Updated %s footprint filters to '%s'." ),
700 aPcbFootprint->GetReference(),
701 EscapeHTML( fpFilters ) );
702 }
703
704 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
705 }
706
707 if( ( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) > 0 )
708 != ( ( aPcbFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
709 {
710 if( m_isDryRun )
711 {
712 if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) )
713 {
714 msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ),
715 aPcbFootprint->GetReference() );
716 }
717 else
718 {
719 msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ),
720 aPcbFootprint->GetReference() );
721 }
722 }
723 else
724 {
725 int attributes = aPcbFootprint->GetAttributes();
726
727 if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) )
728 {
729 attributes |= FP_EXCLUDE_FROM_BOM;
730 msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ),
731 aPcbFootprint->GetReference() );
732 }
733 else
734 {
735 attributes &= ~FP_EXCLUDE_FROM_BOM;
736 msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ),
737 aPcbFootprint->GetReference() );
738 }
739
740 changed = true;
741 aPcbFootprint->SetAttributes( attributes );
742 }
743
744 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
745 }
746
747 if( ( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) > 0 )
748 != ( ( aPcbFootprint->GetAttributes() & FP_DNP ) > 0 ) )
749 {
750 if( m_isDryRun )
751 {
752 if( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) )
753 {
754 msg.Printf( _( "Add %s 'Do not place' fabrication attribute." ),
755 aPcbFootprint->GetReference() );
756 }
757 else
758 {
759 msg.Printf( _( "Remove %s 'Do not place' fabrication attribute." ),
760 aPcbFootprint->GetReference() );
761 }
762 }
763 else
764 {
765 int attributes = aPcbFootprint->GetAttributes();
766
767 if( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) )
768 {
769 attributes |= FP_DNP;
770 msg.Printf( _( "Added %s 'Do not place' fabrication attribute." ),
771 aPcbFootprint->GetReference() );
772 }
773 else
774 {
775 attributes &= ~FP_DNP;
776 msg.Printf( _( "Removed %s 'Do not place' fabrication attribute." ),
777 aPcbFootprint->GetReference() );
778 }
779
780 changed = true;
781 aPcbFootprint->SetAttributes( attributes );
782 }
783
784 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
785 }
786
787 if( ( aNetlistComponent->GetProperties().count( wxT( "exclude_from_pos_files" ) ) > 0 )
788 != ( ( aPcbFootprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES ) > 0 ) )
789 {
790 if( m_isDryRun )
791 {
792 if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_pos_files" ) ) )
793 {
794 msg.Printf( _( "Add %s 'exclude from position files' fabrication attribute." ),
795 aPcbFootprint->GetReference() );
796 }
797 else
798 {
799 msg.Printf( _( "Remove %s 'exclude from position files' fabrication attribute." ),
800 aPcbFootprint->GetReference() );
801 }
802 }
803 else
804 {
805 int attributes = aPcbFootprint->GetAttributes();
806
807 if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_pos_files" ) ) )
808 {
809 attributes |= FP_EXCLUDE_FROM_POS_FILES;
810 msg.Printf( _( "Added %s 'exclude from position files' fabrication attribute." ),
811 aPcbFootprint->GetReference() );
812 }
813 else
814 {
815 attributes &= ~FP_EXCLUDE_FROM_POS_FILES;
816 msg.Printf( _( "Removed %s 'exclude from position files' fabrication attribute." ),
817 aPcbFootprint->GetReference() );
818 }
819
820 changed = true;
821 aPcbFootprint->SetAttributes( attributes );
822 }
823
824 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
825 }
826
827 if( aNetlistComponent->GetDuplicatePadNumbersAreJumpers()
828 != aPcbFootprint->GetDuplicatePadNumbersAreJumpers() )
829 {
830 bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
831
832 if( !m_isDryRun )
833 {
834 changed = true;
835 aPcbFootprint->SetDuplicatePadNumbersAreJumpers( value );
836
837 if( value )
838 {
839 msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
840 aPcbFootprint->GetReference() );
841 }
842 else
843 {
844 msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
845 aPcbFootprint->GetReference() );
846 }
847 }
848 else
849 {
850 if( value )
851 {
852 msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
853 aPcbFootprint->GetReference() );
854 }
855 else
856 {
857 msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
858 aPcbFootprint->GetReference() );
859 }
860 }
861
862 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
863 }
864
865 if( aNetlistComponent->JumperPadGroups() != aPcbFootprint->JumperPadGroups() )
866 {
867 if( !m_isDryRun )
868 {
869 changed = true;
870 aPcbFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
871 msg.Printf( _( "Updated %s jumper pad groups" ), aPcbFootprint->GetReference() );
872 }
873 else
874 {
875 msg.Printf( _( "Update %s jumper pad groups" ), aPcbFootprint->GetReference() );
876 }
877
878 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
879 }
880
881 if( changed && copy )
882 m_commit.Modified( aPcbFootprint, copy );
883 else
884 delete copy;
885
886 return true;
887}
888
889
891 COMPONENT* aNetlistComponent )
892{
893 if( !m_transferGroups )
894 return false;
895
896 wxString msg;
897
898 // Create a copy only if the footprint has not been added during this update
899 FOOTPRINT* copy = nullptr;
900
901 if( !m_commit.GetStatus( aPcbFootprint ) )
902 {
903 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
904 copy->SetParentGroup( nullptr );
905 }
906
907 bool changed = false;
908
909 // These hold the info for group and group KIID coming from the netlist
910 // newGroup may point to an existing group on the board if we find an
911 // incoming group UUID that matches an existing group
912 PCB_GROUP* newGroup = nullptr;
913 KIID newGroupKIID = aNetlistComponent->GetGroup() ? aNetlistComponent->GetGroup()->uuid : 0;
914
915 PCB_GROUP* existingGroup = static_cast<PCB_GROUP*>( aPcbFootprint->GetParentGroup() );
916 KIID existingGroupKIID = existingGroup ? existingGroup->m_Uuid : 0;
917
918 // Find existing group based on matching UUIDs
919 auto it = std::find_if( m_board->Groups().begin(), m_board->Groups().end(),
920 [&](PCB_GROUP* group) {
921 return group->m_Uuid == newGroupKIID;
922 });
923
924 // If we find a group with the same UUID, use it
925 if( it != m_board->Groups().end() )
926 newGroup = *it;
927
928 // No changes, nothing to do
929 if( newGroupKIID == existingGroupKIID )
930 return changed;
931
932 // Remove from existing group
933 if( existingGroupKIID != 0 )
934 {
935 if( m_isDryRun )
936 {
937 msg.Printf( _( "Remove %s from group '%s'." ),
938 aPcbFootprint->GetReference(),
939 EscapeHTML( existingGroup->GetName() ) );
940 }
941 else
942 {
943 msg.Printf( _( "Removed %s from group '%s'." ),
944 aPcbFootprint->GetReference(),
945 EscapeHTML( existingGroup->GetName() ) );
946
947 changed = true;
948 m_commit.Modify( existingGroup, nullptr, RECURSE_MODE::NO_RECURSE );
949 existingGroup->RemoveItem( aPcbFootprint );
950
951 if( existingGroup->GetItems().size() < 2 )
952 {
953 existingGroup->RemoveAll();
954 m_commit.Remove( existingGroup );
955 }
956 }
957
958 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
959 }
960
961 // Add to new group
962 if( newGroupKIID != 0 )
963 {
964 if( m_isDryRun )
965 {
966 msg.Printf( _( "Add %s to group '%s'." ),
967 aPcbFootprint->GetReference(),
968 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
969 }
970 else
971 {
972 msg.Printf( _( "Added %s to group '%s'." ),
973 aPcbFootprint->GetReference(),
974 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
975
976 changed = true;
977
978 if( newGroup == nullptr )
979 {
980 newGroup = new PCB_GROUP( m_board );
981 const_cast<KIID&>( newGroup->m_Uuid ) = newGroupKIID;
982 newGroup->SetName( aNetlistComponent->GetGroup()->name );
983
984 // Add the group to the board manually so we can find it by checking
985 // board groups for later footprints that are checking for existing groups
986 m_board->Add( newGroup );
987 m_commit.Added( newGroup );
988 }
989 else
990 {
991 m_commit.Modify( newGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
992 }
993
994 newGroup->AddItem( aPcbFootprint );
995 }
996
997 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
998 }
999
1000 if( changed && copy )
1001 m_commit.Modified( aPcbFootprint, copy );
1002 else if( copy )
1003 delete copy;
1004
1005 return changed;
1006}
1007
1008
1010 COMPONENT* aNewComponent )
1011{
1012 wxString msg;
1013
1014 // Create a copy only if the footprint has not been added during this update
1015 FOOTPRINT* copy = nullptr;
1016
1017 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
1018 {
1019 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1020 copy->SetParentGroup( nullptr );
1021 }
1022
1023 bool changed = false;
1024
1025 // At this point, the component footprint is updated. Now update the nets.
1026 std::deque<PAD*> pads = aFootprint->Pads();
1027 std::set<wxString> padNetnames;
1028
1029 std::sort( pads.begin(), pads.end(),
1030 []( PAD* a, PAD* b )
1031 {
1032 return a->m_Uuid < b->m_Uuid;
1033 } );
1034
1035 for( PAD* pad : pads )
1036 {
1037 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
1038
1039 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1040 wxT( "Processing pad %s of component %s" ),
1041 pad->GetNumber(),
1042 aNewComponent->GetReference() );
1043
1044 wxString pinFunction;
1045 wxString pinType;
1046
1047 if( net.IsValid() ) // i.e. the pad has a name
1048 {
1049 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1050 wxT( " Found valid net: %s" ),
1051 net.GetNetName() );
1052 pinFunction = net.GetPinFunction();
1053 pinType = net.GetPinType();
1054 }
1055 else
1056 {
1057 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1058 wxT( " No net found for pad %s" ),
1059 pad->GetNumber() );
1060 }
1061
1062 if( !m_isDryRun )
1063 {
1064 if( pad->GetPinFunction() != pinFunction )
1065 {
1066 changed = true;
1067 pad->SetPinFunction( pinFunction );
1068 }
1069
1070 if( pad->GetPinType() != pinType )
1071 {
1072 changed = true;
1073 pad->SetPinType( pinType );
1074 }
1075 }
1076 else
1077 {
1078 cachePinFunction( pad, pinFunction );
1079 }
1080
1081 // Test if new footprint pad has no net (pads not on copper layers have no net).
1082 if( !net.IsValid() || !pad->IsOnCopperLayer() )
1083 {
1084 if( !pad->GetNetname().IsEmpty() )
1085 {
1086 if( m_isDryRun )
1087 {
1088 msg.Printf( _( "Disconnect %s pin %s." ),
1089 aFootprint->GetReference(),
1090 EscapeHTML( pad->GetNumber() ) );
1091 }
1092 else
1093 {
1094 msg.Printf( _( "Disconnected %s pin %s." ),
1095 aFootprint->GetReference(),
1096 EscapeHTML( pad->GetNumber() ) );
1097 }
1098
1099 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1100 }
1101 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
1102 {
1103 // pad is connectable but has no net found in netlist
1104 msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
1105 aFootprint->GetReference(),
1106 EscapeHTML( pad->GetNumber() ),
1107 EscapeHTML( pad->GetNumber() ) );
1108 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
1110 }
1111
1112 if( !m_isDryRun )
1113 {
1114 changed = true;
1115 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
1116
1117 // If the pad has no net from netlist (i.e. not in netlist
1118 // it cannot have a pin function
1119 if( pad->GetNetname().IsEmpty() )
1120 pad->SetPinFunction( wxEmptyString );
1121
1122 }
1123 else
1124 {
1125 cacheNetname( pad, wxEmptyString );
1126 }
1127 }
1128 else // New footprint pad has a net.
1129 {
1130 wxString netName = net.GetNetName();
1131
1132 if( pad->IsNoConnectPad() )
1133 {
1134 netName = wxString::Format( wxS( "%s" ), net.GetNetName() );
1135
1136 for( int jj = 1; !padNetnames.insert( netName ).second; jj++ )
1137 {
1138 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1139 }
1140 }
1141
1142 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1143
1144 if( netinfo && !m_isDryRun )
1145 netinfo->SetIsCurrent( true );
1146
1147 if( pad->GetNetname() != netName )
1148 {
1149
1150 if( netinfo == nullptr )
1151 {
1152 // It might be a new net that has not been added to the board yet
1153 if( m_addedNets.count( netName ) )
1154 netinfo = m_addedNets[ netName ];
1155 }
1156
1157 if( netinfo == nullptr )
1158 {
1159 netinfo = new NETINFO_ITEM( m_board, netName );
1160
1161 // It is a new net, we have to add it
1162 if( !m_isDryRun )
1163 {
1164 changed = true;
1165 m_commit.Add( netinfo );
1166 }
1167
1168 m_addedNets[netName] = netinfo;
1169 msg.Printf( _( "Add net %s." ),
1170 EscapeHTML( UnescapeString( netName ) ) );
1171 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1172 }
1173
1174 if( !pad->GetNetname().IsEmpty() )
1175 {
1176 m_oldToNewNets[ pad->GetNetname() ] = netName;
1177
1178 if( m_isDryRun )
1179 {
1180 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1181 aFootprint->GetReference(),
1182 EscapeHTML( pad->GetNumber() ),
1183 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1184 EscapeHTML( UnescapeString( netName ) ) );
1185 }
1186 else
1187 {
1188 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1189 aFootprint->GetReference(),
1190 EscapeHTML( pad->GetNumber() ),
1191 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1192 EscapeHTML( UnescapeString( netName ) ) );
1193 }
1194 }
1195 else
1196 {
1197 if( m_isDryRun )
1198 {
1199 msg.Printf( _( "Connect %s pin %s to %s."),
1200 aFootprint->GetReference(),
1201 EscapeHTML( pad->GetNumber() ),
1202 EscapeHTML( UnescapeString( netName ) ) );
1203 }
1204 else
1205 {
1206 msg.Printf( _( "Connected %s pin %s to %s."),
1207 aFootprint->GetReference(),
1208 EscapeHTML( pad->GetNumber() ),
1209 EscapeHTML( UnescapeString( netName ) ) );
1210 }
1211 }
1212
1213 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1214
1215 if( !m_isDryRun )
1216 {
1217 changed = true;
1218 pad->SetNet( netinfo );
1219 }
1220 else
1221 {
1222 cacheNetname( pad, netName );
1223 }
1224 }
1225 }
1226 }
1227
1228 if( changed && copy )
1229 m_commit.Modified( aFootprint, copy );
1230 else if( copy )
1231 delete copy;
1232
1233 return true;
1234}
1235
1236
1238{
1239 // Build the footprint-side representation from the netlist component
1240 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1241
1242 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1243 newUnits.push_back( { u.m_unitName, u.m_pins } );
1244
1245 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1246
1247 auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
1248 const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
1249 {
1250 if( a.size() != b.size() )
1251 return false;
1252
1253 for( size_t i = 0; i < a.size(); ++i )
1254 {
1255 if( a[i].m_unitName != b[i].m_unitName )
1256 return false;
1257
1258 if( a[i].m_pins != b[i].m_pins )
1259 return false;
1260 }
1261
1262 return true;
1263 };
1264
1265 if( unitsEqual( curUnits, newUnits ) )
1266 return false;
1267
1268 wxString msg;
1269
1270 if( m_isDryRun )
1271 {
1272 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1273 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1274 return false; // no actual change on board during dry run
1275 }
1276
1277 // Create a copy only if the footprint has not been added during this update
1278 FOOTPRINT* copy = nullptr;
1279
1280 if( !m_commit.GetStatus( aFootprint ) )
1281 {
1282 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1283 copy->SetParentGroup( nullptr );
1284 }
1285
1286 aFootprint->SetUnitInfo( newUnits );
1287
1288 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1289 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1290
1291 if( copy )
1292 m_commit.Modified( aFootprint, copy );
1293
1294 return true;
1295}
1296
1297
1299 const std::vector<FOOTPRINT*>& aFootprints,
1300 const LIB_ID& aBaseFpid )
1301{
1302 const auto& variants = aComponent->GetVariants();
1303
1304 if( variants.empty() )
1305 return;
1306
1307 if( aBaseFpid.empty() )
1308 return;
1309
1310 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
1311
1312 struct VARIANT_INFO
1313 {
1314 wxString name;
1315 const COMPONENT_VARIANT* variant;
1316 LIB_ID activeFpid;
1317 };
1318
1319 std::vector<VARIANT_INFO> variantInfo;
1320 variantInfo.reserve( variants.size() );
1321
1322 for( const auto& [variantName, variant] : variants )
1323 {
1324 LIB_ID activeFpid = aBaseFpid;
1325
1326 auto fieldIt = variant.m_fields.find( footprintFieldName );
1327
1328 if( fieldIt != variant.m_fields.end() && !fieldIt->second.IsEmpty() )
1329 {
1330 LIB_ID parsedId;
1331
1332 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
1333 {
1334 wxString msg;
1335 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
1336 EscapeHTML( fieldIt->second ),
1337 variantName,
1338 aComponent->GetReference() );
1339 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1340 ++m_errorCount;
1341 }
1342 else
1343 {
1344 activeFpid = parsedId;
1345 }
1346 }
1347
1348 variantInfo.push_back( { variantName, &variant, activeFpid } );
1349 }
1350
1351 for( FOOTPRINT* footprint : aFootprints )
1352 {
1353 if( !footprint )
1354 continue;
1355
1356 FOOTPRINT* copy = nullptr;
1357
1358 if( !m_isDryRun && !m_commit.GetStatus( footprint ) )
1359 {
1360 copy = static_cast<FOOTPRINT*>( footprint->Clone() );
1361 copy->SetParentGroup( nullptr );
1362 }
1363
1364 bool changed = false;
1365
1366 for( const VARIANT_INFO& info : variantInfo )
1367 {
1368 const COMPONENT_VARIANT& variant = *info.variant;
1369
1370 // During dry run, just read current state. During actual run, create variant if needed.
1371 const FOOTPRINT_VARIANT* currentVariant = footprint->GetVariant( info.name );
1372
1373 // Check if this footprint is the active one for this variant
1374 bool isActiveFootprint = ( footprint->GetFPID() == info.activeFpid );
1375
1376 // If this footprint is not active for this variant, it should be DNP.
1377 // Otherwise, apply explicit overrides from schematic, or reset to base footprint value.
1378 bool targetDnp;
1379
1380 if( !isActiveFootprint )
1381 {
1382 targetDnp = true;
1383 }
1384 else
1385 {
1386 targetDnp = variant.m_hasDnp ? variant.m_dnp : footprint->IsDNP();
1387 }
1388
1389 bool currentDnp = currentVariant ? currentVariant->GetDNP() : footprint->IsDNP();
1390
1391 if( currentDnp != targetDnp )
1392 {
1393 wxString msg;
1394
1395 if( m_isDryRun )
1396 {
1397 msg.Printf( targetDnp ? _( "Add %s:%s 'Do not place' variant attribute." )
1398 : _( "Remove %s:%s 'Do not place' variant attribute." ),
1399 footprint->GetReference(), info.name );
1400 }
1401 else
1402 {
1403 msg.Printf( targetDnp ? _( "Added %s:%s 'Do not place' variant attribute." )
1404 : _( "Removed %s:%s 'Do not place' variant attribute." ),
1405 footprint->GetReference(), info.name );
1406
1407 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1408
1409 if( fpVariant )
1410 fpVariant->SetDNP( targetDnp );
1411 }
1412
1413 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1414 changed = true;
1415 }
1416
1417 bool targetExcludedFromBOM = variant.m_hasExcludedFromBOM
1418 ? variant.m_excludedFromBOM
1419 : footprint->IsExcludedFromBOM();
1420 bool currentExcludedFromBOM = currentVariant ? currentVariant->GetExcludedFromBOM()
1421 : footprint->IsExcludedFromBOM();
1422
1423 if( currentExcludedFromBOM != targetExcludedFromBOM )
1424 {
1425 wxString msg;
1426
1427 if( m_isDryRun )
1428 {
1429 msg.Printf( targetExcludedFromBOM
1430 ? _( "Add %s:%s 'exclude from BOM' variant attribute." )
1431 : _( "Remove %s:%s 'exclude from BOM' variant attribute." ),
1432 footprint->GetReference(), info.name );
1433 }
1434 else
1435 {
1436 msg.Printf( targetExcludedFromBOM
1437 ? _( "Added %s:%s 'exclude from BOM' variant attribute." )
1438 : _( "Removed %s:%s 'exclude from BOM' variant attribute." ),
1439 footprint->GetReference(), info.name );
1440
1441 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1442
1443 if( fpVariant )
1444 fpVariant->SetExcludedFromBOM( targetExcludedFromBOM );
1445 }
1446
1447 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1448 changed = true;
1449 }
1450
1451 bool targetExcludedFromPosFiles = variant.m_hasExcludedFromPosFiles
1452 ? variant.m_excludedFromPosFiles
1453 : footprint->IsExcludedFromPosFiles();
1454 bool currentExcludedFromPosFiles = currentVariant
1455 ? currentVariant->GetExcludedFromPosFiles()
1456 : footprint->IsExcludedFromPosFiles();
1457
1458 if( currentExcludedFromPosFiles != targetExcludedFromPosFiles )
1459 {
1460 wxString msg;
1461
1462 if( m_isDryRun )
1463 {
1464 msg.Printf( targetExcludedFromPosFiles
1465 ? _( "Add %s:%s 'exclude from position files' variant attribute." )
1466 : _( "Remove %s:%s 'exclude from position files' variant attribute." ),
1467 footprint->GetReference(), info.name );
1468 }
1469 else
1470 {
1471 msg.Printf( targetExcludedFromPosFiles
1472 ? _( "Added %s:%s 'exclude from position files' variant attribute." )
1473 : _( "Removed %s:%s 'exclude from position files' variant attribute." ),
1474 footprint->GetReference(), info.name );
1475
1476 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1477
1478 if( fpVariant )
1479 fpVariant->SetExcludedFromPosFiles( targetExcludedFromPosFiles );
1480 }
1481
1482 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1483 changed = true;
1484 }
1485
1486 for( const auto& [fieldName, fieldValue] : variant.m_fields )
1487 {
1488 if( fieldName.CmpNoCase( footprintFieldName ) == 0 )
1489 continue;
1490
1491 bool hasCurrentValue = currentVariant && currentVariant->HasFieldValue( fieldName );
1492 wxString currentValue = hasCurrentValue ? currentVariant->GetFieldValue( fieldName )
1493 : wxString();
1494
1495 if( currentValue != fieldValue )
1496 {
1497 wxString msg;
1498
1499 if( m_isDryRun )
1500 {
1501 msg.Printf( _( "Change %s:%s field '%s' to '%s'." ),
1502 footprint->GetReference(), info.name, fieldName, fieldValue );
1503 }
1504 else
1505 {
1506 msg.Printf( _( "Changed %s:%s field '%s' to '%s'." ),
1507 footprint->GetReference(), info.name, fieldName, fieldValue );
1508
1509 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1510
1511 if( fpVariant )
1512 fpVariant->SetFieldValue( fieldName, fieldValue );
1513 }
1514
1515 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1516 changed = true;
1517 }
1518 }
1519 }
1520
1521 // For the default variant: if this footprint is not the base footprint
1522 // it should be DNP by default
1523 bool isBaseFootprint = ( footprint->GetFPID() == aBaseFpid );
1524
1525 if( !isBaseFootprint && !footprint->IsDNP() )
1526 {
1527 wxString msg;
1528 msg.Printf( m_isDryRun ? _( "Add %s 'Do not place' fabrication attribute." )
1529 : _( "Added %s 'Do not place' fabrication attribute." ),
1530 footprint->GetReference() );
1531
1532 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1533
1534 if( !m_isDryRun )
1535 footprint->SetDNP( true );
1536
1537 changed = true;
1538 }
1539
1540 if( !m_isDryRun && changed && copy )
1541 m_commit.Modified( footprint, copy );
1542 else
1543 delete copy;
1544 }
1545}
1546
1547
1549{
1550 for( ZONE* zone : m_board->Zones() )
1551 {
1552 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1553 continue;
1554
1555 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1556 }
1557}
1558
1559
1561{
1562 wxString msg;
1563 std::set<wxString> netlistNetnames;
1564
1565 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1566 {
1567 const COMPONENT* component = aNetlist.GetComponent( ii );
1568
1569 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1570 {
1571 const COMPONENT_NET& net = component->GetNet( jj );
1572 netlistNetnames.insert( net.GetNetName() );
1573 }
1574 }
1575
1576 for( PCB_TRACK* via : m_board->Tracks() )
1577 {
1578 if( via->Type() != PCB_VIA_T )
1579 continue;
1580
1581 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1582 {
1583 wxString updatedNetname = wxEmptyString;
1584
1585 // Take via name from name change map if it didn't match to a new pad
1586 // (this is useful for stitching vias that don't connect to tracks)
1587 if( m_oldToNewNets.count( via->GetNetname() ) )
1588 {
1589 updatedNetname = m_oldToNewNets[via->GetNetname()];
1590 }
1591
1592 if( !updatedNetname.IsEmpty() )
1593 {
1594 if( m_isDryRun )
1595 {
1596 wxString originalNetname = via->GetNetname();
1597
1598 msg.Printf( _( "Reconnect via from %s to %s." ),
1599 EscapeHTML( UnescapeString( originalNetname ) ),
1600 EscapeHTML( UnescapeString( updatedNetname ) ) );
1601
1602 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1603 }
1604 else
1605 {
1606 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1607
1608 if( !netinfo )
1609 netinfo = m_addedNets[updatedNetname];
1610
1611 if( netinfo )
1612 {
1613 wxString originalNetname = via->GetNetname();
1614
1615 m_commit.Modify( via );
1616 via->SetNet( netinfo );
1617
1618 msg.Printf( _( "Reconnected via from %s to %s." ),
1619 EscapeHTML( UnescapeString( originalNetname ) ),
1620 EscapeHTML( UnescapeString( updatedNetname ) ) );
1621
1622 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1623 }
1624 }
1625 }
1626 else
1627 {
1628 msg.Printf( _( "Via connected to unknown net (%s)." ),
1629 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1630 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1632 }
1633 }
1634 }
1635
1636 // Board connectivity net names are not the same as schematic connectivity net names.
1637 // Footprints that contain multiple overlapping pads with the same number are suffixed
1638 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1639 // exposed in the zone net name list.
1640 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1641 {
1642 if( netlistNetnames.count( aNetName ) )
1643 return true;
1644
1645 // If the zone net name is a pseudo net name, check if the root net name is in the net
1646 // list. If so, then this is a valid net.
1647 for( const wxString& netName : netlistNetnames )
1648 {
1649 if( aNetName.StartsWith( netName ) )
1650 return true;
1651 }
1652
1653 return false;
1654 };
1655
1656 // Test copper zones to detect "dead" nets (nets without any pad):
1657 for( ZONE* zone : m_board->Zones() )
1658 {
1659 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1660 continue;
1661
1662 if( !isInNetlist( zone->GetNetname() ) )
1663 {
1664 // Look for a pad in the zone's connected-pad-cache which has been updated to
1665 // a new net and use that. While this won't always be the right net, the dead
1666 // net is guaranteed to be wrong.
1667 wxString updatedNetname = wxEmptyString;
1668
1669 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1670 {
1671 if( getNetname( pad ) != zone->GetNetname() )
1672 {
1673 updatedNetname = getNetname( pad );
1674 break;
1675 }
1676 }
1677
1678 // Take zone name from name change map if it didn't match to a new pad
1679 // (this is useful for zones on internal layers)
1680 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1681 {
1682 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1683 }
1684
1685 if( !updatedNetname.IsEmpty() )
1686 {
1687 if( m_isDryRun )
1688 {
1689 wxString originalNetname = zone->GetNetname();
1690
1691 if( !zone->GetZoneName().IsEmpty() )
1692 {
1693 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1694 zone->GetZoneName(),
1695 EscapeHTML( UnescapeString( originalNetname ) ),
1696 EscapeHTML( UnescapeString( updatedNetname ) ) );
1697 }
1698 else
1699 {
1700 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1701 EscapeHTML( UnescapeString( originalNetname ) ),
1702 EscapeHTML( UnescapeString( updatedNetname ) ) );
1703 }
1704
1705 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1706 }
1707 else
1708 {
1709 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1710
1711 if( !netinfo )
1712 netinfo = m_addedNets[ updatedNetname ];
1713
1714 if( netinfo )
1715 {
1716 wxString originalNetname = zone->GetNetname();
1717
1718 m_commit.Modify( zone );
1719 zone->SetNet( netinfo );
1720
1721 if( !zone->GetZoneName().IsEmpty() )
1722 {
1723 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1724 EscapeHTML( zone->GetZoneName() ),
1725 EscapeHTML( UnescapeString( originalNetname ) ),
1726 EscapeHTML( UnescapeString( updatedNetname ) ) );
1727 }
1728 else
1729 {
1730 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
1731 EscapeHTML( UnescapeString( originalNetname ) ),
1732 EscapeHTML( UnescapeString( updatedNetname ) ) );
1733 }
1734
1735 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1736 }
1737 }
1738 }
1739 else
1740 {
1741 if( !zone->GetZoneName().IsEmpty() )
1742 {
1743 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
1744 EscapeHTML( zone->GetZoneName() ) );
1745 }
1746 else
1747 {
1748 wxString layerNames = zone->LayerMaskDescribe();
1749 VECTOR2I pt = zone->GetPosition();
1750
1751 if( m_frame && m_frame->GetPcbNewSettings() )
1752 {
1753 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertXAxis )
1754 pt.x *= -1;
1755
1756 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
1757 pt.y *= -1;
1758 }
1759
1760 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
1761 EscapeHTML( layerNames ),
1762 m_frame ? m_frame->MessageTextFromValue( pt.x )
1764 m_frame ? m_frame->MessageTextFromValue( pt.y )
1766 zone->GetNetname() );
1767 }
1768
1769 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1771 }
1772 }
1773 }
1774
1775 return true;
1776}
1777
1778
1780{
1781 if( !m_transferGroups )
1782 return false;
1783
1784 wxString msg;
1785
1786 for( PCB_GROUP* pcbGroup : m_board->Groups() )
1787 {
1788 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
1789
1790 if( netlistGroup == nullptr )
1791 continue;
1792
1793 if( netlistGroup->name != pcbGroup->GetName() )
1794 {
1795 if( m_isDryRun )
1796 {
1797 msg.Printf( _( "Change group name from '%s' to '%s'." ),
1798 EscapeHTML( pcbGroup->GetName() ),
1799 EscapeHTML( netlistGroup->name ) );
1800 }
1801 else
1802 {
1803 msg.Printf( _( "Changed group name from '%s' to '%s'." ),
1804 EscapeHTML( pcbGroup->GetName() ),
1805 EscapeHTML( netlistGroup->name ) );
1806 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1807 pcbGroup->SetName( netlistGroup->name );
1808 }
1809
1810 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1811 }
1812
1813 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
1814 {
1815 if( m_isDryRun )
1816 {
1817 msg.Printf( _( "Change group library link from '%s' to '%s'." ),
1818 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1819 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1820 }
1821 else
1822 {
1823 msg.Printf( _( "Changed group library link from '%s' to '%s'." ),
1824 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1825 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1826 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1827 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
1828 }
1829
1830 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1831 }
1832 }
1833
1834 return true;
1835}
1836
1837
1839 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
1840{
1841 // Verify that board contains all pads in netlist: if it doesn't then footprints are
1842 // wrong or missing.
1843
1844 wxString msg;
1845 wxString padNumber;
1846
1847 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
1848 {
1849 COMPONENT* component = aNetlist.GetComponent( i );
1850 FOOTPRINT* footprint = aFootprintMap[component];
1851
1852 if( !footprint ) // It can be missing in partial designs
1853 continue;
1854
1855 // Explore all pins/pads in component
1856 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1857 {
1858 padNumber = component->GetNet( jj ).GetPinName();
1859
1860 if( padNumber.IsEmpty() )
1861 {
1862 // bad symbol, report error
1863 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
1864 "to pads in %s." ),
1865 component->GetReference(),
1866 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1867 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1868 ++m_errorCount;
1869 }
1870 else if( !footprint->FindPadByNumber( padNumber ) )
1871 {
1872 // not found: bad footprint, report error
1873 msg.Printf( _( "%s pad %s not found in %s." ),
1874 component->GetReference(),
1875 EscapeHTML( padNumber ),
1876 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1877 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1878 ++m_errorCount;
1879 }
1880 }
1881 }
1882
1883 return true;
1884}
1885
1886
1888{
1889 FOOTPRINT* lastPreexistingFootprint = nullptr;
1890 COMPONENT* component = nullptr;
1891 wxString msg;
1892 std::unordered_set<wxString> sheetPaths;
1893 std::unordered_set<FOOTPRINT*> usedFootprints;
1894
1895 m_errorCount = 0;
1896 m_warningCount = 0;
1898
1899 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
1900
1901 if( !m_board->Footprints().empty() )
1902 lastPreexistingFootprint = m_board->Footprints().back();
1903
1905
1906 // First mark all nets (except <no net>) as stale; we'll update those which are current
1907 // in the following two loops. Also prepare the component class manager for updates.
1908 //
1909 if( !m_isDryRun )
1910 {
1911 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1912 net->SetIsCurrent( net->GetNetCode() == 0 );
1913
1914 m_board->GetComponentClassManager().InitNetlistUpdate();
1915 }
1916
1917 // Next go through the netlist updating all board footprints which have matching component
1918 // entries and adding new footprints for those that don't.
1919 //
1920 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
1921 {
1922 component = aNetlist.GetComponent( i );
1923
1924 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
1925 continue;
1926
1927 msg.Printf( _( "Processing symbol '%s:%s'." ),
1928 component->GetReference(),
1929 EscapeHTML( component->GetFPID().Format().wx_str() ) );
1930 m_reporter->Report( msg, RPT_SEVERITY_INFO );
1931
1932 const LIB_ID& baseFpid = component->GetFPID();
1933 const bool hasBaseFpid = !baseFpid.empty();
1934
1935 if( baseFpid.IsLegacy() )
1936 {
1937 msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. "
1938 "Use the full 'Library:Footprint' format to avoid repeated update "
1939 "notifications." ),
1940 component->GetReference(),
1941 EscapeHTML( baseFpid.Format().wx_str() ) );
1942 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1944 }
1945
1946 std::vector<FOOTPRINT*> matchingFootprints;
1947
1948 for( FOOTPRINT* footprint : m_board->Footprints() )
1949 {
1950 bool match = false;
1951
1953 {
1954 for( const KIID& uuid : component->GetKIIDs() )
1955 {
1956 KIID_PATH base = component->GetPath();
1957 base.push_back( uuid );
1958
1959 if( footprint->GetPath() == base )
1960 {
1961 match = true;
1962 break;
1963 }
1964 }
1965 }
1966 else
1967 {
1968 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
1969 }
1970
1971 if( match )
1972 matchingFootprints.push_back( footprint );
1973
1974 if( footprint == lastPreexistingFootprint )
1975 {
1976 // No sense going through the newly-created footprints: end of loop
1977 break;
1978 }
1979 }
1980
1981 std::vector<LIB_ID> expectedFpids;
1982 std::unordered_set<wxString> expectedFpidKeys;
1983
1984 auto addExpectedFpid =
1985 [&]( const LIB_ID& aFpid )
1986 {
1987 if( aFpid.empty() )
1988 return;
1989
1990 wxString key = aFpid.Format();
1991
1992 if( expectedFpidKeys.insert( key ).second )
1993 expectedFpids.push_back( aFpid );
1994 };
1995
1996 addExpectedFpid( baseFpid );
1997
1998 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
1999
2000 for( const auto& [variantName, variant] : component->GetVariants() )
2001 {
2002 auto fieldIt = variant.m_fields.find( footprintFieldName );
2003
2004 if( fieldIt == variant.m_fields.end() || fieldIt->second.IsEmpty() )
2005 continue;
2006
2007 LIB_ID parsedId;
2008
2009 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
2010 {
2011 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
2012 EscapeHTML( fieldIt->second ),
2013 variantName,
2014 component->GetReference() );
2015 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2016 ++m_errorCount;
2017 continue;
2018 }
2019
2020 addExpectedFpid( parsedId );
2021 }
2022
2023 // When the schematic-side FPID has no library nickname (legacy format like
2024 // "DGG56" instead of "Package_SO:DGG56"), matching should compare only the
2025 // footprint item name. Otherwise the board footprint (which always has a library
2026 // nickname) will never match, causing perpetual "change footprint" notifications.
2027 auto fpidMatches =
2028 [&]( const LIB_ID& aBoardFpid, const LIB_ID& aExpectedFpid ) -> bool
2029 {
2030 if( aExpectedFpid.IsLegacy() )
2031 return aBoardFpid.GetLibItemName() == aExpectedFpid.GetLibItemName();
2032
2033 return aBoardFpid == aExpectedFpid;
2034 };
2035
2036 auto isExpectedFpid =
2037 [&]( const LIB_ID& aFpid ) -> bool
2038 {
2039 if( aFpid.empty() )
2040 return false;
2041
2042 if( expectedFpidKeys.count( aFpid.Format() ) > 0 )
2043 return true;
2044
2045 for( const LIB_ID& expected : expectedFpids )
2046 {
2047 if( fpidMatches( aFpid, expected ) )
2048 return true;
2049 }
2050
2051 return false;
2052 };
2053
2054 auto takeMatchingFootprint =
2055 [&]( const LIB_ID& aFpid ) -> FOOTPRINT*
2056 {
2057 for( FOOTPRINT* footprint : matchingFootprints )
2058 {
2059 if( usedFootprints.count( footprint ) )
2060 continue;
2061
2062 if( fpidMatches( footprint->GetFPID(), aFpid ) )
2063 return footprint;
2064 }
2065
2066 return nullptr;
2067 };
2068
2069 std::vector<FOOTPRINT*> componentFootprints;
2070 componentFootprints.reserve( expectedFpids.size() );
2071 FOOTPRINT* baseFootprint = nullptr;
2072
2073 if( hasBaseFpid )
2074 baseFootprint = takeMatchingFootprint( baseFpid );
2075 else if( !matchingFootprints.empty() )
2076 baseFootprint = matchingFootprints.front();
2077
2078 if( !baseFootprint && m_replaceFootprints && !matchingFootprints.empty() )
2079 {
2080 FOOTPRINT* replaceCandidate = nullptr;
2081
2082 for( FOOTPRINT* footprint : matchingFootprints )
2083 {
2084 if( usedFootprints.count( footprint ) )
2085 continue;
2086
2087 if( isExpectedFpid( footprint->GetFPID() ) )
2088 continue;
2089
2090 replaceCandidate = footprint;
2091 break;
2092 }
2093
2094 if( replaceCandidate )
2095 {
2096 FOOTPRINT* replaced = replaceFootprint( aNetlist, replaceCandidate, component );
2097
2098 if( replaced )
2099 baseFootprint = replaced;
2100 else
2101 baseFootprint = replaceCandidate;
2102 }
2103 }
2104
2105 if( !baseFootprint && hasBaseFpid )
2106 baseFootprint = addNewFootprint( component, baseFpid );
2107
2108 if( baseFootprint )
2109 {
2110 componentFootprints.push_back( baseFootprint );
2111 usedFootprints.insert( baseFootprint );
2112 footprintMap[ component ] = baseFootprint;
2113 }
2114
2115 for( const LIB_ID& fpid : expectedFpids )
2116 {
2117 if( fpid == baseFpid )
2118 continue;
2119
2120 FOOTPRINT* footprint = takeMatchingFootprint( fpid );
2121
2122 if( !footprint )
2123 footprint = addNewFootprint( component, fpid );
2124
2125 if( footprint )
2126 {
2127 componentFootprints.push_back( footprint );
2128 usedFootprints.insert( footprint );
2129 }
2130 }
2131
2132 for( FOOTPRINT* footprint : componentFootprints )
2133 {
2134 if( !footprint )
2135 continue;
2136
2137 updateFootprintParameters( footprint, component );
2138 updateFootprintGroup( footprint, component );
2139 updateComponentPadConnections( footprint, component );
2140 updateComponentClass( footprint, component );
2141 updateComponentUnits( footprint, component );
2142
2143 sheetPaths.insert( footprint->GetSheetname() );
2144 }
2145
2146 if( !componentFootprints.empty() )
2147 applyComponentVariants( component, componentFootprints, baseFpid );
2148 }
2149
2150 updateCopperZoneNets( aNetlist );
2151 updateGroups( aNetlist );
2152
2153 // Finally go through the board footprints and update all those that *don't* have matching
2154 // component entries.
2155 //
2156 for( FOOTPRINT* footprint : m_board->Footprints() )
2157 {
2158 bool matched = false;
2159 bool doDelete = m_deleteUnusedFootprints;
2160
2161 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
2162 doDelete = false;
2163
2164 bool isStaleVariantFootprint = false;
2165
2166 if( usedFootprints.count( footprint ) )
2167 {
2168 matched = true;
2169 }
2170 else
2171 {
2173 component = aNetlist.GetComponentByPath( footprint->GetPath() );
2174 else
2175 component = aNetlist.GetComponentByReference( footprint->GetReference() );
2176
2177 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
2178 {
2179 // When replace footprints is enabled and a component has variant footprints,
2180 // footprints matching by reference but not in usedFootprints are stale variant
2181 // footprints that should be replaced/removed.
2182 if( m_replaceFootprints && !component->GetVariants().empty() )
2183 {
2184 matched = false;
2185 isStaleVariantFootprint = true;
2186 }
2187 else
2188 {
2189 matched = true;
2190 }
2191 }
2192 }
2193
2194 // Stale variant footprints should be deleted when m_replaceFootprints is enabled,
2195 // regardless of m_deleteUnusedFootprints setting.
2196 if( isStaleVariantFootprint )
2197 doDelete = true;
2198
2199 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
2200 {
2201 if( m_isDryRun )
2202 {
2203 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
2204 footprint->GetReference() );
2205 }
2206 else
2207 {
2208 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
2209 footprint->GetReference() );
2210 }
2211
2212 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2214 doDelete = false;
2215 }
2216
2217 if( doDelete && !matched )
2218 {
2219 if( m_isDryRun )
2220 {
2221 msg.Printf( _( "Remove unused footprint %s." ),
2222 footprint->GetReference() );
2223 }
2224 else
2225 {
2226 m_commit.Remove( footprint );
2227 msg.Printf( _( "Removed unused footprint %s." ),
2228 footprint->GetReference() );
2229 }
2230
2231 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2232 }
2233 else if( !m_isDryRun )
2234 {
2235 if( !matched )
2236 footprint->SetPath( KIID_PATH() );
2237
2238 for( PAD* pad : footprint->Pads() )
2239 {
2240 if( pad->GetNet() )
2241 pad->GetNet()->SetIsCurrent( true );
2242 }
2243 }
2244 }
2245
2246 if( !m_isDryRun )
2247 {
2248 // Finalise the component class manager
2249 m_board->GetComponentClassManager().FinishNetlistUpdate();
2250 m_board->SynchronizeComponentClasses( sheetPaths );
2251
2252 m_board->BuildConnectivity();
2253 testConnectivity( aNetlist, footprintMap );
2254
2255 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2256 {
2257 if( !net->IsCurrent() )
2258 {
2259 msg.Printf( _( "Removed unused net %s." ),
2260 EscapeHTML( net->GetNetname() ) );
2261 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2262 }
2263 }
2264
2265 m_board->RemoveUnusedNets( &m_commit );
2266
2267 // Update board variant registry from netlist
2268 const std::vector<wxString>& netlistVariants = aNetlist.GetVariantNames();
2269
2270 if( !netlistVariants.empty() || !m_board->GetVariantNames().empty() )
2271 {
2272 m_reporter->Report( _( "Updating design variants..." ), RPT_SEVERITY_INFO );
2273
2274 auto findBoardVariantName =
2275 [&]( const wxString& aVariantName ) -> wxString
2276 {
2277 for( const wxString& name : m_board->GetVariantNames() )
2278 {
2279 if( name.CmpNoCase( aVariantName ) == 0 )
2280 return name;
2281 }
2282
2283 return wxEmptyString;
2284 };
2285
2286 std::vector<wxString> updatedVariantNames;
2287 updatedVariantNames.reserve( netlistVariants.size() );
2288
2289 for( const wxString& variantName : netlistVariants )
2290 {
2291 wxString actualName = findBoardVariantName( variantName );
2292
2293 if( actualName.IsEmpty() )
2294 {
2295 m_board->AddVariant( variantName );
2296 actualName = findBoardVariantName( variantName );
2297
2298 if( !actualName.IsEmpty() )
2299 {
2300 msg.Printf( _( "Added variant '%s'." ), actualName );
2301 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2302 }
2303 }
2304
2305 if( actualName.IsEmpty() )
2306 continue;
2307
2308 // Update description if changed
2309 wxString newDescription = aNetlist.GetVariantDescription( variantName );
2310 wxString oldDescription = m_board->GetVariantDescription( actualName );
2311
2312 if( newDescription != oldDescription )
2313 {
2314 m_board->SetVariantDescription( actualName, newDescription );
2315 msg.Printf( _( "Updated description for variant '%s'." ), actualName );
2316 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2317 }
2318
2319 updatedVariantNames.push_back( actualName );
2320 }
2321
2322 std::vector<wxString> variantsToRemove;
2323
2324 for( const wxString& existingName : m_board->GetVariantNames() )
2325 {
2326 bool found = false;
2327
2328 for( const wxString& variantName : netlistVariants )
2329 {
2330 if( existingName.CmpNoCase( variantName ) == 0 )
2331 {
2332 found = true;
2333 break;
2334 }
2335 }
2336
2337 if( !found )
2338 variantsToRemove.push_back( existingName );
2339 }
2340
2341 for( const wxString& variantName : variantsToRemove )
2342 {
2343 m_board->DeleteVariant( variantName );
2344 msg.Printf( _( "Removed variant '%s'." ), variantName );
2345 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2346 }
2347
2348 if( !updatedVariantNames.empty() )
2349 m_board->SetVariantNames( updatedVariantNames );
2350 }
2351
2352 // When new footprints are added, the automatic zone refill is disabled because:
2353 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
2354 // (the auto refills rebuild the connectivity with incomplete data)
2355 // * it is useless because zones will be refilled after placing new footprints
2356 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
2357
2358 // Update net, netcode and netclass data after commiting the netlist
2359 m_board->SynchronizeNetsAndNetClasses( true );
2360 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
2361
2362 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
2363 // sure any modification to netclasses gets persisted to project settings through a save.
2364 if( m_frame )
2365 m_frame->OnModify();
2366 }
2367
2368 if( m_isDryRun )
2369 {
2370 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
2371 delete addedNet.second;
2372
2373 m_addedNets.clear();
2374 }
2375
2376 // Update the ratsnest
2377 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2378 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2379
2380 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
2381 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
2382
2383 return true;
2384}
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
std::map< wxString, wxString > m_oldToNewNets
bool updateFootprintGroup(FOOTPRINT *aPcbFootprint, COMPONENT *aNetlistComponent)
bool updateFootprintParameters(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:522
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
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:166
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
void SetDNP(bool aDNP)
Definition footprint.h:160
bool GetExcludedFromPosFiles() const
Definition footprint.h:165
bool GetDNP() const
Definition footprint.h:159
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:188
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:163
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:1049
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:1056
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1050
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 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:314
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:404
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