You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1129 lines
36 KiB

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