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 <dick@softplc.com>
7 * Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
8 *
9 * Copyright (C) 1992-2021 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 <board.h>
33 #include <netinfo.h>
34 #include <footprint.h>
35 #include <pad.h>
36 #include <pcb_track.h>
37 #include <zone.h>
38 #include <string_utils.h>
39 #include <pcbnew_settings.h>
40 #include <pcb_edit_frame.h>
41 #include <netlist_reader/pcb_netlist.h>
42 #include <connectivity/connectivity_data.h>
43 #include <reporter.h>
44
45 #include "board_netlist_updater.h"
46
47
BOARD_NETLIST_UPDATER(PCB_EDIT_FRAME * aFrame,BOARD * aBoard)48 BOARD_NETLIST_UPDATER::BOARD_NETLIST_UPDATER( PCB_EDIT_FRAME* aFrame, BOARD* aBoard ) :
49 m_frame( aFrame ),
50 m_commit( aFrame ),
51 m_board( aBoard )
52 {
53 m_reporter = &NULL_REPORTER::GetInstance();
54
55 m_deleteUnusedFootprints = false;
56 m_isDryRun = false;
57 m_replaceFootprints = true;
58 m_lookupByTimestamp = false;
59
60 m_warningCount = 0;
61 m_errorCount = 0;
62 m_newFootprintsCount = 0;
63 }
64
65
~BOARD_NETLIST_UPDATER()66 BOARD_NETLIST_UPDATER::~BOARD_NETLIST_UPDATER()
67 {
68 }
69
70
71 // These functions allow inspection of pad nets during dry runs by keeping a cache of
72 // current pad netnames indexed by pad.
73
cacheNetname(PAD * aPad,const wxString & aNetname)74 void BOARD_NETLIST_UPDATER::cacheNetname( PAD* aPad, const wxString& aNetname )
75 {
76 m_padNets[ aPad ] = aNetname;
77 }
78
79
getNetname(PAD * aPad)80 wxString BOARD_NETLIST_UPDATER::getNetname( PAD* aPad )
81 {
82 if( m_isDryRun && m_padNets.count( aPad ) )
83 return m_padNets[ aPad ];
84 else
85 return aPad->GetNetname();
86 }
87
88
cachePinFunction(PAD * aPad,const wxString & aPinFunction)89 void BOARD_NETLIST_UPDATER::cachePinFunction( PAD* aPad, const wxString& aPinFunction )
90 {
91 m_padPinFunctions[ aPad ] = aPinFunction;
92 }
93
94
getPinFunction(PAD * aPad)95 wxString BOARD_NETLIST_UPDATER::getPinFunction( PAD* aPad )
96 {
97 if( m_isDryRun && m_padPinFunctions.count( aPad ) )
98 return m_padPinFunctions[ aPad ];
99 else
100 return aPad->GetPinFunction();
101 }
102
103
estimateFootprintInsertionPosition()104 wxPoint BOARD_NETLIST_UPDATER::estimateFootprintInsertionPosition()
105 {
106 wxPoint bestPosition;
107
108 if( !m_board->IsEmpty() )
109 {
110 // Position new components below any existing board features.
111 EDA_RECT bbox = m_board->GetBoardEdgesBoundingBox();
112
113 if( bbox.GetWidth() || bbox.GetHeight() )
114 {
115 bestPosition.x = bbox.Centre().x;
116 bestPosition.y = bbox.GetBottom() + Millimeter2iu( 10 );
117 }
118 }
119 else
120 {
121 // Position new components in the center of the page when the board is empty.
122 wxSize pageSize = m_board->GetPageSettings().GetSizeIU();
123
124 bestPosition.x = pageSize.GetWidth() / 2;
125 bestPosition.y = pageSize.GetHeight() / 2;
126 }
127
128 return bestPosition;
129 }
130
131
addNewFootprint(COMPONENT * aComponent)132 FOOTPRINT* BOARD_NETLIST_UPDATER::addNewFootprint( COMPONENT* aComponent )
133 {
134 wxString msg;
135
136 if( aComponent->GetFPID().empty() )
137 {
138 msg.Printf( _( "Cannot add %s (no footprint assigned)." ),
139 aComponent->GetReference(),
140 aComponent->GetFPID().Format().wx_str() );
141 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
142 ++m_errorCount;
143 return nullptr;
144 }
145
146 FOOTPRINT* footprint = m_frame->LoadFootprint( aComponent->GetFPID() );
147
148 if( footprint == nullptr )
149 {
150 msg.Printf( _( "Cannot add %s (footprint '%s' not found)." ),
151 aComponent->GetReference(),
152 aComponent->GetFPID().Format().wx_str() );
153 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
154 ++m_errorCount;
155 return nullptr;
156 }
157
158 if( m_isDryRun )
159 {
160 msg.Printf( _( "Add %s (footprint '%s')." ),
161 aComponent->GetReference(),
162 aComponent->GetFPID().Format().wx_str() );
163
164 delete footprint;
165 footprint = nullptr;
166 }
167 else
168 {
169 for( PAD* pad : footprint->Pads() )
170 {
171 // Set the pads ratsnest settings to the global settings
172 pad->SetLocalRatsnestVisible( m_frame->GetDisplayOptions().m_ShowGlobalRatsnest );
173
174 // Pads in the library all have orphaned nets. Replace with Default.
175 pad->SetNetCode( 0 );
176 }
177
178 footprint->SetParent( m_board );
179 footprint->SetPosition( estimateFootprintInsertionPosition() );
180
181 // This flag is used to prevent connectivity from considering the footprint during its
182 // initial build after the footprint is committed, because we're going to immediately start
183 // a move operation on the footprint and don't want its pads to drive nets onto vias/tracks
184 // it happens to land on at the initial position.
185 footprint->SetAttributes( footprint->GetAttributes() | FP_JUST_ADDED );
186
187 m_addedFootprints.push_back( footprint );
188 m_commit.Add( footprint );
189
190 msg.Printf( _( "Added %s (footprint '%s')." ),
191 aComponent->GetReference(),
192 aComponent->GetFPID().Format().wx_str() );
193 }
194
195 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
196 m_newFootprintsCount++;
197 return footprint;
198 }
199
200
replaceFootprint(NETLIST & aNetlist,FOOTPRINT * aFootprint,COMPONENT * aNewComponent)201 FOOTPRINT* BOARD_NETLIST_UPDATER::replaceFootprint( NETLIST& aNetlist, FOOTPRINT* aFootprint,
202 COMPONENT* aNewComponent )
203 {
204 wxString msg;
205
206 if( aNewComponent->GetFPID().empty() )
207 {
208 msg.Printf( _( "Cannot update %s (no footprint assigned)." ),
209 aNewComponent->GetReference(),
210 aNewComponent->GetFPID().Format().wx_str() );
211 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
212 ++m_errorCount;
213 return nullptr;
214 }
215
216 FOOTPRINT* newFootprint = m_frame->LoadFootprint( aNewComponent->GetFPID() );
217
218 if( newFootprint == nullptr )
219 {
220 msg.Printf( _( "Cannot update %s (footprint '%s' not found)." ),
221 aNewComponent->GetReference(),
222 aNewComponent->GetFPID().Format().wx_str() );
223 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
224 ++m_errorCount;
225 return nullptr;
226 }
227
228 if( m_isDryRun )
229 {
230 msg.Printf( _( "Change %s footprint from '%s' to '%s'."),
231 aFootprint->GetReference(),
232 aFootprint->GetFPID().Format().wx_str(),
233 aNewComponent->GetFPID().Format().wx_str() );
234
235 delete newFootprint;
236 newFootprint = nullptr;
237 }
238 else
239 {
240 m_frame->ExchangeFootprint( aFootprint, newFootprint, m_commit );
241
242 msg.Printf( _( "Changed %s footprint from '%s' to '%s'."),
243 aFootprint->GetReference(),
244 aFootprint->GetFPID().Format().wx_str(),
245 aNewComponent->GetFPID().Format().wx_str() );
246 }
247
248 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
249 m_newFootprintsCount++;
250 return newFootprint;
251 }
252
253
updateFootprintParameters(FOOTPRINT * aPcbFootprint,COMPONENT * aNetlistComponent)254 bool BOARD_NETLIST_UPDATER::updateFootprintParameters( FOOTPRINT* aPcbFootprint,
255 COMPONENT* aNetlistComponent )
256 {
257 wxString msg;
258
259 // Create a copy only if the footprint has not been added during this update
260 FOOTPRINT* copy = m_commit.GetStatus( aPcbFootprint ) ? nullptr
261 : (FOOTPRINT*) aPcbFootprint->Clone();
262 bool changed = false;
263
264 // Test for reference designator field change.
265 if( aPcbFootprint->GetReference() != aNetlistComponent->GetReference() )
266 {
267 if( m_isDryRun )
268 {
269 msg.Printf( _( "Change %s reference designator to %s." ),
270 aPcbFootprint->GetReference(),
271 aNetlistComponent->GetReference() );
272 }
273 else
274 {
275 msg.Printf( _( "Changed %s reference designator to %s." ),
276 aPcbFootprint->GetReference(),
277 aNetlistComponent->GetReference() );
278
279 changed = true;
280 aPcbFootprint->SetReference( aNetlistComponent->GetReference() );
281 }
282
283 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
284 }
285
286 // Test for value field change.
287 if( aPcbFootprint->GetValue() != aNetlistComponent->GetValue() )
288 {
289 if( m_isDryRun )
290 {
291 msg.Printf( _( "Change %s value from %s to %s." ),
292 aPcbFootprint->GetReference(),
293 aPcbFootprint->GetValue(),
294 aNetlistComponent->GetValue() );
295 }
296 else
297 {
298 msg.Printf( _( "Changed %s value from %s to %s." ),
299 aPcbFootprint->GetReference(),
300 aPcbFootprint->GetValue(),
301 aNetlistComponent->GetValue() );
302
303 changed = true;
304 aPcbFootprint->SetValue( aNetlistComponent->GetValue() );
305 }
306
307 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
308 }
309
310 // Test for time stamp change.
311 KIID_PATH new_path = aNetlistComponent->GetPath();
312
313 if( !aNetlistComponent->GetKIIDs().empty() )
314 new_path.push_back( aNetlistComponent->GetKIIDs().front() );
315
316 if( aPcbFootprint->GetPath() != new_path )
317 {
318 if( m_isDryRun )
319 {
320 msg.Printf( _( "Update %s symbol association from %s to %s." ),
321 aPcbFootprint->GetReference(),
322 aPcbFootprint->GetPath().AsString(),
323 new_path.AsString() );
324 }
325 else
326 {
327 msg.Printf( _( "Updated %s symbol association from %s to %s." ),
328 aPcbFootprint->GetReference(),
329 aPcbFootprint->GetPath().AsString(),
330 new_path.AsString() );
331
332 changed = true;
333 aPcbFootprint->SetPath( new_path );
334 }
335
336 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
337 }
338
339 if( aPcbFootprint->GetProperties() != aNetlistComponent->GetProperties() )
340 {
341 if( m_isDryRun )
342 {
343 msg.Printf( _( "Update %s properties." ),
344 aPcbFootprint->GetReference() );
345 }
346 else
347 {
348 msg.Printf( _( "Updated %s properties." ),
349 aPcbFootprint->GetReference() );
350
351 changed = true;
352 aPcbFootprint->SetProperties( aNetlistComponent->GetProperties() );
353 }
354
355 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
356 }
357
358 if( ( aNetlistComponent->GetProperties().count( "exclude_from_bom" ) > 0 )
359 != ( ( aPcbFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
360 {
361 if( m_isDryRun )
362 {
363 if( aNetlistComponent->GetProperties().count( "exclude_from_bom" ) )
364 {
365 msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ),
366 aPcbFootprint->GetReference() );
367 }
368 else
369 {
370 msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ),
371 aPcbFootprint->GetReference() );
372 }
373 }
374 else
375 {
376 int attributes = aPcbFootprint->GetAttributes();
377
378 if( aNetlistComponent->GetProperties().count( "exclude_from_bom" ) )
379 {
380 attributes |= FP_EXCLUDE_FROM_BOM;
381 msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ),
382 aPcbFootprint->GetReference() );
383 }
384 else
385 {
386 attributes &= ~FP_EXCLUDE_FROM_BOM;
387 msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ),
388 aPcbFootprint->GetReference() );
389 }
390
391 changed = true;
392 aPcbFootprint->SetAttributes( attributes );
393 }
394
395 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
396 }
397
398 if( changed && copy )
399 m_commit.Modified( aPcbFootprint, copy );
400 else
401 delete copy;
402
403 return true;
404 }
405
406
updateComponentPadConnections(FOOTPRINT * aFootprint,COMPONENT * aNewComponent)407 bool BOARD_NETLIST_UPDATER::updateComponentPadConnections( FOOTPRINT* aFootprint,
408 COMPONENT* aNewComponent )
409 {
410 wxString msg;
411
412 // Create a copy only if the footprint has not been added during this update
413 FOOTPRINT* copy = m_commit.GetStatus( aFootprint ) ? nullptr : (FOOTPRINT*) aFootprint->Clone();
414 bool changed = false;
415
416 // At this point, the component footprint is updated. Now update the nets.
417 for( PAD* pad : aFootprint->Pads() )
418 {
419 const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
420
421 wxString pinFunction;
422 wxString pinType;
423
424 if( net.IsValid() ) // i.e. the pad has a name
425 {
426 pinFunction = net.GetPinFunction();
427 pinType = net.GetPinType();
428 }
429
430 if( !m_isDryRun )
431 {
432 if( pad->GetPinFunction() != pinFunction )
433 {
434 changed = true;
435 pad->SetPinFunction( pinFunction );
436 }
437
438 if( pad->GetPinType() != pinType )
439 {
440 changed = true;
441 pad->SetPinType( pinType );
442 }
443 }
444 else
445 {
446 cachePinFunction( pad, pinFunction );
447 }
448
449 // Test if new footprint pad has no net (pads not on copper layers have no net).
450 if( !net.IsValid() || !pad->IsOnCopperLayer() )
451 {
452 if( !pad->GetNetname().IsEmpty() )
453 {
454 if( m_isDryRun )
455 {
456 msg.Printf( _( "Disconnect %s pin %s." ),
457 aFootprint->GetReference(),
458 pad->GetNumber() );
459 }
460 else
461 {
462 msg.Printf( _( "Disconnected %s pin %s." ),
463 aFootprint->GetReference(),
464 pad->GetNumber() );
465 }
466
467 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
468 }
469 else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
470 {
471 // pad is connectable but has no net found in netlist
472 msg.Printf( _( "No net found for symbol %s pin %s." ),
473 aFootprint->GetReference(),
474 pad->GetNumber() );
475 m_reporter->Report( msg, RPT_SEVERITY_WARNING);
476 }
477
478 if( !m_isDryRun )
479 {
480 changed = true;
481 pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
482
483 // If the pad has no net from netlist (i.e. not in netlist
484 // it cannot have a pin function
485 if( pad->GetNetname().IsEmpty() )
486 pad->SetPinFunction( wxEmptyString );
487
488 }
489 else
490 {
491 cacheNetname( pad, wxEmptyString );
492 }
493 }
494 else // New footprint pad has a net.
495 {
496 const wxString& netName = net.GetNetName();
497 NETINFO_ITEM* netinfo = m_board->FindNet( netName );
498
499 if( netinfo && !m_isDryRun )
500 netinfo->SetIsCurrent( true );
501
502 if( pad->GetNetname() != netName )
503 {
504
505 if( netinfo == nullptr )
506 {
507 // It might be a new net that has not been added to the board yet
508 if( m_addedNets.count( netName ) )
509 netinfo = m_addedNets[ netName ];
510 }
511
512 if( netinfo == nullptr )
513 {
514 netinfo = new NETINFO_ITEM( m_board, netName );
515
516 // It is a new net, we have to add it
517 if( !m_isDryRun )
518 {
519 changed = true;
520 m_commit.Add( netinfo );
521 }
522
523 m_addedNets[netName] = netinfo;
524 msg.Printf( _( "Add net %s." ), UnescapeString( netName ) );
525 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
526 }
527
528 if( !pad->GetNetname().IsEmpty() )
529 {
530 m_oldToNewNets[ pad->GetNetname() ] = netName;
531
532 if( m_isDryRun )
533 {
534 msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
535 aFootprint->GetReference(),
536 pad->GetNumber(),
537 UnescapeString( pad->GetNetname() ),
538 UnescapeString( netName ) );
539 }
540 else
541 {
542 msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
543 aFootprint->GetReference(),
544 pad->GetNumber(),
545 UnescapeString( pad->GetNetname() ),
546 UnescapeString( netName ) );
547 }
548 }
549 else
550 {
551 if( m_isDryRun )
552 {
553 msg.Printf( _( "Connect %s pin %s to %s."),
554 aFootprint->GetReference(),
555 pad->GetNumber(),
556 UnescapeString( netName ) );
557 }
558 else
559 {
560 msg.Printf( _( "Connected %s pin %s to %s."),
561 aFootprint->GetReference(),
562 pad->GetNumber(),
563 UnescapeString( netName ) );
564 }
565 }
566
567 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
568
569 if( !m_isDryRun )
570 {
571 changed = true;
572 pad->SetNet( netinfo );
573 }
574 else
575 {
576 cacheNetname( pad, netName );
577 }
578 }
579 }
580 }
581
582 if( changed && copy )
583 m_commit.Modified( aFootprint, copy );
584 else
585 delete copy;
586
587 return true;
588 }
589
590
cacheCopperZoneConnections()591 void BOARD_NETLIST_UPDATER::cacheCopperZoneConnections()
592 {
593 for( ZONE* zone : m_board->Zones() )
594 {
595 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
596 continue;
597
598 m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
599 }
600 }
601
602
updateCopperZoneNets(NETLIST & aNetlist)603 bool BOARD_NETLIST_UPDATER::updateCopperZoneNets( NETLIST& aNetlist )
604 {
605 wxString msg;
606 std::set<wxString> netlistNetnames;
607
608 for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
609 {
610 const COMPONENT* component = aNetlist.GetComponent( ii );
611
612 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
613 {
614 const COMPONENT_NET& net = component->GetNet( jj );
615 netlistNetnames.insert( net.GetNetName() );
616 }
617 }
618
619 for( PCB_TRACK* via : m_board->Tracks() )
620 {
621 if( via->Type() != PCB_VIA_T )
622 continue;
623
624 if( netlistNetnames.count( via->GetNetname() ) == 0 )
625 {
626 wxString updatedNetname = wxEmptyString;
627
628 // Take via name from name change map if it didn't match to a new pad
629 // (this is useful for stitching vias that don't connect to tracks)
630 if( m_oldToNewNets.count( via->GetNetname() ) )
631 {
632 updatedNetname = m_oldToNewNets[via->GetNetname()];
633 }
634
635 if( !updatedNetname.IsEmpty() )
636 {
637 if( m_isDryRun )
638 {
639 msg.Printf( _( "Reconnect via from %s to %s." ),
640 UnescapeString( via->GetNetname() ),
641 UnescapeString( updatedNetname ) );
642
643 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
644 }
645 else
646 {
647 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
648
649 if( !netinfo )
650 netinfo = m_addedNets[updatedNetname];
651
652 if( netinfo )
653 {
654 m_commit.Modify( via );
655 via->SetNet( netinfo );
656
657 msg.Printf( _( "Reconnected via from %s to %s." ),
658 UnescapeString( via->GetNetname() ),
659 UnescapeString( updatedNetname ) );
660
661 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
662 }
663 }
664 }
665 else
666 {
667 msg.Printf( _( "Via connected to unknown net (%s)." ),
668 UnescapeString( via->GetNetname() ) );
669 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
670 ++m_warningCount;
671 }
672 }
673 }
674
675 // Test copper zones to detect "dead" nets (nets without any pad):
676 for( ZONE* zone : m_board->Zones() )
677 {
678 if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
679 continue;
680
681 if( netlistNetnames.count( zone->GetNetname() ) == 0 )
682 {
683 // Look for a pad in the zone's connected-pad-cache which has been updated to
684 // a new net and use that. While this won't always be the right net, the dead
685 // net is guaranteed to be wrong.
686 wxString updatedNetname = wxEmptyString;
687
688 for( PAD* pad : m_zoneConnectionsCache[ zone ] )
689 {
690 if( getNetname( pad ) != zone->GetNetname() )
691 {
692 updatedNetname = getNetname( pad );
693 break;
694 }
695 }
696
697 // Take zone name from name change map if it didn't match to a new pad
698 // (this is useful for zones on internal layers)
699 if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
700 {
701 updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
702 }
703
704 if( !updatedNetname.IsEmpty() )
705 {
706 if( m_isDryRun )
707 {
708 if( !zone->GetZoneName().IsEmpty() )
709 {
710 msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
711 zone->GetZoneName(),
712 UnescapeString( zone->GetNetname() ),
713 UnescapeString( updatedNetname ) );
714 }
715 else
716 {
717 msg.Printf( _( "Reconnect copper zone from %s to %s." ),
718 UnescapeString( zone->GetNetname() ),
719 UnescapeString( updatedNetname ) );
720 }
721
722 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
723 }
724 else
725 {
726 NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
727
728 if( !netinfo )
729 netinfo = m_addedNets[ updatedNetname ];
730
731 if( netinfo )
732 {
733 m_commit.Modify( zone );
734 zone->SetNet( netinfo );
735
736 if( !zone->GetZoneName().IsEmpty() )
737 {
738 msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
739 zone->GetZoneName(),
740 UnescapeString( zone->GetNetname() ),
741 UnescapeString( updatedNetname ) );
742 }
743 else
744 {
745 msg.Printf( _( "Reconnected copper zone from %s to %s." ),
746 UnescapeString( zone->GetNetname() ),
747 UnescapeString( updatedNetname ) );
748 }
749
750 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
751 }
752 }
753 }
754 else
755 {
756 if( !zone->GetZoneName().IsEmpty() )
757 {
758 msg.Printf( _( "Copper zone '%s' has no pads connected." ),
759 zone->GetZoneName() );
760 }
761 else
762 {
763 PCB_LAYER_ID layer = zone->GetLayer();
764 wxPoint pos = zone->GetPosition();
765
766 msg.Printf( _( "Copper zone on layer %s at (%s, %s) has no pads connected." ),
767 m_board->GetLayerName( layer ),
768 MessageTextFromValue( m_frame->GetUserUnits(), pos.x ),
769 MessageTextFromValue( m_frame->GetUserUnits(), pos.y ) );
770 }
771
772 m_reporter->Report( msg, RPT_SEVERITY_WARNING );
773 ++m_warningCount;
774 }
775 }
776 }
777
778 return true;
779 }
780
781
testConnectivity(NETLIST & aNetlist,std::map<COMPONENT *,FOOTPRINT * > & aFootprintMap)782 bool BOARD_NETLIST_UPDATER::testConnectivity( NETLIST& aNetlist,
783 std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
784 {
785 // Verify that board contains all pads in netlist: if it doesn't then footprints are
786 // wrong or missing.
787
788 wxString msg;
789 wxString padNumber;
790
791 for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
792 {
793 COMPONENT* component = aNetlist.GetComponent( i );
794 FOOTPRINT* footprint = aFootprintMap[component];
795
796 if( !footprint ) // It can be missing in partial designs
797 continue;
798
799 // Explore all pins/pads in component
800 for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
801 {
802 padNumber = component->GetNet( jj ).GetPinName();
803
804 if( footprint->FindPadByNumber( padNumber ) )
805 continue; // OK, pad found
806
807 // not found: bad footprint, report error
808 msg.Printf( _( "%s pad %s not found in %s." ),
809 component->GetReference(),
810 padNumber,
811 footprint->GetFPID().Format().wx_str() );
812 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
813 ++m_errorCount;
814 }
815 }
816
817 return true;
818 }
819
820
UpdateNetlist(NETLIST & aNetlist)821 bool BOARD_NETLIST_UPDATER::UpdateNetlist( NETLIST& aNetlist )
822 {
823 FOOTPRINT* lastPreexistingFootprint = nullptr;
824 COMPONENT* component = nullptr;
825 wxString msg;
826
827 m_errorCount = 0;
828 m_warningCount = 0;
829 m_newFootprintsCount = 0;
830
831 std::map<COMPONENT*, FOOTPRINT*> footprintMap;
832
833 if( !m_board->Footprints().empty() )
834 lastPreexistingFootprint = m_board->Footprints().back();
835
836 cacheCopperZoneConnections();
837
838 // First mark all nets (except <no net>) as stale; we'll update those which are current
839 // in the following two loops.
840 //
841 if( !m_isDryRun )
842 {
843 m_board->SetStatus( 0 );
844
845 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
846 net->SetIsCurrent( net->GetNetCode() == 0 );
847 }
848
849 // Next go through the netlist updating all board footprints which have matching component
850 // entries and adding new footprints for those that don't.
851 //
852 for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
853 {
854 component = aNetlist.GetComponent( i );
855
856 if( component->GetProperties().count( "exclude_from_board" ) )
857 continue;
858
859 msg.Printf( _( "Processing symbol '%s:%s'." ),
860 component->GetReference(),
861 component->GetFPID().Format().wx_str() );
862 m_reporter->Report( msg, RPT_SEVERITY_INFO );
863
864 int matchCount = 0;
865
866 for( FOOTPRINT* footprint : m_board->Footprints() )
867 {
868 bool match = false;
869
870 if( m_lookupByTimestamp )
871 {
872 for( const KIID& uuid : component->GetKIIDs() )
873 {
874 KIID_PATH base = component->GetPath();
875 base.push_back( uuid );
876
877 if( footprint->GetPath() == base )
878 {
879 match = true;
880 break;
881 }
882 }
883 }
884 else
885 {
886 match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
887 }
888
889 if( match )
890 {
891 FOOTPRINT* tmp = footprint;
892
893 if( m_replaceFootprints && component->GetFPID() != footprint->GetFPID() )
894 tmp = replaceFootprint( aNetlist, footprint, component );
895
896 if( tmp )
897 {
898 footprintMap[ component ] = tmp;
899
900 updateFootprintParameters( tmp, component );
901 updateComponentPadConnections( tmp, component );
902 }
903
904 matchCount++;
905 }
906
907 if( footprint == lastPreexistingFootprint )
908 {
909 // No sense going through the newly-created footprints: end of loop
910 break;
911 }
912 }
913
914 if( matchCount == 0 )
915 {
916 FOOTPRINT* footprint = addNewFootprint( component );
917
918 if( footprint )
919 {
920 footprintMap[ component ] = footprint;
921
922 updateFootprintParameters( footprint, component );
923 updateComponentPadConnections( footprint, component );
924 }
925 }
926 else if( matchCount > 1 )
927 {
928 msg.Printf( _( "Multiple footprints found for '%s'." ), component->GetReference() );
929 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
930 }
931 }
932
933 updateCopperZoneNets( aNetlist );
934
935 // Finally go through the board footprints and update all those that *don't* have matching
936 // component entries.
937 //
938 for( FOOTPRINT* footprint : m_board->Footprints() )
939 {
940 bool matched = false;
941 bool doDelete = m_deleteUnusedFootprints;
942
943 if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
944 doDelete = false;
945
946 if( m_lookupByTimestamp )
947 component = aNetlist.GetComponentByPath( footprint->GetPath() );
948 else
949 component = aNetlist.GetComponentByReference( footprint->GetReference() );
950
951 if( component && component->GetProperties().count( "exclude_from_board" ) == 0 )
952 matched = true;
953
954 if( doDelete && !matched && footprint->IsLocked() )
955 {
956 if( m_isDryRun )
957 {
958 msg.Printf( _( "Cannot remove unused footprint %s (locked)." ),
959 footprint->GetReference() );
960 }
961 else
962 {
963 msg.Printf( _( "Could not remove unused footprint %s (locked)." ),
964 footprint->GetReference() );
965 }
966
967 m_reporter->Report( msg, RPT_SEVERITY_ERROR );
968 doDelete = false;
969 }
970
971 if( doDelete && !matched )
972 {
973 if( m_isDryRun )
974 {
975 msg.Printf( _( "Remove unused footprint %s." ), footprint->GetReference() );
976 }
977 else
978 {
979 m_commit.Remove( footprint );
980 msg.Printf( _( "Removed unused footprint %s." ), footprint->GetReference() );
981 }
982
983 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
984 }
985 else if( !m_isDryRun )
986 {
987 if( !matched )
988 footprint->SetPath( KIID_PATH() );
989
990 for( PAD* pad : footprint->Pads() )
991 {
992 if( pad->GetNet() )
993 pad->GetNet()->SetIsCurrent( true );
994 }
995 }
996 }
997
998 if( !m_isDryRun )
999 {
1000 m_board->GetConnectivity()->Build( m_board );
1001 testConnectivity( aNetlist, footprintMap );
1002
1003 for( NETINFO_ITEM* net : m_board->GetNetInfo() )
1004 {
1005 if( !net->IsCurrent() )
1006 {
1007 msg.Printf( _( "Removed unused net %s." ), net->GetNetname() );
1008 m_reporter->Report( msg, RPT_SEVERITY_ACTION );
1009 m_commit.Removed( net );
1010 }
1011 }
1012
1013 m_board->GetNetInfo().RemoveUnusedNets();
1014 m_commit.SetResolveNetConflicts();
1015 m_commit.Push( _( "Update netlist" ) );
1016
1017 m_board->SynchronizeNetsAndNetClasses();
1018 m_frame->SaveProjectSettings();
1019 }
1020
1021 if( m_isDryRun )
1022 {
1023 for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
1024 delete addedNet.second;
1025
1026 m_addedNets.clear();
1027 }
1028
1029 // Update the ratsnest
1030 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
1031 m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
1032
1033 msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
1034 m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
1035
1036 return true;
1037 }
1038