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.

1524 lines
51 KiB

5 years ago
5 years ago
5 years ago
5 years ago
2 years ago
2 years ago
2 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@gmail.com>
  8. *
  9. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU General Public License
  13. * as published by the Free Software Foundation; either version 2
  14. * of the License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program; if not, you may find one here:
  23. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  24. * or you may search the http://www.gnu.org website for the version 2 license,
  25. * or you may write to the Free Software Foundation, Inc.,
  26. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  27. */
  28. #include <common.h> // for PAGE_INFO
  29. #include <base_units.h>
  30. #include <board.h>
  31. #include <board_design_settings.h>
  32. #include <component_classes/component_class.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. #include "board_netlist_updater.h"
  45. BOARD_NETLIST_UPDATER::BOARD_NETLIST_UPDATER( PCB_EDIT_FRAME* aFrame, BOARD* aBoard ) :
  46. m_frame( aFrame ),
  47. m_commit( aFrame ),
  48. m_board( aBoard )
  49. {
  50. m_reporter = &NULL_REPORTER::GetInstance();
  51. m_deleteUnusedFootprints = false;
  52. m_isDryRun = false;
  53. m_replaceFootprints = true;
  54. m_lookupByTimestamp = false;
  55. m_overrideLocks = false;
  56. m_warningCount = 0;
  57. m_errorCount = 0;
  58. m_newFootprintsCount = 0;
  59. }
  60. BOARD_NETLIST_UPDATER::~BOARD_NETLIST_UPDATER()
  61. {
  62. }
  63. // These functions allow inspection of pad nets during dry runs by keeping a cache of
  64. // current pad netnames indexed by pad.
  65. void BOARD_NETLIST_UPDATER::cacheNetname( PAD* aPad, const wxString& aNetname )
  66. {
  67. m_padNets[ aPad ] = aNetname;
  68. }
  69. wxString BOARD_NETLIST_UPDATER::getNetname( PAD* aPad )
  70. {
  71. if( m_isDryRun && m_padNets.count( aPad ) )
  72. return m_padNets[ aPad ];
  73. else
  74. return aPad->GetNetname();
  75. }
  76. void BOARD_NETLIST_UPDATER::cachePinFunction( PAD* aPad, const wxString& aPinFunction )
  77. {
  78. m_padPinFunctions[ aPad ] = aPinFunction;
  79. }
  80. wxString BOARD_NETLIST_UPDATER::getPinFunction( PAD* aPad )
  81. {
  82. if( m_isDryRun && m_padPinFunctions.count( aPad ) )
  83. return m_padPinFunctions[ aPad ];
  84. else
  85. return aPad->GetPinFunction();
  86. }
  87. VECTOR2I BOARD_NETLIST_UPDATER::estimateFootprintInsertionPosition()
  88. {
  89. VECTOR2I bestPosition;
  90. if( !m_board->IsEmpty() )
  91. {
  92. // Position new components below any existing board features.
  93. BOX2I bbox = m_board->GetBoardEdgesBoundingBox();
  94. if( bbox.GetWidth() || bbox.GetHeight() )
  95. {
  96. bestPosition.x = bbox.Centre().x;
  97. bestPosition.y = bbox.GetBottom() + pcbIUScale.mmToIU( 10 );
  98. }
  99. }
  100. else
  101. {
  102. // Position new components in the center of the page when the board is empty.
  103. VECTOR2I pageSize = m_board->GetPageSettings().GetSizeIU( pcbIUScale.IU_PER_MILS );
  104. bestPosition.x = pageSize.x / 2;
  105. bestPosition.y = pageSize.y / 2;
  106. }
  107. return bestPosition;
  108. }
  109. FOOTPRINT* BOARD_NETLIST_UPDATER::addNewFootprint( COMPONENT* aComponent )
  110. {
  111. wxString msg;
  112. if( aComponent->GetFPID().empty() )
  113. {
  114. msg.Printf( _( "Cannot add %s (no footprint assigned)." ),
  115. aComponent->GetReference() );
  116. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  117. ++m_errorCount;
  118. return nullptr;
  119. }
  120. FOOTPRINT* footprint = m_frame->LoadFootprint( aComponent->GetFPID() );
  121. if( footprint == nullptr )
  122. {
  123. msg.Printf( _( "Cannot add %s (footprint '%s' not found)." ),
  124. aComponent->GetReference(),
  125. EscapeHTML( aComponent->GetFPID().Format().wx_str() ) );
  126. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  127. ++m_errorCount;
  128. return nullptr;
  129. }
  130. footprint->SetStaticComponentClass(
  131. m_board->GetComponentClassManager().GetNoneComponentClass() );
  132. if( m_isDryRun )
  133. {
  134. msg.Printf( _( "Add %s (footprint '%s')." ),
  135. aComponent->GetReference(),
  136. EscapeHTML( aComponent->GetFPID().Format().wx_str() ) );
  137. delete footprint;
  138. footprint = nullptr;
  139. }
  140. else
  141. {
  142. for( PAD* pad : footprint->Pads() )
  143. {
  144. // Set the pads ratsnest settings to the global settings
  145. pad->SetLocalRatsnestVisible( m_frame->GetPcbNewSettings()->m_Display.m_ShowGlobalRatsnest );
  146. // Pads in the library all have orphaned nets. Replace with Default.
  147. pad->SetNetCode( 0 );
  148. }
  149. footprint->SetParent( m_board );
  150. footprint->SetPosition( estimateFootprintInsertionPosition() );
  151. // This flag is used to prevent connectivity from considering the footprint during its
  152. // initial build after the footprint is committed, because we're going to immediately start
  153. // a move operation on the footprint and don't want its pads to drive nets onto vias/tracks
  154. // it happens to land on at the initial position.
  155. footprint->SetAttributes( footprint->GetAttributes() | FP_JUST_ADDED );
  156. m_addedFootprints.push_back( footprint );
  157. m_commit.Add( footprint );
  158. msg.Printf( _( "Added %s (footprint '%s')." ),
  159. aComponent->GetReference(),
  160. EscapeHTML( aComponent->GetFPID().Format().wx_str() ) );
  161. }
  162. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  163. m_newFootprintsCount++;
  164. return footprint;
  165. }
  166. void BOARD_NETLIST_UPDATER::updateComponentClass( FOOTPRINT* aFootprint, COMPONENT* aNewComponent )
  167. {
  168. wxString curClassName, newClassName;
  169. COMPONENT_CLASS* newClass = nullptr;
  170. if( const COMPONENT_CLASS* curClass = aFootprint->GetStaticComponentClass() )
  171. curClassName = curClass->GetName();
  172. // Calculate the new component class
  173. if( m_isDryRun )
  174. {
  175. newClassName = COMPONENT_CLASS_MANAGER::GetFullClassNameForConstituents(
  176. aNewComponent->GetComponentClassNames() );
  177. }
  178. else
  179. {
  180. newClass = m_board->GetComponentClassManager().GetEffectiveStaticComponentClass(
  181. aNewComponent->GetComponentClassNames() );
  182. newClassName = newClass->GetName();
  183. }
  184. if( curClassName == newClassName )
  185. return;
  186. wxString msg;
  187. if( m_isDryRun )
  188. {
  189. if( curClassName == wxEmptyString && newClassName != wxEmptyString )
  190. {
  191. msg.Printf( _( "Change %s component class to '%s'." ),
  192. aFootprint->GetReference(),
  193. EscapeHTML( newClassName ) );
  194. }
  195. else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
  196. {
  197. msg.Printf( _( "Remove %s component class (currently '%s')." ),
  198. aFootprint->GetReference(),
  199. EscapeHTML( curClassName ) );
  200. }
  201. else
  202. {
  203. msg.Printf( _( "Change %s component class from '%s' to '%s'." ),
  204. aFootprint->GetReference(),
  205. EscapeHTML( curClassName ),
  206. EscapeHTML( newClassName ) );
  207. }
  208. }
  209. else
  210. {
  211. wxASSERT_MSG( newClass != nullptr, "Component class should not be nullptr" );
  212. aFootprint->SetStaticComponentClass( newClass );
  213. if( curClassName == wxEmptyString && newClassName != wxEmptyString )
  214. {
  215. msg.Printf( _( "Changed %s component class to '%s'." ),
  216. aFootprint->GetReference(),
  217. EscapeHTML( newClassName ) );
  218. }
  219. else if( curClassName != wxEmptyString && newClassName == wxEmptyString )
  220. {
  221. msg.Printf( _( "Removed %s component class (was '%s')." ),
  222. aFootprint->GetReference(),
  223. EscapeHTML( curClassName ) );
  224. }
  225. else
  226. {
  227. msg.Printf( _( "Changed %s component class from '%s' to '%s'." ),
  228. aFootprint->GetReference(),
  229. EscapeHTML( curClassName ),
  230. EscapeHTML( newClassName ) );
  231. }
  232. }
  233. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  234. }
  235. FOOTPRINT* BOARD_NETLIST_UPDATER::replaceFootprint( NETLIST& aNetlist, FOOTPRINT* aFootprint,
  236. COMPONENT* aNewComponent )
  237. {
  238. wxString msg;
  239. if( aNewComponent->GetFPID().empty() )
  240. {
  241. msg.Printf( _( "Cannot update %s (no footprint assigned)." ),
  242. aNewComponent->GetReference() );
  243. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  244. ++m_errorCount;
  245. return nullptr;
  246. }
  247. FOOTPRINT* newFootprint = m_frame->LoadFootprint( aNewComponent->GetFPID() );
  248. if( newFootprint == nullptr )
  249. {
  250. msg.Printf( _( "Cannot update %s (footprint '%s' not found)." ),
  251. aNewComponent->GetReference(),
  252. EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
  253. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  254. ++m_errorCount;
  255. return nullptr;
  256. }
  257. if( m_isDryRun )
  258. {
  259. if( aFootprint->IsLocked() && !m_overrideLocks )
  260. {
  261. msg.Printf( _( "Cannot change %s footprint from '%s' to '%s' (footprint is locked)."),
  262. aFootprint->GetReference(),
  263. EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
  264. EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
  265. m_reporter->Report( msg, RPT_SEVERITY_WARNING );
  266. ++m_warningCount;
  267. delete newFootprint;
  268. return nullptr;
  269. }
  270. else
  271. {
  272. msg.Printf( _( "Change %s footprint from '%s' to '%s'."),
  273. aFootprint->GetReference(),
  274. EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
  275. EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
  276. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  277. ++m_newFootprintsCount;
  278. delete newFootprint;
  279. return nullptr;
  280. }
  281. }
  282. else
  283. {
  284. if( aFootprint->IsLocked() && !m_overrideLocks )
  285. {
  286. msg.Printf( _( "Could not change %s footprint from '%s' to '%s' (footprint is locked)."),
  287. aFootprint->GetReference(),
  288. EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
  289. EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
  290. m_reporter->Report( msg, RPT_SEVERITY_WARNING );
  291. ++m_warningCount;
  292. delete newFootprint;
  293. return nullptr;
  294. }
  295. else
  296. {
  297. m_frame->ExchangeFootprint( aFootprint, newFootprint, m_commit );
  298. msg.Printf( _( "Changed %s footprint from '%s' to '%s'."),
  299. aFootprint->GetReference(),
  300. EscapeHTML( aFootprint->GetFPID().Format().wx_str() ),
  301. EscapeHTML( aNewComponent->GetFPID().Format().wx_str() ) );
  302. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  303. ++m_newFootprintsCount;
  304. return newFootprint;
  305. }
  306. }
  307. }
  308. bool BOARD_NETLIST_UPDATER::updateFootprintParameters( FOOTPRINT* aPcbFootprint,
  309. COMPONENT* aNetlistComponent )
  310. {
  311. wxString msg;
  312. // Create a copy only if the footprint has not been added during this update
  313. FOOTPRINT* copy = nullptr;
  314. if( !m_commit.GetStatus( aPcbFootprint ) )
  315. {
  316. copy = static_cast<FOOTPRINT*>( aPcbFootprint->Clone() );
  317. copy->SetParentGroup( nullptr );
  318. }
  319. bool changed = false;
  320. // Test for reference designator field change.
  321. if( aPcbFootprint->GetReference() != aNetlistComponent->GetReference() )
  322. {
  323. if( m_isDryRun )
  324. {
  325. msg.Printf( _( "Change %s reference designator to %s." ),
  326. aPcbFootprint->GetReference(),
  327. aNetlistComponent->GetReference() );
  328. }
  329. else
  330. {
  331. msg.Printf( _( "Changed %s reference designator to %s." ),
  332. aPcbFootprint->GetReference(),
  333. aNetlistComponent->GetReference() );
  334. changed = true;
  335. aPcbFootprint->SetReference( aNetlistComponent->GetReference() );
  336. }
  337. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  338. }
  339. // Test for value field change.
  340. if( aPcbFootprint->GetValue() != aNetlistComponent->GetValue() )
  341. {
  342. if( m_isDryRun )
  343. {
  344. msg.Printf( _( "Change %s value from %s to %s." ),
  345. aPcbFootprint->GetReference(),
  346. EscapeHTML( aPcbFootprint->GetValue() ),
  347. EscapeHTML( aNetlistComponent->GetValue() ) );
  348. }
  349. else
  350. {
  351. msg.Printf( _( "Changed %s value from %s to %s." ),
  352. aPcbFootprint->GetReference(),
  353. EscapeHTML( aPcbFootprint->GetValue() ),
  354. EscapeHTML( aNetlistComponent->GetValue() ) );
  355. changed = true;
  356. aPcbFootprint->SetValue( aNetlistComponent->GetValue() );
  357. }
  358. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  359. }
  360. // Test for time stamp change.
  361. KIID_PATH new_path = aNetlistComponent->GetPath();
  362. if( !aNetlistComponent->GetKIIDs().empty() )
  363. new_path.push_back( aNetlistComponent->GetKIIDs().front() );
  364. if( aPcbFootprint->GetPath() != new_path )
  365. {
  366. if( m_isDryRun )
  367. {
  368. msg.Printf( _( "Update %s symbol association from %s to %s." ),
  369. aPcbFootprint->GetReference(),
  370. EscapeHTML( aPcbFootprint->GetPath().AsString() ),
  371. EscapeHTML( new_path.AsString() ) );
  372. }
  373. else
  374. {
  375. msg.Printf( _( "Updated %s symbol association from %s to %s." ),
  376. aPcbFootprint->GetReference(),
  377. EscapeHTML( aPcbFootprint->GetPath().AsString() ),
  378. EscapeHTML( new_path.AsString() ) );
  379. changed = true;
  380. aPcbFootprint->SetPath( new_path );
  381. }
  382. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  383. }
  384. nlohmann::ordered_map<wxString, wxString> fpFieldsAsMap;
  385. for( PCB_FIELD* field : aPcbFootprint->GetFields() )
  386. {
  387. // These fields are individually checked above
  388. if( field->IsReference() || field->IsValue() || field->IsComponentClass() )
  389. {
  390. continue;
  391. }
  392. fpFieldsAsMap[field->GetName()] = field->GetText();
  393. }
  394. // Remove the ref/value/footprint fields that are individually handled
  395. nlohmann::ordered_map<wxString, wxString> compFields = aNetlistComponent->GetFields();
  396. compFields.erase( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
  397. compFields.erase( GetCanonicalFieldName( FIELD_T::VALUE ) );
  398. compFields.erase( GetCanonicalFieldName( FIELD_T::FOOTPRINT ) );
  399. // Remove any component class fields - these are not editable in the pcb editor
  400. compFields.erase( wxT( "Component Class" ) );
  401. // Fields are stored as an ordered map, but we don't (yet) support reordering
  402. // the footprint fields to match the symbol, so we manually check the fields
  403. // in the order they are stored in the symbol.
  404. bool same = true;
  405. for( const auto& [name, value] : compFields )
  406. {
  407. if( fpFieldsAsMap.count( name ) == 0 || fpFieldsAsMap[name] != value )
  408. {
  409. same = false;
  410. break;
  411. }
  412. }
  413. for( const auto& [name, value] : fpFieldsAsMap )
  414. {
  415. if( compFields.count( name ) == 0 )
  416. {
  417. same = false;
  418. break;
  419. }
  420. }
  421. if( !same )
  422. {
  423. if( m_isDryRun )
  424. {
  425. msg.Printf( _( "Update %s fields." ), aPcbFootprint->GetReference() );
  426. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  427. // Remove fields that aren't present in the symbol
  428. for( PCB_FIELD* field : aPcbFootprint->GetFields() )
  429. {
  430. if( field->IsMandatory() )
  431. continue;
  432. if( compFields.count( field->GetName() ) == 0 )
  433. {
  434. msg.Printf( _( "Remove %s footprint fields not in symbol." ),
  435. aPcbFootprint->GetReference() );
  436. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  437. break;
  438. }
  439. }
  440. }
  441. else
  442. {
  443. msg.Printf( _( "Updated %s fields." ), aPcbFootprint->GetReference() );
  444. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  445. changed = true;
  446. // Add or change field value
  447. for( auto& [name, value] : compFields )
  448. {
  449. if( aPcbFootprint->HasField( name ) )
  450. {
  451. aPcbFootprint->GetField( name )->SetText( value );
  452. }
  453. else
  454. {
  455. PCB_FIELD* newField = new PCB_FIELD( aPcbFootprint, FIELD_T::USER );
  456. aPcbFootprint->Add( newField );
  457. newField->SetName( name );
  458. newField->SetText( value );
  459. newField->SetVisible( false );
  460. newField->SetLayer( aPcbFootprint->GetLayer() == F_Cu ? F_Fab : B_Fab );
  461. // Give the relative position (0,0) in footprint
  462. newField->SetPosition( aPcbFootprint->GetPosition() );
  463. // Give the footprint orientation
  464. newField->Rotate( aPcbFootprint->GetPosition(), aPcbFootprint->GetOrientation() );
  465. if( m_frame )
  466. newField->StyleFromSettings( m_frame->GetDesignSettings() );
  467. }
  468. }
  469. // Remove fields that aren't present in the symbol
  470. bool warned = false;
  471. for( PCB_FIELD* field : aPcbFootprint->GetFields() )
  472. {
  473. if( field->IsMandatory() )
  474. continue;
  475. if( compFields.count( field->GetName() ) == 0 )
  476. {
  477. if( !warned )
  478. {
  479. warned = true;
  480. msg.Printf( _( "Removed %s footprint fields not in symbol." ),
  481. aPcbFootprint->GetReference() );
  482. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  483. }
  484. aPcbFootprint->Remove( field );
  485. if( m_frame )
  486. m_frame->GetCanvas()->GetView()->Remove( field );
  487. delete field;
  488. }
  489. }
  490. }
  491. }
  492. wxString sheetname;
  493. wxString sheetfile;
  494. wxString fpFilters;
  495. wxString humanSheetPath = aNetlistComponent->GetHumanReadablePath();
  496. if( !humanSheetPath.empty() )
  497. sheetname = humanSheetPath;
  498. else if( aNetlistComponent->GetProperties().count( wxT( "Sheetname" ) ) > 0 )
  499. sheetname = aNetlistComponent->GetProperties().at( wxT( "Sheetname" ) );
  500. if( aNetlistComponent->GetProperties().count( wxT( "Sheetfile" ) ) > 0 )
  501. sheetfile = aNetlistComponent->GetProperties().at( wxT( "Sheetfile" ) );
  502. if( aNetlistComponent->GetProperties().count( wxT( "ki_fp_filters" ) ) > 0 )
  503. fpFilters = aNetlistComponent->GetProperties().at( wxT( "ki_fp_filters" ) );
  504. if( sheetname != aPcbFootprint->GetSheetname() )
  505. {
  506. if( m_isDryRun )
  507. {
  508. msg.Printf( _( "Update %s sheetname to '%s'." ),
  509. aPcbFootprint->GetReference(),
  510. EscapeHTML( sheetname ) );
  511. }
  512. else
  513. {
  514. aPcbFootprint->SetSheetname( sheetname );
  515. msg.Printf( _( "Updated %s sheetname to '%s'." ),
  516. aPcbFootprint->GetReference(),
  517. EscapeHTML( sheetname ) );
  518. }
  519. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  520. }
  521. if( sheetfile != aPcbFootprint->GetSheetfile() )
  522. {
  523. if( m_isDryRun )
  524. {
  525. msg.Printf( _( "Update %s sheetfile to '%s'." ),
  526. aPcbFootprint->GetReference(),
  527. EscapeHTML( sheetfile ) );
  528. }
  529. else
  530. {
  531. aPcbFootprint->SetSheetfile( sheetfile );
  532. msg.Printf( _( "Updated %s sheetfile to '%s'." ),
  533. aPcbFootprint->GetReference(),
  534. EscapeHTML( sheetfile ) );
  535. }
  536. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  537. }
  538. if( fpFilters != aPcbFootprint->GetFilters() )
  539. {
  540. if( m_isDryRun )
  541. {
  542. msg.Printf( _( "Update %s footprint filters to '%s'." ),
  543. aPcbFootprint->GetReference(),
  544. EscapeHTML( fpFilters ) );
  545. }
  546. else
  547. {
  548. aPcbFootprint->SetFilters( fpFilters );
  549. msg.Printf( _( "Updated %s footprint filters to '%s'." ),
  550. aPcbFootprint->GetReference(),
  551. EscapeHTML( fpFilters ) );
  552. }
  553. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  554. }
  555. if( ( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) > 0 )
  556. != ( ( aPcbFootprint->GetAttributes() & FP_EXCLUDE_FROM_BOM ) > 0 ) )
  557. {
  558. if( m_isDryRun )
  559. {
  560. if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) )
  561. {
  562. msg.Printf( _( "Add %s 'exclude from BOM' fabrication attribute." ),
  563. aPcbFootprint->GetReference() );
  564. }
  565. else
  566. {
  567. msg.Printf( _( "Remove %s 'exclude from BOM' fabrication attribute." ),
  568. aPcbFootprint->GetReference() );
  569. }
  570. }
  571. else
  572. {
  573. int attributes = aPcbFootprint->GetAttributes();
  574. if( aNetlistComponent->GetProperties().count( wxT( "exclude_from_bom" ) ) )
  575. {
  576. attributes |= FP_EXCLUDE_FROM_BOM;
  577. msg.Printf( _( "Added %s 'exclude from BOM' fabrication attribute." ),
  578. aPcbFootprint->GetReference() );
  579. }
  580. else
  581. {
  582. attributes &= ~FP_EXCLUDE_FROM_BOM;
  583. msg.Printf( _( "Removed %s 'exclude from BOM' fabrication attribute." ),
  584. aPcbFootprint->GetReference() );
  585. }
  586. changed = true;
  587. aPcbFootprint->SetAttributes( attributes );
  588. }
  589. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  590. }
  591. if( ( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) > 0 )
  592. != ( ( aPcbFootprint->GetAttributes() & FP_DNP ) > 0 ) )
  593. {
  594. if( m_isDryRun )
  595. {
  596. if( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) )
  597. {
  598. msg.Printf( _( "Add %s 'Do not place' fabrication attribute." ),
  599. aPcbFootprint->GetReference() );
  600. }
  601. else
  602. {
  603. msg.Printf( _( "Remove %s 'Do not place' fabrication attribute." ),
  604. aPcbFootprint->GetReference() );
  605. }
  606. }
  607. else
  608. {
  609. int attributes = aPcbFootprint->GetAttributes();
  610. if( aNetlistComponent->GetProperties().count( wxT( "dnp" ) ) )
  611. {
  612. attributes |= FP_DNP;
  613. msg.Printf( _( "Added %s 'Do not place' fabrication attribute." ),
  614. aPcbFootprint->GetReference() );
  615. }
  616. else
  617. {
  618. attributes &= ~FP_DNP;
  619. msg.Printf( _( "Removed %s 'Do not place' fabrication attribute." ),
  620. aPcbFootprint->GetReference() );
  621. }
  622. changed = true;
  623. aPcbFootprint->SetAttributes( attributes );
  624. }
  625. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  626. }
  627. if( aNetlistComponent->GetDuplicatePadNumbersAreJumpers()
  628. != aPcbFootprint->GetDuplicatePadNumbersAreJumpers() )
  629. {
  630. bool value = aNetlistComponent->GetDuplicatePadNumbersAreJumpers();
  631. if( !m_isDryRun )
  632. {
  633. changed = true;
  634. aPcbFootprint->SetDuplicatePadNumbersAreJumpers( value );
  635. if( value )
  636. {
  637. msg.Printf( _( "Added %s 'duplicate pad numbers are jumpers' attribute." ),
  638. aPcbFootprint->GetReference() );
  639. }
  640. else
  641. {
  642. msg.Printf( _( "Removed %s 'duplicate pad numbers are jumpers' attribute." ),
  643. aPcbFootprint->GetReference() );
  644. }
  645. }
  646. else
  647. {
  648. if( value )
  649. {
  650. msg.Printf( _( "Add %s 'duplicate pad numbers are jumpers' attribute." ),
  651. aPcbFootprint->GetReference() );
  652. }
  653. else
  654. {
  655. msg.Printf( _( "Remove %s 'duplicate pad numbers are jumpers' attribute." ),
  656. aPcbFootprint->GetReference() );
  657. }
  658. }
  659. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  660. }
  661. if( aNetlistComponent->JumperPadGroups() != aPcbFootprint->JumperPadGroups() )
  662. {
  663. if( !m_isDryRun )
  664. {
  665. changed = true;
  666. aPcbFootprint->JumperPadGroups() = aNetlistComponent->JumperPadGroups();
  667. msg.Printf( _( "Updated %s jumper pad groups" ), aPcbFootprint->GetReference() );
  668. }
  669. else
  670. {
  671. msg.Printf( _( "Update %s jumper pad groups" ), aPcbFootprint->GetReference() );
  672. }
  673. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  674. }
  675. if( changed && copy )
  676. m_commit.Modified( aPcbFootprint, copy );
  677. else if( copy )
  678. delete copy;
  679. return true;
  680. }
  681. bool BOARD_NETLIST_UPDATER::updateComponentPadConnections( FOOTPRINT* aFootprint,
  682. COMPONENT* aNewComponent )
  683. {
  684. wxString msg;
  685. // Create a copy only if the footprint has not been added during this update
  686. FOOTPRINT* copy = nullptr;
  687. if( !m_isDryRun && !m_commit.GetStatus( aFootprint ) )
  688. {
  689. copy = static_cast<FOOTPRINT*>( aFootprint->Clone() );
  690. copy->SetParentGroup( nullptr );
  691. }
  692. bool changed = false;
  693. // At this point, the component footprint is updated. Now update the nets.
  694. std::deque<PAD*> pads = aFootprint->Pads();
  695. std::set<wxString> padNetnames;
  696. std::sort( pads.begin(), pads.end(),
  697. []( PAD* a, PAD* b )
  698. {
  699. return a->m_Uuid < b->m_Uuid;
  700. } );
  701. for( PAD* pad : pads )
  702. {
  703. const COMPONENT_NET& net = aNewComponent->GetNet( pad->GetNumber() );
  704. wxString pinFunction;
  705. wxString pinType;
  706. if( net.IsValid() ) // i.e. the pad has a name
  707. {
  708. pinFunction = net.GetPinFunction();
  709. pinType = net.GetPinType();
  710. }
  711. if( !m_isDryRun )
  712. {
  713. if( pad->GetPinFunction() != pinFunction )
  714. {
  715. changed = true;
  716. pad->SetPinFunction( pinFunction );
  717. }
  718. if( pad->GetPinType() != pinType )
  719. {
  720. changed = true;
  721. pad->SetPinType( pinType );
  722. }
  723. }
  724. else
  725. {
  726. cachePinFunction( pad, pinFunction );
  727. }
  728. // Test if new footprint pad has no net (pads not on copper layers have no net).
  729. if( !net.IsValid() || !pad->IsOnCopperLayer() )
  730. {
  731. if( !pad->GetNetname().IsEmpty() )
  732. {
  733. if( m_isDryRun )
  734. {
  735. msg.Printf( _( "Disconnect %s pin %s." ),
  736. aFootprint->GetReference(),
  737. EscapeHTML( pad->GetNumber() ) );
  738. }
  739. else
  740. {
  741. msg.Printf( _( "Disconnected %s pin %s." ),
  742. aFootprint->GetReference(),
  743. EscapeHTML( pad->GetNumber() ) );
  744. }
  745. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  746. }
  747. else if( pad->IsOnCopperLayer() && !pad->GetNumber().IsEmpty() )
  748. {
  749. // pad is connectable but has no net found in netlist
  750. msg.Printf( _( "No net found for component %s pad %s (no pin %s in symbol)." ),
  751. aFootprint->GetReference(),
  752. EscapeHTML( pad->GetNumber() ),
  753. EscapeHTML( pad->GetNumber() ) );
  754. m_reporter->Report( msg, RPT_SEVERITY_WARNING);
  755. ++m_warningCount;
  756. }
  757. if( !m_isDryRun )
  758. {
  759. changed = true;
  760. pad->SetNetCode( NETINFO_LIST::UNCONNECTED );
  761. // If the pad has no net from netlist (i.e. not in netlist
  762. // it cannot have a pin function
  763. if( pad->GetNetname().IsEmpty() )
  764. pad->SetPinFunction( wxEmptyString );
  765. }
  766. else
  767. {
  768. cacheNetname( pad, wxEmptyString );
  769. }
  770. }
  771. else // New footprint pad has a net.
  772. {
  773. wxString netName = net.GetNetName();
  774. if( pad->IsNoConnectPad() )
  775. {
  776. netName = wxString::Format( wxS( "%s" ),
  777. EscapeHTML( net.GetNetName() ) );
  778. for( int jj = 1; !padNetnames.insert( netName ).second; jj++ )
  779. {
  780. netName = wxString::Format( wxS( "%s_%d" ),
  781. EscapeHTML( net.GetNetName() ), jj );
  782. }
  783. }
  784. NETINFO_ITEM* netinfo = m_board->FindNet( netName );
  785. if( netinfo && !m_isDryRun )
  786. netinfo->SetIsCurrent( true );
  787. if( pad->GetNetname() != netName )
  788. {
  789. if( netinfo == nullptr )
  790. {
  791. // It might be a new net that has not been added to the board yet
  792. if( m_addedNets.count( netName ) )
  793. netinfo = m_addedNets[ netName ];
  794. }
  795. if( netinfo == nullptr )
  796. {
  797. netinfo = new NETINFO_ITEM( m_board, netName );
  798. // It is a new net, we have to add it
  799. if( !m_isDryRun )
  800. {
  801. changed = true;
  802. m_commit.Add( netinfo );
  803. }
  804. m_addedNets[netName] = netinfo;
  805. msg.Printf( _( "Add net %s." ),
  806. EscapeHTML( UnescapeString( netName ) ) );
  807. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  808. }
  809. if( !pad->GetNetname().IsEmpty() )
  810. {
  811. m_oldToNewNets[ pad->GetNetname() ] = netName;
  812. if( m_isDryRun )
  813. {
  814. msg.Printf( _( "Reconnect %s pin %s from %s to %s."),
  815. aFootprint->GetReference(),
  816. EscapeHTML( pad->GetNumber() ),
  817. EscapeHTML( UnescapeString( pad->GetNetname() ) ),
  818. EscapeHTML( UnescapeString( netName ) ) );
  819. }
  820. else
  821. {
  822. msg.Printf( _( "Reconnected %s pin %s from %s to %s."),
  823. aFootprint->GetReference(),
  824. EscapeHTML( pad->GetNumber() ),
  825. EscapeHTML( UnescapeString( pad->GetNetname() ) ),
  826. EscapeHTML( UnescapeString( netName ) ) );
  827. }
  828. }
  829. else
  830. {
  831. if( m_isDryRun )
  832. {
  833. msg.Printf( _( "Connect %s pin %s to %s."),
  834. aFootprint->GetReference(),
  835. EscapeHTML( pad->GetNumber() ),
  836. EscapeHTML( UnescapeString( netName ) ) );
  837. }
  838. else
  839. {
  840. msg.Printf( _( "Connected %s pin %s to %s."),
  841. aFootprint->GetReference(),
  842. EscapeHTML( pad->GetNumber() ),
  843. EscapeHTML( UnescapeString( netName ) ) );
  844. }
  845. }
  846. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  847. if( !m_isDryRun )
  848. {
  849. changed = true;
  850. pad->SetNet( netinfo );
  851. }
  852. else
  853. {
  854. cacheNetname( pad, netName );
  855. }
  856. }
  857. }
  858. }
  859. if( changed && copy )
  860. m_commit.Modified( aFootprint, copy );
  861. else if( copy )
  862. delete copy;
  863. return true;
  864. }
  865. void BOARD_NETLIST_UPDATER::cacheCopperZoneConnections()
  866. {
  867. for( ZONE* zone : m_board->Zones() )
  868. {
  869. if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
  870. continue;
  871. m_zoneConnectionsCache[ zone ] = m_board->GetConnectivity()->GetConnectedPads( zone );
  872. }
  873. }
  874. bool BOARD_NETLIST_UPDATER::updateCopperZoneNets( NETLIST& aNetlist )
  875. {
  876. wxString msg;
  877. std::set<wxString> netlistNetnames;
  878. for( int ii = 0; ii < (int) aNetlist.GetCount(); ii++ )
  879. {
  880. const COMPONENT* component = aNetlist.GetComponent( ii );
  881. for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
  882. {
  883. const COMPONENT_NET& net = component->GetNet( jj );
  884. netlistNetnames.insert( net.GetNetName() );
  885. }
  886. }
  887. for( PCB_TRACK* via : m_board->Tracks() )
  888. {
  889. if( via->Type() != PCB_VIA_T )
  890. continue;
  891. if( netlistNetnames.count( via->GetNetname() ) == 0 )
  892. {
  893. wxString updatedNetname = wxEmptyString;
  894. // Take via name from name change map if it didn't match to a new pad
  895. // (this is useful for stitching vias that don't connect to tracks)
  896. if( m_oldToNewNets.count( via->GetNetname() ) )
  897. {
  898. updatedNetname = m_oldToNewNets[via->GetNetname()];
  899. }
  900. if( !updatedNetname.IsEmpty() )
  901. {
  902. if( m_isDryRun )
  903. {
  904. wxString originalNetname = via->GetNetname();
  905. msg.Printf( _( "Reconnect via from %s to %s." ),
  906. EscapeHTML( UnescapeString( originalNetname ) ),
  907. EscapeHTML( UnescapeString( updatedNetname ) ) );
  908. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  909. }
  910. else
  911. {
  912. NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
  913. if( !netinfo )
  914. netinfo = m_addedNets[updatedNetname];
  915. if( netinfo )
  916. {
  917. wxString originalNetname = via->GetNetname();
  918. m_commit.Modify( via );
  919. via->SetNet( netinfo );
  920. msg.Printf( _( "Reconnected via from %s to %s." ),
  921. EscapeHTML( UnescapeString( originalNetname ) ),
  922. EscapeHTML( UnescapeString( updatedNetname ) ) );
  923. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  924. }
  925. }
  926. }
  927. else
  928. {
  929. msg.Printf( _( "Via connected to unknown net (%s)." ),
  930. EscapeHTML( UnescapeString( via->GetNetname() ) ) );
  931. m_reporter->Report( msg, RPT_SEVERITY_WARNING );
  932. ++m_warningCount;
  933. }
  934. }
  935. }
  936. // Test copper zones to detect "dead" nets (nets without any pad):
  937. for( ZONE* zone : m_board->Zones() )
  938. {
  939. if( !zone->IsOnCopperLayer() || zone->GetIsRuleArea() )
  940. continue;
  941. if( netlistNetnames.count( zone->GetNetname() ) == 0 )
  942. {
  943. // Look for a pad in the zone's connected-pad-cache which has been updated to
  944. // a new net and use that. While this won't always be the right net, the dead
  945. // net is guaranteed to be wrong.
  946. wxString updatedNetname = wxEmptyString;
  947. for( PAD* pad : m_zoneConnectionsCache[ zone ] )
  948. {
  949. if( getNetname( pad ) != zone->GetNetname() )
  950. {
  951. updatedNetname = getNetname( pad );
  952. break;
  953. }
  954. }
  955. // Take zone name from name change map if it didn't match to a new pad
  956. // (this is useful for zones on internal layers)
  957. if( updatedNetname.IsEmpty() && m_oldToNewNets.count( zone->GetNetname() ) )
  958. {
  959. updatedNetname = m_oldToNewNets[ zone->GetNetname() ];
  960. }
  961. if( !updatedNetname.IsEmpty() )
  962. {
  963. if( m_isDryRun )
  964. {
  965. wxString originalNetname = zone->GetNetname();
  966. if( !zone->GetZoneName().IsEmpty() )
  967. {
  968. msg.Printf( _( "Reconnect copper zone '%s' from %s to %s." ),
  969. zone->GetZoneName(),
  970. EscapeHTML( UnescapeString( originalNetname ) ),
  971. EscapeHTML( UnescapeString( updatedNetname ) ) );
  972. }
  973. else
  974. {
  975. msg.Printf( _( "Reconnect copper zone from %s to %s." ),
  976. EscapeHTML( UnescapeString( originalNetname ) ),
  977. EscapeHTML( UnescapeString( updatedNetname ) ) );
  978. }
  979. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  980. }
  981. else
  982. {
  983. NETINFO_ITEM* netinfo = m_board->FindNet( updatedNetname );
  984. if( !netinfo )
  985. netinfo = m_addedNets[ updatedNetname ];
  986. if( netinfo )
  987. {
  988. wxString originalNetname = zone->GetNetname();
  989. m_commit.Modify( zone );
  990. zone->SetNet( netinfo );
  991. if( !zone->GetZoneName().IsEmpty() )
  992. {
  993. msg.Printf( _( "Reconnected copper zone '%s' from %s to %s." ),
  994. EscapeHTML( zone->GetZoneName() ),
  995. EscapeHTML( UnescapeString( originalNetname ) ),
  996. EscapeHTML( UnescapeString( updatedNetname ) ) );
  997. }
  998. else
  999. {
  1000. msg.Printf( _( "Reconnected copper zone from %s to %s." ),
  1001. EscapeHTML( UnescapeString( originalNetname ) ),
  1002. EscapeHTML( UnescapeString( updatedNetname ) ) );
  1003. }
  1004. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  1005. }
  1006. }
  1007. }
  1008. else
  1009. {
  1010. if( !zone->GetZoneName().IsEmpty() )
  1011. {
  1012. msg.Printf( _( "Copper zone '%s' has no pads connected." ),
  1013. EscapeHTML( zone->GetZoneName() ) );
  1014. }
  1015. else
  1016. {
  1017. PCB_LAYER_ID layer = zone->GetLayer();
  1018. VECTOR2I pos = zone->GetPosition();
  1019. if( m_frame && m_frame->GetPcbNewSettings() )
  1020. {
  1021. if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertXAxis )
  1022. pos.x *= -1;
  1023. if( m_frame->GetPcbNewSettings()->m_Display.m_DisplayInvertYAxis )
  1024. pos.y *= -1;
  1025. }
  1026. msg.Printf( _( "Copper zone on layer %s at (%s, %s) has no pads connected." ),
  1027. EscapeHTML( m_board->GetLayerName( layer ) ),
  1028. m_frame->MessageTextFromValue( pos.x ),
  1029. m_frame->MessageTextFromValue( pos.y ) );
  1030. }
  1031. m_reporter->Report( msg, RPT_SEVERITY_WARNING );
  1032. ++m_warningCount;
  1033. }
  1034. }
  1035. }
  1036. return true;
  1037. }
  1038. bool BOARD_NETLIST_UPDATER::testConnectivity( NETLIST& aNetlist,
  1039. std::map<COMPONENT*, FOOTPRINT*>& aFootprintMap )
  1040. {
  1041. // Verify that board contains all pads in netlist: if it doesn't then footprints are
  1042. // wrong or missing.
  1043. wxString msg;
  1044. wxString padNumber;
  1045. for( int i = 0; i < (int) aNetlist.GetCount(); i++ )
  1046. {
  1047. COMPONENT* component = aNetlist.GetComponent( i );
  1048. FOOTPRINT* footprint = aFootprintMap[component];
  1049. if( !footprint ) // It can be missing in partial designs
  1050. continue;
  1051. // Explore all pins/pads in component
  1052. for( unsigned jj = 0; jj < component->GetNetCount(); jj++ )
  1053. {
  1054. padNumber = component->GetNet( jj ).GetPinName();
  1055. if( padNumber.IsEmpty() )
  1056. {
  1057. // bad symbol, report error
  1058. msg.Printf( _( "Symbol %s has pins with no number. These pins can not be matched "
  1059. "to pads in %s." ),
  1060. component->GetReference(),
  1061. EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
  1062. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  1063. ++m_errorCount;
  1064. }
  1065. else if( !footprint->FindPadByNumber( padNumber ) )
  1066. {
  1067. // not found: bad footprint, report error
  1068. msg.Printf( _( "%s pad %s not found in %s." ),
  1069. component->GetReference(),
  1070. EscapeHTML( padNumber ),
  1071. EscapeHTML( footprint->GetFPID().Format().wx_str() ) );
  1072. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  1073. ++m_errorCount;
  1074. }
  1075. }
  1076. }
  1077. return true;
  1078. }
  1079. bool BOARD_NETLIST_UPDATER::UpdateNetlist( NETLIST& aNetlist )
  1080. {
  1081. FOOTPRINT* lastPreexistingFootprint = nullptr;
  1082. COMPONENT* component = nullptr;
  1083. wxString msg;
  1084. std::unordered_set<wxString> sheetPaths;
  1085. m_errorCount = 0;
  1086. m_warningCount = 0;
  1087. m_newFootprintsCount = 0;
  1088. std::map<COMPONENT*, FOOTPRINT*> footprintMap;
  1089. if( !m_board->Footprints().empty() )
  1090. lastPreexistingFootprint = m_board->Footprints().back();
  1091. cacheCopperZoneConnections();
  1092. // First mark all nets (except <no net>) as stale; we'll update those which are current
  1093. // in the following two loops. Also prepare the component class manager for updates.
  1094. //
  1095. if( !m_isDryRun )
  1096. {
  1097. for( NETINFO_ITEM* net : m_board->GetNetInfo() )
  1098. net->SetIsCurrent( net->GetNetCode() == 0 );
  1099. m_board->GetComponentClassManager().InitNetlistUpdate();
  1100. }
  1101. // Next go through the netlist updating all board footprints which have matching component
  1102. // entries and adding new footprints for those that don't.
  1103. //
  1104. for( unsigned i = 0; i < aNetlist.GetCount(); i++ )
  1105. {
  1106. component = aNetlist.GetComponent( i );
  1107. if( component->GetProperties().count( wxT( "exclude_from_board" ) ) )
  1108. continue;
  1109. msg.Printf( _( "Processing symbol '%s:%s'." ),
  1110. component->GetReference(),
  1111. EscapeHTML( component->GetFPID().Format().wx_str() ) );
  1112. m_reporter->Report( msg, RPT_SEVERITY_INFO );
  1113. int matchCount = 0;
  1114. for( FOOTPRINT* footprint : m_board->Footprints() )
  1115. {
  1116. bool match = false;
  1117. if( m_lookupByTimestamp )
  1118. {
  1119. for( const KIID& uuid : component->GetKIIDs() )
  1120. {
  1121. KIID_PATH base = component->GetPath();
  1122. base.push_back( uuid );
  1123. if( footprint->GetPath() == base )
  1124. {
  1125. match = true;
  1126. break;
  1127. }
  1128. }
  1129. }
  1130. else
  1131. {
  1132. match = footprint->GetReference().CmpNoCase( component->GetReference() ) == 0;
  1133. }
  1134. if( match )
  1135. {
  1136. FOOTPRINT* tmp = footprint;
  1137. if( m_replaceFootprints && component->GetFPID() != footprint->GetFPID() )
  1138. tmp = replaceFootprint( aNetlist, footprint, component );
  1139. if( !tmp )
  1140. tmp = footprint;
  1141. if( tmp )
  1142. {
  1143. footprintMap[ component ] = tmp;
  1144. updateFootprintParameters( tmp, component );
  1145. updateComponentPadConnections( tmp, component );
  1146. updateComponentClass( tmp, component );
  1147. sheetPaths.insert( footprint->GetSheetname() );
  1148. }
  1149. matchCount++;
  1150. }
  1151. if( footprint == lastPreexistingFootprint )
  1152. {
  1153. // No sense going through the newly-created footprints: end of loop
  1154. break;
  1155. }
  1156. }
  1157. if( matchCount == 0 )
  1158. {
  1159. FOOTPRINT* footprint = addNewFootprint( component );
  1160. if( footprint )
  1161. {
  1162. footprintMap[ component ] = footprint;
  1163. updateFootprintParameters( footprint, component );
  1164. updateComponentPadConnections( footprint, component );
  1165. updateComponentClass( footprint, component );
  1166. sheetPaths.insert( footprint->GetSheetname() );
  1167. }
  1168. }
  1169. else if( matchCount > 1 )
  1170. {
  1171. msg.Printf( _( "Multiple footprints found for %s." ),
  1172. component->GetReference() );
  1173. m_reporter->Report( msg, RPT_SEVERITY_ERROR );
  1174. m_errorCount++;
  1175. }
  1176. }
  1177. updateCopperZoneNets( aNetlist );
  1178. // Finally go through the board footprints and update all those that *don't* have matching
  1179. // component entries.
  1180. //
  1181. for( FOOTPRINT* footprint : m_board->Footprints() )
  1182. {
  1183. bool matched = false;
  1184. bool doDelete = m_deleteUnusedFootprints;
  1185. if( ( footprint->GetAttributes() & FP_BOARD_ONLY ) > 0 )
  1186. doDelete = false;
  1187. if( m_lookupByTimestamp )
  1188. component = aNetlist.GetComponentByPath( footprint->GetPath() );
  1189. else
  1190. component = aNetlist.GetComponentByReference( footprint->GetReference() );
  1191. if( component && component->GetProperties().count( wxT( "exclude_from_board" ) ) == 0 )
  1192. matched = true;
  1193. if( doDelete && !matched && footprint->IsLocked() && !m_overrideLocks )
  1194. {
  1195. if( m_isDryRun )
  1196. {
  1197. msg.Printf( _( "Cannot remove unused footprint %s (footprint is locked)." ),
  1198. footprint->GetReference() );
  1199. }
  1200. else
  1201. {
  1202. msg.Printf( _( "Could not remove unused footprint %s (footprint is locked)." ),
  1203. footprint->GetReference() );
  1204. }
  1205. m_reporter->Report( msg, RPT_SEVERITY_WARNING );
  1206. m_warningCount++;
  1207. doDelete = false;
  1208. }
  1209. if( doDelete && !matched )
  1210. {
  1211. if( m_isDryRun )
  1212. {
  1213. msg.Printf( _( "Remove unused footprint %s." ),
  1214. footprint->GetReference() );
  1215. }
  1216. else
  1217. {
  1218. if( footprint->GetParentGroup() )
  1219. m_commit.Stage( footprint, CHT_UNGROUP );
  1220. m_commit.Remove( footprint );
  1221. msg.Printf( _( "Removed unused footprint %s." ),
  1222. footprint->GetReference() );
  1223. }
  1224. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  1225. }
  1226. else if( !m_isDryRun )
  1227. {
  1228. if( !matched )
  1229. footprint->SetPath( KIID_PATH() );
  1230. for( PAD* pad : footprint->Pads() )
  1231. {
  1232. if( pad->GetNet() )
  1233. pad->GetNet()->SetIsCurrent( true );
  1234. }
  1235. }
  1236. }
  1237. if( !m_isDryRun )
  1238. {
  1239. // Finalise the component class manager
  1240. m_board->GetComponentClassManager().FinishNetlistUpdate();
  1241. m_board->SynchronizeComponentClasses( sheetPaths );
  1242. m_board->BuildConnectivity();
  1243. testConnectivity( aNetlist, footprintMap );
  1244. for( NETINFO_ITEM* net : m_board->GetNetInfo() )
  1245. {
  1246. if( !net->IsCurrent() )
  1247. {
  1248. msg.Printf( _( "Removed unused net %s." ),
  1249. EscapeHTML( net->GetNetname() ) );
  1250. m_reporter->Report( msg, RPT_SEVERITY_ACTION );
  1251. }
  1252. }
  1253. m_board->RemoveUnusedNets( &m_commit );
  1254. // When new footprints are added, the automatic zone refill is disabled because:
  1255. // * it creates crashes when calculating dynamic ratsnests if auto refill is enabled.
  1256. // (the auto refills rebuild the connectivity with incomplete data)
  1257. // * it is useless because zones will be refilled after placing new footprints
  1258. m_commit.Push( _( "Update Netlist" ), m_newFootprintsCount ? ZONE_FILL_OP : 0 );
  1259. // Update net, netcode and netclass data after commiting the netlist
  1260. m_board->SynchronizeNetsAndNetClasses( true );
  1261. m_board->GetConnectivity()->RefreshNetcodeMap( m_board );
  1262. // Although m_commit will probably also set this, it's not guaranteed, and we need to make
  1263. // sure any modification to netclasses gets persisted to project settings through a save.
  1264. m_frame->OnModify();
  1265. }
  1266. if( m_isDryRun )
  1267. {
  1268. for( const std::pair<const wxString, NETINFO_ITEM*>& addedNet : m_addedNets )
  1269. delete addedNet.second;
  1270. m_addedNets.clear();
  1271. }
  1272. // Update the ratsnest
  1273. m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
  1274. m_reporter->ReportTail( wxT( "" ), RPT_SEVERITY_ACTION );
  1275. msg.Printf( _( "Total warnings: %d, errors: %d." ), m_warningCount, m_errorCount );
  1276. m_reporter->ReportTail( msg, RPT_SEVERITY_INFO );
  1277. return true;
  1278. }