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