KiCad PCB EDA Suite
Loading...
Searching...
No Matches
board_netlist_updater.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2015 CERN
6 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
7 * Copyright (C) 2011 Wayne Stambaugh <[email protected]>
8 *
9 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, you may find one here:
23 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
24 * or you may search the http://www.gnu.org website for the version 2 license,
25 * or you may write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29
30#include <common.h> // for PAGE_INFO
31
32#include <base_units.h>
33#include <board.h>
36#include <netinfo.h>
37#include <footprint.h>
38#include <pad.h>
39#include <pcb_group.h>
40#include <pcb_track.h>
41#include <zone.h>
42#include <string_utils.h>
43#include <pcbnew_settings.h>
44#include <pcb_edit_frame.h>
47#include <reporter.h>
48#include <wx/log.h>
49
51
52
54 m_frame( aFrame ),
55 m_commit( aFrame ),
56 m_board( aBoard )
57{
59
61 m_isDryRun = false;
63 m_lookupByTimestamp = false;
64 m_transferGroups = false;
65 m_overrideLocks = false;
66 m_updateFields = false;
67 m_removeExtraFields = false;
68
70 m_errorCount = 0;
72}
73
74
78
79
80// These functions allow inspection of pad nets during dry runs by keeping a cache of
81// current pad netnames indexed by pad.
82
83void BOARD_NETLIST_UPDATER::cacheNetname( PAD* aPad, const wxString& aNetname )
84{
85 m_padNets[ aPad ] = aNetname;
86}
87
88
90{
91 if( m_isDryRun && m_padNets.count( aPad ) )
92 return m_padNets[ aPad ];
93 else
94 return aPad->GetNetname();
95}
96
97
98void BOARD_NETLIST_UPDATER::cachePinFunction( PAD* aPad, const wxString& aPinFunction )
99{
100 m_padPinFunctions[ aPad ] = aPinFunction;
101}
102
103
105{
106 if( m_isDryRun && m_padPinFunctions.count( aPad ) )
107 return m_padPinFunctions[ aPad ];
108 else
109 return aPad->GetPinFunction();
110}
111
112
114{
115 VECTOR2I bestPosition;
116
117 if( !m_board->IsEmpty() )
118 {
119 // Position new components below any existing board features.
120 BOX2I bbox = m_board->GetBoardEdgesBoundingBox();
121
122 if( bbox.GetWidth() || bbox.GetHeight() )
123 {
124 bestPosition.x = bbox.Centre().x;
125 bestPosition.y = bbox.GetBottom() + pcbIUScale.mmToIU( 10 );
126 }
127 }
128 else
129 {
130 // Position new components in the center of the page when the board is empty.
131 VECTOR2I pageSize = m_board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
132
133 bestPosition.x = pageSize.x / 2;
134 bestPosition.y = pageSize.y / 2;
135 }
136
137 return bestPosition;
138}
139
140
142{
143 wxString msg;
144
145 if( aComponent->GetFPID().empty() )
146 {
147 msg.Printf( _( "Cannot add %s (no footprint assigned)." ),
148 aComponent->GetReference() );
149 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
150 ++m_errorCount;
151 return nullptr;
152 }
153
154 FOOTPRINT* footprint = m_frame->LoadFootprint( aComponent->GetFPID() );
155
156 if( footprint == nullptr )
157 {
158 msg.Printf( _( "Cannot add %s (footprint '%s' not found)." ),
159 aComponent->GetReference(),
160 EscapeHTML( aComponent->GetFPID().Format().wx_str() ) );
161 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
162 ++m_errorCount;
163 return nullptr;
164 }
165
166 footprint->SetStaticComponentClass(
167 m_board->GetComponentClassManager().GetNoneComponentClass() );
168
169 if( m_isDryRun )
170 {
171 msg.Printf( _( "Add %s (footprint '%s')." ),
172 aComponent->GetReference(),
173 EscapeHTML( aComponent->GetFPID().Format().wx_str() ) );
174
175 delete footprint;
176 footprint = nullptr;
177 }
178 else
179 {
180 for( PAD* pad : footprint->Pads() )
181 {
182 // Set the pads ratsnest settings to the global settings
183 pad->SetLocalRatsnestVisible( m_frame->GetPcbNewSettings()->m_Display.m_ShowGlobalRatsnest );
184
185 // Pads in the library all have orphaned nets. Replace with Default.
186 pad->SetNetCode( 0 );
187 }
188
189 footprint->SetParent( m_board );
191
192 // This flag is used to prevent connectivity from considering the footprint during its
193 // initial build after the footprint is committed, because we're going to immediately start
194 // a move operation on the footprint and don't want its pads to drive nets onto vias/tracks
195 // it happens to land on at the initial position.
196 footprint->SetAttributes( footprint->GetAttributes() | FP_JUST_ADDED );
197
198 m_addedFootprints.push_back( footprint );
199 m_commit.Add( footprint );
200
201 msg.Printf( _( "Added %s (footprint '%s')." ),
202 aComponent->GetReference(),
203 EscapeHTML( aComponent->GetFPID().Format().wx_str() ) );
204 }
205
206 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
208 return footprint;
209}
210
211
213{
214 wxString curClassName, newClassName;
215 COMPONENT_CLASS* newClass = nullptr;
216
217 if( const COMPONENT_CLASS* curClass = aFootprint->GetStaticComponentClass() )
218 curClassName = curClass->GetName();
219
220 // Calculate the new component class
221 if( m_isDryRun )
222 {
224 aNewComponent->GetComponentClassNames() );
225 }
226 else
227 {
228 newClass = m_board->GetComponentClassManager().GetEffectiveStaticComponentClass(
229 aNewComponent->GetComponentClassNames() );
230 newClassName = newClass->GetName();
231 }
232
233 if( curClassName == newClassName )
234 return;
235
236 wxString msg;
237
238 if( m_isDryRun )
239 {
240 if( curClassName == wxEmptyString && newClassName != wxEmptyString )
241 {
242 msg.Printf( _( "Change %s component class to '%s'." ),
243 aFootprint->GetReference(),
244 EscapeHTML( newClassName ) );
245 }
246 else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
247 {
248 msg.Printf( _( "Remove %s component class (currently '%s')." ),
249 aFootprint->GetReference(),
250 EscapeHTML( curClassName ) );
251 }
252 else
253 {
254 msg.Printf( _( "Change %s component class from '%s' to '%s'." ),
255 aFootprint->GetReference(),
256 EscapeHTML( curClassName ),
257 EscapeHTML( newClassName ) );
258 }
259 }
260 else
261 {
262 wxASSERT_MSG( newClass != nullptr, "Component class should not be nullptr" );
263
264 aFootprint->SetStaticComponentClass( newClass );
265
266 if( curClassName == wxEmptyString && newClassName != wxEmptyString )
267 {
268 msg.Printf( _( "Changed %s component class to '%s'." ),
269 aFootprint->GetReference(),
270 EscapeHTML( newClassName ) );
271 }
272 else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
273 {
274 msg.Printf( _( "Removed %s component class (was '%s')." ),
275 aFootprint->GetReference(),
276 EscapeHTML( curClassName ) );
277 }
278 else
279 {
280 msg.Printf( _( "Changed %s component class from '%s' to '%s'." ),
281 aFootprint->GetReference(),
282 EscapeHTML( curClassName ),
283 EscapeHTML( newClassName ) );
284 }
285 }
286
287 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
288}
289
290
292 COMPONENT* aNewComponent )
293{
294 wxString msg;
295
296 if( aNewComponent->GetFPID().empty() )
297 {
298 msg.Printf( _( "Cannot update %s (no footprint assigned)." ),
299 aNewComponent->GetReference() );
300 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
301 ++m_errorCount;
302 return nullptr;
303 }
304
305 FOOTPRINT* newFootprint = m_frame->LoadFootprint( aNewComponent->GetFPID() );
306
307 if( newFootprint == nullptr )
308 {
309 msg.Printf( _( "Cannot update %s (footprint '%s' not found)." ),
310 aNewComponent->GetReference(),
311 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
312 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
313 ++m_errorCount;
314 return nullptr;
315 }
316
317 if( m_isDryRun )
318 {
319 if( aFootprint->IsLocked() && !m_overrideLocks )
320 {
321 msg.Printf( _( "Cannot change %s footprint from '%s' to '%s' (footprint is locked)."),
322 aFootprint->GetReference(),
323 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
324 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
325 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
327 delete newFootprint;
328 return nullptr;
329 }
330 else
331 {
332 msg.Printf( _( "Change %s footprint from '%s' to '%s'."),
333 aFootprint->GetReference(),
334 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
335 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
336 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
338 delete newFootprint;
339 return nullptr;
340 }
341 }
342 else
343 {
344 if( aFootprint->IsLocked() && !m_overrideLocks )
345 {
346 msg.Printf( _( "Could not change %s footprint from '%s' to '%s' (footprint is locked)."),
347 aFootprint->GetReference(),
348 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
349 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
350 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
352 delete newFootprint;
353 return nullptr;
354 }
355 else
356 {
357 // Expand the footprint pad layers
358 newFootprint->FixUpPadsForBoard( m_board );
359
360 m_frame->ExchangeFootprint( aFootprint, newFootprint, m_commit );
361
362 msg.Printf( _( "Changed %s footprint from '%s' to '%s'."),
363 aFootprint->GetReference(),
364 EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
365 EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
366 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
368 return newFootprint;
369 }
370 }
371 }
372
373
375 COMPONENT* aNetlistComponent )
376{
377 wxString msg;
378
379 // Create a copy only if the footprint has not been added during this update
380 FOOTPRINT* copy = nullptr;
381
382 if( !m_commit.GetStatus( aPcbFootprint ) )
383 {
384 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
385 copy->SetParentGroup( nullptr );
386 }
387
388 bool changed = false;
389
390 // Test for reference designator field change.
391 if( aPcbFootprint->GetReference() != aNetlistComponent->GetReference() )
392 {
393 if( m_isDryRun )
394 {
395 msg.Printf( _( "Change %s reference designator to %s." ),
396 aPcbFootprint->GetReference(),
397 aNetlistComponent->GetReference() );
398 }
399 else
400 {
401 msg.Printf( _( "Changed %s reference designator to %s." ),
402 aPcbFootprint->GetReference(),
403 aNetlistComponent->GetReference() );
404
405 changed = true;
406 aPcbFootprint->SetReference( aNetlistComponent->GetReference() );
407 }
408
409 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
410 }
411
412 // Test for value field change.
413 if( aPcbFootprint->GetValue() != aNetlistComponent->GetValue() )
414 {
415 if( m_isDryRun )
416 {
417 msg.Printf( _( "Change %s value from %s to %s." ),
418 aPcbFootprint->GetReference(),
419 EscapeHTML( aPcbFootprint->GetValue() ),
420 EscapeHTML( aNetlistComponent->GetValue() ) );
421 }
422 else
423 {
424 msg.Printf( _( "Changed %s value from %s to %s." ),
425 aPcbFootprint->GetReference(),
426 EscapeHTML( aPcbFootprint->GetValue() ),
427 EscapeHTML( aNetlistComponent->GetValue() ) );
428
429 changed = true;
430 aPcbFootprint->SetValue( aNetlistComponent->GetValue() );
431 }
432
433 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
434 }
435
436 // Test for time stamp change.
437 KIID_PATH new_path = aNetlistComponent->GetPath();
438
439 if( !aNetlistComponent->GetKIIDs().empty() )
440 new_path.push_back( aNetlistComponent->GetKIIDs().front() );
441
442 if( aPcbFootprint->GetPath() != new_path )
443 {
444 if( m_isDryRun )
445 {
446 msg.Printf( _( "Update %s symbol association from %s to %s." ),
447 aPcbFootprint->GetReference(),
448 EscapeHTML( aPcbFootprint->GetPath().AsString() ),
449 EscapeHTML( new_path.AsString() ) );
450 }
451 else
452 {
453 msg.Printf( _( "Updated %s symbol association from %s to %s." ),
454 aPcbFootprint->GetReference(),
455 EscapeHTML( aPcbFootprint->GetPath().AsString() ),
456 EscapeHTML( new_path.AsString() ) );
457
458 changed = true;
459 aPcbFootprint->SetPath( new_path );
460 }
461
462 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
463 }
464
465 nlohmann::ordered_map<wxString, wxString> fpFieldsAsMap;
466
467 for( PCB_FIELD* field : aPcbFootprint->GetFields() )
468 {
469 // These fields are individually checked above
470 if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
471 {
472 continue;
473 }
474
475 fpFieldsAsMap[field->GetName()] = field->GetText();
476 }
477
478 // Remove the ref/value/footprint fields that are individually handled
479 nlohmann::ordered_map<wxString, wxString> compFields = aNetlistComponent->GetFields();
480 compFields.erase( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
481 compFields.erase( GetCanonicalFieldName( FIELD_T::VALUE ) );
482 compFields.erase( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) );
483
484 // Remove any component class fields - these are not editable in the pcb editor
485 compFields.erase( wxT( "Component Class" ) );
486
487 // Fields are stored as an ordered map, but we don't (yet) support reordering
488 // the footprint fields to match the symbol, so we manually check the fields
489 // in the order they are stored in the symbol.
490 bool same = true;
491 bool remove_only = true;
492
493 for( const auto& [name, value] : compFields )
494 {
495 if( fpFieldsAsMap.count( name ) == 0 || fpFieldsAsMap[name] != value )
496 {
497 same = false;
498 remove_only = false;
499 break;
500 }
501 }
502
503 for( const auto& [name, value] : fpFieldsAsMap )
504 {
505 if( compFields.count( name ) == 0 )
506 {
507 same = false;
508 break;
509 }
510 }
511
512 if( !same )
513 {
514 if( m_isDryRun )
515 {
516 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
517 {
518 msg.Printf( _( "Update %s fields." ), aPcbFootprint->GetReference() );
519 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
520 }
521
522 // Remove fields that aren't present in the symbol
523 for( PCB_FIELD* field : aPcbFootprint->GetFields() )
524 {
525 if( field->IsMandatory() )
526 continue;
527
528 if( compFields.count( field->GetName() ) == 0 )
529 {
531 {
532 msg.Printf( _( "Remove %s footprint fields not in symbol." ),
533 aPcbFootprint->GetReference() );
534 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
535 }
536
537 break;
538 }
539 }
540 }
541 else
542 {
543 if( m_updateFields && ( !remove_only || m_removeExtraFields ) )
544 {
545 msg.Printf( _( "Updated %s fields." ), aPcbFootprint->GetReference() );
546 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
547
548 changed = true;
549
550 // Add or change field value
551 for( auto& [name, value] : compFields )
552 {
553 if( aPcbFootprint->HasField( name ) )
554 {
555 aPcbFootprint->GetField( name )->SetText( value );
556 }
557 else
558 {
559 PCB_FIELD* newField = new PCB_FIELD( aPcbFootprint, FIELD_T::USER );
560 aPcbFootprint->Add( newField );
561
562 newField->SetName( name );
563 newField->SetText( value );
564 newField->SetVisible( false );
565 newField->SetLayer( aPcbFootprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
566
567 // Give the relative position (0,0) in footprint
568 newField->SetPosition( aPcbFootprint->GetPosition() );
569 // Give the footprint orientation
570 newField->Rotate( aPcbFootprint->GetPosition(), aPcbFootprint->GetOrientation() );
571
572 if( m_frame )
573 newField->StyleFromSettings( m_frame->GetDesignSettings(), true );
574 }
575 }
576 }
577
579 {
580 bool warned = false;
581
582 std::vector<PCB_FIELD*> fieldList;
583 aPcbFootprint->GetFields( fieldList, false );
584
585 for( PCB_FIELD* field : fieldList )
586 {
587 if( field->IsMandatory() )
588 continue;
589
590 if( compFields.count( field->GetName() ) == 0 )
591 {
592 if( !warned )
593 {
594 warned = true;
595 msg.Printf( _( "Removed %s footprint fields not in symbol." ),
596 aPcbFootprint->GetReference() );
597 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
598 }
599
600 aPcbFootprint->Remove( field );
601
602 if( m_frame )
603 m_frame->GetCanvas()->GetView()->Remove( field );
604
605 delete field;
606 }
607 }
608 }
609 }
610 }
611
612 wxString sheetname;
613 wxString sheetfile;
614 wxString fpFilters;
615
616 wxString humanSheetPath = aNetlistComponent->GetHumanReadablePath();
617
618 if( !humanSheetPath.empty() )
619 sheetname = humanSheetPath;
620 else if( aNetlistComponent->GetProperties().count( wxT( "Sheetname" ) ) > 0 )
621 sheetname = aNetlistComponent->GetProperties().at( wxT( "Sheetname" ) );
622
623 if( aNetlistComponent->GetProperties().count( wxT( "Sheetfile" ) ) > 0 )
624 sheetfile = aNetlistComponent->GetProperties().at( wxT( "Sheetfile" ) );
625
626 if( aNetlistComponent->GetProperties().count( wxT( "ki_fp_filters" ) ) > 0 )
627 fpFilters = aNetlistComponent->GetProperties().at( wxT( "ki_fp_filters" ) );
628
629 if( sheetname != aPcbFootprint->GetSheetname() )
630 {
631 if( m_isDryRun )
632 {
633 msg.Printf( _( "Update %s sheetname to '%s'." ),
634 aPcbFootprint->GetReference(),
635 EscapeHTML( sheetname ) );
636 }
637 else
638 {
639 aPcbFootprint->SetSheetname( sheetname );
640 msg.Printf( _( "Updated %s sheetname to '%s'." ),
641 aPcbFootprint->GetReference(),
642 EscapeHTML( sheetname ) );
643 }
644
645 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
646 }
647
648 if( sheetfile != aPcbFootprint->GetSheetfile() )
649 {
650 if( m_isDryRun )
651 {
652 msg.Printf( _( "Update %s sheetfile to '%s'." ),
653 aPcbFootprint->GetReference(),
654 EscapeHTML( sheetfile ) );
655 }
656 else
657 {
658 aPcbFootprint->SetSheetfile( sheetfile );
659 msg.Printf( _( "Updated %s sheetfile to '%s'." ),
660 aPcbFootprint->GetReference(),
661 EscapeHTML( sheetfile ) );
662 }
663
664 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
665 }
666
667 if( fpFilters != aPcbFootprint->GetFilters() )
668 {
669 if( m_isDryRun )
670 {
671 msg.Printf( _( "Update %s footprint filters to '%s'." ),
672 aPcbFootprint->GetReference(),
673 EscapeHTML( fpFilters ) );
674 }
675 else
676 {
677 aPcbFootprint->SetFilters( fpFilters );
678 msg.Printf( _( "Updated %s footprint filters to '%s'." ),
679 aPcbFootprint->GetReference(),
680 EscapeHTML( fpFilters ) );
681 }
682
683 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
684 }
685
686 if( ( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) > 0 )
687 != ( ( aPcbFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
688 {
689 if( m_isDryRun )
690 {
691 if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) )
692 {
693 msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ),
694 aPcbFootprint->GetReference() );
695 }
696 else
697 {
698 msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ),
699 aPcbFootprint->GetReference() );
700 }
701 }
702 else
703 {
704 int attributes = aPcbFootprint->GetAttributes();
705
706 if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) )
707 {
708 attributes |= FP_EXCLUDE_FROM_BOM;
709 msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ),
710 aPcbFootprint->GetReference() );
711 }
712 else
713 {
714 attributes &= ~FP_EXCLUDE_FROM_BOM;
715 msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ),
716 aPcbFootprint->GetReference() );
717 }
718
719 changed = true;
720 aPcbFootprint->SetAttributes( attributes );
721 }
722
723 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
724 }
725
726 if( ( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) > 0 )
727 != ( ( aPcbFootprint->GetAttributes() & FP_DNP ) > 0 ) )
728 {
729 if( m_isDryRun )
730 {
731 if( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) )
732 {
733 msg.Printf( _( "Add %s 'Do not place' fabrication attribute." ),
734 aPcbFootprint->GetReference() );
735 }
736 else
737 {
738 msg.Printf( _( "Remove %s 'Do not place' fabrication attribute." ),
739 aPcbFootprint->GetReference() );
740 }
741 }
742 else
743 {
744 int attributes = aPcbFootprint->GetAttributes();
745
746 if( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) )
747 {
748 attributes |= FP_DNP;
749 msg.Printf( _( "Added %s 'Do not place' fabrication attribute." ),
750 aPcbFootprint->GetReference() );
751 }
752 else
753 {
754 attributes &= ~FP_DNP;
755 msg.Printf( _( "Removed %s 'Do not place' fabrication attribute." ),
756 aPcbFootprint->GetReference() );
757 }
758
759 changed = true;
760 aPcbFootprint->SetAttributes( attributes );
761 }
762
763 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
764 }
765
766 if( aNetlistComponent->GetDuplicatePadNumbersAreJumpers()
767 != aPcbFootprint->GetDuplicatePadNumbersAreJumpers() )
768 {
769 bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
770
771 if( !m_isDryRun )
772 {
773 changed = true;
774 aPcbFootprint->SetDuplicatePadNumbersAreJumpers( value );
775
776 if( value )
777 {
778 msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
779 aPcbFootprint->GetReference() );
780 }
781 else
782 {
783 msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
784 aPcbFootprint->GetReference() );
785 }
786 }
787 else
788 {
789 if( value )
790 {
791 msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
792 aPcbFootprint->GetReference() );
793 }
794 else
795 {
796 msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
797 aPcbFootprint->GetReference() );
798 }
799 }
800
801 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
802 }
803
804 if( aNetlistComponent->JumperPadGroups() != aPcbFootprint->JumperPadGroups() )
805 {
806 if( !m_isDryRun )
807 {
808 changed = true;
809 aPcbFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
810 msg.Printf( _( "Updated %s jumper pad groups" ), aPcbFootprint->GetReference() );
811 }
812 else
813 {
814 msg.Printf( _( "Update %s jumper pad groups" ), aPcbFootprint->GetReference() );
815 }
816
817 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
818 }
819
820 if( changed && copy )
821 m_commit.Modified( aPcbFootprint, copy );
822 else
823 delete copy;
824
825 return true;
826}
827
828
830 COMPONENT* aNetlistComponent )
831{
832 if( !m_transferGroups )
833 return false;
834
835 wxString msg;
836
837 // Create a copy only if the footprint has not been added during this update
838 FOOTPRINT* copy = nullptr;
839
840 if( !m_commit.GetStatus( aPcbFootprint ) )
841 {
842 copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
843 copy->SetParentGroup( nullptr );
844 }
845
846 bool changed = false;
847
848 // These hold the info for group and group KIID coming from the netlist
849 // newGroup may point to an existing group on the board if we find an
850 // incoming group UUID that matches an existing group
851 PCB_GROUP* newGroup = nullptr;
852 KIID newGroupKIID = aNetlistComponent->GetGroup() ? aNetlistComponent->GetGroup()->uuid : 0;
853
854 PCB_GROUP* existingGroup = static_cast<PCB_GROUP*>( aPcbFootprint->GetParentGroup() );
855 KIID existingGroupKIID = existingGroup ? existingGroup->m_Uuid : 0;
856
857 // Find existing group based on matching UUIDs
858 auto it = std::find_if( m_board->Groups().begin(), m_board->Groups().end(),
859 [&](PCB_GROUP* group) {
860 return group->m_Uuid == newGroupKIID;
861 });
862
863 // If we find a group with the same UUID, use it
864 if( it != m_board->Groups().end() )
865 newGroup = *it;
866
867 // No changes, nothing to do
868 if( newGroupKIID == existingGroupKIID )
869 return changed;
870
871 // Remove from existing group
872 if( existingGroupKIID != 0 )
873 {
874 if( m_isDryRun )
875 {
876 msg.Printf( _( "Remove %s from group '%s'." ),
877 aPcbFootprint->GetReference(),
878 EscapeHTML( existingGroup->GetName() ) );
879 }
880 else
881 {
882 msg.Printf( _( "Removed %s from group '%s'." ),
883 aPcbFootprint->GetReference(),
884 EscapeHTML( existingGroup->GetName() ) );
885
886 changed = true;
887 m_commit.Modify( existingGroup, nullptr, RECURSE_MODE::NO_RECURSE );
888 existingGroup->RemoveItem( aPcbFootprint );
889 }
890
891 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
892 }
893
894 // Add to new group
895 if( newGroupKIID != 0 )
896 {
897 if( m_isDryRun )
898 {
899 msg.Printf( _( "Add %s to group '%s'." ),
900 aPcbFootprint->GetReference(),
901 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
902 }
903 else
904 {
905 msg.Printf( _( "Added %s group '%s'." ),
906 aPcbFootprint->GetReference(),
907 EscapeHTML( aNetlistComponent->GetGroup()->name ) );
908
909 changed = true;
910
911 if( newGroup == nullptr )
912 {
913 newGroup = new PCB_GROUP( m_board );
914 const_cast<KIID&>( newGroup->m_Uuid ) = newGroupKIID;
915 newGroup->SetName( aNetlistComponent->GetGroup()->name );
916
917 // Add the group to the board manually so we can find it by checking
918 // board groups for later footprints that are checking for existing groups
919 m_board->Add( newGroup );
920 m_commit.Added( newGroup );
921 }
922 else
923 {
924 m_commit.Modify( newGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
925 }
926
927 newGroup->AddItem( aPcbFootprint );
928 }
929
930 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
931 }
932
933 if( changed && copy )
934 m_commit.Modified( aPcbFootprint, copy );
935 else if( copy )
936 delete copy;
937
938 return changed;
939}
940
941
943 COMPONENT* aNewComponent )
944{
945 wxString msg;
946
947 // Create a copy only if the footprint has not been added during this update
948 FOOTPRINT* copy = nullptr;
949
950 if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
951 {
952 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
953 copy->SetParentGroup( nullptr );
954 }
955
956 bool changed = false;
957
958 // At this point, the component footprint is updated. Now update the nets.
959 std::deque<PAD*> pads = aFootprint->Pads();
960 std::set<wxString> padNetnames;
961
962 std::sort( pads.begin(), pads.end(),
963 []( PAD* a, PAD* b )
964 {
965 return a->m_Uuid < b->m_Uuid;
966 } );
967
968 for( PAD* pad : pads )
969 {
970 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
971
972 wxLogTrace( wxT( "NETLIST_UPDATE" ),
973 wxT( "Processing pad %s of component %s" ),
974 pad->GetNumber(),
975 aNewComponent->GetReference() );
976
977 wxString pinFunction;
978 wxString pinType;
979
980 if( net.IsValid() ) // i.e. the pad has a name
981 {
982 wxLogTrace( wxT( "NETLIST_UPDATE" ),
983 wxT( " Found valid net: %s" ),
984 net.GetNetName() );
985 pinFunction = net.GetPinFunction();
986 pinType = net.GetPinType();
987 }
988 else
989 {
990 wxLogTrace( wxT( "NETLIST_UPDATE" ),
991 wxT( " No net found for pad %s" ),
992 pad->GetNumber() );
993 }
994
995 if( !m_isDryRun )
996 {
997 if( pad->GetPinFunction() != pinFunction )
998 {
999 changed = true;
1000 pad->SetPinFunction( pinFunction );
1001 }
1002
1003 if( pad->GetPinType() != pinType )
1004 {
1005 changed = true;
1006 pad->SetPinType( pinType );
1007 }
1008 }
1009 else
1010 {
1011 cachePinFunction( pad, pinFunction );
1012 }
1013
1014 // Test if new footprint pad has no net (pads not on copper layers have no net).
1015 if( !net.IsValid() || !pad->IsOnCopperLayer() )
1016 {
1017 if( !pad->GetNetname().IsEmpty() )
1018 {
1019 if( m_isDryRun )
1020 {
1021 msg.Printf( _( "Disconnect %s pin %s." ),
1022 aFootprint->GetReference(),
1023 EscapeHTML( pad->GetNumber() ) );
1024 }
1025 else
1026 {
1027 msg.Printf( _( "Disconnected %s pin %s." ),
1028 aFootprint->GetReference(),
1029 EscapeHTML( pad->GetNumber() ) );
1030 }
1031
1032 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1033 }
1034 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
1035 {
1036 // pad is connectable but has no net found in netlist
1037 msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
1038 aFootprint->GetReference(),
1039 EscapeHTML( pad->GetNumber() ),
1040 EscapeHTML( pad->GetNumber() ) );
1041 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
1043 }
1044
1045 if( !m_isDryRun )
1046 {
1047 changed = true;
1048 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
1049
1050 // If the pad has no net from netlist (i.e. not in netlist
1051 // it cannot have a pin function
1052 if( pad->GetNetname().IsEmpty() )
1053 pad->SetPinFunction( wxEmptyString );
1054
1055 }
1056 else
1057 {
1058 cacheNetname( pad, wxEmptyString );
1059 }
1060 }
1061 else // New footprint pad has a net.
1062 {
1063 wxString netName = net.GetNetName();
1064
1065 if( pad->IsNoConnectPad() )
1066 {
1067 netName = wxString::Format( wxS( "%s" ), net.GetNetName() );
1068
1069 for( int jj = 1; !padNetnames.insert( netName ).second; jj++ )
1070 {
1071 netName = wxString::Format( wxS( "%s_%d" ), net.GetNetName(), jj );
1072 }
1073 }
1074
1075 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
1076
1077 if( netinfo && !m_isDryRun )
1078 netinfo->SetIsCurrent( true );
1079
1080 if( pad->GetNetname() != netName )
1081 {
1082
1083 if( netinfo == nullptr )
1084 {
1085 // It might be a new net that has not been added to the board yet
1086 if( m_addedNets.count( netName ) )
1087 netinfo = m_addedNets[ netName ];
1088 }
1089
1090 if( netinfo == nullptr )
1091 {
1092 netinfo = new NETINFO_ITEM( m_board, netName );
1093
1094 // It is a new net, we have to add it
1095 if( !m_isDryRun )
1096 {
1097 changed = true;
1098 m_commit.Add( netinfo );
1099 }
1100
1101 m_addedNets[netName] = netinfo;
1102 msg.Printf( _( "Add net %s." ),
1103 EscapeHTML( UnescapeString( netName ) ) );
1104 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1105 }
1106
1107 if( !pad->GetNetname().IsEmpty() )
1108 {
1109 m_oldToNewNets[ pad->GetNetname() ] = netName;
1110
1111 if( m_isDryRun )
1112 {
1113 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
1114 aFootprint->GetReference(),
1115 EscapeHTML( pad->GetNumber() ),
1116 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1117 EscapeHTML( UnescapeString( netName ) ) );
1118 }
1119 else
1120 {
1121 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
1122 aFootprint->GetReference(),
1123 EscapeHTML( pad->GetNumber() ),
1124 EscapeHTML( UnescapeString( pad->GetNetname() ) ),
1125 EscapeHTML( UnescapeString( netName ) ) );
1126 }
1127 }
1128 else
1129 {
1130 if( m_isDryRun )
1131 {
1132 msg.Printf( _( "Connect %s pin %s to %s."),
1133 aFootprint->GetReference(),
1134 EscapeHTML( pad->GetNumber() ),
1135 EscapeHTML( UnescapeString( netName ) ) );
1136 }
1137 else
1138 {
1139 msg.Printf( _( "Connected %s pin %s to %s."),
1140 aFootprint->GetReference(),
1141 EscapeHTML( pad->GetNumber() ),
1142 EscapeHTML( UnescapeString( netName ) ) );
1143 }
1144 }
1145
1146 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1147
1148 if( !m_isDryRun )
1149 {
1150 changed = true;
1151 pad->SetNet( netinfo );
1152 }
1153 else
1154 {
1155 cacheNetname( pad, netName );
1156 }
1157 }
1158 }
1159 }
1160
1161 if( changed && copy )
1162 m_commit.Modified( aFootprint, copy );
1163 else if( copy )
1164 delete copy;
1165
1166 return true;
1167}
1168
1169
1171{
1172 // Build the footprint-side representation from the netlist component
1173 std::vector<FOOTPRINT::FP_UNIT_INFO> newUnits;
1174
1175 for( const COMPONENT::UNIT_INFO& u : aNewComponent->GetUnitInfo() )
1176 newUnits.push_back( { u.m_unitName, u.m_pins } );
1177
1178 const std::vector<FOOTPRINT::FP_UNIT_INFO>& curUnits = aFootprint->GetUnitInfo();
1179
1180 auto unitsEqual = []( const std::vector<FOOTPRINT::FP_UNIT_INFO>& a,
1181 const std::vector<FOOTPRINT::FP_UNIT_INFO>& b )
1182 {
1183 if( a.size() != b.size() )
1184 return false;
1185
1186 for( size_t i = 0; i < a.size(); ++i )
1187 {
1188 if( a[i].m_unitName != b[i].m_unitName )
1189 return false;
1190
1191 if( a[i].m_pins != b[i].m_pins )
1192 return false;
1193 }
1194
1195 return true;
1196 };
1197
1198 if( unitsEqual( curUnits, newUnits ) )
1199 return false;
1200
1201 wxString msg;
1202
1203 if( m_isDryRun )
1204 {
1205 msg.Printf( _( "Update %s unit metadata." ), aFootprint->GetReference() );
1206 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1207 return false; // no actual change on board during dry run
1208 }
1209
1210 // Create a copy only if the footprint has not been added during this update
1211 FOOTPRINT* copy = nullptr;
1212
1213 if( !m_commit.GetStatus( aFootprint ) )
1214 {
1215 copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
1216 copy->SetParentGroup( nullptr );
1217 }
1218
1219 aFootprint->SetUnitInfo( newUnits );
1220
1221 msg.Printf( _( "Updated %s unit metadata." ), aFootprint->GetReference() );
1222 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1223
1224 if( copy )
1225 m_commit.Modified( aFootprint, copy );
1226
1227 return true;
1228}
1229
1230
1232{
1233 for( ZONE* zone : m_board->Zones() )
1234 {
1235 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1236 continue;
1237
1238 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
1239 }
1240}
1241
1242
1244{
1245 wxString msg;
1246 std::set<wxString> netlistNetnames;
1247
1248 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
1249 {
1250 const COMPONENT* component = aNetlist.GetComponent( ii );
1251
1252 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1253 {
1254 const COMPONENT_NET& net = component->GetNet( jj );
1255 netlistNetnames.insert( net.GetNetName() );
1256 }
1257 }
1258
1259 for( PCB_TRACK* via : m_board->Tracks() )
1260 {
1261 if( via->Type() != PCB_VIA_T )
1262 continue;
1263
1264 if( netlistNetnames.count( via->GetNetname() ) == 0 )
1265 {
1266 wxString updatedNetname = wxEmptyString;
1267
1268 // Take via name from name change map if it didn't match to a new pad
1269 // (this is useful for stitching vias that don't connect to tracks)
1270 if( m_oldToNewNets.count( via->GetNetname() ) )
1271 {
1272 updatedNetname = m_oldToNewNets[via->GetNetname()];
1273 }
1274
1275 if( !updatedNetname.IsEmpty() )
1276 {
1277 if( m_isDryRun )
1278 {
1279 wxString originalNetname = via->GetNetname();
1280
1281 msg.Printf( _( "Reconnect via from %s to %s." ),
1282 EscapeHTML( UnescapeString( originalNetname ) ),
1283 EscapeHTML( UnescapeString( updatedNetname ) ) );
1284
1285 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1286 }
1287 else
1288 {
1289 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1290
1291 if( !netinfo )
1292 netinfo = m_addedNets[updatedNetname];
1293
1294 if( netinfo )
1295 {
1296 wxString originalNetname = via->GetNetname();
1297
1298 m_commit.Modify( via );
1299 via->SetNet( netinfo );
1300
1301 msg.Printf( _( "Reconnected via from %s to %s." ),
1302 EscapeHTML( UnescapeString( originalNetname ) ),
1303 EscapeHTML( UnescapeString( updatedNetname ) ) );
1304
1305 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1306 }
1307 }
1308 }
1309 else
1310 {
1311 msg.Printf( _( "Via connected to unknown net (%s)." ),
1312 EscapeHTML( UnescapeString( via->GetNetname() ) ) );
1313 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1315 }
1316 }
1317 }
1318
1319 // Board connectivity net names are not the same as schematic connectivity net names.
1320 // Footprints that contain multiple overlapping pads with the same number are suffixed
1321 // with "_N" for internal use. Somewhere along the line, these pseudo net names were
1322 // exposed in the zone net name list.
1323 auto isInNetlist = [&]( const wxString& aNetName ) -> bool
1324 {
1325 if( netlistNetnames.count( aNetName ) )
1326 return true;
1327
1328 // If the zone net name is a pseudo net name, check if the root net name is in the net
1329 // list. If so, then this is a valid net.
1330 for( const wxString& netName : netlistNetnames )
1331 {
1332 if( aNetName.StartsWith( netName ) )
1333 return true;
1334 }
1335
1336 return false;
1337 };
1338
1339 // Test copper zones to detect "dead" nets (nets without any pad):
1340 for( ZONE* zone : m_board->Zones() )
1341 {
1342 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
1343 continue;
1344
1345 if( !isInNetlist( zone->GetNetname() ) )
1346 {
1347 // Look for a pad in the zone's connected-pad-cache which has been updated to
1348 // a new net and use that. While this won't always be the right net, the dead
1349 // net is guaranteed to be wrong.
1350 wxString updatedNetname = wxEmptyString;
1351
1352 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
1353 {
1354 if( getNetname( pad ) != zone->GetNetname() )
1355 {
1356 updatedNetname = getNetname( pad );
1357 break;
1358 }
1359 }
1360
1361 // Take zone name from name change map if it didn't match to a new pad
1362 // (this is useful for zones on internal layers)
1363 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
1364 {
1365 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
1366 }
1367
1368 if( !updatedNetname.IsEmpty() )
1369 {
1370 if( m_isDryRun )
1371 {
1372 wxString originalNetname = zone->GetNetname();
1373
1374 if( !zone->GetZoneName().IsEmpty() )
1375 {
1376 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
1377 zone->GetZoneName(),
1378 EscapeHTML( UnescapeString( originalNetname ) ),
1379 EscapeHTML( UnescapeString( updatedNetname ) ) );
1380 }
1381 else
1382 {
1383 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
1384 EscapeHTML( UnescapeString( originalNetname ) ),
1385 EscapeHTML( UnescapeString( updatedNetname ) ) );
1386 }
1387
1388 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1389 }
1390 else
1391 {
1392 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
1393
1394 if( !netinfo )
1395 netinfo = m_addedNets[ updatedNetname ];
1396
1397 if( netinfo )
1398 {
1399 wxString originalNetname = zone->GetNetname();
1400
1401 m_commit.Modify( zone );
1402 zone->SetNet( netinfo );
1403
1404 if( !zone->GetZoneName().IsEmpty() )
1405 {
1406 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
1407 EscapeHTML( zone->GetZoneName() ),
1408 EscapeHTML( UnescapeString( originalNetname ) ),
1409 EscapeHTML( UnescapeString( updatedNetname ) ) );
1410 }
1411 else
1412 {
1413 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
1414 EscapeHTML( UnescapeString( originalNetname ) ),
1415 EscapeHTML( UnescapeString( updatedNetname ) ) );
1416 }
1417
1418 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1419 }
1420 }
1421 }
1422 else
1423 {
1424 if( !zone->GetZoneName().IsEmpty() )
1425 {
1426 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
1427 EscapeHTML( zone->GetZoneName() ) );
1428 }
1429 else
1430 {
1431 wxString layerNames = zone->LayerMaskDescribe();
1432 VECTOR2I pt = zone->GetPosition();
1433
1434 if( m_frame && m_frame->GetPcbNewSettings() )
1435 {
1436 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertXAxis )
1437 pt.x *= -1;
1438
1439 if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
1440 pt.y *= -1;
1441 }
1442
1443 msg.Printf( _( "Copper zone on %s at (%s, %s) has no pads connected to net \"%s\"." ),
1444 EscapeHTML( layerNames ),
1445 m_frame ? m_frame->MessageTextFromValue( pt.x )
1447 m_frame ? m_frame->MessageTextFromValue( pt.y )
1449 zone->GetNetname() );
1450 }
1451
1452 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1454 }
1455 }
1456 }
1457
1458 return true;
1459}
1460
1461
1463{
1464 if( !m_transferGroups )
1465 return false;
1466
1467 for( PCB_GROUP* pcbGroup : m_board->Groups() )
1468 {
1469 NETLIST_GROUP* netlistGroup = aNetlist.GetGroupByUuid( pcbGroup->m_Uuid );
1470
1471 if( netlistGroup == nullptr )
1472 continue;
1473
1474 if( netlistGroup->name != pcbGroup->GetName() )
1475 {
1476 if( m_isDryRun )
1477 {
1478 wxString msg;
1479 msg.Printf( _( "Change group name to '%s' to '%s'." ), EscapeHTML( pcbGroup->GetName() ),
1480 EscapeHTML( netlistGroup->name ) );
1481 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1482 }
1483 else
1484 {
1485 wxString msg;
1486 msg.Printf( _( "Changed group name to '%s' to '%s'." ), EscapeHTML( pcbGroup->GetName() ),
1487 EscapeHTML( netlistGroup->name ) );
1488 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1489 pcbGroup->SetName( netlistGroup->name );
1490 }
1491 }
1492
1493 if( netlistGroup->libId != pcbGroup->GetDesignBlockLibId() )
1494 {
1495 if( m_isDryRun )
1496 {
1497 wxString msg;
1498 msg.Printf( _( "Change group library link to '%s'." ),
1499 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1500 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1501 }
1502 else
1503 {
1504 wxString msg;
1505 msg.Printf( _( "Changed group library link to '%s'." ),
1506 EscapeHTML( netlistGroup->libId.GetUniStringLibId() ) );
1507 m_commit.Modify( pcbGroup->AsEdaItem(), nullptr, RECURSE_MODE::NO_RECURSE );
1508 pcbGroup->SetDesignBlockLibId( netlistGroup->libId );
1509 }
1510 }
1511 }
1512
1513 return true;
1514}
1515
1516
1518 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
1519{
1520 // Verify that board contains all pads in netlist: if it doesn't then footprints are
1521 // wrong or missing.
1522
1523 wxString msg;
1524 wxString padNumber;
1525
1526 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
1527 {
1528 COMPONENT* component = aNetlist.GetComponent( i );
1529 FOOTPRINT* footprint = aFootprintMap[component];
1530
1531 if( !footprint ) // It can be missing in partial designs
1532 continue;
1533
1534 // Explore all pins/pads in component
1535 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
1536 {
1537 padNumber = component->GetNet( jj ).GetPinName();
1538
1539 if( padNumber.IsEmpty() )
1540 {
1541 // bad symbol, report error
1542 msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
1543 "to pads in %s." ),
1544 component->GetReference(),
1545 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1546 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1547 ++m_errorCount;
1548 }
1549 else if( !footprint->FindPadByNumber( padNumber ) )
1550 {
1551 // not found: bad footprint, report error
1552 msg.Printf( _( "%s pad %s not found in %s." ),
1553 component->GetReference(),
1554 EscapeHTML( padNumber ),
1555 EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
1556 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1557 ++m_errorCount;
1558 }
1559 }
1560 }
1561
1562 return true;
1563}
1564
1565
1567{
1568 FOOTPRINT* lastPreexistingFootprint = nullptr;
1569 COMPONENT* component = nullptr;
1570 wxString msg;
1571 std::unordered_set<wxString> sheetPaths;
1572
1573 m_errorCount = 0;
1574 m_warningCount = 0;
1576
1577 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
1578
1579 if( !m_board->Footprints().empty() )
1580 lastPreexistingFootprint = m_board->Footprints().back();
1581
1583
1584 // First mark all nets (except <no net>) as stale; we'll update those which are current
1585 // in the following two loops. Also prepare the component class manager for updates.
1586 //
1587 if( !m_isDryRun )
1588 {
1589 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1590 net->SetIsCurrent( net->GetNetCode() == 0 );
1591
1592 m_board->GetComponentClassManager().InitNetlistUpdate();
1593 }
1594
1595 // Next go through the netlist updating all board footprints which have matching component
1596 // entries and adding new footprints for those that don't.
1597 //
1598 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
1599 {
1600 component = aNetlist.GetComponent( i );
1601
1602 if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
1603 continue;
1604
1605 msg.Printf( _( "Processing symbol '%s:%s'." ),
1606 component->GetReference(),
1607 EscapeHTML( component->GetFPID().Format().wx_str() ) );
1608 m_reporter->Report( msg, RPT_SEVERITY_INFO );
1609
1610 int matchCount = 0;
1611
1612 for( FOOTPRINT* footprint : m_board->Footprints() )
1613 {
1614 bool match = false;
1615
1617 {
1618 for( const KIID& uuid : component->GetKIIDs() )
1619 {
1620 KIID_PATH base = component->GetPath();
1621 base.push_back( uuid );
1622
1623 if( footprint->GetPath() == base )
1624 {
1625 match = true;
1626 break;
1627 }
1628 }
1629 }
1630 else
1631 {
1632 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
1633 }
1634
1635 if( match )
1636 {
1637 FOOTPRINT* tmp = footprint;
1638
1639 if( m_replaceFootprints && component->GetFPID() != footprint->GetFPID() )
1640 tmp = replaceFootprint( aNetlist, footprint, component );
1641
1642 if( !tmp )
1643 tmp = footprint;
1644
1645 if( tmp )
1646 {
1647 footprintMap[ component ] = tmp;
1648
1649 updateFootprintParameters( tmp, component );
1650 updateFootprintGroup( tmp, component );
1651 updateComponentPadConnections( tmp, component );
1652 updateComponentClass( tmp, component );
1653 updateComponentUnits( tmp, component );
1654
1655 sheetPaths.insert( footprint->GetSheetname() );
1656 }
1657
1658 matchCount++;
1659 }
1660
1661 if( footprint == lastPreexistingFootprint )
1662 {
1663 // No sense going through the newly-created footprints: end of loop
1664 break;
1665 }
1666 }
1667
1668 if( matchCount == 0 )
1669 {
1670 FOOTPRINT* footprint = addNewFootprint( component );
1671
1672 if( footprint )
1673 {
1674 footprintMap[ component ] = footprint;
1675
1676 updateFootprintParameters( footprint, component );
1677 updateFootprintGroup( footprint, component );
1678 updateComponentPadConnections( footprint, component );
1679 updateComponentClass( footprint, component );
1680 updateComponentUnits( footprint, component );
1681
1682 sheetPaths.insert( footprint->GetSheetname() );
1683 }
1684 }
1685 else if( matchCount > 1 )
1686 {
1687 msg.Printf( _( "Multiple footprints found for %s." ),
1688 component->GetReference() );
1689 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
1690 m_errorCount++;
1691 }
1692 }
1693
1694 updateCopperZoneNets( aNetlist );
1695 updateGroups( aNetlist );
1696
1697 // Finally go through the board footprints and update all those that *don't* have matching
1698 // component entries.
1699 //
1700 for( FOOTPRINT* footprint : m_board->Footprints() )
1701 {
1702 bool matched = false;
1703 bool doDelete = m_deleteUnusedFootprints;
1704
1705 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
1706 doDelete = false;
1707
1709 component = aNetlist.GetComponentByPath( footprint->GetPath() );
1710 else
1711 component = aNetlist.GetComponentByReference( footprint->GetReference() );
1712
1713 if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
1714 matched = true;
1715
1716 if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
1717 {
1718 if( m_isDryRun )
1719 {
1720 msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
1721 footprint->GetReference() );
1722 }
1723 else
1724 {
1725 msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
1726 footprint->GetReference() );
1727 }
1728
1729 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
1731 doDelete = false;
1732 }
1733
1734 if( doDelete && !matched )
1735 {
1736 if( m_isDryRun )
1737 {
1738 msg.Printf( _( "Remove unused footprint %s." ),
1739 footprint->GetReference() );
1740 }
1741 else
1742 {
1743 m_commit.Remove( footprint );
1744 msg.Printf( _( "Removed unused footprint %s." ),
1745 footprint->GetReference() );
1746 }
1747
1748 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1749 }
1750 else if( !m_isDryRun )
1751 {
1752 if( !matched )
1753 footprint->SetPath( KIID_PATH() );
1754
1755 for( PAD* pad : footprint->Pads() )
1756 {
1757 if( pad->GetNet() )
1758 pad->GetNet()->SetIsCurrent( true );
1759 }
1760 }
1761 }
1762
1763 if( !m_isDryRun )
1764 {
1765 // Finalise the component class manager
1766 m_board->GetComponentClassManager().FinishNetlistUpdate();
1767 m_board->SynchronizeComponentClasses( sheetPaths );
1768
1769 m_board->BuildConnectivity();
1770 testConnectivity( aNetlist, footprintMap );
1771
1772 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1773 {
1774 if( !net->IsCurrent() )
1775 {
1776 msg.Printf( _( "Removed unused net %s." ),
1777 EscapeHTML( net->GetNetname() ) );
1778 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1779 }
1780 }
1781
1782 m_board->RemoveUnusedNets( &m_commit );
1783
1784 // When new footprints are added, the automatic zone refill is disabled because:
1785 // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
1786 // (the auto refills rebuild the connectivity with incomplete data)
1787 // * it is useless because zones will be refilled after placing new footprints
1788 m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
1789
1790 // Update net, netcode and netclass data after commiting the netlist
1791 m_board->SynchronizeNetsAndNetClasses( true );
1792 m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
1793
1794 // Although m_commit will probably also set this, it's not guaranteed, and we need to make
1795 // sure any modification to netclasses gets persisted to project settings through a save.
1796 if( m_frame )
1797 m_frame->OnModify();
1798 }
1799
1800 if( m_isDryRun )
1801 {
1802 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
1803 delete addedNet.second;
1804
1805 m_addedNets.clear();
1806 }
1807
1808 // Update the ratsnest
1809 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
1810 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
1811
1812 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
1813 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
1814
1815 return true;
1816}
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
#define ZONE_FILL_OP
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition board_item.h:284
std::map< PAD *, wxString > m_padNets
void cacheNetname(PAD *aPad, const wxString &aNetname)
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
wxString getNetname(PAD *aPad)
wxString getPinFunction(PAD *aPad)
std::vector< FOOTPRINT * > m_addedFootprints
std::map< wxString, wxString > m_oldToNewNets
void updateComponentClass(FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
bool updateFootprintGroup(FOOTPRINT *aPcbFootprint, COMPONENT *aNetlistComponent)
bool updateFootprintParameters(FOOTPRINT *aPcbFootprint, COMPONENT *aNetlistComponent)
std::map< wxString, NETINFO_ITEM * > m_addedNets
bool testConnectivity(NETLIST &aNetlist, std::map< COMPONENT *, FOOTPRINT * > &aFootprintMap)
bool updateCopperZoneNets(NETLIST &aNetlist)
void cachePinFunction(PAD *aPad, const wxString &aPinFunction)
bool updateGroups(NETLIST &aNetlist)
BOARD_NETLIST_UPDATER(PCB_EDIT_FRAME *aFrame, BOARD *aBoard)
bool updateComponentPadConnections(FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
std::map< PAD *, wxString > m_padPinFunctions
std::map< ZONE *, std::vector< PAD * > > m_zoneConnectionsCache
FOOTPRINT * replaceFootprint(NETLIST &aNetlist, FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
bool updateComponentUnits(FOOTPRINT *aFootprint, COMPONENT *aNewComponent)
FOOTPRINT * addNewFootprint(COMPONENT *aComponent)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
constexpr size_type GetWidth() const
Definition box2.h:214
constexpr Vec Centre() const
Definition box2.h:97
constexpr size_type GetHeight() const
Definition box2.h:215
constexpr coord_type GetBottom() const
Definition box2.h:222
static wxString GetFullClassNameForConstituents(const std::unordered_set< wxString > &classNames)
Gets the full effective class name for the given set of constituent classes.
A lightweight representation of a component class.
const wxString & GetName() const
Fetches the full name of this component class.
Used to store the component pin name to net name (and pin function) associations stored in a netlist.
const wxString & GetNetName() const
const wxString & GetPinFunction() const
const wxString & GetPinName() const
const wxString & GetPinType() const
Store all of the related component information found in a netlist.
const std::vector< UNIT_INFO > & GetUnitInfo() const
const wxString & GetHumanReadablePath() const
const COMPONENT_NET & GetNet(unsigned aIndex) const
const KIID_PATH & GetPath() const
const wxString & GetReference() const
const wxString & GetValue() const
const nlohmann::ordered_map< wxString, wxString > & GetFields() const
const std::map< wxString, wxString > & GetProperties() const
NETLIST_GROUP * GetGroup() const
const std::vector< KIID > & GetKIIDs() const
bool GetDuplicatePadNumbersAreJumpers() const
const LIB_ID & GetFPID() const
unsigned GetNetCount() const
std::unordered_set< wxString > & GetComponentClassNames()
std::vector< std::set< wxString > > & JumperPadGroups()
wxString GetName() const
Definition eda_group.h:51
void RemoveItem(EDA_ITEM *aItem)
Remove item from group.
Definition eda_group.cpp:40
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:27
void SetName(const wxString &aName)
Definition eda_group.h:52
const KIID m_Uuid
Definition eda_item.h:516
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.h:113
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:394
virtual void SetText(const wxString &aText)
Definition eda_text.cpp:278
bool GetDuplicatePadNumbersAreJumpers() const
Definition footprint.h:860
void SetPosition(const VECTOR2I &aPos) override
EDA_ANGLE GetOrientation() const
Definition footprint.h:248
void Remove(BOARD_ITEM *aItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
wxString GetSheetname() const
Definition footprint.h:287
void SetPath(const KIID_PATH &aPath)
Definition footprint.h:285
void SetFilters(const wxString &aFilters)
Definition footprint.h:294
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:760
void SetAttributes(int aAttributes)
Definition footprint.h:328
void SetSheetfile(const wxString &aSheetfile)
Definition footprint.h:291
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::vector< std::set< wxString > > & JumperPadGroups()
Each jumper pad group is a set of pad numbers that should be treated as internally connected.
Definition footprint.h:867
void SetDuplicatePadNumbersAreJumpers(bool aEnabled)
Definition footprint.h:861
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:224
int GetAttributes() const
Definition footprint.h:327
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition footprint.h:257
wxString GetSheetfile() const
Definition footprint.h:290
const LIB_ID & GetFPID() const
Definition footprint.h:269
void SetReference(const wxString &aReference)
Definition footprint.h:667
bool IsLocked() const override
Definition footprint.h:454
void SetValue(const wxString &aValue)
Definition footprint.h:688
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
void SetUnitInfo(const std::vector< FP_UNIT_INFO > &aUnits)
Definition footprint.h:759
wxString GetFilters() const
Definition footprint.h:293
void SetSheetname(const wxString &aSheetname)
Definition footprint.h:288
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
const wxString & GetValue() const
Definition footprint.h:683
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:661
const KIID_PATH & GetPath() const
Definition footprint.h:284
VECTOR2I GetPosition() const override
Definition footprint.h:245
PAD * FindPadByNumber(const wxString &aPadNumber, PAD *aSearchAfterMe=nullptr) const
Return a PAD with a matching number.
wxString AsString() const
Definition kiid.cpp:356
Definition kiid.h:49
bool empty() const
Definition lib_id.h:193
wxString GetUniStringLibId() const
Definition lib_id.h:148
UTF8 Format() const
Definition lib_id.cpp:119
Handle the data for a net.
Definition netinfo.h:54
void SetIsCurrent(bool isCurrent)
Definition netinfo.h:136
static const int UNCONNECTED
Constant that holds the "unconnected net" number (typically 0) all items "connected" to this net are ...
Definition netinfo.h:247
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
unsigned GetCount() const
COMPONENT * GetComponentByPath(const KIID_PATH &aPath)
Return a COMPONENT by aPath.
COMPONENT * GetComponentByReference(const wxString &aReference)
Return a COMPONENT by aReference.
NETLIST_GROUP * GetGroupByUuid(const KIID &aUuid)
Return a NETLIST_GROUP by aUuid.
COMPONENT * GetComponent(unsigned aIndex)
Return the COMPONENT at aIndex.
static REPORTER & GetInstance()
Definition reporter.cpp:96
Definition pad.h:55
const wxString & GetPinFunction() const
Definition pad.h:148
The main frame for Pcbnew.
void SetName(const wxString &aName)
Definition pcb_field.h:108
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:53
EDA_ITEM * AsEdaItem() override
Definition pcb_group.h:60
void StyleFromSettings(const BOARD_DESIGN_SETTINGS &settings, bool aCheckSide) override
Definition pcb_text.cpp:311
virtual void SetPosition(const VECTOR2I &aPos) override
Definition pcb_text.h:84
void Rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle) override
Rotate this object.
Definition pcb_text.cpp:401
wxString wx_str() const
Definition utf8.cpp:45
Handle a list of polygons defining a copper zone.
Definition zone.h:74
The common library.
#define _(s)
@ NO_RECURSE
Definition eda_item.h:52
@ FP_DNP
Definition footprint.h:87
@ FP_BOARD_ONLY
Definition footprint.h:85
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:84
@ FP_JUST_ADDED
Definition footprint.h:86
@ F_Fab
Definition layer_ids.h:119
@ F_Cu
Definition layer_ids.h:64
@ B_Fab
Definition layer_ids.h:118
KICOMMON_API wxString MessageTextFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A helper to convert the double length aValue to a string in inches, millimeters, or unscaled units.
Class to handle a set of BOARD_ITEMs.
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
@ RPT_SEVERITY_ACTION
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
wxString UnescapeString(const wxString &aSource)
@ USER
The field ID hasn't been set yet; field is invalid.
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
wxString GetCanonicalFieldName(FIELD_T aFieldType)
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695