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, see <https://www.gnu.org/licenses/>.
23 */
24
25
26#include <common.h> // for PAGE_INFO
27
28#include <base_units.h>
29#include <board.h>
34#include <netinfo.h>
35#include <footprint.h>
37#include <pad.h>
38#include <pcb_group.h>
39#include <pcb_track.h>
40#include <zone.h>
41#include <string_utils.h>
42#include <algorithm>
43#include <limits>
44#include <pcb_edit_frame.h>
45#include <pcbnew_settings.h>
48#include <reporter.h>
50#include <tool/tool_manager.h>
51#include <wx/log.h>
52
54
55
57 m_frame( aFrame ),
59 m_commit( aFrame->GetToolManager() ),
60 m_board( aBoard ),
61 m_reporter( &NULL_REPORTER::GetInstance() )
62{
63}
64
65
67 m_frame( nullptr ),
69 m_commit( aToolManager ),
70 m_board( aBoard ),
71 m_reporter( &NULL_REPORTER::GetInstance() )
72{
73}
74
75
79
80
82 REPORTER* aReporter, bool aDryRun )
83{
84 for( NETINFO_ITEM* net : aBoard->GetNetInfo() )
85 {
86 const wxString previous = net->GetNetChain();
87 wxString next = aNetlist.GetNetChainFor( net->GetNetname() );
88
89 if( !previous.IsEmpty() && next.IsEmpty() && aReporter && !aDryRun )
90 {
91 aReporter->Report(
92 wxString::Format(
93 _( "Net chain assignment '%s' on net '%s' cleared by netlist "
94 "update." ),
95 previous, net->GetNetname() ),
97 }
98
99 if( !aDryRun )
100 {
101 net->SetNetChain( next );
102
103 if( previous != next )
104 {
105 for( int i = 0; i < 2; ++i )
106 net->ClearTerminalPad( i );
107 }
108 }
109 }
110}
111
112
114{
115 const std::shared_ptr<NET_SETTINGS>& netSettings = aBoard->GetDesignSettings().m_NetSettings;
116
117 if( !netSettings )
118 return;
119
120 netSettings->ClearNetChainClasses();
121 netSettings->ClearNetChainNetClasses();
122
123 for( const auto& [chain, className] : aNetlist.GetSignalChainClasses() )
124 netSettings->SetNetChainClass( chain, className );
125
126 for( const auto& [chain, netclass] : aNetlist.GetNetChainNetClasses() )
127 netSettings->SetNetChainNetClass( chain, netclass );
128
129 // Chain membership on the board has just been refreshed from the netlist, so assignments
130 // derived from the previous membership are stale. The caller's
131 // SynchronizeNetsAndNetClasses() rebuilds them from the maps set above.
132 netSettings->ClearChainPatternAssignments( NET_CHAIN_SOURCE::BOARD );
133}
134
135
136// These functions allow inspection of pad nets during dry runs by keeping a cache of
137// current pad netnames indexed by pad.
138
139void BOARD_NETLIST_UPDATER::cacheNetname( PAD* aPad, const wxString& aNetname )
140{
141 m_padNets[ aPad ] = aNetname;
142}
143
144
146{
147 if( m_isDryRun && m_padNets.count( aPad ) )
148 return m_padNets[ aPad ];
149 else
150 return aPad->GetNetname();
151}
152
153
154void BOARD_NETLIST_UPDATER::cachePinFunction( PAD* aPad, const wxString& aPinFunction )
155{
156 m_padPinFunctions[ aPad ] = aPinFunction;
157}
158
159
161{
162 if( m_isDryRun && m_padPinFunctions.count( aPad ) )
163 return m_padPinFunctions[ aPad ];
164 else
165 return aPad->GetPinFunction();
166}
167
168
170{
171 VECTOR2I bestPosition;
172
173 if( !m_board->IsEmpty() )
174 {
175 // Position new components below any existing board features.
176 BOX2I bbox = m_board->GetBoardEdgesBoundingBox();
177
178 if( bbox.GetWidth() || bbox.GetHeight() )
179 {
180 bestPosition.x = bbox.Centre().x;
181 bestPosition.y = bbox.GetBottom() + pcbIUScale.mmToIU( 10 );
182 }
183 }
184 else
185 {
186 // Position new components in the center of the page when the board is empty.
187 VECTOR2I pageSize = m_board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
188
189 bestPosition.x = pageSize.x / 2;
190 bestPosition.y = pageSize.y / 2;
191 }
192
193 return bestPosition;
194}
195
196
198{
199 return addNewFootprint( aComponent, aComponent->GetFPID() );
200}
201
202
204{
205 wxString msg;
206
207 if( aFootprintId.empty() )
208 {
209 msg.Printf( _( "Cannot add %s (no footprint assigned)." ),
210 aComponent->GetReference() );
211 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
212 ++m_errorCount;
213 return nullptr;
214 }
215
216 FOOTPRINT* footprint = LoadFootprintFromProject( m_board, aFootprintId );
217
218 if( footprint == nullptr )
219 {
220 msg.Printf( _( "Cannot add %s (footprint '%s' not found)." ),
221 aComponent->GetReference(),
222 EscapeHTML( aFootprintId.Format().wx_str() ) );
223 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
224 ++m_errorCount;
225 return nullptr;
226 }
227
228 footprint->SetStaticComponentClass(
229 m_board->GetComponentClassManager().GetNoneComponentClass() );
230
231 if( m_isDryRun )
232 {
233 msg.Printf( _( "Add %s (footprint '%s')." ),
234 aComponent->GetReference(),
235 EscapeHTML( aFootprintId.Format().wx_str() ) );
236
237 delete footprint;
238 footprint = nullptr;
239 }
240 else
241 {
242 for( PAD* pad : footprint->Pads() )
243 {
244 bool showRatsnest = true;
245
246 if( m_settings )
247 showRatsnest = m_settings->m_Display.m_ShowGlobalRatsnest;
248
249 pad->SetLocalRatsnestVisible( showRatsnest );
250
251 // Pads in the library all have orphaned nets. Replace with Default.
252 pad->SetNetCode( 0 );
253 }
254
255 footprint->SetParent( m_board );
257
258 // This flag is used to prevent connectivity from considering the footprint during its
259 // initial build after the footprint is committed, because we're going to immediately start
260 // a move operation on the footprint and don't want its pads to drive nets onto vias/tracks
261 // it happens to land on at the initial position.
262 if( m_willDrag )
263 footprint->SetAttributes( footprint->GetAttributes() | FP_JUST_ADDED );
264
265 m_addedFootprints.push_back( footprint );
266 m_commit.Add( footprint );
267
268 msg.Printf( _( "Added %s (footprint '%s')." ),
269 aComponent->GetReference(),
270 EscapeHTML( aFootprintId.Format().wx_str() ) );
271 }
272
273 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
275 return footprint;
276}
277
278
280{
281 wxString curClassName, newClassName;
282 COMPONENT_CLASS* newClass = nullptr;
283
284 if( const COMPONENT_CLASS* curClass = aFootprint->GetStaticComponentClass() )
285 curClassName = curClass->GetName();
286
287 // Calculate the new component class
288 if( m_isDryRun )
289 {
291 aNewComponent->GetComponentClassNames() );
292 }
293 else
294 {
295 newClass = m_board->GetComponentClassManager().GetEffectiveStaticComponentClass(
296 aNewComponent->GetComponentClassNames() );
297 newClassName = newClass->GetName();
298 }
299
300 if( curClassName == newClassName )
301 return false;
302
303 // Create a copy for undo if the footprint has not been added during this update
304 FOOTPRINT* copy = nullptr;
305
306 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
307 {
308 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
309 copy->SetParentGroup( nullptr );
310 }
311
312 wxString msg;
313
314 if( m_isDryRun )
315 {
316 if( curClassName == wxEmptyString && newClassName != wxEmptyString )
317 {
318 msg.Printf( _( "Change %s component class to '%s'." ),
319 aFootprint->GetReference(),
320 EscapeHTML( newClassName ) );
321 }
322 else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
323 {
324 msg.Printf( _( "Remove %s component class (currently '%s')." ),
325 aFootprint->GetReference(),
326 EscapeHTML( curClassName ) );
327 }
328 else
329 {
330 msg.Printf( _( "Change %s component class from '%s' to '%s'." ),
331 aFootprint->GetReference(),
332 EscapeHTML( curClassName ),
333 EscapeHTML( newClassName ) );
334 }
335 }
336 else
337 {
338 wxASSERT_MSG( newClass != nullptr, "Component class should not be nullptr" );
339
340 aFootprint->SetStaticComponentClass( newClass );
341
342 if( curClassName == wxEmptyString && newClassName != wxEmptyString )
343 {
344 msg.Printf( _( "Changed %s component class to '%s'." ),
345 aFootprint->GetReference(),
346 EscapeHTML( newClassName ) );
347 }
348 else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
349 {
350 msg.Printf( _( "Removed %s component class (was '%s')." ),
351 aFootprint->GetReference(),
352 EscapeHTML( curClassName ) );
353 }
354 else
355 {
356 msg.Printf( _( "Changed %s component class from '%s' to '%s'." ),
357 aFootprint->GetReference(),
358 EscapeHTML( curClassName ),
359 EscapeHTML( newClassName ) );
360 }
361 }
362
363 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
364
365 if( copy )
366 m_commit.Modified( aFootprint, copy );
367
368 return true;
369}
370
371
373 COMPONENT* aNewComponent )
374{
375 wxString msg;
376
377 if( aNewComponent->GetFPID().empty() )
378 {
379 msg.Printf( _( "Cannot update %s (no footprint assigned)." ),
380 aNewComponent->GetReference() );
381 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
382 ++m_errorCount;
383 return nullptr;
384 }
385
386 FOOTPRINT* newFootprint = LoadFootprintFromProject( m_board, aNewComponent->GetFPID() );
387
388 if( newFootprint == nullptr )
389 {
390 msg.Printf( _( "Cannot update %s (footprint '%s' not found)." ),
391 aNewComponent->GetReference(),
392 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
393 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
394 ++m_errorCount;
395 return nullptr;
396 }
397
398 if( m_isDryRun )
399 {
400 if( aFootprint->IsLocked() && !m_overrideLocks )
401 {
402 msg.Printf( _( "Cannot change %s footprint from '%s' to '%s' (footprint is locked)."),
403 aFootprint->GetReference(),
404 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
405 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
406 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
408 delete newFootprint;
409 return nullptr;
410 }
411 else
412 {
413 msg.Printf( _( "Change %s footprint from '%s' to '%s'."),
414 aFootprint->GetReference(),
415 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
416 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
417 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
419 delete newFootprint;
420 return nullptr;
421 }
422 }
423 else
424 {
425 if( aFootprint->IsLocked() && !m_overrideLocks )
426 {
427 msg.Printf( _( "Could not change %s footprint from '%s' to '%s' (footprint is locked)."),
428 aFootprint->GetReference(),
429 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
430 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
431 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
433 delete newFootprint;
434 return nullptr;
435 }
436 else
437 {
438 // Expand the footprint pad layers
439 newFootprint->FixUpPadsForBoard( m_board );
440
441 m_board->ExchangeFootprint( aFootprint, newFootprint, m_commit,
442 m_frame->GetPcbNewSettings()->m_FlipDirection, true );
443
444 msg.Printf( _( "Changed %s footprint from '%s' to '%s'."),
445 aFootprint->GetReference(),
446 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
447 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
448 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
450 return newFootprint;
451 }
452 }
453 }
454
455
457{
458 wxString msg;
459
460 const COMPONENT_VARIANT* firstAssociatedVariant = nullptr;
461
462 if( fpidsEquivalent( aFootprint->GetFPID(), aNetlistComponent->GetFPID() ) )
463 {
464 for( const auto& [_, test] : aNetlistComponent->GetVariants() )
465 {
467 && aFootprint->GetFPIDAsString()
469 {
470 firstAssociatedVariant = &test;
471 break;
472 }
473 }
474 }
475
476 // Create a copy only if the footprint has not been added during this update
477 FOOTPRINT* copy = nullptr;
478
479 if( !m_commit.GetStatus( aFootprint ) )
480 {
481 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
482 copy->SetParentGroup( nullptr );
483 }
484
485 bool changed = false;
486
487 // Test for reference designator field change.
488 if( aFootprint->GetReference() != aNetlistComponent->GetReference() )
489 {
490 if( m_isDryRun )
491 {
492 msg.Printf( _( "Change %s reference designator to %s." ),
493 aFootprint->GetReference(),
494 aNetlistComponent->GetReference() );
495 }
496 else
497 {
498 msg.Printf( _( "Changed %s reference designator to %s." ),
499 aFootprint->GetReference(),
500 aNetlistComponent->GetReference() );
501
502 changed = true;
503 aFootprint->SetReference( aNetlistComponent->GetReference() );
504 }
505
506 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
507 }
508
509 // Test for value field change.
510 wxString netlistValue = aNetlistComponent->GetValue();
511
512 if( firstAssociatedVariant != nullptr
513 && firstAssociatedVariant->m_fields.count( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) ) )
514 {
515 netlistValue = firstAssociatedVariant->m_fields.at( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) );
516 }
517
518 if( aFootprint->GetValue() != netlistValue )
519 {
520 if( m_isDryRun )
521 {
522 msg.Printf( _( "Change %s value from %s to %s." ),
523 aFootprint->GetReference(),
524 EscapeHTML( aFootprint->GetValue() ),
526 }
527 else
528 {
529 msg.Printf( _( "Changed %s value from %s to %s." ),
530 aFootprint->GetReference(),
531 EscapeHTML( aFootprint->GetValue() ),
533
534 changed = true;
535 aFootprint->SetValue( netlistValue );
536 }
537
538 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
539 }
540
541 // Test for time stamp change.
542 KIID_PATH new_path = aNetlistComponent->GetPath();
543
544 if( !aNetlistComponent->GetKIIDs().empty() )
545 new_path.push_back( aNetlistComponent->GetKIIDs().front() );
546
547 if( aFootprint->GetPath() != new_path )
548 {
549 if( m_isDryRun )
550 {
551 msg.Printf( _( "Update %s symbol association from %s to %s." ),
552 aFootprint->GetReference(),
553 EscapeHTML( aFootprint->GetPath().AsString() ),
554 EscapeHTML( new_path.AsString() ) );
555 }
556 else
557 {
558 msg.Printf( _( "Updated %s symbol association from %s to %s." ),
559 aFootprint->GetReference(),
560 EscapeHTML( aFootprint->GetPath().AsString() ),
561 EscapeHTML( new_path.AsString() ) );
562
563 changed = true;
564 aFootprint->SetPath( new_path );
565 }
566
567 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
568 }
569
570 nlohmann::ordered_map<wxString, wxString> fpFieldsAsMap;
571
572 for( PCB_FIELD* field : aFootprint->GetFields() )
573 {
574 // These fields are individually checked above
575 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
576 {
577 continue;
578 }
579
580 fpFieldsAsMap[field->GetName()] = field->GetText();
581 }
582
583 nlohmann::ordered_map<wxString, wxString> compFields = aNetlistComponent->GetFields();
584
585 if( firstAssociatedVariant != nullptr )
586 {
587 for( const auto& [name, value] : firstAssociatedVariant->m_fields )
588 compFields[name] = value;
589 }
590
591 // Remove the ref/value/footprint fields that are individually handled
593 compFields.erase( GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED ) );
595
596 // Remove any component class fields - these are not editable in the pcb editor
597 compFields.erase( wxT( "Component Class" ) );
598
599 // Fields are stored as an ordered map, but we don't (yet) support reordering the footprint fields to
600 // match the symbol, so we manually check the fields in the order they are stored in the symbol.
601 bool same = true;
602 bool remove_only = true;
603
604 for( const auto& [name, value] : compFields )
605 {
606 if( fpFieldsAsMap.count( name ) == 0 || fpFieldsAsMap[name] != value )
607 {
608 same = false;
609 remove_only = false;
610 break;
611 }
612 }
613
614 for( const auto& [name, value] : fpFieldsAsMap )
615 {
616 if( compFields.count( name ) == 0 )
617 {
618 same = false;
619 break;
620 }
621 }
622
623 if( !same )
624 {
625 if( m_isDryRun )
626 {
627 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
628 {
629 msg.Printf( _( "Update %s fields." ), aFootprint->GetReference() );
630 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
631 }
632
633 // Remove fields that aren't present in the symbol
634 for( PCB_FIELD* field : aFootprint->GetFields() )
635 {
636 if( field->IsMandatory() )
637 continue;
638
639 if( compFields.count( field->GetName() ) == 0 )
640 {
642 {
643 msg.Printf( _( "Remove %s footprint fields not in symbol." ), aFootprint->GetReference() );
644 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
645 }
646
647 break;
648 }
649 }
650 }
651 else
652 {
653 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
654 {
655 msg.Printf( _( "Updated %s fields." ), aFootprint->GetReference() );
656 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
657
658 changed = true;
659
660 // Add or change field value
661 for( auto& [name, value] : compFields )
662 {
663 if( aFootprint->HasField( name ) )
664 {
665 aFootprint->GetField( name )->SetText( value );
666 }
667 else
668 {
669 PCB_FIELD* newField = new PCB_FIELD( aFootprint, FIELD_T::USER );
670 aFootprint->Add( newField );
671
672 newField->SetName( name );
673 newField->SetText( value );
674 newField->SetVisible( false );
675 newField->SetLayer( aFootprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
676
677 // Give the relative position (0,0) in footprint
678 newField->SetPosition( aFootprint->GetPosition() );
679 // Give the footprint orientation
680 newField->Rotate( aFootprint->GetPosition(), aFootprint->GetOrientation() );
681
682 newField->StyleFromSettings( m_board->GetDesignSettings(), true );
683 }
684 }
685 }
686
688 {
689 bool warned = false;
690
691 std::vector<PCB_FIELD*> fieldList;
692 aFootprint->GetFields( fieldList, false );
693
694 for( PCB_FIELD* field : fieldList )
695 {
696 if( field->IsMandatory() )
697 continue;
698
699 if( compFields.count( field->GetName() ) == 0 )
700 {
701 if( !warned )
702 {
703 warned = true;
704 msg.Printf( _( "Removed %s footprint fields not in symbol." ), aFootprint->GetReference() );
705 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
706 }
707
708 aFootprint->Remove( field );
709
710 if( m_frame )
711 m_frame->GetCanvas()->GetView()->Remove( field );
712
713 delete field;
714 }
715 }
716 }
717 }
718 }
719
720 wxString sheetname;
721 wxString sheetfile;
722 wxString fpFilters;
723
724 wxString humanSheetPath = aNetlistComponent->GetHumanReadablePath();
725
726 if( !humanSheetPath.empty() )
727 sheetname = humanSheetPath;
728 else if( aNetlistComponent->GetProperties().count( wxT( "Sheetname" ) ) > 0 )
729 sheetname = aNetlistComponent->GetProperties().at( wxT( "Sheetname" ) );
730
731 if( aNetlistComponent->GetProperties().count( wxT( "Sheetfile" ) ) > 0 )
732 sheetfile = aNetlistComponent->GetProperties().at( wxT( "Sheetfile" ) );
733
734 if( aNetlistComponent->GetProperties().count( wxT( "ki_fp_filters" ) ) > 0 )
735 fpFilters = aNetlistComponent->GetProperties().at( wxT( "ki_fp_filters" ) );
736
737 if( sheetname != aFootprint->GetSheetname() )
738 {
739 if( m_isDryRun )
740 {
741 msg.Printf( _( "Update %s sheetname to '%s'." ),
742 aFootprint->GetReference(),
743 EscapeHTML( sheetname ) );
744 }
745 else
746 {
747 msg.Printf( _( "Updated %s sheetname to '%s'." ),
748 aFootprint->GetReference(),
749 EscapeHTML( sheetname ) );
750
751 changed = true;
752 aFootprint->SetSheetname( sheetname );
753 }
754
755 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
756 }
757
758 if( sheetfile != aFootprint->GetSheetfile() )
759 {
760 if( m_isDryRun )
761 {
762 msg.Printf( _( "Update %s sheetfile to '%s'." ),
763 aFootprint->GetReference(),
764 EscapeHTML( sheetfile ) );
765 }
766 else
767 {
768 msg.Printf( _( "Updated %s sheetfile to '%s'." ),
769 aFootprint->GetReference(),
770 EscapeHTML( sheetfile ) );
771
772 changed = true;
773 aFootprint->SetSheetfile( sheetfile );
774 }
775
776 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
777 }
778
779 if( fpFilters != aFootprint->GetFilters() )
780 {
781 if( m_isDryRun )
782 {
783 msg.Printf( _( "Update %s footprint filters to '%s'." ),
784 aFootprint->GetReference(),
785 EscapeHTML( fpFilters ) );
786 }
787 else
788 {
789 msg.Printf( _( "Updated %s footprint filters to '%s'." ),
790 aFootprint->GetReference(),
791 EscapeHTML( fpFilters ) );
792
793 changed = true;
794 aFootprint->SetFilters( fpFilters );
795 }
796
797 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
798 }
799
800 bool netlistExcludeFromBOM = aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) > 0;
801
802 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromBOM )
803 netlistExcludeFromBOM = firstAssociatedVariant->m_excludedFromBOM;
804
805 if( m_updateFields && netlistExcludeFromBOM != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
806 {
807 if( m_isDryRun )
808 {
809 if( netlistExcludeFromBOM )
810 msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
811 else
812 msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
813 }
814 else
815 {
816 int attributes = aFootprint->GetAttributes();
817
818 if( netlistExcludeFromBOM )
819 {
820 attributes |= FP_EXCLUDE_FROM_BOM;
821 msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
822 }
823 else
824 {
825 attributes &= ~FP_EXCLUDE_FROM_BOM;
826 msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ), aFootprint->GetReference() );
827 }
828
829 changed = true;
830 aFootprint->SetAttributes( attributes );
831 }
832
833 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
834 }
835
836 bool netlistExcludeFromSim = aNetlistComponent->GetProperties().count( wxT( "exclude_from_sim" ) ) > 0;
837
838 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromSim )
839 netlistExcludeFromSim = firstAssociatedVariant->m_excludedFromSim;
840
842 && netlistExcludeFromSim != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_SIM ) > 0 ) )
843 {
844 if( m_isDryRun )
845 {
846 if( netlistExcludeFromSim )
847 msg.Printf( _( "Add %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
848 else
849 msg.Printf( _( "Remove %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
850 }
851 else
852 {
853 int attributes = aFootprint->GetAttributes();
854
855 if( netlistExcludeFromSim )
856 {
857 attributes |= FP_EXCLUDE_FROM_SIM;
858 msg.Printf( _( "Added %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
859 }
860 else
861 {
862 attributes &= ~FP_EXCLUDE_FROM_SIM;
863 msg.Printf( _( "Removed %s 'exclude from simulation' attribute." ), aFootprint->GetReference() );
864 }
865
866 changed = true;
867 aFootprint->SetAttributes( attributes );
868 }
869
870 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
871 }
872
873 bool netlistDNP = aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) > 0;
874
875 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasDnp )
876 netlistDNP = firstAssociatedVariant->m_dnp;
877
878 if( m_updateFields && netlistDNP != ( ( aFootprint->GetAttributes() & FP_DNP ) > 0 ) )
879 {
880 if( m_isDryRun )
881 {
882 if( netlistDNP )
883 msg.Printf( _( "Add %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
884 else
885 msg.Printf( _( "Remove %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
886 }
887 else
888 {
889 int attributes = aFootprint->GetAttributes();
890
891 if( netlistDNP )
892 {
893 attributes |= FP_DNP;
894 msg.Printf( _( "Added %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
895 }
896 else
897 {
898 attributes &= ~FP_DNP;
899 msg.Printf( _( "Removed %s 'Do not place' fabrication attribute." ), aFootprint->GetReference() );
900 }
901
902 changed = true;
903 aFootprint->SetAttributes( attributes );
904 }
905
906 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
907 }
908
909 bool netlistExcludeFromPosFiles = aNetlistComponent->GetProperties().count( wxT( "exclude_from_pos_files" ) ) > 0;
910
911 if( firstAssociatedVariant != nullptr && firstAssociatedVariant->m_hasExcludedFromPosFiles )
912 netlistExcludeFromPosFiles = firstAssociatedVariant->m_excludedFromPosFiles;
913
915 && netlistExcludeFromPosFiles != ( ( aFootprint->GetAttributes() & FP_EXCLUDE_FROM_POS_FILES ) > 0 ) )
916 {
917 if( m_isDryRun )
918 {
919 if( netlistExcludeFromPosFiles )
920 {
921 msg.Printf( _( "Add %s 'exclude from position files' fabrication attribute." ),
922 aFootprint->GetReference() );
923 }
924 else
925 {
926 msg.Printf( _( "Remove %s 'exclude from position files' fabrication attribute." ),
927 aFootprint->GetReference() );
928 }
929 }
930 else
931 {
932 int attributes = aFootprint->GetAttributes();
933
934 if( netlistExcludeFromPosFiles )
935 {
936 attributes |= FP_EXCLUDE_FROM_POS_FILES;
937 msg.Printf( _( "Added %s 'exclude from position files' fabrication attribute." ),
938 aFootprint->GetReference() );
939 }
940 else
941 {
942 attributes &= ~FP_EXCLUDE_FROM_POS_FILES;
943 msg.Printf( _( "Removed %s 'exclude from position files' fabrication attribute." ),
944 aFootprint->GetReference() );
945 }
946
947 changed = true;
948 aFootprint->SetAttributes( attributes );
949 }
950
951 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
952 }
953
955 && aNetlistComponent->GetDuplicatePadNumbersAreJumpers() != aFootprint->GetDuplicatePadNumbersAreJumpers() )
956 {
957 bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
958
959 if( !m_isDryRun )
960 {
961 changed = true;
962 aFootprint->SetDuplicatePadNumbersAreJumpers( value );
963
964 if( value )
965 {
966 msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
967 aFootprint->GetReference() );
968 }
969 else
970 {
971 msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
972 aFootprint->GetReference() );
973 }
974 }
975 else
976 {
977 if( value )
978 {
979 msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
980 aFootprint->GetReference() );
981 }
982 else
983 {
984 msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
985 aFootprint->GetReference() );
986 }
987 }
988
989 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
990 }
991
992 if( m_updateFields && aNetlistComponent->JumperPadGroups() != aFootprint->JumperPadGroups() )
993 {
994 if( !m_isDryRun )
995 {
996 changed = true;
997 aFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
998 msg.Printf( _( "Updated %s jumper pad groups" ), aFootprint->GetReference() );
999 }
1000 else
1001 {
1002 msg.Printf( _( "Update %s jumper pad groups" ), aFootprint->GetReference() );
1003 }
1004
1005 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1006 }
1007
1008 if( changed && copy )
1009 m_commit.Modified( aFootprint, copy );
1010 else
1011 delete copy;
1012
1013 return true;
1014}
1015
1016
1017bool BOARD_NETLIST_UPDATER::updateFootprintGroup( FOOTPRINT* aPcbFootprint, COMPONENT* aNetlistComponent )
1018{
1019 if( !m_transferGroups )
1020 return false;
1021
1022 KIID newGroupKIID = aNetlistComponent->GetGroup() ? aNetlistComponent->GetGroup()->uuid : 0;
1023 PCB_GROUP* existingGroup = static_cast<PCB_GROUP*>( aPcbFootprint->GetParentGroup() );
1024 KIID existingGroupKIID = existingGroup ? existingGroup->m_Uuid : 0;
1025
1026 // No changes, nothing to do
1027 if( newGroupKIID == existingGroupKIID )
1028 return false;
1029
1030 wxString msg;
1031
1032 // Create a copy only if the footprint has not been added during this update
1033 FOOTPRINT* copy = nullptr;
1034
1035 if( !m_isDryRun && !m_commit.GetStatus( aPcbFootprint ) )
1036 {
1037 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
1038 copy->SetParentGroup( nullptr );
1039 }
1040
1041 bool changed = false;
1042
1043 // newGroup may point to an existing group on the board if we find an
1044 // incoming group UUID that matches an existing group
1045 PCB_GROUP* newGroup = nullptr;
1046
1047 // Find existing group based on matching UUIDs
1048 auto it = std::find_if( m_board->Groups().begin(), m_board->Groups().end(),
1049 [&](PCB_GROUP* group) {
1050 return group->m_Uuid == newGroupKIID;
1051 });
1052
1053 // If we find a group with the same UUID, use it
1054 if( it != m_board->Groups().end() )
1055 newGroup = *it;
1056
1057 // Remove from existing group
1058 if( existingGroupKIID != 0 )
1059 {
1060 if( m_isDryRun )
1061 {
1062 msg.Printf( _( "Remove %s from group '%s'." ),
1063 aPcbFootprint->GetReference(),
1064 EscapeHTML( existingGroup->GetName() ) );
1065 }
1066 else
1067 {
1068 msg.Printf( _( "Removed %s from group '%s'." ),
1069 aPcbFootprint->GetReference(),
1070 EscapeHTML( existingGroup->GetName() ) );
1071
1072 changed = true;
1073 m_commit.Modify( existingGroup, nullptr, RECURSE_MODE::NO_RECURSE );
1074 existingGroup->RemoveItem( aPcbFootprint );
1075
1076 if( existingGroup->GetItems().size() < 2 )
1077 {
1078 existingGroup->RemoveAll();
1079 m_commit.Remove( existingGroup );
1080 }
1081 }
1082
1083 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1084 }
1085
1086 // Add to new group
1087 if( newGroupKIID != 0 )
1088 {
1089 if( m_isDryRun )
1090 {
1091 msg.Printf( _( "Add %s to group '%s'." ),
1092 aPcbFootprint->GetReference(),
1093 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
1094 }
1095 else
1096 {
1097 msg.Printf( _( "Added %s to group '%s'." ),
1098 aPcbFootprint->GetReference(),
1099 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
1100
1101 changed = true;
1102
1103 if( newGroup == nullptr )
1104 {
1105 newGroup = new PCB_GROUP( m_board );
1106 newGroup->SetUuid( newGroupKIID );
1107 newGroup->SetName( aNetlistComponent->GetGroup()->name );
1108
1109 // Add the group to the board manually so we can find it by checking
1110 // board groups for later footprints that are checking for existing groups
1111 m_board->Add( newGroup );
1112 m_commit.Added( newGroup );
1113 m_addedGroups.push_back( newGroup );
1114 }
1115 else
1116 {
1117 m_commit.Modify( newGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1118 }
1119
1120 newGroup->AddItem( aPcbFootprint );
1121 }
1122
1123 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1124 }
1125
1126 if( changed && copy )
1127 m_commit.Modified( aPcbFootprint, copy );
1128 else if( copy )
1129 delete copy;
1130
1131 return changed;
1132}
1133
1134
1136{
1137 wxString msg;
1138
1139 // Create a copy only if the footprint has not been added during this update
1140 FOOTPRINT* copy = nullptr;
1141
1142 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
1143 {
1144 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1145 copy->SetParentGroup( nullptr );
1146 }
1147
1148 bool changed = false;
1149
1150 // At this point, the component footprint is updated. Now update the nets.
1151 std::deque<PAD*> pads = aFootprint->Pads();
1152 std::set<wxString> padNetnames;
1153
1154 std::sort( pads.begin(), pads.end(),
1155 []( PAD* a, PAD* b )
1156 {
1157 return a->m_Uuid < b->m_Uuid;
1158 } );
1159
1160 for( PAD* pad : pads )
1161 {
1162 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
1163
1164 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1165 wxT( "Processing pad %s of component %s" ),
1166 pad->GetNumber(),
1167 aNewComponent->GetReference() );
1168
1169 wxString pinFunction;
1170 wxString pinType;
1171
1172 if( net.IsValid() ) // i.e. the pad has a name
1173 {
1174 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1175 wxT( " Found valid net: %s" ),
1176 net.GetNetName() );
1177 pinFunction = net.GetPinFunction();
1178 pinType = net.GetPinType();
1179 }
1180 else
1181 {
1182 wxLogTrace( wxT( "NETLIST_UPDATE" ),
1183 wxT( " No net found for pad %s" ),
1184 pad->GetNumber() );
1185 }
1186
1187 if( !m_isDryRun )
1188 {
1189 if( pad->GetPinFunction() != pinFunction )
1190 {
1191 changed = true;
1192 pad->SetPinFunction( pinFunction );
1193 }
1194
1195 if( pad->GetPinType() != pinType )
1196 {
1197 changed = true;
1198 pad->SetPinType( pinType );
1199 }
1200 }
1201 else
1202 {
1203 cachePinFunction( pad, pinFunction );
1204 }
1205
1206 // Test if new footprint pad has no net (pads not on copper layers have no net).
1207 if( !net.IsValid() || !pad->IsOnCopperLayer() )
1208 {
1209 if( !pad->GetNetname().IsEmpty() )
1210 {
1211 if( m_isDryRun )
1212 {
1213 msg.Printf( _( "Disconnect %s pin %s." ),
1214 aFootprint->GetReference(),
1215 EscapeHTML( pad->GetNumber() ) );
1216 }
1217 else
1218 {
1219 msg.Printf( _( "Disconnected %s pin %s." ),
1220 aFootprint->GetReference(),
1221 EscapeHTML( pad->GetNumber() ) );
1222 }
1223
1224 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1225 }
1226 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
1227 {
1228 // pad is connectable but has no net found in netlist
1229 msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
1230 aFootprint->GetReference(),
1231 EscapeHTML( pad->GetNumber() ),
1232 EscapeHTML( pad->GetNumber() ) );
1233 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
1235 }
1236
1237 if( !m_isDryRun )
1238 {
1239 if( pad->GetNetCode() != NETINFO_LIST::UNCONNECTED )
1240 {
1241 changed = true;
1242 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
1243 }
1244
1245 // If the pad has no net from netlist (i.e. not in netlist
1246 // it cannot have a pin function
1247 if( pad->GetNetname().IsEmpty() && !pad->GetPinFunction().IsEmpty() )
1248 {
1249 changed = true;
1250 pad->SetPinFunction( wxEmptyString );
1251 }
1252 }
1253 else
1254 {
1255 cacheNetname( pad, wxEmptyString );
1256 }
1257 }
1258 else // New footprint pad has a net.
1259 {
1260 wxString netName = net.GetNetName();
1261
1262 if( pad->IsNoConnectPad() )
1263 {
1264 netName = wxString::Format( wxS( "%s" ), net.GetNetName() );
1265
1266 for( int jj = 1; !padNetnames.insert( netName ).second
1267 || ( netName != net.GetNetName() && m_schematicNetNames.count( netName ) );
1268 jj++ )
1269 {
1270 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1271 }
1272 }
1273
1274 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1275
1276 if( netinfo && !m_isDryRun )
1277 netinfo->SetIsCurrent( true );
1278
1279 if( pad->GetNetname() != netName )
1280 {
1281
1282 if( netinfo == nullptr )
1283 {
1284 // It might be a new net that has not been added to the board yet
1285 if( m_addedNets.count( netName ) )
1286 netinfo = m_addedNets[ netName ];
1287 }
1288
1289 if( netinfo == nullptr )
1290 {
1291 netinfo = new NETINFO_ITEM( m_board, netName );
1292
1293 // It is a new net, we have to add it
1294 if( !m_isDryRun )
1295 {
1296 changed = true;
1297 m_commit.Add( netinfo );
1298 }
1299
1300 m_addedNets[netName] = netinfo;
1301 msg.Printf( _( "Add net %s." ),
1302 EscapeHTML( UnescapeString( netName ) ) );
1303 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1304 }
1305
1306 if( !pad->GetNetname().IsEmpty() )
1307 {
1308 m_oldToNewNets[ pad->GetNetname() ] = netName;
1309
1310 if( m_isDryRun )
1311 {
1312 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1313 aFootprint->GetReference(),
1314 EscapeHTML( pad->GetNumber() ),
1315 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1316 EscapeHTML( UnescapeString( netName ) ) );
1317 }
1318 else
1319 {
1320 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1321 aFootprint->GetReference(),
1322 EscapeHTML( pad->GetNumber() ),
1323 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1324 EscapeHTML( UnescapeString( netName ) ) );
1325 }
1326 }
1327 else
1328 {
1329 if( m_isDryRun )
1330 {
1331 msg.Printf( _( "Connect %s pin %s to %s."),
1332 aFootprint->GetReference(),
1333 EscapeHTML( pad->GetNumber() ),
1334 EscapeHTML( UnescapeString( netName ) ) );
1335 }
1336 else
1337 {
1338 msg.Printf( _( "Connected %s pin %s to %s."),
1339 aFootprint->GetReference(),
1340 EscapeHTML( pad->GetNumber() ),
1341 EscapeHTML( UnescapeString( netName ) ) );
1342 }
1343 }
1344
1345 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1346
1347 if( !m_isDryRun )
1348 {
1349 changed = true;
1350 pad->SetNet( netinfo );
1351 }
1352 else
1353 {
1354 cacheNetname( pad, netName );
1355 }
1356 }
1357 }
1358 }
1359
1360 if( changed && copy )
1361 m_commit.Modified( aFootprint, copy );
1362 else
1363 delete copy;
1364
1365 return true;
1366}
1367
1368
1370{
1371 // Build the footprint-side representation from the netlist component
1372 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1373
1374 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1375 newUnits.push_back( { u.m_unitName, u.m_pins } );
1376
1377 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1378
1379 if( std::ranges::equal( curUnits, newUnits,
1380 []( const auto& a, const auto& b )
1381 {
1382 return a.m_unitName == b.m_unitName && a.m_pins == b.m_pins;
1383 } ) )
1384 return false;
1385
1386 wxString msg;
1387
1388 if( m_isDryRun )
1389 {
1390 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1391 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1392 return false; // no actual change on board during dry run
1393 }
1394
1395 // Create a copy only if the footprint has not been added during this update
1396 FOOTPRINT* copy = nullptr;
1397
1398 if( !m_commit.GetStatus( aFootprint ) )
1399 {
1400 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1401 copy->SetParentGroup( nullptr );
1402 }
1403
1404 aFootprint->SetUnitInfo( newUnits );
1405
1406 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1407 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1408
1409 if( copy )
1410 m_commit.Modified( aFootprint, copy );
1411
1412 return true;
1413}
1414
1415
1416bool BOARD_NETLIST_UPDATER::fpidsEquivalent( const LIB_ID& aBoardFpid, const LIB_ID& aSchematicFpid )
1417{
1418 if( aSchematicFpid.IsLegacy() )
1419 return aBoardFpid.GetLibItemName() == aSchematicFpid.GetLibItemName();
1420
1421 return aBoardFpid == aSchematicFpid;
1422}
1423
1424
1426 const std::vector<FOOTPRINT*>& aFootprints,
1427 const LIB_ID& aBaseFpid )
1428{
1429 wxString msg;
1430 const auto& variants = aComponent->GetVariants();
1431
1432 if( aBaseFpid.empty() )
1433 return;
1434
1435 const wxString FOOTPRINT_FIELD_NAME = GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED );
1436
1437 struct VARIANT_INFO
1438 {
1439 wxString name;
1440 const COMPONENT_VARIANT* variant;
1441 LIB_ID variantFPID;
1442 };
1443
1444 std::vector<VARIANT_INFO> variantInfo;
1445 variantInfo.reserve( variants.size() );
1446
1447 for( const auto& [variantName, variant] : variants )
1448 {
1449 LIB_ID variantFPID = aBaseFpid;
1450
1451 auto fieldIt = variant.m_fields.find( FOOTPRINT_FIELD_NAME );
1452
1453 if( fieldIt != variant.m_fields.end() && !fieldIt->second.IsEmpty() )
1454 {
1455 LIB_ID parsedId;
1456
1457 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
1458 {
1459 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
1460 EscapeHTML( fieldIt->second ),
1461 variantName,
1462 aComponent->GetReference() );
1463 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1464 ++m_errorCount;
1465 }
1466 else
1467 {
1468 variantFPID = parsedId;
1469 }
1470 }
1471
1472 variantInfo.push_back( { variantName, &variant, variantFPID } );
1473 }
1474
1475 for( FOOTPRINT* footprint : aFootprints )
1476 {
1477 if( !footprint )
1478 continue;
1479
1480 FOOTPRINT* copy = nullptr;
1481
1482 if( !m_isDryRun && !m_commit.GetStatus( footprint ) )
1483 {
1484 copy = static_cast<FOOTPRINT*>( footprint->Clone() );
1485 copy->SetParentGroup( nullptr );
1486 }
1487
1488 bool changed = false;
1489
1490 auto printAttributeMessage =
1491 [&]( bool add, const wxString& attrName, const wxString& variantName )
1492 {
1493 if( m_isDryRun )
1494 {
1495 if( aFootprints.size() > 1 )
1496 {
1497 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s (footprint %s)." )
1498 : _( "Remove %s '%s' attribute from variant %s (footprint %s)." ),
1499 footprint->GetReference(),
1500 attrName,
1501 variantName,
1502 footprint->GetFPIDAsString() );
1503 }
1504 else
1505 {
1506 msg.Printf( add ? _( "Add %s '%s' attribute to variant %s." )
1507 : _( "Remove %s '%s' attribute from variant %s." ),
1508 footprint->GetReference(),
1509 attrName,
1510 variantName );
1511 }
1512 }
1513 else
1514 {
1515 if( aFootprints.size() > 1 )
1516 {
1517 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s (footprint %s)." )
1518 : _( "Removed %s '%s' attribute from variant %s (footprint %s)." ),
1519 footprint->GetReference(),
1520 attrName,
1521 variantName,
1522 footprint->GetFPIDAsString() );
1523 }
1524 else
1525 {
1526 msg.Printf( add ? _( "Added %s '%s' attribute to variant %s." )
1527 : _( "Removed %s '%s' attribute from variant %s." ),
1528 footprint->GetReference(),
1529 attrName,
1530 variantName );
1531 }
1532 }
1533 };
1534
1535 bool isBaseFootprint = fpidsEquivalent( footprint->GetFPID(), aBaseFpid );
1536
1537 // The footprint's own DNP flag before this pass forces the default-variant hiding below.
1538 // The per-variant target for a footprint that IS the active choice must fall back to this
1539 // original flag, not the forced one, so the active footprint stays populated.
1540 const bool baseFootprintDnp = footprint->IsDNP();
1541 bool effectiveFootprintDnp = baseFootprintDnp;
1542
1543 // A footprint that is not the component's base footprint is DNP by default (it stands in
1544 // only for the variants that select it). This runs before the per-variant loop so the loop
1545 // sees the correct effective DNP when deciding whether an explicit per-variant override is
1546 // needed; otherwise a footprint kept populated for its own variant would not converge until
1547 // a second netlist update.
1548 if( !isBaseFootprint && !effectiveFootprintDnp )
1549 {
1550 msg.Printf( m_isDryRun ? _( "Add %s 'Do not place' fabrication attribute." )
1551 : _( "Added %s 'Do not place' fabrication attribute." ),
1552 footprint->GetReference() );
1553
1554 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1555
1556 if( !m_isDryRun )
1557 footprint->SetDNP( true );
1558
1559 // Track the forced DNP locally so the per-variant loop below sees the correct effective
1560 // state even in dry run, where SetDNP() is intentionally not applied.
1561 effectiveFootprintDnp = true;
1562 changed = true;
1563 }
1564
1565 std::set<wxString> excessVariants;
1566
1567 for( const auto& [variantName, _] : footprint->GetVariants() )
1568 excessVariants.insert( variantName );
1569
1570 for( const VARIANT_INFO& info : variantInfo )
1571 {
1572 const COMPONENT_VARIANT& variant = *info.variant;
1573
1574 // During dry run, just read current state. During actual run, create variant if needed.
1575 FOOTPRINT_VARIANT* currentVariant = footprint->GetVariant( info.name );
1576
1577 // Check if this footprint is the active one for this variant
1578 bool isAssociatedFootprint = fpidsEquivalent( footprint->GetFPID(), info.variantFPID );
1579
1580 // If this footprint is not active, and it's not one of multiple footprints that share
1581 // a RefDes (one per variant), then it doesn't need variant info for it.
1582 if( !isAssociatedFootprint && aFootprints.size() <= 1 )
1583 continue;
1584
1585 excessVariants.erase( info.name );
1586 bool targetDnp = variant.m_hasDnp ? variant.m_dnp : baseFootprintDnp;
1587 bool currentDnp = currentVariant ? currentVariant->GetDNP() : effectiveFootprintDnp;
1588
1589 // When multiple footprints share a RefDes (one per variant), a footprint that is not
1590 // the active choice for this variant must be DNP for it so the 3D viewer and other
1591 // consumers hide it. The base footprint carries no global DNP flag, so it needs an
1592 // explicit per-variant override; non-base footprints are already globally DNP above.
1593 if( !isAssociatedFootprint )
1594 targetDnp = true;
1595
1596 if( currentDnp != targetDnp )
1597 {
1598 printAttributeMessage( targetDnp, _( "Do not place" ), info.name );
1599
1600 if( !m_isDryRun )
1601 {
1602 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1603 fpVariant->SetDNP( targetDnp );
1604 }
1605
1606 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1607 changed = true;
1608 }
1609
1610 bool targetExcludedFromBOM = variant.m_hasExcludedFromBOM ? variant.m_excludedFromBOM
1611 : footprint->IsExcludedFromBOM();
1612 bool currentExcludedFromBOM = currentVariant ? currentVariant->GetExcludedFromBOM()
1613 : footprint->IsExcludedFromBOM();
1614
1615 if( currentExcludedFromBOM != targetExcludedFromBOM )
1616 {
1617 printAttributeMessage( targetExcludedFromBOM, _( "exclude from BOM" ), info.name );
1618
1619 if( !m_isDryRun )
1620 {
1621 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1622 fpVariant->SetExcludedFromBOM( targetExcludedFromBOM );
1623 }
1624
1625 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1626 changed = true;
1627 }
1628
1629 bool targetExcludedFromSim = variant.m_hasExcludedFromSim ? variant.m_excludedFromSim
1630 : footprint->IsExcludedFromSim();
1631 bool currentExcludedFromSim = currentVariant ? currentVariant->GetExcludedFromSim()
1632 : footprint->IsExcludedFromSim();
1633
1634 if( currentExcludedFromSim != targetExcludedFromSim )
1635 {
1636 printAttributeMessage( targetExcludedFromSim, _( "exclude from simulation" ), info.name );
1637
1638 if( !m_isDryRun )
1639 {
1640 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1641 fpVariant->SetExcludedFromSim( targetExcludedFromSim );
1642 }
1643
1644 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1645 changed = true;
1646 }
1647
1648 bool targetExcludedFromPosFiles = variant.m_hasExcludedFromPosFiles ? variant.m_excludedFromPosFiles
1649 : footprint->IsExcludedFromPosFiles();
1650 bool currentExcludedFromPosFiles = currentVariant ? currentVariant->GetExcludedFromPosFiles()
1651 : footprint->IsExcludedFromPosFiles();
1652
1653 if( currentExcludedFromPosFiles != targetExcludedFromPosFiles )
1654 {
1655 printAttributeMessage( targetExcludedFromPosFiles, _( "exclude from position files" ), info.name );
1656
1657 if( !m_isDryRun )
1658 {
1659 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1660 fpVariant->SetExcludedFromPosFiles( targetExcludedFromPosFiles );
1661 }
1662
1663 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1664 changed = true;
1665 }
1666
1667 if( m_updateFields )
1668 {
1669 std::map<wxString, wxString> fpFieldsAsMap;
1670
1671 for( PCB_FIELD* field : footprint->GetFields() )
1672 fpFieldsAsMap[field->GetName()] = field->GetText();
1673
1674 for( const auto& [fieldName, fieldValue] : variant.m_fields )
1675 {
1676 if( fieldName.CmpNoCase( FOOTPRINT_FIELD_NAME ) == 0 )
1677 continue;
1678
1679 wxString currentValue;
1680
1681 if( currentVariant && currentVariant->HasFieldValue( fieldName ) )
1682 currentValue = currentVariant->GetFieldValue( fieldName );
1683 else
1684 currentValue = fpFieldsAsMap[fieldName];
1685
1686 fpFieldsAsMap.erase( fieldName ); // leave fpFieldsAsMap containing only unprocessed fields
1687
1688 if( currentValue != fieldValue )
1689 {
1690 if( m_isDryRun )
1691 {
1692 if( aFootprints.size() > 1 )
1693 {
1694 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s (footprint %s)." ),
1695 footprint->GetReference(),
1696 fieldName,
1697 fieldValue,
1698 info.name,
1699 footprint->GetFPIDAsString() );
1700 }
1701 else
1702 {
1703 msg.Printf( _( "Change %s field '%s' to '%s' on variant %s." ),
1704 footprint->GetReference(),
1705 fieldName,
1706 fieldValue,
1707 info.name );
1708 }
1709 }
1710 else
1711 {
1712 if( aFootprints.size() > 1 )
1713 {
1714 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s (footprint %s)." ),
1715 footprint->GetReference(),
1716 fieldName,
1717 fieldValue,
1718 info.name,
1719 footprint->GetFPIDAsString() );
1720 }
1721 else
1722 {
1723 msg.Printf( _( "Changed %s field '%s' to '%s' on variant %s." ),
1724 footprint->GetReference(),
1725 fieldName,
1726 fieldValue,
1727 info.name );
1728 }
1729
1730 if( FOOTPRINT_VARIANT* fpVariant = footprint->AddVariant( info.name ) )
1731 fpVariant->SetFieldValue( fieldName, fieldValue );
1732 }
1733
1734 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1735 changed = true;
1736 }
1737 }
1738
1739 // Go through everything left in fpFieldsAsMap and clear any variant field definitions
1740 // that are no longer present in the schematic
1741 for( const auto& [fieldName, fieldValue] : fpFieldsAsMap )
1742 {
1743 if( fieldName.CmpNoCase( FOOTPRINT_FIELD_NAME ) == 0 )
1744 continue;
1745
1746 if( currentVariant && currentVariant->HasFieldValue( fieldName ) )
1747 {
1748 if( m_isDryRun )
1749 {
1750 if( aFootprints.size() > 1 )
1751 {
1752 msg.Printf( _( "Remove %s field '%s' override on variant %s (footprint %s)." ),
1753 footprint->GetReference(),
1754 fieldName,
1755 info.name,
1756 footprint->GetFPIDAsString() );
1757 }
1758 else
1759 {
1760 msg.Printf( _( "Remove %s field '%s' override on variant %s." ),
1761 footprint->GetReference(),
1762 fieldName,
1763 info.name );
1764 }
1765 }
1766 else
1767 {
1768 if( aFootprints.size() > 1 )
1769 {
1770 msg.Printf( _( "Removed %s field '%s' override on variant %s (footprint %s)." ),
1771 footprint->GetReference(),
1772 fieldName,
1773 info.name,
1774 footprint->GetFPIDAsString() );
1775 }
1776 else
1777 {
1778 msg.Printf( _( "Removed %s field '%s' override on variant %s." ),
1779 footprint->GetReference(),
1780 fieldName,
1781 info.name );
1782 }
1783 }
1784
1785 currentVariant->RemoveFieldValue( fieldName );
1786 }
1787 }
1788 }
1789 }
1790
1791 for( const wxString& excess : excessVariants )
1792 {
1793 if( m_isDryRun )
1794 {
1795 msg.Printf( _( "Remove variant %s:%s no longer associated with footprint %s." ),
1796 footprint->GetReference(),
1797 excess,
1798 footprint->GetFPIDAsString() );
1799 }
1800 else
1801 {
1802 msg.Printf( _( "Removed variant %s:%s no longer associated with footprint %s." ),
1803 footprint->GetReference(),
1804 excess,
1805 footprint->GetFPIDAsString() );
1806
1807 footprint->DeleteVariant( excess );
1808 }
1809
1810 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1811 changed = true;
1812 }
1813
1814 if( !m_isDryRun && changed && copy )
1815 m_commit.Modified( footprint, copy );
1816 else
1817 delete copy;
1818 }
1819}
1820
1821
1823{
1824 for( ZONE* zone : m_board->Zones() )
1825 {
1826 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1827 continue;
1828
1829 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1830 }
1831}
1832
1833
1835{
1836 wxString msg;
1837 std::set<wxString> netlistNetnames;
1838
1839 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1840 {
1841 const COMPONENT* component = aNetlist.GetComponent( ii );
1842
1843 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1844 {
1845 const COMPONENT_NET& net = component->GetNet( jj );
1846 netlistNetnames.insert( net.GetNetName() );
1847 }
1848 }
1849
1850 for( PCB_TRACK* via : m_board->Tracks() )
1851 {
1852 if( via->Type() != PCB_VIA_T )
1853 continue;
1854
1855 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1856 {
1857 wxString updatedNetname = wxEmptyString;
1858
1859 // Take via name from name change map if it didn't match to a new pad
1860 // (this is useful for stitching vias that don't connect to tracks)
1861 if( m_oldToNewNets.count( via->GetNetname() ) )
1862 {
1863 updatedNetname = m_oldToNewNets[via->GetNetname()];
1864 }
1865
1866 if( !updatedNetname.IsEmpty() )
1867 {
1868 if( m_isDryRun )
1869 {
1870 wxString originalNetname = via->GetNetname();
1871
1872 msg.Printf( _( "Reconnect via from %s to %s." ),
1873 EscapeHTML( UnescapeString( originalNetname ) ),
1874 EscapeHTML( UnescapeString( updatedNetname ) ) );
1875
1876 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1877 }
1878 else
1879 {
1880 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1881
1882 if( !netinfo )
1883 netinfo = m_addedNets[updatedNetname];
1884
1885 if( netinfo )
1886 {
1887 wxString originalNetname = via->GetNetname();
1888
1889 m_commit.Modify( via );
1890 via->SetNet( netinfo );
1891
1892 msg.Printf( _( "Reconnected via from %s to %s." ),
1893 EscapeHTML( UnescapeString( originalNetname ) ),
1894 EscapeHTML( UnescapeString( updatedNetname ) ) );
1895
1896 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1897 }
1898 }
1899 }
1900 else
1901 {
1902 msg.Printf( _( "Via connected to unknown net (%s)." ),
1903 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1904 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1906 }
1907 }
1908 }
1909
1910 // Board connectivity net names are not the same as schematic connectivity net names.
1911 // Footprints that contain multiple overlapping pads with the same number are suffixed
1912 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1913 // exposed in the zone net name list.
1914 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1915 {
1916 if( netlistNetnames.count( aNetName ) )
1917 return true;
1918
1919 // If the zone net name is a pseudo net name, check if the root net name is in the net
1920 // list. If so, then this is a valid net.
1921 for( const wxString& netName : netlistNetnames )
1922 {
1923 if( aNetName.StartsWith( netName ) )
1924 return true;
1925 }
1926
1927 return false;
1928 };
1929
1930 // Test copper zones to detect "dead" nets (nets without any pad):
1931 for( ZONE* zone : m_board->Zones() )
1932 {
1933 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1934 continue;
1935
1936 if( !isInNetlist( zone->GetNetname() ) )
1937 {
1938 // Look for a pad in the zone's connected-pad-cache which has been updated to
1939 // a new net and use that. While this won't always be the right net, the dead
1940 // net is guaranteed to be wrong.
1941 wxString updatedNetname = wxEmptyString;
1942
1943 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1944 {
1945 if( getNetname( pad ) != zone->GetNetname() )
1946 {
1947 updatedNetname = getNetname( pad );
1948 break;
1949 }
1950 }
1951
1952 // Take zone name from name change map if it didn't match to a new pad
1953 // (this is useful for zones on internal layers)
1954 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1955 {
1956 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1957 }
1958
1959 if( !updatedNetname.IsEmpty() )
1960 {
1961 if( m_isDryRun )
1962 {
1963 wxString originalNetname = zone->GetNetname();
1964
1965 if( !zone->GetZoneName().IsEmpty() )
1966 {
1967 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1968 zone->GetZoneName(),
1969 EscapeHTML( UnescapeString( originalNetname ) ),
1970 EscapeHTML( UnescapeString( updatedNetname ) ) );
1971 }
1972 else
1973 {
1974 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1975 EscapeHTML( UnescapeString( originalNetname ) ),
1976 EscapeHTML( UnescapeString( updatedNetname ) ) );
1977 }
1978
1979 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1980 }
1981 else
1982 {
1983 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1984
1985 if( !netinfo )
1986 netinfo = m_addedNets[ updatedNetname ];
1987
1988 if( netinfo )
1989 {
1990 wxString originalNetname = zone->GetNetname();
1991
1992 m_commit.Modify( zone );
1993 zone->SetNet( netinfo );
1994
1995 if( !zone->GetZoneName().IsEmpty() )
1996 {
1997 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1998 EscapeHTML( zone->GetZoneName() ),
1999 EscapeHTML( UnescapeString( originalNetname ) ),
2000 EscapeHTML( UnescapeString( updatedNetname ) ) );
2001 }
2002 else
2003 {
2004 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
2005 EscapeHTML( UnescapeString( originalNetname ) ),
2006 EscapeHTML( UnescapeString( updatedNetname ) ) );
2007 }
2008
2009 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2010 }
2011 }
2012 }
2013 else
2014 {
2015 if( !zone->GetZoneName().IsEmpty() )
2016 {
2017 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
2018 EscapeHTML( zone->GetZoneName() ) );
2019 }
2020 else
2021 {
2022 wxString layerNames = zone->LayerMaskDescribe();
2023 VECTOR2I pt = zone->GetPosition();
2024
2025 if( m_settings )
2026 {
2027 if( m_settings->m_Display.m_DisplayInvertXAxis )
2028 pt.x *= -1;
2029
2030 if( m_settings->m_Display.m_DisplayInvertYAxis )
2031 pt.y *= -1;
2032 }
2033
2034 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
2035 EscapeHTML( layerNames ),
2036 m_frame ? m_frame->MessageTextFromValue( pt.x )
2039 pt.x ),
2040 m_frame ? m_frame->MessageTextFromValue( pt.y )
2043 pt.y ),
2044 zone->GetNetname() );
2045 }
2046
2047 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2049 }
2050 }
2051 }
2052
2053 return true;
2054}
2055
2056
2058{
2059 if( !m_transferGroups )
2060 return false;
2061
2062 wxString msg;
2063
2064 for( PCB_GROUP* pcbGroup : m_board->Groups() )
2065 {
2066 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
2067
2068 if( netlistGroup == nullptr )
2069 continue;
2070
2071 if( netlistGroup->name != pcbGroup->GetName() )
2072 {
2073 if( m_isDryRun )
2074 {
2075 msg.Printf( _( "Change group name from '%s' to '%s'." ),
2076 EscapeHTML( pcbGroup->GetName() ),
2077 EscapeHTML( netlistGroup->name ) );
2078 }
2079 else
2080 {
2081 msg.Printf( _( "Changed group name from '%s' to '%s'." ),
2082 EscapeHTML( pcbGroup->GetName() ),
2083 EscapeHTML( netlistGroup->name ) );
2084 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2085 pcbGroup->SetName( netlistGroup->name );
2086 }
2087
2088 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2089 }
2090
2091 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
2092 {
2093 if( m_isDryRun )
2094 {
2095 msg.Printf( _( "Change group library link from '%s' to '%s'." ),
2096 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
2097 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
2098 }
2099 else
2100 {
2101 msg.Printf( _( "Changed group library link from '%s' to '%s'." ),
2102 EscapeHTML( pcbGroup->GetDesignBlockLibId().GetUniStringLibId() ),
2103 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
2104 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2105 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
2106 }
2107
2108 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2109 }
2110
2111 // A group member may be another group's uuid (a nested group). Restore that
2112 // parent/child relationship on the board.
2113 for( const KIID_PATH& member : netlistGroup->members )
2114 {
2115 if( member.empty() )
2116 continue;
2117
2118 KIID memberGroupUuid =
2119 member.size() == 1 ? member.front() : KIID::FromName( std::string( member.AsString().ToUTF8() ) );
2120
2121 PCB_GROUP* childGroup = nullptr;
2122
2123 for( PCB_GROUP* candidate : m_board->Groups() )
2124 {
2125 if( candidate->m_Uuid == memberGroupUuid )
2126 {
2127 childGroup = candidate;
2128 break;
2129 }
2130 }
2131
2132 if( !childGroup || childGroup == pcbGroup || childGroup->GetParentGroup() == pcbGroup )
2133 {
2134 continue;
2135 }
2136
2137 if( m_isDryRun )
2138 {
2139 msg.Printf( _( "Add group '%s' to group '%s'." ), EscapeHTML( childGroup->GetName() ),
2140 EscapeHTML( pcbGroup->GetName() ) );
2141 }
2142 else
2143 {
2144 msg.Printf( _( "Added group '%s' to group '%s'." ), EscapeHTML( childGroup->GetName() ),
2145 EscapeHTML( pcbGroup->GetName() ) );
2146 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2147 m_commit.Modify( childGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
2148 pcbGroup->AddItem( childGroup );
2149 }
2150
2151 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2152 }
2153 }
2154
2155 return true;
2156}
2157
2158
2160 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
2161{
2162 // Verify that board contains all pads in netlist: if it doesn't then footprints are
2163 // wrong or missing.
2164
2165 wxString msg;
2166 wxString padNumber;
2167
2168 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
2169 {
2170 COMPONENT* component = aNetlist.GetComponent( i );
2171 FOOTPRINT* footprint = aFootprintMap[component];
2172
2173 if( !footprint ) // It can be missing in partial designs
2174 continue;
2175
2176 // Explore all pins/pads in component
2177 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
2178 {
2179 padNumber = component->GetNet( jj ).GetPinName();
2180
2181 if( padNumber.IsEmpty() )
2182 {
2183 // bad symbol, report error
2184 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
2185 "to pads in %s." ),
2186 component->GetReference(),
2187 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
2188 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2189 ++m_errorCount;
2190 }
2191 else if( !footprint->FindPadByNumber( padNumber ) )
2192 {
2193 // not found: bad footprint, report error
2194 msg.Printf( _( "%s pad %s not found in %s." ),
2195 component->GetReference(),
2196 EscapeHTML( padNumber ),
2197 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
2198 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2199 ++m_errorCount;
2200 }
2201 }
2202 }
2203
2204 return true;
2205}
2206
2207
2209{
2210 FOOTPRINT* lastPreexistingFootprint = nullptr;
2211 COMPONENT* component = nullptr;
2212 wxString msg;
2213 std::unordered_set<wxString> sheetPaths;
2214 std::unordered_set<FOOTPRINT*> usedFootprints;
2215
2216 m_errorCount = 0;
2217 m_warningCount = 0;
2219
2220 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
2221
2222 if( !m_board->Footprints().empty() )
2223 lastPreexistingFootprint = m_board->Footprints().back();
2224
2226
2227 // First mark all nets (except <no net>) as stale; we'll update those which are current
2228 // in the following two loops. Also prepare the component class manager for updates.
2229 //
2230 if( !m_isDryRun )
2231 {
2232 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2233 net->SetIsCurrent( net->GetNetCode() == 0 );
2234
2235 m_board->GetComponentClassManager().InitNetlistUpdate();
2236 }
2237
2238 // Collect all schematic net names so NC pad deduplication can avoid collisions
2239 for( unsigned ii = 0; ii < aNetlist.GetCount(); ii++ )
2240 {
2241 COMPONENT* comp = aNetlist.GetComponent( ii );
2242
2243 for( unsigned jj = 0; jj < comp->GetNetCount(); jj++ )
2244 m_schematicNetNames.insert( comp->GetNet( jj ).GetNetName() );
2245 }
2246
2247 // Next go through the netlist updating all board footprints which have matching component
2248 // entries and adding new footprints for those that don't.
2249 //
2250 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
2251 {
2252 component = aNetlist.GetComponent( i );
2253
2254 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
2255 continue;
2256
2257 msg.Printf( _( "Processing symbol '%s:%s'." ),
2258 component->GetReference(),
2259 EscapeHTML( component->GetFPID().Format().wx_str() ) );
2260 m_reporter->Report( msg, RPT_SEVERITY_INFO );
2261
2262 const LIB_ID& baseFpid = component->GetFPID();
2263 const bool hasBaseFpid = !baseFpid.empty();
2264
2265 if( baseFpid.IsLegacy() )
2266 {
2267 msg.Printf( _( "Warning: %s footprint '%s' is missing a library name. "
2268 "Use the full 'Library:Footprint' format to avoid repeated update "
2269 "notifications." ),
2270 component->GetReference(),
2271 EscapeHTML( baseFpid.Format().wx_str() ) );
2272 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2274 }
2275
2276 std::vector<FOOTPRINT*> matchingFootprints;
2277
2278 for( FOOTPRINT* footprint : m_board->Footprints() )
2279 {
2280 bool match = false;
2281
2283 {
2284 for( const KIID& uuid : component->GetKIIDs() )
2285 {
2286 KIID_PATH base = component->GetPath();
2287 base.push_back( uuid );
2288
2289 if( footprint->GetPath() == base )
2290 {
2291 match = true;
2292 break;
2293 }
2294 }
2295 }
2296 else
2297 {
2298 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
2299 }
2300
2301 if( match )
2302 {
2303 if( footprint->IsBoardOnly() )
2304 {
2305 msg.Printf( _( "Found a footprint matching %s, but the footprint's 'Not in schematic' "
2306 "flag indicates there should be no matching symbol." ),
2307 component->GetReference() );
2308 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2310 }
2311 else
2312 {
2313 matchingFootprints.push_back( footprint );
2314 }
2315 }
2316
2317 if( footprint == lastPreexistingFootprint )
2318 {
2319 // No sense going through the newly-created footprints: end of loop
2320 break;
2321 }
2322 }
2323
2324 std::vector<LIB_ID> expectedFpids;
2325 std::unordered_set<wxString> expectedFpidKeys;
2326
2327 auto addExpectedFpid =
2328 [&]( const LIB_ID& aFpid )
2329 {
2330 if( aFpid.empty() )
2331 return;
2332
2333 wxString key = aFpid.Format();
2334
2335 if( expectedFpidKeys.insert( key ).second )
2336 expectedFpids.push_back( aFpid );
2337 };
2338
2339 addExpectedFpid( baseFpid );
2340
2341 const wxString footprintFieldName = GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED );
2342
2343 for( const auto& [variantName, variant] : component->GetVariants() )
2344 {
2345 auto fieldIt = variant.m_fields.find( footprintFieldName );
2346
2347 if( fieldIt == variant.m_fields.end() || fieldIt->second.IsEmpty() )
2348 continue;
2349
2350 LIB_ID parsedId;
2351
2352 if( parsedId.Parse( fieldIt->second, true ) >= 0 )
2353 {
2354 msg.Printf( _( "Invalid footprint ID '%s' for variant '%s' on %s." ),
2355 EscapeHTML( fieldIt->second ),
2356 variantName,
2357 component->GetReference() );
2358 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
2359 ++m_errorCount;
2360 continue;
2361 }
2362
2363 addExpectedFpid( parsedId );
2364 }
2365
2366 auto isExpectedFpid =
2367 [&]( const LIB_ID& aFpid ) -> bool
2368 {
2369 if( aFpid.empty() )
2370 return false;
2371
2372 if( expectedFpidKeys.count( aFpid.Format() ) > 0 )
2373 return true;
2374
2375 for( const LIB_ID& expected : expectedFpids )
2376 {
2377 if( fpidsEquivalent( aFpid, expected ) )
2378 return true;
2379 }
2380
2381 return false;
2382 };
2383
2384 auto takeMatchingFootprint =
2385 [&]( const LIB_ID& aFpid ) -> FOOTPRINT*
2386 {
2387 for( FOOTPRINT* footprint : matchingFootprints )
2388 {
2389 if( usedFootprints.count( footprint ) )
2390 continue;
2391
2392 if( fpidsEquivalent( footprint->GetFPID(), aFpid ) )
2393 return footprint;
2394 }
2395
2396 return nullptr;
2397 };
2398
2399 std::vector<FOOTPRINT*> componentFootprints;
2400 componentFootprints.reserve( expectedFpids.size() );
2401 FOOTPRINT* baseFootprint = nullptr;
2402
2403 if( hasBaseFpid )
2404 baseFootprint = takeMatchingFootprint( baseFpid );
2405 else if( !matchingFootprints.empty() )
2406 baseFootprint = matchingFootprints.front();
2407
2408 if( !baseFootprint && m_replaceFootprints && !matchingFootprints.empty() )
2409 {
2410 FOOTPRINT* replaceCandidate = nullptr;
2411
2412 for( FOOTPRINT* footprint : matchingFootprints )
2413 {
2414 if( usedFootprints.count( footprint ) )
2415 continue;
2416
2417 if( isExpectedFpid( footprint->GetFPID() ) )
2418 continue;
2419
2420 replaceCandidate = footprint;
2421 break;
2422 }
2423
2424 if( replaceCandidate )
2425 {
2426 FOOTPRINT* replaced = replaceFootprint( aNetlist, replaceCandidate, component );
2427
2428 if( replaced )
2429 baseFootprint = replaced;
2430 else
2431 baseFootprint = replaceCandidate;
2432 }
2433 }
2434
2435 if( !baseFootprint && !m_replaceFootprints )
2436 {
2437 for( FOOTPRINT* footprint : matchingFootprints )
2438 {
2439 if( usedFootprints.count( footprint ) )
2440 continue;
2441
2442 if( isExpectedFpid( footprint->GetFPID() ) )
2443 continue;
2444
2445 baseFootprint = footprint;
2446 break;
2447 }
2448 }
2449
2450 if( !baseFootprint && ( hasBaseFpid || expectedFpids.empty() ) )
2451 baseFootprint = addNewFootprint( component, baseFpid );
2452
2453 if( baseFootprint )
2454 {
2455 componentFootprints.push_back( baseFootprint );
2456 usedFootprints.insert( baseFootprint );
2457 footprintMap[ component ] = baseFootprint;
2458 }
2459
2460 for( const LIB_ID& fpid : expectedFpids )
2461 {
2462 // Both IDs are schematic-derived, so either side may be legacy; compare in both
2463 // directions so a bare base name and a qualified variant name for the same
2464 // footprint are not split into a duplicate.
2465 if( fpidsEquivalent( fpid, baseFpid ) || fpidsEquivalent( baseFpid, fpid ) )
2466 continue;
2467
2468 FOOTPRINT* footprint = takeMatchingFootprint( fpid );
2469
2470 if( !footprint )
2471 footprint = addNewFootprint( component, fpid );
2472
2473 if( footprint )
2474 {
2475 componentFootprints.push_back( footprint );
2476 usedFootprints.insert( footprint );
2477 }
2478 }
2479
2480 for( FOOTPRINT* footprint : componentFootprints )
2481 {
2482 if( !footprint )
2483 continue;
2484
2485 updateFootprintParameters( footprint, component );
2486 updateFootprintGroup( footprint, component );
2487 updateComponentPadConnections( footprint, component );
2488 updateComponentClass( footprint, component );
2489 updateComponentUnits( footprint, component );
2490
2491 sheetPaths.insert( footprint->GetSheetname() );
2492 }
2493
2494 if( !componentFootprints.empty() )
2495 applyComponentVariants( component, componentFootprints, baseFpid );
2496 }
2497
2498 updateCopperZoneNets( aNetlist );
2499 updateGroups( aNetlist );
2500
2501 // Finally go through the board footprints and update all those that *don't* have matching
2502 // component entries.
2503 //
2504 for( FOOTPRINT* footprint : m_board->Footprints() )
2505 {
2506 bool matched = false;
2507 bool doDelete = m_deleteUnusedFootprints;
2508
2509 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
2510 doDelete = false;
2511
2512 bool isStaleVariantFootprint = false;
2513
2514 if( usedFootprints.count( footprint ) )
2515 {
2516 matched = true;
2517 }
2518 else
2519 {
2521 component = aNetlist.GetComponentByPath( footprint->GetPath() );
2522 else
2523 component = aNetlist.GetComponentByReference( footprint->GetReference() );
2524
2525 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
2526 {
2527 // When replace footprints is enabled and a component has variant footprints,
2528 // footprints matching by reference but not in usedFootprints are stale variant
2529 // footprints that should be replaced/removed.
2530 if( m_replaceFootprints && !component->GetVariants().empty() )
2531 {
2532 matched = false;
2533 isStaleVariantFootprint = true;
2534 }
2535 else
2536 {
2537 matched = true;
2538 }
2539 }
2540 }
2541
2542 // Stale variant footprints should be deleted when m_replaceFootprints is enabled,
2543 // regardless of m_deleteUnusedFootprints setting.
2544 if( isStaleVariantFootprint )
2545 doDelete = true;
2546
2547 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
2548 {
2549 if( m_isDryRun )
2550 {
2551 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
2552 footprint->GetReference() );
2553 }
2554 else
2555 {
2556 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
2557 footprint->GetReference() );
2558 }
2559
2560 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
2562 doDelete = false;
2563 }
2564
2565 if( doDelete && !matched )
2566 {
2567 if( m_isDryRun )
2568 {
2569 msg.Printf( _( "Remove unused footprint %s." ),
2570 footprint->GetReference() );
2571 }
2572 else
2573 {
2574 m_commit.Remove( footprint );
2575 msg.Printf( _( "Removed unused footprint %s." ),
2576 footprint->GetReference() );
2577 }
2578
2579 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2580 }
2581 else if( !m_isDryRun )
2582 {
2583 if( !matched )
2584 footprint->SetPath( KIID_PATH() );
2585
2586 for( PAD* pad : footprint->Pads() )
2587 {
2588 if( pad->GetNet() )
2589 pad->GetNet()->SetIsCurrent( true );
2590 }
2591 }
2592 }
2593
2594 if( !m_isDryRun )
2595 {
2596 m_board->ClearNetChainColors();
2597
2598 // Finalise the component class manager
2599 m_board->GetComponentClassManager().FinishNetlistUpdate();
2600 m_board->SynchronizeComponentClasses( sheetPaths );
2601
2602 m_board->BuildConnectivity();
2603 testConnectivity( aNetlist, footprintMap );
2604
2605 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2606 {
2607 if( !net->IsCurrent() )
2608 {
2609 msg.Printf( _( "Removed unused net %s." ), EscapeHTML( net->GetNetname() ) );
2610 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2611 }
2612 }
2613
2614 m_board->RemoveUnusedNets( &m_commit );
2615
2616 // Update board variant registry from netlist
2617 const std::vector<wxString>& netlistVariants = aNetlist.GetVariantNames();
2618
2619 if( !netlistVariants.empty() || !m_board->GetVariantNames().empty() )
2620 {
2621 m_reporter->Report( _( "Updating design variants..." ), RPT_SEVERITY_INFO );
2622
2623 auto findBoardVariantName =
2624 [&]( const wxString& aVariantName ) -> wxString
2625 {
2626 for( const wxString& name : m_board->GetVariantNames() )
2627 {
2628 if( name.CmpNoCase( aVariantName ) == 0 )
2629 return name;
2630 }
2631
2632 return wxEmptyString;
2633 };
2634
2635 std::vector<wxString> updatedVariantNames;
2636 updatedVariantNames.reserve( netlistVariants.size() );
2637
2638 for( const wxString& variantName : netlistVariants )
2639 {
2640 wxString actualName = findBoardVariantName( variantName );
2641
2642 if( actualName.IsEmpty() )
2643 {
2644 m_board->AddVariant( variantName );
2645 actualName = findBoardVariantName( variantName );
2646
2647 if( !actualName.IsEmpty() )
2648 {
2649 msg.Printf( _( "Added variant '%s'." ), actualName );
2650 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2651 }
2652 }
2653
2654 if( actualName.IsEmpty() )
2655 continue;
2656
2657 // Update description if changed
2658 wxString newDescription = aNetlist.GetVariantDescription( variantName );
2659 wxString oldDescription = m_board->GetVariantDescription( actualName );
2660
2661 if( newDescription != oldDescription )
2662 {
2663 m_board->SetVariantDescription( actualName, newDescription );
2664 msg.Printf( _( "Updated description for variant '%s'." ), actualName );
2665 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2666 }
2667
2668 updatedVariantNames.push_back( actualName );
2669 }
2670
2671 std::vector<wxString> variantsToRemove;
2672
2673 for( const wxString& existingName : m_board->GetVariantNames() )
2674 {
2675 bool found = false;
2676
2677 for( const wxString& variantName : netlistVariants )
2678 {
2679 if( existingName.CmpNoCase( variantName ) == 0 )
2680 {
2681 found = true;
2682 break;
2683 }
2684 }
2685
2686 if( !found )
2687 variantsToRemove.push_back( existingName );
2688 }
2689
2690 for( const wxString& variantName : variantsToRemove )
2691 {
2692 m_board->DeleteVariant( variantName );
2693 msg.Printf( _( "Removed variant '%s'." ), variantName );
2694 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
2695 }
2696
2697 if( !updatedVariantNames.empty() )
2698 m_board->SetVariantNames( updatedVariantNames );
2699 }
2700
2701 // When new footprints are added, the automatic zone refill is disabled because:
2702 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
2703 // (the auto refills rebuild the connectivity with incomplete data)
2704 // * it is useless because zones will be refilled after placing new footprints
2705 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
2706
2707 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
2708
2709 // Netlist is authoritative for chain assignment, so the terminal-pin reapplication
2710 // below starts from a clean slate.
2712
2713 // Net chains may specify a display colour override; lift that into the
2714 // board-side lookup so the PCB painter can use it when highlighting.
2715 for( const auto& [chain, colorStr] : aNetlist.GetNetChainColors() )
2716 {
2717 if( !colorStr.IsEmpty() )
2718 {
2719 KIGFX::COLOR4D color;
2720
2721 if( color.SetFromHexString( colorStr ) )
2722 m_board->SetNetChainColor( chain, color );
2723 }
2724 }
2725
2726 ApplyChainNetclasses( m_board, aNetlist );
2727
2728 // Always resync after chain cleanup so existing NETINFO_ITEM effective-netclass
2729 // pointers pick up cleared/changed chain entries even when no chain carries a netclass.
2730 m_board->SynchronizeNetsAndNetClasses( true );
2731
2732 for( const auto& [chain, terminalPins] : aNetlist.GetNetChainTerminalPins() )
2733 {
2734 PAD* pads[2] = { nullptr, nullptr };
2735
2736 for( size_t i = 0; i < terminalPins.size() && i < 2; ++i )
2737 {
2738 const wxString& ref = terminalPins[i].first;
2739 const wxString& pin = terminalPins[i].second;
2740 FOOTPRINT* fp = m_board->FindFootprintByReference( ref );
2741
2742 if( !fp )
2743 continue;
2744
2745 PAD* candidate = nullptr;
2746 PAD* best = nullptr;
2747 int bestDist = std::numeric_limits<int>::max();
2748 BOX2I bbox = fp->GetBoundingBox();
2749
2750 while( ( candidate = fp->FindPadByNumber( pin, candidate ) ) )
2751 {
2752 VECTOR2I pos = candidate->GetPosition();
2753 int dist = std::min( { pos.x - bbox.GetLeft(), bbox.GetRight() - pos.x,
2754 pos.y - bbox.GetTop(), bbox.GetBottom() - pos.y } );
2755
2756 if( !best || dist < bestDist || ( dist == bestDist && candidate->m_Uuid < best->m_Uuid ) )
2757 {
2758 best = candidate;
2759 bestDist = dist;
2760 }
2761 }
2762
2763 pads[i] = best;
2764 }
2765
2766 for( int i = 0; i < 2; ++i )
2767 {
2768 if( !pads[i] )
2769 continue;
2770
2771 NETINFO_ITEM* termNet = pads[i]->GetNet();
2772
2773 if( !termNet || termNet->GetNetChain() != chain )
2774 continue;
2775
2776 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
2777 {
2778 if( net != termNet && net->GetNetChain() == chain && net->GetTerminalPad( i ) )
2779 net->ClearTerminalPad( i );
2780 }
2781
2782 termNet->SetTerminal( i, pads[i] );
2783 }
2784 }
2785
2786 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
2787 // sure any modification to netclasses gets persisted to project settings through a save.
2788 if( m_frame )
2789 m_frame->OnModify();
2790 }
2791
2792 if( m_isDryRun )
2793 {
2794 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
2795 delete addedNet.second;
2796
2797 m_addedNets.clear();
2798 }
2799
2800 // Update the ratsnest
2801 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2802 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
2803
2804 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
2805 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
2806
2807 return true;
2808}
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
#define ZONE_FILL_OP
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
NETINFO_ITEM * GetNet() const
Return NETINFO_ITEM object for a given item.
std::shared_ptr< NET_SETTINGS > m_NetSettings
void SetUuid(const KIID &aUuid)
bool IsLocked() const override
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:383
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 getPinFunction(PAD *aPad)
std::vector< FOOTPRINT * > m_addedFootprints
bool updateFootprintParameters(FOOTPRINT *aFootprint, COMPONENT *aNetlistComponent)
std::map< wxString, wxString > m_oldToNewNets
static void ApplyChainAssignments(BOARD *aBoard, const NETLIST &aNetlist, REPORTER *aReporter, bool aDryRun)
Apply the netlist's chain assignments to every NETINFO_ITEM on the board.
static void ApplyChainNetclasses(BOARD *aBoard, const NETLIST &aNetlist)
Mirror the netlist's chain-to-class and chain-to-netclass maps into the project's NET_SETTINGS.
bool updateFootprintGroup(FOOTPRINT *aPcbFootprint, COMPONENT *aNetlistComponent)
static bool fpidsEquivalent(const LIB_ID &aBoardFpid, const LIB_ID &aSchematicFpid)
Compare a board footprint ID against a schematic-derived footprint ID, ignoring the library nickname ...
std::map< wxString, NETINFO_ITEM * > m_addedNets
std::vector< PCB_GROUP * > m_addedGroups
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)
Construct an updater for interactive use from the board editor.
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)
std::set< wxString > m_schematicNetNames
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1217
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
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()
JUMPER_GROUP_SET & JumperPadGroups()
std::unordered_set< EDA_ITEM * > & GetItems()
Definition eda_group.h:78
wxString GetName() const
Definition eda_group.h:75
void RemoveAll()
Definition eda_group.cpp:86
void RemoveItem(EDA_ITEM *aItem)
Remove item from group.
Definition eda_group.cpp:77
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
void SetName(const wxString &aName)
Definition eda_group.h:76
const KIID m_Uuid
Definition eda_item.h:599
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:347
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:236
Variant information for a footprint.
Definition footprint.h:178
bool HasFieldValue(const wxString &aFieldName) const
Definition footprint.h:237
wxString GetFieldValue(const wxString &aFieldName) const
Get a field value override for this variant.
Definition footprint.h:209
bool GetExcludedFromSim() const
Definition footprint.h:198
bool GetExcludedFromBOM() const
Definition footprint.h:195
bool GetExcludedFromPosFiles() const
Definition footprint.h:201
void RemoveFieldValue(const wxString &aFieldName)
Remove a field value override for this variant.
Definition footprint.h:232
bool GetDNP() const
Definition footprint.h:192
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:1127
void SetPosition(const VECTOR2I &aPos) override
EDA_ANGLE GetOrientation() const
Definition footprint.h:382
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
wxString GetSheetname() const
Definition footprint.h:469
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:456
void SetFilters(const wxString &aFilters)
Definition footprint.h:476
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:909
void SetAttributes(int aAttributes)
Definition footprint.h:510
void SetSheetfile(const wxString &aSheetfile)
Definition footprint.h:473
EDA_ITEM * Clone() const override
Create a duplicate of this item with linked list members set to NULL.
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:1128
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:355
int GetAttributes() const
Definition footprint.h:509
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:393
wxString GetFPIDAsString() const
Definition footprint.h:419
wxString GetSheetfile() const
Definition footprint.h:472
const LIB_ID & GetFPID() const
Definition footprint.h:417
void SetReference(const wxString &aReference)
Definition footprint.h:799
void SetValue(const wxString &aValue)
Definition footprint.h:822
JUMPER_GROUP_SET & JumperPadGroups()
Each jumper pad group is a set of pad numbers that should be treated as internally connected.
Definition footprint.h:1134
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Adds an item to the container.
void SetUnitInfo(const std::vector< FP_UNIT_INFO > &aUnits)
Definition footprint.h:908
wxString GetFilters() const
Definition footprint.h:475
void SetSheetname(const wxString &aSheetname)
Definition footprint.h:470
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:817
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:793
const KIID_PATH & GetPath() const
Definition footprint.h:455
VECTOR2I GetPosition() const override
Definition footprint.h:379
PAD * FindPadByNumber(const wxString &aPadNumber, PAD *aSearchAfterMe=nullptr) const
Return a PAD with a matching number.
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
bool SetFromHexString(const wxString &aColorString)
Definition color4d.cpp:176
wxString AsString() const
Definition kiid.cpp:423
Definition kiid.h:46
static KIID FromName(const std::string &aName)
Return a KIID derived from a name, the same name always gives the same KIID.
Definition kiid.cpp:237
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool empty() const
Definition lib_id.h:189
wxString GetUniStringLibId() const
Definition lib_id.h:144
UTF8 Format() const
Definition lib_id.cpp:132
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
bool IsLegacy() const
Definition lib_id.h:176
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetChain() const
Definition netinfo.h:122
void SetTerminal(int aIndex, PAD *aPad)
Set the terminal-pad pointer and the persisted UUID at aIndex from a single pad, keeping the two view...
void SetIsCurrent(bool isCurrent)
Definition netinfo.h:160
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:281
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
const std::map< wxString, std::vector< std::pair< wxString, wxString > > > & GetNetChainTerminalPins() const
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.
const std::map< wxString, wxString > & GetNetChainNetClasses() const
const std::map< wxString, wxString > & GetSignalChainClasses() const
NETLIST_GROUP * GetGroupByUuid(const KIID &aUuid)
Return a NETLIST_GROUP by aUuid.
const std::map< wxString, wxString > & GetNetChainColors() const
COMPONENT * GetComponent(unsigned aIndex)
Return the COMPONENT at aIndex.
wxString GetNetChainFor(const wxString &aNet) const
wxString GetVariantDescription(const wxString &aVariantName) const
A singleton reporter that reports to nowhere.
Definition reporter.h:267
Definition pad.h:61
const wxString & GetPinFunction() const
Definition pad.h:154
VECTOR2I GetPosition() const override
Definition pad.cpp:255
The main frame for Pcbnew.
void SetName(const wxString &aName)
Definition pcb_field.h:121
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
EDA_ITEM * AsEdaItem() override
Definition pcb_group.h:59
void StyleFromSettings(const BOARD_DESIGN_SETTINGS &aSettings, bool aCheckSide) override
Definition pcb_text.cpp:371
void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:103
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:583
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition reporter.h:102
Master controller class:
wxString wx_str() const
Definition utf8.cpp:41
Handle a list of polygons defining a copper zone.
Definition zone.h:70
#define _(s)
@ NO_RECURSE
Definition eda_item.h:52
@ FP_DNP
Definition footprint.h:92
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:88
@ FP_BOARD_ONLY
Definition footprint.h:90
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:89
@ FP_JUST_ADDED
Definition footprint.h:91
@ FP_EXCLUDE_FROM_SIM
Definition footprint.h:93
@ F_Fab
Definition layer_ids.h:115
@ F_Cu
Definition layer_ids.h:60
@ B_Fab
Definition layer_ids.h:114
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.
FOOTPRINT * LoadFootprintFromProject(BOARD *aBoard, const LIB_ID &aFootprintId, bool aKeepUuid)
Load a footprint from the project library table and apply board default settings.
CITER next(CITER it)
Definition ptree.cpp:120
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
T * GetAppSettings(const char *aFilename)
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
std::vector< KIID > members
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ 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".
@ UNTRANSLATED
KIBIS_COMPONENT * comp
KIBIS_PIN * pin
VECTOR3I expected(15, 30, 45)
static std::string netlistValue(const SEXPR::SEXPR &aNode, const std::string &aHead)
const SHAPE_LINE_CHAIN chain
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708