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