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
952 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
953 }
954
955 // Add to new group
956 if( newGroupKIID != 0 )
957 {
958 if( m_isDryRun )
959 {
960 msg.Printf( _( "Add %s to group '%s'." ),
961 aPcbFootprint->GetReference(),
962 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
963 }
964 else
965 {
966 msg.Printf( _( "Added %s to group '%s'." ),
967 aPcbFootprint->GetReference(),
968 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
969
970 changed = true;
971
972 if( newGroup == nullptr )
973 {
974 newGroup = new PCB_GROUP( m_board );
975 const_cast<KIID&>( newGroup->m_Uuid ) = newGroupKIID;
976 newGroup->SetName( aNetlistComponent->GetGroup()->name );
977
978 // Add the group to the board manually so we can find it by checking
979 // board groups for later footprints that are checking for existing groups
980 m_board->Add( newGroup );
981 m_commit.Added( newGroup );
982 }
983 else
984 {
985 m_commit.Modify( newGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
986 }
987
988 newGroup->AddItem( aPcbFootprint );
989 }
990
991 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
992 }
993
994 if( changed && copy )
995 m_commit.Modified( aPcbFootprint, copy );
996 else if( copy )
997 delete copy;
998
999 return changed;
1000}
1001
1002
1004 COMPONENT* aNewComponent )
1005{
1006 wxString msg;
1007
1008 // Create a copy only if the footprint has not been added during this update
1009 FOOTPRINT* copy = nullptr;
1010
1011 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
1012 {
1013 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1014 copy->SetParentGroup( nullptr );
1015 }
1016
1017 bool changed = false;
1018
1019 // At this point, the component footprint is updated. Now update the nets.
1020 std::deque<PAD*> pads = aFootprint->Pads();
1021 std::set<wxString> padNetnames;
1022
1023 std::sort( pads.begin(), pads.end(),
1024 []( PAD* a, PAD* b )
1025 {
1026 return a->m_Uuid < b->m_Uuid;
1027 } );
1028
1029 for( PAD* pad : pads )
1030 {
1031 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
1032
1033 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1034 wxT( "Processing pad %s of component %s" ),
1035 pad->GetNumber(),
1036 aNewComponent->GetReference() );
1037
1038 wxString pinFunction;
1039 wxString pinType;
1040
1041 if( net.IsValid() ) // i.e. the pad has a name
1042 {
1043 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1044 wxT( " Found valid net: %s" ),
1045 net.GetNetName() );
1046 pinFunction = net.GetPinFunction();
1047 pinType = net.GetPinType();
1048 }
1049 else
1050 {
1051 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1052 wxT( " No net found for pad %s" ),
1053 pad->GetNumber() );
1054 }
1055
1056 if( !m_isDryRun )
1057 {
1058 if( pad->GetPinFunction() != pinFunction )
1059 {
1060 changed = true;
1061 pad->SetPinFunction( pinFunction );
1062 }
1063
1064 if( pad->GetPinType() != pinType )
1065 {
1066 changed = true;
1067 pad->SetPinType( pinType );
1068 }
1069 }
1070 else
1071 {
1072 cachePinFunction( pad, pinFunction );
1073 }
1074
1075 // Test if new footprint pad has no net (pads not on copper layers have no net).
1076 if( !net.IsValid() || !pad->IsOnCopperLayer() )
1077 {
1078 if( !pad->GetNetname().IsEmpty() )
1079 {
1080 if( m_isDryRun )
1081 {
1082 msg.Printf( _( "Disconnect %s pin %s." ),
1083 aFootprint->GetReference(),
1084 EscapeHTML( pad->GetNumber() ) );
1085 }
1086 else
1087 {
1088 msg.Printf( _( "Disconnected %s pin %s." ),
1089 aFootprint->GetReference(),
1090 EscapeHTML( pad->GetNumber() ) );
1091 }
1092
1093 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1094 }
1095 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
1096 {
1097 // pad is connectable but has no net found in netlist
1098 msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
1099 aFootprint->GetReference(),
1100 EscapeHTML( pad->GetNumber() ),
1101 EscapeHTML( pad->GetNumber() ) );
1102 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
1104 }
1105
1106 if( !m_isDryRun )
1107 {
1108 changed = true;
1109 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
1110
1111 // If the pad has no net from netlist (i.e. not in netlist
1112 // it cannot have a pin function
1113 if( pad->GetNetname().IsEmpty() )
1114 pad->SetPinFunction( wxEmptyString );
1115
1116 }
1117 else
1118 {
1119 cacheNetname( pad, wxEmptyString );
1120 }
1121 }
1122 else // New footprint pad has a net.
1123 {
1124 wxString netName = net.GetNetName();
1125
1126 if( pad->IsNoConnectPad() )
1127 {
1128 netName = wxString::Format( wxS( "%s" ), net.GetNetName() );
1129
1130 for( int jj = 1; !padNetnames.insert( netName ).second; jj++ )
1131 {
1132 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1133 }
1134 }
1135
1136 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1137
1138 if( netinfo && !m_isDryRun )
1139 netinfo->SetIsCurrent( true );
1140
1141 if( pad->GetNetname() != netName )
1142 {
1143
1144 if( netinfo == nullptr )
1145 {
1146 // It might be a new net that has not been added to the board yet
1147 if( m_addedNets.count( netName ) )
1148 netinfo = m_addedNets[ netName ];
1149 }
1150
1151 if( netinfo == nullptr )
1152 {
1153 netinfo = new NETINFO_ITEM( m_board, netName );
1154
1155 // It is a new net, we have to add it
1156 if( !m_isDryRun )
1157 {
1158 changed = true;
1159 m_commit.Add( netinfo );
1160 }
1161
1162 m_addedNets[netName] = netinfo;
1163 msg.Printf( _( "Add net %s." ),
1164 EscapeHTML( UnescapeString( netName ) ) );
1165 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1166 }
1167
1168 if( !pad->GetNetname().IsEmpty() )
1169 {
1170 m_oldToNewNets[ pad->GetNetname() ] = netName;
1171
1172 if( m_isDryRun )
1173 {
1174 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1175 aFootprint->GetReference(),
1176 EscapeHTML( pad->GetNumber() ),
1177 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1178 EscapeHTML( UnescapeString( netName ) ) );
1179 }
1180 else
1181 {
1182 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1183 aFootprint->GetReference(),
1184 EscapeHTML( pad->GetNumber() ),
1185 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1186 EscapeHTML( UnescapeString( netName ) ) );
1187 }
1188 }
1189 else
1190 {
1191 if( m_isDryRun )
1192 {
1193 msg.Printf( _( "Connect %s pin %s to %s."),
1194 aFootprint->GetReference(),
1195 EscapeHTML( pad->GetNumber() ),
1196 EscapeHTML( UnescapeString( netName ) ) );
1197 }
1198 else
1199 {
1200 msg.Printf( _( "Connected %s pin %s to %s."),
1201 aFootprint->GetReference(),
1202 EscapeHTML( pad->GetNumber() ),
1203 EscapeHTML( UnescapeString( netName ) ) );
1204 }
1205 }
1206
1207 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1208
1209 if( !m_isDryRun )
1210 {
1211 changed = true;
1212 pad->SetNet( netinfo );
1213 }
1214 else
1215 {
1216 cacheNetname( pad, netName );
1217 }
1218 }
1219 }
1220 }
1221
1222 if( changed && copy )
1223 m_commit.Modified( aFootprint, copy );
1224 else if( copy )
1225 delete copy;
1226
1227 return true;
1228}
1229
1230
1232{
1233 // Build the footprint-side representation from the netlist component
1234 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1235
1236 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1237 newUnits.push_back( { u.m_unitName, u.m_pins } );
1238
1239 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1240
1241 auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
1242 const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
1243 {
1244 if( a.size() != b.size() )
1245 return false;
1246
1247 for( size_t i = 0; i < a.size(); ++i )
1248 {
1249 if( a[i].m_unitName != b[i].m_unitName )
1250 return false;
1251
1252 if( a[i].m_pins != b[i].m_pins )
1253 return false;
1254 }
1255
1256 return true;
1257 };
1258
1259 if( unitsEqual( curUnits, newUnits ) )
1260 return false;
1261
1262 wxString msg;
1263
1264 if( m_isDryRun )
1265 {
1266 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1267 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1268 return false; // no actual change on board during dry run
1269 }
1270
1271 // Create a copy only if the footprint has not been added during this update
1272 FOOTPRINT* copy = nullptr;
1273
1274 if( !m_commit.GetStatus( aFootprint ) )
1275 {
1276 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1277 copy->SetParentGroup( nullptr );
1278 }
1279
1280 aFootprint->SetUnitInfo( newUnits );
1281
1282 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1283 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1284
1285 if( copy )
1286 m_commit.Modified( aFootprint, copy );
1287
1288 return true;
1289}
1290
1291
1293 const std::vector<FOOTPRINT*>& aFootprints,
1294 const LIB_ID& aBaseFpid )
1295{
1296 const auto& variants = aComponent->GetVariants();
1297
1298 if( variants.empty() )
1299 return;
1300
1301 if( aBaseFpid.empty() )
1302 return;
1303
1304 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
1305
1306 struct VARIANT_INFO
1307 {
1308 wxString name;
1309 const COMPONENT_VARIANT* variant;
1310 LIB_ID activeFpid;
1311 };
1312
1313 std::vector<VARIANT_INFO> variantInfo;
1314 variantInfo.reserve( variants.size() );
1315
1316 for( const auto& [variantName, variant] : variants )
1317 {
1318 LIB_ID activeFpid = aBaseFpid;
1319
1320 auto fieldIt = variant.m_fields.find( footprintFieldName );
1321
1322 if( fieldIt != variant.m_fields.end() && !fieldIt->second.IsEmpty() )
1323 {
1324 LIB_ID parsedId;
1325
1326 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
1327 {
1328 wxString msg;
1329 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
1330 EscapeHTML( fieldIt->second ),
1331 variantName,
1332 aComponent->GetReference() );
1333 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1334 ++m_errorCount;
1335 }
1336 else
1337 {
1338 activeFpid = parsedId;
1339 }
1340 }
1341
1342 variantInfo.push_back( { variantName, &variant, activeFpid } );
1343 }
1344
1345 for( FOOTPRINT* footprint : aFootprints )
1346 {
1347 if( !footprint )
1348 continue;
1349
1350 FOOTPRINT* copy = nullptr;
1351
1352 if( !m_isDryRun && !m_commit.GetStatus( footprint ) )
1353 {
1354 copy = static_cast<FOOTPRINT*>( footprint->Clone() );
1355 copy->SetParentGroup( nullptr );
1356 }
1357
1358 bool changed = false;
1359
1360 for( const VARIANT_INFO& info : variantInfo )
1361 {
1362 const COMPONENT_VARIANT& variant = *info.variant;
1363
1364 // During dry run, just read current state. During actual run, create variant if needed.
1365 const FOOTPRINT_VARIANT* currentVariant = footprint->GetVariant( info.name );
1366
1367 // Check if this footprint is the active one for this variant
1368 bool isActiveFootprint = ( footprint->GetFPID() == info.activeFpid );
1369
1370 // If this footprint is not active for this variant, it should be DNP.
1371 // Otherwise, apply explicit overrides from schematic, or reset to base footprint value.
1372 bool targetDnp;
1373
1374 if( !isActiveFootprint )
1375 {
1376 targetDnp = true;
1377 }
1378 else
1379 {
1380 targetDnp = variant.m_hasDnp ? variant.m_dnp : footprint->IsDNP();
1381 }
1382
1383 bool currentDnp = currentVariant ? currentVariant->GetDNP() : footprint->IsDNP();
1384
1385 if( currentDnp != targetDnp )
1386 {
1387 wxString msg;
1388
1389 if( m_isDryRun )
1390 {
1391 msg.Printf( targetDnp ? _( "Add %s:%s 'Do not place' variant attribute." )
1392 : _( "Remove %s:%s 'Do not place' variant attribute." ),
1393 footprint->GetReference(), info.name );
1394 }
1395 else
1396 {
1397 msg.Printf( targetDnp ? _( "Added %s:%s 'Do not place' variant attribute." )
1398 : _( "Removed %s:%s 'Do not place' variant attribute." ),
1399 footprint->GetReference(), info.name );
1400
1401 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1402
1403 if( fpVariant )
1404 fpVariant->SetDNP( targetDnp );
1405 }
1406
1407 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1408 changed = true;
1409 }
1410
1411 bool targetExcludedFromBOM = variant.m_hasExcludedFromBOM
1412 ? variant.m_excludedFromBOM
1413 : footprint->IsExcludedFromBOM();
1414 bool currentExcludedFromBOM = currentVariant ? currentVariant->GetExcludedFromBOM()
1415 : footprint->IsExcludedFromBOM();
1416
1417 if( currentExcludedFromBOM != targetExcludedFromBOM )
1418 {
1419 wxString msg;
1420
1421 if( m_isDryRun )
1422 {
1423 msg.Printf( targetExcludedFromBOM
1424 ? _( "Add %s:%s 'exclude from BOM' variant attribute." )
1425 : _( "Remove %s:%s 'exclude from BOM' variant attribute." ),
1426 footprint->GetReference(), info.name );
1427 }
1428 else
1429 {
1430 msg.Printf( targetExcludedFromBOM
1431 ? _( "Added %s:%s 'exclude from BOM' variant attribute." )
1432 : _( "Removed %s:%s 'exclude from BOM' variant attribute." ),
1433 footprint->GetReference(), info.name );
1434
1435 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1436
1437 if( fpVariant )
1438 fpVariant->SetExcludedFromBOM( targetExcludedFromBOM );
1439 }
1440
1441 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1442 changed = true;
1443 }
1444
1445 bool targetExcludedFromPosFiles = variant.m_hasExcludedFromPosFiles
1446 ? variant.m_excludedFromPosFiles
1447 : footprint->IsExcludedFromPosFiles();
1448 bool currentExcludedFromPosFiles = currentVariant
1449 ? currentVariant->GetExcludedFromPosFiles()
1450 : footprint->IsExcludedFromPosFiles();
1451
1452 if( currentExcludedFromPosFiles != targetExcludedFromPosFiles )
1453 {
1454 wxString msg;
1455
1456 if( m_isDryRun )
1457 {
1458 msg.Printf( targetExcludedFromPosFiles
1459 ? _( "Add %s:%s 'exclude from position files' variant attribute." )
1460 : _( "Remove %s:%s 'exclude from position files' variant attribute." ),
1461 footprint->GetReference(), info.name );
1462 }
1463 else
1464 {
1465 msg.Printf( targetExcludedFromPosFiles
1466 ? _( "Added %s:%s 'exclude from position files' variant attribute." )
1467 : _( "Removed %s:%s 'exclude from position files' variant attribute." ),
1468 footprint->GetReference(), info.name );
1469
1470 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1471
1472 if( fpVariant )
1473 fpVariant->SetExcludedFromPosFiles( targetExcludedFromPosFiles );
1474 }
1475
1476 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1477 changed = true;
1478 }
1479
1480 for( const auto& [fieldName, fieldValue] : variant.m_fields )
1481 {
1482 if( fieldName.CmpNoCase( footprintFieldName ) == 0 )
1483 continue;
1484
1485 bool hasCurrentValue = currentVariant && currentVariant->HasFieldValue( fieldName );
1486 wxString currentValue = hasCurrentValue ? currentVariant->GetFieldValue( fieldName )
1487 : wxString();
1488
1489 if( currentValue != fieldValue )
1490 {
1491 wxString msg;
1492
1493 if( m_isDryRun )
1494 {
1495 msg.Printf( _( "Change %s:%s field '%s' to '%s'." ),
1496 footprint->GetReference(), info.name, fieldName, fieldValue );
1497 }
1498 else
1499 {
1500 msg.Printf( _( "Changed %s:%s field '%s' to '%s'." ),
1501 footprint->GetReference(), info.name, fieldName, fieldValue );
1502
1503 FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name );
1504
1505 if( fpVariant )
1506 fpVariant->SetFieldValue( fieldName, fieldValue );
1507 }
1508
1509 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1510 changed = true;
1511 }
1512 }
1513 }
1514
1515 // For the default variant: if this footprint is not the base footprint
1516 // it should be DNP by default
1517 bool isBaseFootprint = ( footprint->GetFPID() == aBaseFpid );
1518
1519 if( !isBaseFootprint && !footprint->IsDNP() )
1520 {
1521 wxString msg;
1522 msg.Printf( m_isDryRun ? _( "Add %s 'Do not place' fabrication attribute." )
1523 : _( "Added %s 'Do not place' fabrication attribute." ),
1524 footprint->GetReference() );
1525
1526 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1527
1528 if( !m_isDryRun )
1529 footprint->SetDNP( true );
1530
1531 changed = true;
1532 }
1533
1534 if( !m_isDryRun && changed && copy )
1535 m_commit.Modified( footprint, copy );
1536 else
1537 delete copy;
1538 }
1539}
1540
1541
1543{
1544 for( ZONE* zone : m_board->Zones() )
1545 {
1546 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1547 continue;
1548
1549 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1550 }
1551}
1552
1553
1555{
1556 wxString msg;
1557 std::set<wxString> netlistNetnames;
1558
1559 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1560 {
1561 const COMPONENT* component = aNetlist.GetComponent( ii );
1562
1563 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1564 {
1565 const COMPONENT_NET& net = component->GetNet( jj );
1566 netlistNetnames.insert( net.GetNetName() );
1567 }
1568 }
1569
1570 for( PCB_TRACK* via : m_board->Tracks() )
1571 {
1572 if( via->Type() != PCB_VIA_T )
1573 continue;
1574
1575 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1576 {
1577 wxString updatedNetname = wxEmptyString;
1578
1579 // Take via name from name change map if it didn't match to a new pad
1580 // (this is useful for stitching vias that don't connect to tracks)
1581 if( m_oldToNewNets.count( via->GetNetname() ) )
1582 {
1583 updatedNetname = m_oldToNewNets[via->GetNetname()];
1584 }
1585
1586 if( !updatedNetname.IsEmpty() )
1587 {
1588 if( m_isDryRun )
1589 {
1590 wxString originalNetname = via->GetNetname();
1591
1592 msg.Printf( _( "Reconnect via from %s to %s." ),
1593 EscapeHTML( UnescapeString( originalNetname ) ),
1594 EscapeHTML( UnescapeString( updatedNetname ) ) );
1595
1596 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1597 }
1598 else
1599 {
1600 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1601
1602 if( !netinfo )
1603 netinfo = m_addedNets[updatedNetname];
1604
1605 if( netinfo )
1606 {
1607 wxString originalNetname = via->GetNetname();
1608
1609 m_commit.Modify( via );
1610 via->SetNet( netinfo );
1611
1612 msg.Printf( _( "Reconnected via from %s to %s." ),
1613 EscapeHTML( UnescapeString( originalNetname ) ),
1614 EscapeHTML( UnescapeString( updatedNetname ) ) );
1615
1616 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1617 }
1618 }
1619 }
1620 else
1621 {
1622 msg.Printf( _( "Via connected to unknown net (%s)." ),
1623 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1624 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1626 }
1627 }
1628 }
1629
1630 // Board connectivity net names are not the same as schematic connectivity net names.
1631 // Footprints that contain multiple overlapping pads with the same number are suffixed
1632 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1633 // exposed in the zone net name list.
1634 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1635 {
1636 if( netlistNetnames.count( aNetName ) )
1637 return true;
1638
1639 // If the zone net name is a pseudo net name, check if the root net name is in the net
1640 // list. If so, then this is a valid net.
1641 for( const wxString& netName : netlistNetnames )
1642 {
1643 if( aNetName.StartsWith( netName ) )
1644 return true;
1645 }
1646
1647 return false;
1648 };
1649
1650 // Test copper zones to detect "dead" nets (nets without any pad):
1651 for( ZONE* zone : m_board->Zones() )
1652 {
1653 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1654 continue;
1655
1656 if( !isInNetlist( zone->GetNetname() ) )
1657 {
1658 // Look for a pad in the zone's connected-pad-cache which has been updated to
1659 // a new net and use that. While this won't always be the right net, the dead
1660 // net is guaranteed to be wrong.
1661 wxString updatedNetname = wxEmptyString;
1662
1663 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1664 {
1665 if( getNetname( pad ) != zone->GetNetname() )
1666 {
1667 updatedNetname = getNetname( pad );
1668 break;
1669 }
1670 }
1671
1672 // Take zone name from name change map if it didn't match to a new pad
1673 // (this is useful for zones on internal layers)
1674 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1675 {
1676 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1677 }
1678
1679 if( !updatedNetname.IsEmpty() )
1680 {
1681 if( m_isDryRun )
1682 {
1683 wxString originalNetname = zone->GetNetname();
1684
1685 if( !zone->GetZoneName().IsEmpty() )
1686 {
1687 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1688 zone->GetZoneName(),
1689 EscapeHTML( UnescapeString( originalNetname ) ),
1690 EscapeHTML( UnescapeString( updatedNetname ) ) );
1691 }
1692 else
1693 {
1694 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1695 EscapeHTML( UnescapeString( originalNetname ) ),
1696 EscapeHTML( UnescapeString( updatedNetname ) ) );
1697 }
1698
1699 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1700 }
1701 else
1702 {
1703 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1704
1705 if( !netinfo )
1706 netinfo = m_addedNets[ updatedNetname ];
1707
1708 if( netinfo )
1709 {
1710 wxString originalNetname = zone->GetNetname();
1711
1712 m_commit.Modify( zone );
1713 zone->SetNet( netinfo );
1714
1715 if( !zone->GetZoneName().IsEmpty() )
1716 {
1717 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1718 EscapeHTML( zone->GetZoneName() ),
1719 EscapeHTML( UnescapeString( originalNetname ) ),
1720 EscapeHTML( UnescapeString( updatedNetname ) ) );
1721 }
1722 else
1723 {
1724 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
1725 EscapeHTML( UnescapeString( originalNetname ) ),
1726 EscapeHTML( UnescapeString( updatedNetname ) ) );
1727 }
1728
1729 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1730 }
1731 }
1732 }
1733 else
1734 {
1735 if( !zone->GetZoneName().IsEmpty() )
1736 {
1737 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
1738 EscapeHTML( zone->GetZoneName() ) );
1739 }
1740 else
1741 {
1742 wxString layerNames = zone->LayerMaskDescribe();
1743 VECTOR2I pt = zone->GetPosition();
1744
1745 if( m_frame && m_frame->GetPcbNewSettings() )
1746 {
1747 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertXAxis )
1748 pt.x *= -1;
1749
1750 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
1751 pt.y *= -1;
1752 }
1753
1754 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
1755 EscapeHTML( layerNames ),
1756 m_frame ? m_frame->MessageTextFromValue( pt.x )
1758 m_frame ? m_frame->MessageTextFromValue( pt.y )
1760 zone->GetNetname() );
1761 }
1762
1763 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1765 }
1766 }
1767 }
1768
1769 return true;
1770}
1771
1772
1774{
1775 if( !m_transferGroups )
1776 return false;
1777
1778 wxString msg;
1779
1780 for( PCB_GROUP* pcbGroup : m_board->Groups() )
1781 {
1782 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
1783
1784 if( netlistGroup == nullptr )
1785 continue;
1786
1787 if( netlistGroup->name != pcbGroup->GetName() )
1788 {
1789 if( m_isDryRun )
1790 {
1791 msg.Printf( _( "Change group name from '%s' to '%s'." ),
1792 EscapeHTML( pcbGroup->GetName() ),
1793 EscapeHTML( netlistGroup->name ) );
1794 }
1795 else
1796 {
1797 msg.Printf( _( "Changed group name from '%s' to '%s'." ),
1798 EscapeHTML( pcbGroup->GetName() ),
1799 EscapeHTML( netlistGroup->name ) );
1800 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1801 pcbGroup->SetName( netlistGroup->name );
1802 }
1803
1804 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1805 }
1806
1807 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
1808 {
1809 if( m_isDryRun )
1810 {
1811 msg.Printf( _( "Change group library link from '%s' to '%s'." ),
1812 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1813 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1814 }
1815 else
1816 {
1817 msg.Printf( _( "Changed group library link from '%s' to '%s'." ),
1818 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
1819 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1820 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1821 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
1822 }
1823
1824 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1825 }
1826 }
1827
1828 return true;
1829}
1830
1831
1833 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
1834{
1835 // Verify that board contains all pads in netlist: if it doesn't then footprints are
1836 // wrong or missing.
1837
1838 wxString msg;
1839 wxString padNumber;
1840
1841 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
1842 {
1843 COMPONENT* component = aNetlist.GetComponent( i );
1844 FOOTPRINT* footprint = aFootprintMap[component];
1845
1846 if( !footprint ) // It can be missing in partial designs
1847 continue;
1848
1849 // Explore all pins/pads in component
1850 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1851 {
1852 padNumber = component->GetNet( jj ).GetPinName();
1853
1854 if( padNumber.IsEmpty() )
1855 {
1856 // bad symbol, report error
1857 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
1858 "to pads in %s." ),
1859 component->GetReference(),
1860 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1861 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1862 ++m_errorCount;
1863 }
1864 else if( !footprint->FindPadByNumber( padNumber ) )
1865 {
1866 // not found: bad footprint, report error
1867 msg.Printf( _( "%s pad %s not found in %s." ),
1868 component->GetReference(),
1869 EscapeHTML( padNumber ),
1870 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1871 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1872 ++m_errorCount;
1873 }
1874 }
1875 }
1876
1877 return true;
1878}
1879
1880
1882{
1883 FOOTPRINT* lastPreexistingFootprint = nullptr;
1884 COMPONENT* component = nullptr;
1885 wxString msg;
1886 std::unordered_set<wxString> sheetPaths;
1887 std::unordered_set<FOOTPRINT*> usedFootprints;
1888
1889 m_errorCount = 0;
1890 m_warningCount = 0;
1892
1893 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
1894
1895 if( !m_board->Footprints().empty() )
1896 lastPreexistingFootprint = m_board->Footprints().back();
1897
1899
1900 // First mark all nets (except <no net>) as stale; we'll update those which are current
1901 // in the following two loops. Also prepare the component class manager for updates.
1902 //
1903 if( !m_isDryRun )
1904 {
1905 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1906 net->SetIsCurrent( net->GetNetCode() == 0 );
1907
1908 m_board->GetComponentClassManager().InitNetlistUpdate();
1909 }
1910
1911 // Next go through the netlist updating all board footprints which have matching component
1912 // entries and adding new footprints for those that don't.
1913 //
1914 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
1915 {
1916 component = aNetlist.GetComponent( i );
1917
1918 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
1919 continue;
1920
1921 msg.Printf( _( "Processing symbol '%s:%s'." ),
1922 component->GetReference(),
1923 EscapeHTML( component->GetFPID().Format().wx_str() ) );
1924 m_reporter->Report( msg, RPT_SEVERITY_INFO );
1925
1926 const LIB_ID& baseFpid = component->GetFPID();
1927 const bool hasBaseFpid = !baseFpid.empty();
1928
1929 if( baseFpid.IsLegacy() )
1930 {
1931 msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. "
1932 "Use the full 'Library:Footprint' format to avoid repeated update "
1933 "notifications." ),
1934 component->GetReference(),
1935 EscapeHTML( baseFpid.Format().wx_str() ) );
1936 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1938 }
1939
1940 std::vector<FOOTPRINT*> matchingFootprints;
1941
1942 for( FOOTPRINT* footprint : m_board->Footprints() )
1943 {
1944 bool match = false;
1945
1947 {
1948 for( const KIID& uuid : component->GetKIIDs() )
1949 {
1950 KIID_PATH base = component->GetPath();
1951 base.push_back( uuid );
1952
1953 if( footprint->GetPath() == base )
1954 {
1955 match = true;
1956 break;
1957 }
1958 }
1959 }
1960 else
1961 {
1962 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
1963 }
1964
1965 if( match )
1966 matchingFootprints.push_back( footprint );
1967
1968 if( footprint == lastPreexistingFootprint )
1969 {
1970 // No sense going through the newly-created footprints: end of loop
1971 break;
1972 }
1973 }
1974
1975 std::vector<LIB_ID> expectedFpids;
1976 std::unordered_set<wxString> expectedFpidKeys;
1977
1978 auto addExpectedFpid =
1979 [&]( const LIB_ID& aFpid )
1980 {
1981 if( aFpid.empty() )
1982 return;
1983
1984 wxString key = aFpid.Format();
1985
1986 if( expectedFpidKeys.insert( key ).second )
1987 expectedFpids.push_back( aFpid );
1988 };
1989
1990 addExpectedFpid( baseFpid );
1991
1992 const wxString footprintFieldName = GetCanonicalFieldName( FIELD_T::FOOTPRINT );
1993
1994 for( const auto& [variantName, variant] : component->GetVariants() )
1995 {
1996 auto fieldIt = variant.m_fields.find( footprintFieldName );
1997
1998 if( fieldIt == variant.m_fields.end() || fieldIt->second.IsEmpty() )
1999 continue;
2000
2001 LIB_ID parsedId;
2002
2003 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
2004 {
2005 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
2006 EscapeHTML( fieldIt->second ),
2007 variantName,
2008 component->GetReference() );
2009 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2010 ++m_errorCount;
2011 continue;
2012 }
2013
2014 addExpectedFpid( parsedId );
2015 }
2016
2017 // When the schematic-side FPID has no library nickname (legacy format like
2018 // "DGG56" instead of "Package_SO:DGG56"), matching should compare only the
2019 // footprint item name. Otherwise the board footprint (which always has a library
2020 // nickname) will never match, causing perpetual "change footprint" notifications.
2021 auto fpidMatches =
2022 [&]( const LIB_ID& aBoardFpid, const LIB_ID& aExpectedFpid ) -> bool
2023 {
2024 if( aExpectedFpid.IsLegacy() )
2025 return aBoardFpid.GetLibItemName() == aExpectedFpid.GetLibItemName();
2026
2027 return aBoardFpid == aExpectedFpid;
2028 };
2029
2030 auto isExpectedFpid =
2031 [&]( const LIB_ID& aFpid ) -> bool
2032 {
2033 if( aFpid.empty() )
2034 return false;
2035
2036 if( expectedFpidKeys.count( aFpid.Format() ) > 0 )
2037 return true;
2038
2039 for( const LIB_ID& expected : expectedFpids )
2040 {
2041 if( fpidMatches( aFpid, expected ) )
2042 return true;
2043 }
2044
2045 return false;
2046 };
2047
2048 auto takeMatchingFootprint =
2049 [&]( const LIB_ID& aFpid ) -> FOOTPRINT*
2050 {
2051 for( FOOTPRINT* footprint : matchingFootprints )
2052 {
2053 if( usedFootprints.count( footprint ) )
2054 continue;
2055
2056 if( fpidMatches( footprint->GetFPID(), aFpid ) )
2057 return footprint;
2058 }
2059
2060 return nullptr;
2061 };
2062
2063 std::vector<FOOTPRINT*> componentFootprints;
2064 componentFootprints.reserve( expectedFpids.size() );
2065 FOOTPRINT* baseFootprint = nullptr;
2066
2067 if( hasBaseFpid )
2068 baseFootprint = takeMatchingFootprint( baseFpid );
2069 else if( !matchingFootprints.empty() )
2070 baseFootprint = matchingFootprints.front();
2071
2072 if( !baseFootprint && m_replaceFootprints && !matchingFootprints.empty() )
2073 {
2074 FOOTPRINT* replaceCandidate = nullptr;
2075
2076 for( FOOTPRINT* footprint : matchingFootprints )
2077 {
2078 if( usedFootprints.count( footprint ) )
2079 continue;
2080
2081 if( isExpectedFpid( footprint->GetFPID() ) )
2082 continue;
2083
2084 replaceCandidate = footprint;
2085 break;
2086 }
2087
2088 if( replaceCandidate )
2089 {
2090 FOOTPRINT* replaced = replaceFootprint( aNetlist, replaceCandidate, component );
2091
2092 if( replaced )
2093 baseFootprint = replaced;
2094 else
2095 baseFootprint = replaceCandidate;
2096 }
2097 }
2098
2099 if( !baseFootprint && hasBaseFpid )
2100 baseFootprint = addNewFootprint( component, baseFpid );
2101
2102 if( baseFootprint )
2103 {
2104 componentFootprints.push_back( baseFootprint );
2105 usedFootprints.insert( baseFootprint );
2106 footprintMap[ component ] = baseFootprint;
2107 }
2108
2109 for( const LIB_ID& fpid : expectedFpids )
2110 {
2111 if( fpid == baseFpid )
2112 continue;
2113
2114 FOOTPRINT* footprint = takeMatchingFootprint( fpid );
2115
2116 if( !footprint )
2117 footprint = addNewFootprint( component, fpid );
2118
2119 if( footprint )
2120 {
2121 componentFootprints.push_back( footprint );
2122 usedFootprints.insert( footprint );
2123 }
2124 }
2125
2126 for( FOOTPRINT* footprint : componentFootprints )
2127 {
2128 if( !footprint )
2129 continue;
2130
2131 updateFootprintParameters( footprint, component );
2132 updateFootprintGroup( footprint, component );
2133 updateComponentPadConnections( footprint, component );
2134 updateComponentClass( footprint, component );
2135 updateComponentUnits( footprint, component );
2136
2137 sheetPaths.insert( footprint->GetSheetname() );
2138 }
2139
2140 if( !componentFootprints.empty() )
2141 applyComponentVariants( component, componentFootprints, baseFpid );
2142 }
2143
2144 updateCopperZoneNets( aNetlist );
2145 updateGroups( aNetlist );
2146
2147 // Finally go through the board footprints and update all those that *don't* have matching
2148 // component entries.
2149 //
2150 for( FOOTPRINT* footprint : m_board->Footprints() )
2151 {
2152 bool matched = false;
2153 bool doDelete = m_deleteUnusedFootprints;
2154
2155 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
2156 doDelete = false;
2157
2158 bool isStaleVariantFootprint = false;
2159
2160 if( usedFootprints.count( footprint ) )
2161 {
2162 matched = true;
2163 }
2164 else
2165 {
2167 component = aNetlist.GetComponentByPath( footprint->GetPath() );
2168 else
2169 component = aNetlist.GetComponentByReference( footprint->GetReference() );
2170
2171 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
2172 {
2173 // When replace footprints is enabled and a component has variant footprints,
2174 // footprints matching by reference but not in usedFootprints are stale variant
2175 // footprints that should be replaced/removed.
2176 if( m_replaceFootprints && !component->GetVariants().empty() )
2177 {
2178 matched = false;
2179 isStaleVariantFootprint = true;
2180 }
2181 else
2182 {
2183 matched = true;
2184 }
2185 }
2186 }
2187
2188 // Stale variant footprints should be deleted when m_replaceFootprints is enabled,
2189 // regardless of m_deleteUnusedFootprints setting.
2190 if( isStaleVariantFootprint )
2191 doDelete = true;
2192
2193 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
2194 {
2195 if( m_isDryRun )
2196 {
2197 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
2198 footprint->GetReference() );
2199 }
2200 else
2201 {
2202 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
2203 footprint->GetReference() );
2204 }
2205
2206 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2208 doDelete = false;
2209 }
2210
2211 if( doDelete && !matched )
2212 {
2213 if( m_isDryRun )
2214 {
2215 msg.Printf( _( "Remove unused footprint %s." ),
2216 footprint->GetReference() );
2217 }
2218 else
2219 {
2220 m_commit.Remove( footprint );
2221 msg.Printf( _( "Removed unused footprint %s." ),
2222 footprint->GetReference() );
2223 }
2224
2225 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2226 }
2227 else if( !m_isDryRun )
2228 {
2229 if( !matched )
2230 footprint->SetPath( KIID_PATH() );
2231
2232 for( PAD* pad : footprint->Pads() )
2233 {
2234 if( pad->GetNet() )
2235 pad->GetNet()->SetIsCurrent( true );
2236 }
2237 }
2238 }
2239
2240 if( !m_isDryRun )
2241 {
2242 // Finalise the component class manager
2243 m_board->GetComponentClassManager().FinishNetlistUpdate();
2244 m_board->SynchronizeComponentClasses( sheetPaths );
2245
2246 m_board->BuildConnectivity();
2247 testConnectivity( aNetlist, footprintMap );
2248
2249 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2250 {
2251 if( !net->IsCurrent() )
2252 {
2253 msg.Printf( _( "Removed unused net %s." ),
2254 EscapeHTML( net->GetNetname() ) );
2255 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2256 }
2257 }
2258
2259 m_board->RemoveUnusedNets( &m_commit );
2260
2261 // Update board variant registry from netlist
2262 const std::vector<wxString>& netlistVariants = aNetlist.GetVariantNames();
2263
2264 if( !netlistVariants.empty() || !m_board->GetVariantNames().empty() )
2265 {
2266 m_reporter->Report( _( "Updating design variants..." ), RPT_SEVERITY_INFO );
2267
2268 auto findBoardVariantName =
2269 [&]( const wxString& aVariantName ) -> wxString
2270 {
2271 for( const wxString& name : m_board->GetVariantNames() )
2272 {
2273 if( name.CmpNoCase( aVariantName ) == 0 )
2274 return name;
2275 }
2276
2277 return wxEmptyString;
2278 };
2279
2280 std::vector<wxString> updatedVariantNames;
2281 updatedVariantNames.reserve( netlistVariants.size() );
2282
2283 for( const wxString& variantName : netlistVariants )
2284 {
2285 wxString actualName = findBoardVariantName( variantName );
2286
2287 if( actualName.IsEmpty() )
2288 {
2289 m_board->AddVariant( variantName );
2290 actualName = findBoardVariantName( variantName );
2291
2292 if( !actualName.IsEmpty() )
2293 {
2294 msg.Printf( _( "Added variant '%s'." ), actualName );
2295 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2296 }
2297 }
2298
2299 if( actualName.IsEmpty() )
2300 continue;
2301
2302 // Update description if changed
2303 wxString newDescription = aNetlist.GetVariantDescription( variantName );
2304 wxString oldDescription = m_board->GetVariantDescription( actualName );
2305
2306 if( newDescription != oldDescription )
2307 {
2308 m_board->SetVariantDescription( actualName, newDescription );
2309 msg.Printf( _( "Updated description for variant '%s'." ), actualName );
2310 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2311 }
2312
2313 updatedVariantNames.push_back( actualName );
2314 }
2315
2316 std::vector<wxString> variantsToRemove;
2317
2318 for( const wxString& existingName : m_board->GetVariantNames() )
2319 {
2320 bool found = false;
2321
2322 for( const wxString& variantName : netlistVariants )
2323 {
2324 if( existingName.CmpNoCase( variantName ) == 0 )
2325 {
2326 found = true;
2327 break;
2328 }
2329 }
2330
2331 if( !found )
2332 variantsToRemove.push_back( existingName );
2333 }
2334
2335 for( const wxString& variantName : variantsToRemove )
2336 {
2337 m_board->DeleteVariant( variantName );
2338 msg.Printf( _( "Removed variant '%s'." ), variantName );
2339 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2340 }
2341
2342 if( !updatedVariantNames.empty() )
2343 m_board->SetVariantNames( updatedVariantNames );
2344 }
2345
2346 // When new footprints are added, the automatic zone refill is disabled because:
2347 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
2348 // (the auto refills rebuild the connectivity with incomplete data)
2349 // * it is useless because zones will be refilled after placing new footprints
2350 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
2351
2352 // Update net, netcode and netclass data after commiting the netlist
2353 m_board->SynchronizeNetsAndNetClasses( true );
2354 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
2355
2356 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
2357 // sure any modification to netclasses gets persisted to project settings through a save.
2358 if( m_frame )
2359 m_frame->OnModify();
2360 }
2361
2362 if( m_isDryRun )
2363 {
2364 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
2365 delete addedNet.second;
2366
2367 m_addedNets.clear();
2368 }
2369
2370 // Update the ratsnest
2371 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2372 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2373
2374 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
2375 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
2376
2377 return true;
2378}
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()
wxString GetName() const
Definition eda_group.h:51
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:144
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:191
void SetExcludedFromPosFiles(bool aExclude)
Definition footprint.h:164
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:171
bool GetExcludedFromBOM() const
Definition footprint.h:160
void SetDNP(bool aDNP)
Definition footprint.h:158
bool GetExcludedFromPosFiles() const
Definition footprint.h:163
bool GetDNP() const
Definition footprint.h:157
void SetFieldValue(const wxString &aFieldName, const wxString &aValue)
Set a field value override for this variant.
Definition footprint.h:186
void SetExcludedFromBOM(bool aExclude)
Definition footprint.h:161
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:1047
void SetPosition(const VECTOR2I &aPos) override
EDA_ANGLE GetOrientation() const
Definition footprint.h:328
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
wxString GetSheetname() const
Definition footprint.h:375
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:373
void SetFilters(const wxString &aFilters)
Definition footprint.h:382
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:848
void SetAttributes(int aAttributes)
Definition footprint.h:416
void SetSheetfile(const wxString &aSheetfile)
Definition footprint.h:379
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::vector< std::set< wxString > > & JumperPadGroups()
Each jumper pad group is a set of pad numbers that should be treated as internally connected.
Definition footprint.h:1054
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1048
bool HasField(const wxString &aFieldName) const
PCB_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this footprint.
std::deque< PAD * > & Pads()
Definition footprint.h:304
int GetAttributes() const
Definition footprint.h:415
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:337
wxString GetSheetfile() const
Definition footprint.h:378
const LIB_ID & GetFPID() const
Definition footprint.h:349
void SetReference(const wxString &aReference)
Definition footprint.h:755
bool IsLocked() const override
Definition footprint.h:542
void SetValue(const wxString &aValue)
Definition footprint.h:776
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:847
wxString GetFilters() const
Definition footprint.h:381
void SetSheetname(const wxString &aSheetname)
Definition footprint.h:376
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:771
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:749
const KIID_PATH & GetPath() const
Definition footprint.h:372
VECTOR2I GetPosition() const override
Definition footprint.h:325
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:88
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:84
@ FP_BOARD_ONLY
Definition footprint.h:86
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:85
@ FP_JUST_ADDED
Definition footprint.h:87
@ 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