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