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.

1798 lines
60 KiB

2 years ago
11 months ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or (at your
  9. * option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <widgets/pcb_net_inspector_panel.h>
  20. #include <widgets/pcb_net_inspector_panel_data_model.h>
  21. #include <advanced_config.h>
  22. #include <board_design_settings.h>
  23. #include <confirm.h>
  24. #include <connectivity/connectivity_algo.h>
  25. #include <dialogs/dialog_text_entry.h>
  26. #include <footprint.h>
  27. #include <pad.h>
  28. #include <pcb_edit_frame.h>
  29. #include <pcb_painter.h>
  30. #include <pgm_base.h>
  31. #include <settings/settings_manager.h>
  32. #include <validators.h>
  33. #include <wildcards_and_files_ext.h>
  34. #include <eda_pattern_match.h>
  35. #include <wx/wupdlock.h>
  36. #include <wx/filedlg.h>
  37. #include <algorithm>
  38. #include <thread_pool.h>
  39. PCB_NET_INSPECTOR_PANEL::PCB_NET_INSPECTOR_PANEL( wxWindow* parent, PCB_EDIT_FRAME* aFrame ) :
  40. NET_INSPECTOR_PANEL( parent, aFrame ), m_frame( aFrame ), m_dataModel( new DATA_MODEL( *this ) )
  41. {
  42. m_board = m_frame->GetBoard();
  43. m_netsList->AssociateModel( &*m_dataModel );
  44. // Rebuild nets list
  45. buildNetsList( true );
  46. // Register the panel to receive board change notifications
  47. if( m_board != nullptr )
  48. {
  49. PCB_NET_INSPECTOR_PANEL::OnBoardHighlightNetChanged( *m_board );
  50. m_board->AddListener( this );
  51. }
  52. // Connect to board events
  53. m_frame->Bind( EDA_EVT_UNITS_CHANGED, &PCB_NET_INSPECTOR_PANEL::onUnitsChanged, this );
  54. // Connect to wxDataViewCtrl events
  55. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_EXPANDED, &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow,
  56. this );
  57. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_COLLAPSED, &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow,
  58. this );
  59. m_netsList->Bind( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK,
  60. &PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu, this );
  61. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU,
  62. &PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu, this );
  63. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_ACTIVATED,
  64. &PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated, this );
  65. m_netsList->Bind( wxEVT_DATAVIEW_COLUMN_SORTED,
  66. &PCB_NET_INSPECTOR_PANEL::OnColumnSorted, this );
  67. }
  68. PCB_NET_INSPECTOR_PANEL::~PCB_NET_INSPECTOR_PANEL()
  69. {
  70. PCB_NET_INSPECTOR_PANEL::SaveSettings();
  71. m_netsList->AssociateModel( nullptr );
  72. // Disconnect from board events
  73. m_frame->Unbind( EDA_EVT_UNITS_CHANGED, &PCB_NET_INSPECTOR_PANEL::onUnitsChanged, this );
  74. // Connect to wxDataViewCtrl events
  75. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_EXPANDED, &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow,
  76. this );
  77. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_COLLAPSED,
  78. &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow, this );
  79. m_netsList->Unbind( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK,
  80. &PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu, this );
  81. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU,
  82. &PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu, this );
  83. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_ACTIVATED,
  84. &PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated, this );
  85. m_netsList->Unbind( wxEVT_DATAVIEW_COLUMN_SORTED,
  86. &PCB_NET_INSPECTOR_PANEL::OnColumnSorted, this );
  87. }
  88. /*****************************************************************************************
  89. *
  90. * Grid / model columns configuration
  91. *
  92. * ***************************************************************************************/
  93. void PCB_NET_INSPECTOR_PANEL::buildColumns()
  94. {
  95. m_columns.clear();
  96. // Set up the column display vector
  97. m_columns.emplace_back( 0u, UNDEFINED_LAYER, _( "Name" ), _( "Net Name" ),
  98. CSV_COLUMN_DESC::CSV_QUOTE, false );
  99. m_columns.emplace_back( 1u, UNDEFINED_LAYER, _( "Netclass" ), _( "Netclass" ),
  100. CSV_COLUMN_DESC::CSV_QUOTE, false );
  101. m_columns.emplace_back( 2u, UNDEFINED_LAYER, _( "Total Length" ), _( "Net Length" ),
  102. CSV_COLUMN_DESC::CSV_NONE, true );
  103. m_columns.emplace_back( 3u, UNDEFINED_LAYER, _( "Via Count" ), _( "Via Count" ),
  104. CSV_COLUMN_DESC::CSV_NONE, false );
  105. m_columns.emplace_back( 4u, UNDEFINED_LAYER, _( "Via Length" ), _( "Via Length" ),
  106. CSV_COLUMN_DESC::CSV_NONE, true );
  107. m_columns.emplace_back( 5u, UNDEFINED_LAYER, _( "Track Length" ), _( "Track Length" ),
  108. CSV_COLUMN_DESC::CSV_NONE, true );
  109. m_columns.emplace_back( 6u, UNDEFINED_LAYER, _( "Die Length" ), _( "Die Length" ),
  110. CSV_COLUMN_DESC::CSV_NONE, true );
  111. m_columns.emplace_back( 7u, UNDEFINED_LAYER, _( "Pad Count" ), _( "Pad Count" ), CSV_COLUMN_DESC::CSV_NONE, false );
  112. const std::vector<std::function<void( void )>> add_col{
  113. [&]()
  114. {
  115. m_netsList->AppendTextColumn( m_columns[COLUMN_NAME].display_name,
  116. m_columns[COLUMN_NAME], wxDATAVIEW_CELL_INERT, -1,
  117. wxALIGN_LEFT,
  118. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE );
  119. },
  120. [&]()
  121. {
  122. m_netsList->AppendTextColumn( m_columns[COLUMN_NETCLASS].display_name,
  123. m_columns[COLUMN_NETCLASS], wxDATAVIEW_CELL_INERT, -1,
  124. wxALIGN_LEFT,
  125. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  126. | wxDATAVIEW_COL_SORTABLE );
  127. },
  128. [&]()
  129. {
  130. m_netsList->AppendTextColumn( m_columns[COLUMN_TOTAL_LENGTH].display_name,
  131. m_columns[COLUMN_TOTAL_LENGTH], wxDATAVIEW_CELL_INERT, -1,
  132. wxALIGN_CENTER,
  133. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  134. | wxDATAVIEW_COL_SORTABLE );
  135. },
  136. [&]()
  137. {
  138. m_netsList->AppendTextColumn( m_columns[COLUMN_VIA_COUNT].display_name,
  139. m_columns[COLUMN_VIA_COUNT], wxDATAVIEW_CELL_INERT, -1,
  140. wxALIGN_CENTER,
  141. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  142. | wxDATAVIEW_COL_SORTABLE );
  143. },
  144. [&]()
  145. {
  146. m_netsList->AppendTextColumn( m_columns[COLUMN_VIA_LENGTH].display_name,
  147. m_columns[COLUMN_VIA_LENGTH], wxDATAVIEW_CELL_INERT, -1,
  148. wxALIGN_CENTER,
  149. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  150. | wxDATAVIEW_COL_SORTABLE );
  151. },
  152. [&]()
  153. {
  154. m_netsList->AppendTextColumn( m_columns[COLUMN_BOARD_LENGTH].display_name,
  155. m_columns[COLUMN_BOARD_LENGTH], wxDATAVIEW_CELL_INERT, -1,
  156. wxALIGN_CENTER,
  157. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  158. | wxDATAVIEW_COL_SORTABLE );
  159. },
  160. [&]()
  161. {
  162. m_netsList->AppendTextColumn( m_columns[COLUMN_PAD_DIE_LENGTH].display_name,
  163. m_columns[COLUMN_PAD_DIE_LENGTH], wxDATAVIEW_CELL_INERT,
  164. -1, wxALIGN_CENTER,
  165. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  166. | wxDATAVIEW_COL_SORTABLE );
  167. },
  168. [&]()
  169. {
  170. m_netsList->AppendTextColumn( m_columns[COLUMN_PAD_COUNT].display_name,
  171. m_columns[COLUMN_PAD_COUNT], wxDATAVIEW_CELL_INERT, -1,
  172. wxALIGN_CENTER,
  173. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  174. | wxDATAVIEW_COL_SORTABLE );
  175. }
  176. };
  177. // If we have not yet loaded the first board, use a dummy local settings object to ensure we
  178. // don't over-write existing board settings (note that PCB_EDIT_FRAME loads the local settings
  179. // object prior to loading the board; the two are not synced and we need to account for that)
  180. PANEL_NET_INSPECTOR_SETTINGS* cfg = nullptr;
  181. if( m_boardLoaded )
  182. {
  183. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  184. cfg = &localSettings.m_NetInspectorPanel;
  185. }
  186. else
  187. {
  188. cfg = new PANEL_NET_INSPECTOR_SETTINGS();
  189. }
  190. // Reset the column display settings if column count doesn't match
  191. const int totalNumColumns = add_col.size() + m_board->GetCopperLayerCount();
  192. if( (int) cfg->col_order.size() != totalNumColumns
  193. || (int) cfg->col_hidden.size() != totalNumColumns )
  194. {
  195. cfg->col_order.resize( totalNumColumns );
  196. cfg->col_hidden.resize( totalNumColumns );
  197. for( int i = 0; i < totalNumColumns; ++i )
  198. {
  199. cfg->col_order[i] = i;
  200. cfg->col_hidden[i] = false;
  201. }
  202. }
  203. // Check that all rows are unique to protect against corrupted settings data
  204. std::set<int> col_order_set( cfg->col_order.begin(), cfg->col_order.end() );
  205. if( col_order_set.size() != cfg->col_order.size() )
  206. {
  207. for( std::size_t i = 0; i < cfg->col_order.size(); ++i )
  208. cfg->col_order[i] = static_cast<int>( i );
  209. }
  210. // Add column records for copper layers
  211. for( PCB_LAYER_ID layer : m_board->GetEnabledLayers().Seq() )
  212. {
  213. if( !IsCopperLayer( layer ) )
  214. continue;
  215. m_columns.emplace_back( m_columns.size(), layer, m_board->GetLayerName( layer ), m_board->GetLayerName( layer ),
  216. CSV_COLUMN_DESC::CSV_NONE, true );
  217. }
  218. // Add display columns in settings order
  219. for( const int i : cfg->col_order )
  220. {
  221. const int addModelColumn = i;
  222. if( addModelColumn >= (int) add_col.size() )
  223. {
  224. m_netsList->AppendTextColumn( m_board->GetLayerName( m_columns[addModelColumn].layer ),
  225. m_columns[addModelColumn], wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  226. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  227. | wxDATAVIEW_COL_SORTABLE );
  228. }
  229. else
  230. {
  231. add_col.at( i )();
  232. }
  233. }
  234. // Set the name column as the expander row
  235. if( wxDataViewColumn* col = getDisplayedColumnForModelField( COLUMN_NAME ) )
  236. {
  237. m_netsList->SetExpanderColumn( col );
  238. }
  239. adjustListColumnSizes( cfg );
  240. // Delete the temporary config if used
  241. if( !m_boardLoaded )
  242. {
  243. delete cfg;
  244. }
  245. }
  246. void PCB_NET_INSPECTOR_PANEL::adjustListColumnSizes( PANEL_NET_INSPECTOR_SETTINGS* cfg ) const
  247. {
  248. wxWindowUpdateLocker locker( m_netsList );
  249. if( cfg->col_widths.size() != m_columns.size() )
  250. {
  251. int minValueWidth = GetTextExtent( wxT( "00000,000 mm" ) ).x;
  252. int minNumberWidth = GetTextExtent( wxT( "000" ) ).x;
  253. int minNameWidth = GetTextExtent( wxT( "MMMMMMMMMMMM" ) ).x;
  254. // Considering left and right margins.
  255. // For wxRenderGeneric it is 5px.
  256. // Also account for the sorting arrow in the column header.
  257. // Column 0 also needs space for any potential expander icons.
  258. constexpr int margins = 15;
  259. constexpr int extra_width = 30;
  260. auto getTargetWidth =
  261. [&]( int columnID )
  262. {
  263. switch( columnID )
  264. {
  265. case COLUMN_NAME: return minNameWidth + extra_width;
  266. case COLUMN_NETCLASS: return minNameWidth + margins;
  267. case COLUMN_VIA_COUNT: return minNumberWidth + margins;
  268. case COLUMN_PAD_COUNT: return minNumberWidth + margins;
  269. default: return minValueWidth + margins;
  270. }
  271. };
  272. wxASSERT( m_columns.size() == cfg->col_order.size() );
  273. for( size_t i = 0; i < m_columns.size(); ++i )
  274. {
  275. const int modelColumn = cfg->col_order[i];
  276. int titleSize = GetTextExtent( m_columns[modelColumn].display_name ).x;
  277. titleSize = modelColumn == COLUMN_NAME ? titleSize + extra_width : titleSize + margins;
  278. const int valSize = getTargetWidth( modelColumn );
  279. m_netsList->GetColumn( i )->SetWidth( std::max( titleSize, valSize ) );
  280. }
  281. }
  282. else
  283. {
  284. wxASSERT( m_columns.size() == cfg->col_hidden.size() );
  285. wxASSERT( m_columns.size() == cfg->col_widths.size() );
  286. for( size_t ii = 0; ii < m_columns.size(); ++ii )
  287. {
  288. const int newWidth = cfg->col_widths[ii];
  289. // Make sure we end up with something non-zero so we can resize it
  290. m_netsList->GetColumn( ii )->SetWidth( std::max( newWidth, 10 ) );
  291. m_netsList->GetColumn( ii )->SetHidden( cfg->col_hidden[ii] );
  292. }
  293. }
  294. m_netsList->Refresh();
  295. }
  296. bool PCB_NET_INSPECTOR_PANEL::restoreSortColumn( const int sortingColumnId, const bool sortOrderAsc ) const
  297. {
  298. if( sortingColumnId != -1 )
  299. {
  300. if( wxDataViewColumn* col = getDisplayedColumnForModelField( sortingColumnId ) )
  301. {
  302. col->SetSortOrder( sortOrderAsc );
  303. m_dataModel->Resort();
  304. return true;
  305. }
  306. }
  307. return false;
  308. }
  309. wxDataViewColumn* PCB_NET_INSPECTOR_PANEL::getDisplayedColumnForModelField( const int columnId ) const
  310. {
  311. for( unsigned int i = 0; i < m_netsList->GetColumnCount(); ++i )
  312. {
  313. wxDataViewColumn* col = m_netsList->GetColumn( i );
  314. if( static_cast<int>( col->GetModelColumn() ) == columnId )
  315. {
  316. return col;
  317. }
  318. }
  319. return nullptr;
  320. }
  321. /*****************************************************************************************
  322. *
  323. * Nets list generation
  324. *
  325. * ***************************************************************************************/
  326. void PCB_NET_INSPECTOR_PANEL::buildNetsList( const bool rebuildColumns )
  327. {
  328. // Only build the list of nets if there is a board present
  329. if( !m_board )
  330. return;
  331. m_inBuildNetsList = true;
  332. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  333. PANEL_NET_INSPECTOR_SETTINGS* cfg = &localSettings.m_NetInspectorPanel;
  334. // Refresh all filtering / grouping settings
  335. m_filterByNetName = cfg->filter_by_net_name;
  336. m_filterByNetclass = cfg->filter_by_netclass;
  337. m_showZeroPadNets = cfg->show_zero_pad_nets;
  338. m_groupByNetclass = cfg->group_by_netclass;
  339. m_groupByConstraint = cfg->group_by_constraint;
  340. // Attempt to keep any expanded groups open
  341. if( m_boardLoaded && !m_boardLoading )
  342. {
  343. cfg->expanded_rows.clear();
  344. DATA_MODEL* model = static_cast<DATA_MODEL*>( m_netsList->GetModel() );
  345. for( const auto& groupItems = model->getGroupDataViewItems();
  346. auto& [groupName, groupItem] : groupItems )
  347. {
  348. if( m_netsList->IsExpanded( groupItem ) )
  349. cfg->expanded_rows.push_back( groupName );
  350. }
  351. }
  352. // When rebuilding the netlist, try to keep the row selection
  353. wxDataViewItemArray sel;
  354. m_netsList->GetSelections( sel );
  355. std::vector<int> prev_selected_netcodes;
  356. prev_selected_netcodes.reserve( sel.GetCount() );
  357. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  358. {
  359. const LIST_ITEM* item = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  360. prev_selected_netcodes.push_back( item->GetNetCode() );
  361. }
  362. int sorting_column_id = cfg->sorting_column;
  363. bool sort_order_asc = cfg->sort_order_asc;
  364. if( wxDataViewColumn* sorting_column = m_netsList->GetSortingColumn() )
  365. {
  366. if( !m_boardLoading )
  367. {
  368. sorting_column_id = static_cast<int>( sorting_column->GetModelColumn() );
  369. sort_order_asc = sorting_column->IsSortOrderAscending();
  370. }
  371. // On GTK, wxDVC will crash if we rebuild with a sorting column set.
  372. sorting_column->UnsetAsSortKey();
  373. }
  374. if( rebuildColumns )
  375. {
  376. m_netsList->ClearColumns();
  377. buildColumns();
  378. }
  379. m_dataModel->deleteAllItems();
  380. m_custom_group_rules.clear();
  381. for( const wxString& rule : cfg->custom_group_rules )
  382. m_custom_group_rules.push_back( std::make_unique<EDA_COMBINED_MATCHER>( rule, CTX_NET ) );
  383. m_dataModel->addCustomGroups();
  384. std::vector<NETINFO_ITEM*> netCodes;
  385. for( NETINFO_ITEM* ni : m_board->GetNetInfo() )
  386. {
  387. if( netFilterMatches( ni, cfg ) )
  388. netCodes.emplace_back( ni );
  389. }
  390. std::ranges::sort( netCodes,
  391. []( const NETINFO_ITEM* a, const NETINFO_ITEM* b )
  392. {
  393. return a->GetNetCode() < b->GetNetCode();
  394. } );
  395. std::vector<std::unique_ptr<LIST_ITEM>> lengths = calculateNets( netCodes, m_showZeroPadNets );
  396. m_dataModel->addItems( lengths );
  397. // Try to re-enable the sorting column
  398. if( !restoreSortColumn( sorting_column_id, sort_order_asc ))
  399. {
  400. // By default, sort by Name column
  401. restoreSortColumn( COLUMN_NAME, true );
  402. }
  403. // Try to restore the expanded groups
  404. if( m_boardLoaded )
  405. {
  406. m_rowExpanding = true;
  407. std::vector<std::pair<wxString, wxDataViewItem>> groupItems = m_dataModel->getGroupDataViewItems();
  408. for( wxString& groupName : cfg->expanded_rows )
  409. {
  410. auto pred =
  411. [&groupName]( const std::pair<wxString, wxDataViewItem>& item )
  412. {
  413. return groupName == item.first;
  414. };
  415. auto tableItem = std::ranges::find_if( groupItems, pred );
  416. if( tableItem != groupItems.end() )
  417. m_netsList->Expand( tableItem->second );
  418. }
  419. m_rowExpanding = false;
  420. }
  421. // Try to restore the selected rows
  422. sel.Clear();
  423. for( const int& nc : prev_selected_netcodes )
  424. {
  425. if( std::optional<LIST_ITEM_ITER> r = m_dataModel->findItem( nc ) )
  426. {
  427. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  428. sel.Add( wxDataViewItem( list_item.get() ) );
  429. }
  430. }
  431. if( !sel.IsEmpty() )
  432. {
  433. m_netsList->SetSelections( sel );
  434. m_netsList->EnsureVisible( sel.Item( 0 ) );
  435. }
  436. else
  437. {
  438. m_netsList->UnselectAll();
  439. }
  440. m_inBuildNetsList = false;
  441. }
  442. bool PCB_NET_INSPECTOR_PANEL::netFilterMatches( NETINFO_ITEM* aNet,
  443. PANEL_NET_INSPECTOR_SETTINGS* cfg ) const
  444. {
  445. if( cfg == nullptr )
  446. {
  447. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  448. cfg = &localSettings.m_NetInspectorPanel;
  449. }
  450. // Never show an unconnected net
  451. if( aNet->GetNetCode() <= 0 )
  452. return false;
  453. const wxString filterString = UnescapeString( m_searchCtrl->GetValue() ).Upper();
  454. const wxString netName = UnescapeString( aNet->GetNetname() ).Upper();
  455. const NETCLASS* netClass = aNet->GetNetClass();
  456. const wxString netClassName = UnescapeString( netClass->GetName() ).Upper();
  457. bool matched = false;
  458. // No filter - match all
  459. if( filterString.Length() == 0 )
  460. matched = true;
  461. // Search on net class
  462. if( !matched && cfg->filter_by_netclass && netClassName.Find( filterString ) != wxNOT_FOUND )
  463. matched = true;
  464. // Search on net name
  465. if( !matched && cfg->filter_by_net_name && netName.Find( filterString ) != wxNOT_FOUND )
  466. matched = true;
  467. // Remove unconnected nets if required
  468. if( matched )
  469. {
  470. if( !m_showUnconnectedNets )
  471. matched = !netName.StartsWith( wxT( "UNCONNECTED-(" ) );
  472. }
  473. return matched;
  474. }
  475. struct NETCODE_CMP_LESS
  476. {
  477. bool operator()( const CN_ITEM* a, const CN_ITEM* b ) const { return a->Net() < b->Net(); }
  478. bool operator()( const CN_ITEM* a, int b ) const { return a->Net() < b; }
  479. bool operator()( int a, const CN_ITEM* b ) const { return a < b->Net(); }
  480. };
  481. std::vector<CN_ITEM*> PCB_NET_INSPECTOR_PANEL::relevantConnectivityItems() const
  482. {
  483. // Pre-filter the connectivity items and sort them by netcode. This avoids quadratic runtime when building the whole
  484. // net list.
  485. const auto type_bits = std::bitset<MAX_STRUCT_TYPE_ID>()
  486. .set( PCB_TRACE_T )
  487. .set( PCB_ARC_T )
  488. .set( PCB_VIA_T )
  489. .set( PCB_PAD_T );
  490. std::vector<CN_ITEM*> cn_items;
  491. cn_items.reserve( 1024 );
  492. for( CN_ITEM* cn_item : m_board->GetConnectivity()->GetConnectivityAlgo()->ItemList() )
  493. {
  494. if( cn_item->Valid() && type_bits[cn_item->Parent()->Type()] )
  495. cn_items.push_back( cn_item );
  496. }
  497. std::ranges::sort( cn_items, NETCODE_CMP_LESS() );
  498. return cn_items;
  499. }
  500. std::vector<std::unique_ptr<PCB_NET_INSPECTOR_PANEL::LIST_ITEM>>
  501. PCB_NET_INSPECTOR_PANEL::calculateNets( const std::vector<NETINFO_ITEM*>& aNetCodes, bool aIncludeZeroPadNets ) const
  502. {
  503. std::vector<std::unique_ptr<LIST_ITEM>> results;
  504. LENGTH_CALCULATION* calc = m_board->GetLengthCalculation();
  505. const std::vector<CN_ITEM*> conItems = relevantConnectivityItems();
  506. // First assemble the LENGTH_CALCULATION_ITEMs for board items which match the nets we need to recompute
  507. // Precondition: conItems and aNetCodes are sorted in increasing netcode value
  508. // Functionality: This extracts any items from conItems which have a netcode which is present in aNetCodes
  509. std::unordered_map<int, std::vector<LENGTH_CALCULATION_ITEM>> netItemsMap;
  510. std::vector<NETINFO_ITEM*> foundNets;
  511. auto itemItr = conItems.begin();
  512. auto netCodeItr = aNetCodes.begin();
  513. while( itemItr != conItems.end() && netCodeItr != aNetCodes.end() )
  514. {
  515. const int curNetCode = ( *netCodeItr )->GetNetCode();
  516. const int curItemNetCode = ( *itemItr )->Net();
  517. if( curItemNetCode == curNetCode )
  518. {
  519. if( foundNets.empty() || foundNets.back() != *netCodeItr )
  520. foundNets.emplace_back( *netCodeItr );
  521. // Take the item
  522. LENGTH_CALCULATION_ITEM lengthItem = calc->GetLengthCalculationItem( ( *itemItr )->Parent() );
  523. netItemsMap[curItemNetCode].emplace_back( std::move( lengthItem ) );
  524. ++itemItr;
  525. }
  526. else if( curItemNetCode < curNetCode )
  527. {
  528. // Fast-forward through items
  529. while( itemItr != conItems.end() && ( *itemItr )->Net() < curNetCode )
  530. ++itemItr;
  531. }
  532. else if( curItemNetCode > curNetCode )
  533. {
  534. // Fast-forward through required net codes
  535. while( netCodeItr != aNetCodes.end() && curItemNetCode > ( *netCodeItr )->GetNetCode() )
  536. ++netCodeItr;
  537. }
  538. }
  539. // Now calculate the length statistics for each net. This includes potentially expensive path optimisations, so
  540. // parallelize this work.
  541. std::mutex resultsMutex;
  542. thread_pool& tp = GetKiCadThreadPool();
  543. auto resultsFuture = tp.parallelize_loop(
  544. 0, foundNets.size(),
  545. [&, this, calc]( const int start, const int end )
  546. {
  547. for( int i = start; i < end; ++i )
  548. {
  549. int netCode = foundNets[i]->GetNetCode();
  550. constexpr PATH_OPTIMISATIONS opts = { .OptimiseViaLayers = true,
  551. .MergeTracks = true,
  552. .OptimiseTracesInPads = true,
  553. .InferViaInPad = false };
  554. LENGTH_DETAILS lengthDetails =
  555. calc->CalculateLengthDetails( netItemsMap[netCode], opts, nullptr, nullptr, true );
  556. if( aIncludeZeroPadNets || lengthDetails.NumPads > 0 )
  557. {
  558. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( foundNets[i] );
  559. new_item->SetPadCount( lengthDetails.NumPads );
  560. new_item->SetLayerCount( m_board->GetCopperLayerCount() );
  561. new_item->SetPadDieLength( lengthDetails.PadToDieLength );
  562. new_item->SetViaCount( lengthDetails.NumVias );
  563. new_item->SetViaLength( lengthDetails.ViaLength );
  564. new_item->SetLayerWireLengths( *lengthDetails.LayerLengths );
  565. std::scoped_lock lock( resultsMutex );
  566. results.emplace_back( std::move( new_item ) );
  567. }
  568. }
  569. } );
  570. resultsFuture.get();
  571. return results;
  572. }
  573. /*****************************************************************************************
  574. *
  575. * Formatting helpers
  576. *
  577. * ***************************************************************************************/
  578. wxString PCB_NET_INSPECTOR_PANEL::formatNetCode( const NETINFO_ITEM* aNet )
  579. {
  580. return wxString::Format( wxT( "%.3d" ), aNet->GetNetCode() );
  581. }
  582. wxString PCB_NET_INSPECTOR_PANEL::formatNetName( const NETINFO_ITEM* aNet )
  583. {
  584. return UnescapeString( aNet->GetNetname() );
  585. }
  586. wxString PCB_NET_INSPECTOR_PANEL::formatCount( const unsigned int aValue )
  587. {
  588. return wxString::Format( wxT( "%u" ), aValue );
  589. }
  590. wxString PCB_NET_INSPECTOR_PANEL::formatLength( const int64_t aValue ) const
  591. {
  592. return m_frame->MessageTextFromValue( aValue,
  593. // don't include unit label in the string when reporting
  594. !m_inReporting );
  595. }
  596. void PCB_NET_INSPECTOR_PANEL::updateDisplayedRowValues( const std::optional<LIST_ITEM_ITER>& aRow ) const
  597. {
  598. if( !aRow )
  599. return;
  600. wxDataViewItemArray sel;
  601. m_netsList->GetSelections( sel );
  602. m_dataModel->updateItem( aRow );
  603. if( !sel.IsEmpty() )
  604. {
  605. m_netsList->SetSelections( sel );
  606. m_netsList->EnsureVisible( sel.Item( 0 ) );
  607. }
  608. }
  609. /*****************************************************************************************
  610. *
  611. * BOARD_LISTENER event handling
  612. *
  613. * ***************************************************************************************/
  614. void PCB_NET_INSPECTOR_PANEL::OnBoardChanged()
  615. {
  616. m_board = m_frame->GetBoard();
  617. if( m_board )
  618. m_board->AddListener( this );
  619. m_boardLoaded = true;
  620. m_boardLoading = true;
  621. const PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  622. auto& cfg = localSettings.m_NetInspectorPanel;
  623. m_searchCtrl->SetValue( cfg.filter_text );
  624. buildNetsList( true );
  625. m_boardLoading = false;
  626. }
  627. void PCB_NET_INSPECTOR_PANEL::OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  628. {
  629. const std::vector<BOARD_ITEM*> item{ aBoardItem };
  630. updateBoardItems( item );
  631. }
  632. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsAdded( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems )
  633. {
  634. updateBoardItems( aBoardItems );
  635. }
  636. void PCB_NET_INSPECTOR_PANEL::updateBoardItems( const std::vector<BOARD_ITEM*>& aBoardItems )
  637. {
  638. if( !IsShownOnScreen() )
  639. return;
  640. // Rebuild full list for large changes
  641. if( aBoardItems.size()
  642. > static_cast<size_t>( ADVANCED_CFG::GetCfg().m_NetInspectorBulkUpdateOptimisationThreshold ) )
  643. {
  644. buildNetsList();
  645. }
  646. else
  647. {
  648. std::vector<NETINFO_ITEM*> changedNets;
  649. for( BOARD_ITEM* boardItem : aBoardItems )
  650. {
  651. if( NETINFO_ITEM* net = dynamic_cast<NETINFO_ITEM*>( boardItem ) )
  652. {
  653. // A new net has been added to the board. Add it to our list if it passes the netname filter test.
  654. if( netFilterMatches( net ) )
  655. changedNets.emplace_back( net );
  656. }
  657. else if( BOARD_CONNECTED_ITEM* i = dynamic_cast<BOARD_CONNECTED_ITEM*>( boardItem ) )
  658. {
  659. changedNets.emplace_back( i->GetNet() );
  660. }
  661. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( boardItem ) )
  662. {
  663. for( const PAD* pad : footprint->Pads() )
  664. {
  665. if( netFilterMatches( pad->GetNet() ) )
  666. changedNets.emplace_back( pad->GetNet() );
  667. }
  668. }
  669. }
  670. std::ranges::sort( changedNets,
  671. []( const NETINFO_ITEM* a, const NETINFO_ITEM* b )
  672. {
  673. return a->GetNetCode() < b->GetNetCode();
  674. } );
  675. updateNets( changedNets );
  676. }
  677. m_netsList->Refresh();
  678. }
  679. void PCB_NET_INSPECTOR_PANEL::updateNets( const std::vector<NETINFO_ITEM*>& aNets ) const
  680. {
  681. std::vector<NETINFO_ITEM*> netsToUpdate;
  682. std::unordered_set<NETINFO_ITEM*> netsToDelete;
  683. for( NETINFO_ITEM* net : aNets )
  684. {
  685. // Add all nets to the deletion list - we will prune this later to only contain unhandled nets
  686. netsToDelete.insert( net );
  687. // Only calculate nets that match the current filter
  688. if( netFilterMatches( net ) )
  689. netsToUpdate.emplace_back( net );
  690. }
  691. m_netsList->Freeze();
  692. std::vector<std::unique_ptr<LIST_ITEM>> newListItems = calculateNets( aNets, true );
  693. for( std::unique_ptr<LIST_ITEM>& newListItem : newListItems )
  694. {
  695. // Remove the handled net from the deletion list
  696. netsToDelete.erase( newListItem->GetNet() );
  697. std::optional<LIST_ITEM_ITER> curNetRow = m_dataModel->findItem( newListItem->GetNetCode() );
  698. if( !m_showZeroPadNets && newListItem->GetPadCount() == 0 )
  699. {
  700. m_dataModel->deleteItem( curNetRow );
  701. continue;
  702. }
  703. if( !curNetRow )
  704. {
  705. m_dataModel->addItem( std::move( newListItem ) );
  706. continue;
  707. }
  708. const std::unique_ptr<LIST_ITEM>& curListItem = *curNetRow.value();
  709. if( curListItem->GetNetName() != newListItem->GetNetName() )
  710. {
  711. // If the name has changed, it might require re-grouping. It's easier to remove and re-insert it.
  712. m_dataModel->deleteItem( curNetRow );
  713. m_dataModel->addItem( std::move( newListItem ) );
  714. }
  715. else
  716. {
  717. curListItem->SetPadCount( newListItem->GetPadCount() );
  718. curListItem->SetPadDieLength( newListItem->GetPadDieLength() );
  719. curListItem->SetViaCount( newListItem->GetViaCount() );
  720. curListItem->SetViaLength( newListItem->GetViaLength() );
  721. curListItem->SetLayerWireLengths( newListItem->GetLayerWireLengths() );
  722. updateDisplayedRowValues( curNetRow );
  723. }
  724. }
  725. // Delete any nets we have not yet handled
  726. for( const NETINFO_ITEM* netToDelete : netsToDelete )
  727. m_dataModel->deleteItem( m_dataModel->findItem( netToDelete->GetNetCode() ) );
  728. m_netsList->Thaw();
  729. }
  730. void PCB_NET_INSPECTOR_PANEL::OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  731. {
  732. const std::vector<BOARD_ITEM*> item{ aBoardItem };
  733. updateBoardItems( item );
  734. }
  735. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsRemoved( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems )
  736. {
  737. updateBoardItems( aBoardItems );
  738. }
  739. void PCB_NET_INSPECTOR_PANEL::OnBoardNetSettingsChanged( BOARD& aBoard )
  740. {
  741. if( !IsShownOnScreen() )
  742. return;
  743. buildNetsList();
  744. }
  745. void PCB_NET_INSPECTOR_PANEL::OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  746. {
  747. const std::vector<BOARD_ITEM*> item{ aBoardItem };
  748. updateBoardItems( item );
  749. }
  750. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsChanged( BOARD& aBoard,
  751. std::vector<BOARD_ITEM*>& aBoardItems )
  752. {
  753. updateBoardItems( aBoardItems );
  754. }
  755. void PCB_NET_INSPECTOR_PANEL::OnBoardCompositeUpdate( BOARD& aBoard,
  756. std::vector<BOARD_ITEM*>& aAddedItems,
  757. std::vector<BOARD_ITEM*>& aRemovedItems,
  758. std::vector<BOARD_ITEM*>& aChangedItems )
  759. {
  760. if( !IsShownOnScreen() )
  761. return;
  762. std::vector<BOARD_ITEM*> allItems{ aAddedItems.begin(), aAddedItems.end() };
  763. allItems.insert( allItems.end(), aRemovedItems.begin(), aRemovedItems.end() );
  764. allItems.insert( allItems.end(), aChangedItems.begin(), aChangedItems.end() );
  765. updateBoardItems( allItems );
  766. }
  767. void PCB_NET_INSPECTOR_PANEL::OnBoardHighlightNetChanged( BOARD& aBoard )
  768. {
  769. if( m_highlightingNets || !IsShownOnScreen() )
  770. return;
  771. if( !m_board->IsHighLightNetON() )
  772. {
  773. m_netsList->UnselectAll();
  774. }
  775. else
  776. {
  777. const std::set<int>& selected_codes = m_board->GetHighLightNetCodes();
  778. wxDataViewItemArray new_selection;
  779. new_selection.Alloc( selected_codes.size() );
  780. for( const int code : selected_codes )
  781. {
  782. if( std::optional<LIST_ITEM_ITER> r = m_dataModel->findItem( code ) )
  783. new_selection.Add( wxDataViewItem( &***r ) );
  784. }
  785. m_netsList->SetSelections( new_selection );
  786. if( !new_selection.IsEmpty() )
  787. m_netsList->EnsureVisible( new_selection.Item( 0 ) );
  788. }
  789. }
  790. /*****************************************************************************************
  791. *
  792. * UI-generated event handling
  793. *
  794. * ***************************************************************************************/
  795. void PCB_NET_INSPECTOR_PANEL::OnShowPanel()
  796. {
  797. buildNetsList();
  798. OnBoardHighlightNetChanged( *m_board );
  799. }
  800. void PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu( wxDataViewEvent& event )
  801. {
  802. bool multipleSelections = false;
  803. const LIST_ITEM* selItem = nullptr;
  804. if( m_netsList->GetSelectedItemsCount() == 1 )
  805. {
  806. selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  807. }
  808. else
  809. {
  810. if( m_netsList->GetSelectedItemsCount() > 1 )
  811. multipleSelections = true;
  812. }
  813. wxMenu menu;
  814. // Net edit menu items
  815. wxMenuItem* highlightNet = new wxMenuItem( &menu, ID_HIGHLIGHT_SELECTED_NETS,
  816. _( "Highlight Selected Net" ),
  817. wxEmptyString, wxITEM_NORMAL );
  818. menu.Append( highlightNet );
  819. wxMenuItem* clearHighlighting = new wxMenuItem( &menu, ID_CLEAR_HIGHLIGHTING,
  820. _( "Clear Net Highlighting" ),
  821. wxEmptyString, wxITEM_NORMAL );
  822. menu.Append( clearHighlighting );
  823. RENDER_SETTINGS* renderSettings = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  824. const std::set<int>& selected_codes = renderSettings->GetHighlightNetCodes();
  825. if( selected_codes.size() == 0 )
  826. clearHighlighting->Enable( false );
  827. menu.AppendSeparator();
  828. wxMenuItem* renameNet = new wxMenuItem( &menu, ID_RENAME_NET, _( "Rename Selected Net..." ),
  829. wxEmptyString, wxITEM_NORMAL );
  830. menu.Append( renameNet );
  831. wxMenuItem* deleteNet = new wxMenuItem( &menu, ID_DELETE_NET, _( "Delete Selected Net" ),
  832. wxEmptyString, wxITEM_NORMAL );
  833. menu.Append( deleteNet );
  834. menu.AppendSeparator();
  835. wxMenuItem* addNet = new wxMenuItem( &menu, ID_ADD_NET, _( "Add Net..." ),
  836. wxEmptyString, wxITEM_NORMAL );
  837. menu.Append( addNet );
  838. if( !selItem && !multipleSelections )
  839. {
  840. highlightNet->Enable( false );
  841. deleteNet->Enable( false );
  842. renameNet->Enable( false );
  843. }
  844. else
  845. {
  846. if( multipleSelections || selItem->GetIsGroup() )
  847. {
  848. highlightNet->SetItemLabel( _( "Highlight Selected Nets" ) );
  849. renameNet->Enable( false );
  850. deleteNet->SetItemLabel( _( "Delete Selected Nets" ) );
  851. }
  852. }
  853. menu.AppendSeparator();
  854. wxMenuItem* removeSelectedGroup = new wxMenuItem( &menu, ID_REMOVE_SELECTED_GROUP,
  855. _( "Remove Selected Custom Group" ),
  856. wxEmptyString, wxITEM_NORMAL );
  857. menu.Append( removeSelectedGroup );
  858. if( !selItem || !selItem->GetIsGroup() )
  859. removeSelectedGroup->Enable( false );
  860. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onContextMenuSelection, this );
  861. PopupMenu( &menu );
  862. }
  863. void PCB_NET_INSPECTOR_PANEL::OnSearchTextChanged( wxCommandEvent& event )
  864. {
  865. SaveSettings();
  866. buildNetsList();
  867. }
  868. void PCB_NET_INSPECTOR_PANEL::onAddGroup()
  869. {
  870. wxString newGroupName;
  871. NETNAME_VALIDATOR validator( &newGroupName );
  872. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Group name / pattern:" ), _( "New Group" ), newGroupName );
  873. wxStaticText* help = new wxStaticText( &dlg, wxID_ANY,
  874. _( "(Use /.../ to indicate a regular expression.)" ) );
  875. help->SetFont( KIUI::GetInfoFont( this ).Italic() );
  876. dlg.m_ContentSizer->Add( help, 0, wxALL|wxEXPAND, 5 );
  877. dlg.SetTextValidator( validator );
  878. dlg.GetSizer()->SetSizeHints( &dlg );
  879. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  880. return; //Aborted by user
  881. newGroupName = UnescapeString( dlg.GetValue() );
  882. if( newGroupName == "" )
  883. return;
  884. if( std::ranges::find_if( m_custom_group_rules,
  885. [&]( std::unique_ptr<EDA_COMBINED_MATCHER>& rule )
  886. {
  887. return rule->GetPattern() == newGroupName;
  888. } )
  889. == m_custom_group_rules.end() )
  890. {
  891. m_custom_group_rules.push_back( std::make_unique<EDA_COMBINED_MATCHER>( newGroupName,
  892. CTX_NET ) );
  893. SaveSettings();
  894. }
  895. buildNetsList();
  896. }
  897. void PCB_NET_INSPECTOR_PANEL::onClearHighlighting()
  898. {
  899. m_highlightingNets = true;
  900. m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false );
  901. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  902. m_frame->GetCanvas()->Refresh();
  903. m_highlightingNets = false;
  904. }
  905. void PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow( wxCommandEvent& event )
  906. {
  907. if( !m_rowExpanding )
  908. SaveSettings();
  909. }
  910. void PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu( wxCommandEvent& event )
  911. {
  912. wxMenu menu;
  913. generateShowHideColumnMenu( &menu );
  914. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onContextMenuSelection, this );
  915. PopupMenu( &menu );
  916. }
  917. void PCB_NET_INSPECTOR_PANEL::OnConfigButton( wxCommandEvent& event )
  918. {
  919. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  920. auto& cfg = localSettings.m_NetInspectorPanel;
  921. const LIST_ITEM* selItem = nullptr;
  922. if( m_netsList->GetSelectedItemsCount() == 1 )
  923. {
  924. selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  925. }
  926. wxMenu menu;
  927. // Filtering menu items
  928. wxMenuItem* filterByNetName = new wxMenuItem( &menu, ID_FILTER_BY_NET_NAME,
  929. _( "Filter by Net Name" ),
  930. wxEmptyString, wxITEM_CHECK );
  931. menu.Append( filterByNetName );
  932. filterByNetName->Check( cfg.filter_by_net_name );
  933. wxMenuItem* filterByNetclass = new wxMenuItem( &menu, ID_FILTER_BY_NETCLASS,
  934. _( "Filter by Netclass" ),
  935. wxEmptyString, wxITEM_CHECK );
  936. menu.Append( filterByNetclass );
  937. filterByNetclass->Check( cfg.filter_by_netclass );
  938. menu.AppendSeparator();
  939. // Grouping menu items
  940. //wxMenuItem* groupConstraint =
  941. // new wxMenuItem( &menu, ID_GROUP_BY_CONSTRAINT, _( "Group by DRC Constraint" ),
  942. // wxEmptyString, wxITEM_CHECK );
  943. //groupConstraint->Check( m_group_by_constraint );
  944. //menu.Append( groupConstraint );
  945. wxMenuItem* groupNetclass = new wxMenuItem( &menu, ID_GROUP_BY_NETCLASS,
  946. _( "Group by Netclass" ),
  947. wxEmptyString, wxITEM_CHECK );
  948. menu.Append( groupNetclass );
  949. groupNetclass->Check( m_groupByNetclass );
  950. menu.AppendSeparator();
  951. wxMenuItem* addGroup = new wxMenuItem( &menu, ID_ADD_GROUP, _( "Add Custom Group..." ),
  952. wxEmptyString, wxITEM_NORMAL );
  953. menu.Append( addGroup );
  954. wxMenuItem* removeSelectedGroup = new wxMenuItem( &menu, ID_REMOVE_SELECTED_GROUP,
  955. _( "Remove Selected Custom Group" ),
  956. wxEmptyString, wxITEM_NORMAL );
  957. menu.Append( removeSelectedGroup );
  958. if( !selItem || !selItem->GetIsGroup() )
  959. removeSelectedGroup->Enable( false );
  960. wxMenuItem* removeCustomGroups = new wxMenuItem( &menu, ID_REMOVE_GROUPS,
  961. _( "Remove All Custom Groups" ),
  962. wxEmptyString, wxITEM_NORMAL );
  963. menu.Append( removeCustomGroups );
  964. removeCustomGroups->Enable( m_custom_group_rules.size() != 0 );
  965. menu.AppendSeparator();
  966. wxMenuItem* showZeroNetPads = new wxMenuItem( &menu, ID_SHOW_ZERO_NET_PADS,
  967. _( "Show Zero Pad Nets" ),
  968. wxEmptyString, wxITEM_CHECK );
  969. menu.Append( showZeroNetPads );
  970. showZeroNetPads->Check( m_showZeroPadNets );
  971. wxMenuItem* showUnconnectedNets = new wxMenuItem( &menu, ID_SHOW_UNCONNECTED_NETS,
  972. _( "Show Unconnected Nets" ),
  973. wxEmptyString, wxITEM_CHECK );
  974. menu.Append( showUnconnectedNets );
  975. showUnconnectedNets->Check( m_showUnconnectedNets );
  976. menu.AppendSeparator();
  977. // Report generation
  978. wxMenuItem* generateReport = new wxMenuItem( &menu, ID_GENERATE_REPORT,
  979. _( "Save Net Inspector Report..." ),
  980. wxEmptyString, wxITEM_NORMAL );
  981. menu.Append( generateReport );
  982. menu.AppendSeparator();
  983. // Show / hide columns menu items
  984. wxMenu* colsMenu = new wxMenu();
  985. generateShowHideColumnMenu( colsMenu );
  986. menu.AppendSubMenu( colsMenu, _( "Show / Hide Columns" ) );
  987. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onContextMenuSelection, this );
  988. PopupMenu( &menu );
  989. }
  990. void PCB_NET_INSPECTOR_PANEL::generateShowHideColumnMenu( wxMenu* target )
  991. {
  992. for( int i = 1; i <= COLUMN_LAST_STATIC_COL; ++i )
  993. {
  994. wxMenuItem* opt = new wxMenuItem( target, ID_HIDE_COLUMN + i, m_columns[i].display_name,
  995. wxEmptyString, wxITEM_CHECK );
  996. wxDataViewColumn* col = getDisplayedColumnForModelField( i );
  997. target->Append( opt );
  998. opt->Check( !col->IsHidden() );
  999. }
  1000. target->AppendSeparator();
  1001. for( std::size_t i = COLUMN_LAST_STATIC_COL + 1; i < m_columns.size(); ++i )
  1002. {
  1003. wxMenuItem* opt = new wxMenuItem( target, ID_HIDE_COLUMN + i, m_columns[i].display_name,
  1004. wxEmptyString, wxITEM_CHECK );
  1005. wxDataViewColumn* col = getDisplayedColumnForModelField( i );
  1006. target->Append( opt );
  1007. opt->Check( !col->IsHidden() );
  1008. }
  1009. }
  1010. void PCB_NET_INSPECTOR_PANEL::onContextMenuSelection( wxCommandEvent& event )
  1011. {
  1012. bool saveAndRebuild = true;
  1013. switch( event.GetId() )
  1014. {
  1015. case ID_ADD_NET:
  1016. onAddNet();
  1017. break;
  1018. case ID_RENAME_NET:
  1019. onRenameSelectedNet();
  1020. break;
  1021. case ID_DELETE_NET:
  1022. onDeleteSelectedNet();
  1023. break;
  1024. case ID_ADD_GROUP:
  1025. onAddGroup();
  1026. break;
  1027. case ID_GROUP_BY_CONSTRAINT: m_groupByConstraint = !m_groupByConstraint; break;
  1028. case ID_GROUP_BY_NETCLASS: m_groupByNetclass = !m_groupByNetclass; break;
  1029. case ID_FILTER_BY_NET_NAME: m_filterByNetName = !m_filterByNetName; break;
  1030. case ID_FILTER_BY_NETCLASS: m_filterByNetclass = !m_filterByNetclass; break;
  1031. case ID_REMOVE_SELECTED_GROUP:
  1032. onRemoveSelectedGroup();
  1033. break;
  1034. case ID_REMOVE_GROUPS:
  1035. m_custom_group_rules.clear();
  1036. break;
  1037. case ID_SHOW_ZERO_NET_PADS: m_showZeroPadNets = !m_showZeroPadNets; break;
  1038. case ID_SHOW_UNCONNECTED_NETS: m_showUnconnectedNets = !m_showUnconnectedNets; break;
  1039. case ID_GENERATE_REPORT:
  1040. generateReport();
  1041. saveAndRebuild = false;
  1042. break;
  1043. case ID_HIGHLIGHT_SELECTED_NETS:
  1044. highlightSelectedNets();
  1045. saveAndRebuild = false;
  1046. break;
  1047. case ID_CLEAR_HIGHLIGHTING:
  1048. onClearHighlighting();
  1049. saveAndRebuild = false;
  1050. break;
  1051. default:
  1052. if( event.GetId() >= ID_HIDE_COLUMN )
  1053. {
  1054. const int columnId = event.GetId() - ID_HIDE_COLUMN;
  1055. wxDataViewColumn* col = getDisplayedColumnForModelField( columnId );
  1056. // Make sure we end up with something non-zero so we can resize it
  1057. col->SetWidth( std::max( col->GetWidth(), 10 ) );
  1058. col->SetHidden( !col->IsHidden() );
  1059. }
  1060. break;
  1061. }
  1062. if( saveAndRebuild )
  1063. {
  1064. SaveSettings();
  1065. buildNetsList();
  1066. }
  1067. }
  1068. void PCB_NET_INSPECTOR_PANEL::onRemoveSelectedGroup()
  1069. {
  1070. if( m_netsList->GetSelectedItemsCount() == 1 )
  1071. {
  1072. auto* selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1073. if( selItem->GetIsGroup() )
  1074. {
  1075. const wxString groupName = selItem->GetGroupName();
  1076. const auto groupIter = std::ranges::find_if( m_custom_group_rules,
  1077. [&]( std::unique_ptr<EDA_COMBINED_MATCHER>& rule )
  1078. {
  1079. return rule->GetPattern() == groupName;
  1080. } );
  1081. if( groupIter != m_custom_group_rules.end() )
  1082. {
  1083. m_custom_group_rules.erase( groupIter );
  1084. SaveSettings();
  1085. buildNetsList();
  1086. }
  1087. }
  1088. }
  1089. }
  1090. void PCB_NET_INSPECTOR_PANEL::generateReport()
  1091. {
  1092. wxFileDialog dlg( this, _( "Save Net Inspector Report File" ), "", "",
  1093. _( "Report file" ) + AddFileExtListToFilter( { "csv" } ),
  1094. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  1095. if( dlg.ShowModal() == wxID_CANCEL )
  1096. return;
  1097. wxTextFile f( dlg.GetPath() );
  1098. f.Create();
  1099. wxString txt;
  1100. m_inReporting = true;
  1101. // Print Header:
  1102. for( auto&& col : m_columns )
  1103. {
  1104. txt += '"';
  1105. if( col.has_units )
  1106. {
  1107. txt += wxString::Format( _( "%s (%s)" ),
  1108. col.csv_name,
  1109. EDA_UNIT_UTILS::GetLabel( m_frame->GetUserUnits() ) );
  1110. }
  1111. else
  1112. {
  1113. txt += col.csv_name;
  1114. }
  1115. txt += wxT( "\";" );
  1116. }
  1117. f.AddLine( txt );
  1118. // Print list of nets:
  1119. const unsigned int num_rows = m_dataModel->itemCount();
  1120. for( unsigned int row = 0; row < num_rows; row++ )
  1121. {
  1122. auto& i = m_dataModel->itemAt( row );
  1123. if( i.GetIsGroup() || i.GetNetCode() == 0 )
  1124. continue;
  1125. txt = "";
  1126. for( auto&& col : m_columns )
  1127. {
  1128. if( static_cast<int>( col.csv_flags ) & static_cast<int>( CSV_COLUMN_DESC::CSV_QUOTE ) )
  1129. txt += '"' + m_dataModel->valueAt( col.num, row ).GetString() + wxT( "\";" );
  1130. else
  1131. txt += m_dataModel->valueAt( col.num, row ).GetString() + ';';
  1132. }
  1133. f.AddLine( txt );
  1134. }
  1135. m_inReporting = false;
  1136. f.Write();
  1137. f.Close();
  1138. }
  1139. void PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated( wxDataViewEvent& event )
  1140. {
  1141. highlightSelectedNets();
  1142. }
  1143. void PCB_NET_INSPECTOR_PANEL::highlightSelectedNets()
  1144. {
  1145. // ignore selection changes while the whole list is being rebuilt.
  1146. if( m_inBuildNetsList )
  1147. return;
  1148. m_highlightingNets = true;
  1149. RENDER_SETTINGS* renderSettings = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  1150. if( m_netsList->HasSelection() )
  1151. {
  1152. wxDataViewItemArray sel;
  1153. m_netsList->GetSelections( sel );
  1154. renderSettings->SetHighlight( false );
  1155. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1156. {
  1157. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1158. if( ii->GetIsGroup() )
  1159. {
  1160. for( auto c = ii->ChildrenBegin(), end = ii->ChildrenEnd(); c != end; ++c )
  1161. renderSettings->SetHighlight( true, ( *c )->GetNetCode(), true );
  1162. }
  1163. else
  1164. {
  1165. renderSettings->SetHighlight( true, ii->GetNetCode(), true );
  1166. }
  1167. }
  1168. }
  1169. else
  1170. {
  1171. renderSettings->SetHighlight( false );
  1172. }
  1173. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1174. m_frame->GetCanvas()->Refresh();
  1175. m_highlightingNets = false;
  1176. }
  1177. void PCB_NET_INSPECTOR_PANEL::OnColumnSorted( wxDataViewEvent& event )
  1178. {
  1179. if( !m_inBuildNetsList )
  1180. SaveSettings();
  1181. }
  1182. void PCB_NET_INSPECTOR_PANEL::OnParentSetupChanged()
  1183. {
  1184. // Rebuilt the nets list, and force rebuild of columns in case the stackup has changed
  1185. buildNetsList( true );
  1186. }
  1187. void PCB_NET_INSPECTOR_PANEL::onAddNet()
  1188. {
  1189. wxString newNetName;
  1190. NETNAME_VALIDATOR validator( &newNetName );
  1191. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "New Net" ), newNetName );
  1192. dlg.SetTextValidator( validator );
  1193. while( true )
  1194. {
  1195. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  1196. return; //Aborted by user
  1197. newNetName = dlg.GetValue();
  1198. if( m_board->FindNet( newNetName ) )
  1199. {
  1200. DisplayError( this,
  1201. wxString::Format( _( "Net name '%s' is already in use." ), newNetName ) );
  1202. newNetName = wxEmptyString;
  1203. }
  1204. else
  1205. {
  1206. break;
  1207. }
  1208. }
  1209. NETINFO_ITEM* newnet = new NETINFO_ITEM( m_board, dlg.GetValue(), 0 );
  1210. m_board->Add( newnet );
  1211. // We'll get an OnBoardItemAdded callback from this to update our listbox
  1212. m_frame->OnModify();
  1213. }
  1214. void PCB_NET_INSPECTOR_PANEL::onRenameSelectedNet()
  1215. {
  1216. if( m_netsList->GetSelectedItemsCount() == 1 )
  1217. {
  1218. const LIST_ITEM* sel = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1219. if( sel->GetIsGroup() )
  1220. return;
  1221. NETINFO_ITEM* net = sel->GetNet();
  1222. wxString fullNetName = net->GetNetname();
  1223. wxString netPath;
  1224. wxString shortNetName;
  1225. if( fullNetName.Contains( wxT( "/" ) ) )
  1226. {
  1227. netPath = fullNetName.BeforeLast( '/' ) + '/';
  1228. shortNetName = fullNetName.AfterLast( '/' );
  1229. }
  1230. else
  1231. {
  1232. shortNetName = fullNetName;
  1233. }
  1234. wxString unescapedShortName = UnescapeString( shortNetName );
  1235. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "Rename Net" ), unescapedShortName );
  1236. NETNAME_VALIDATOR validator( &unescapedShortName );
  1237. dlg.SetTextValidator( validator );
  1238. while( true )
  1239. {
  1240. if( dlg.ShowModal() != wxID_OK || dlg.GetValue() == unescapedShortName )
  1241. return;
  1242. unescapedShortName = dlg.GetValue();
  1243. if( unescapedShortName.IsEmpty() )
  1244. {
  1245. DisplayError( this, wxString::Format( _( "Net name cannot be empty." ),
  1246. unescapedShortName ) );
  1247. continue;
  1248. }
  1249. shortNetName = EscapeString( unescapedShortName, CTX_NETNAME );
  1250. fullNetName = netPath + shortNetName;
  1251. if( m_board->FindNet( shortNetName ) || m_board->FindNet( fullNetName ) )
  1252. {
  1253. DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ),
  1254. unescapedShortName ) );
  1255. unescapedShortName = wxEmptyString;
  1256. }
  1257. else
  1258. {
  1259. break;
  1260. }
  1261. }
  1262. for( BOARD_CONNECTED_ITEM* boardItem : m_frame->GetBoard()->AllConnectedItems() )
  1263. {
  1264. if( boardItem->GetNet() == net )
  1265. boardItem->SetFlags( CANDIDATE );
  1266. else
  1267. boardItem->ClearFlags( CANDIDATE );
  1268. }
  1269. // the changed name might require re-grouping. remove/re-insert is easier.
  1270. auto removed_item = m_dataModel->deleteItem( m_dataModel->findItem( net ) );
  1271. m_board->Remove( net );
  1272. net->SetNetname( fullNetName );
  1273. m_board->Add( net );
  1274. for( BOARD_CONNECTED_ITEM* boardItem : m_frame->GetBoard()->AllConnectedItems() )
  1275. {
  1276. if( boardItem->GetFlags() & CANDIDATE )
  1277. boardItem->SetNet( net );
  1278. }
  1279. buildNetsList();
  1280. if( std::optional<LIST_ITEM_ITER> r = m_dataModel->findItem( net ) )
  1281. m_netsList->Select( wxDataViewItem( r.value()->get() ) );
  1282. m_frame->OnModify();
  1283. // Currently only tracks and pads have netname annotations and need to be redrawn,
  1284. // but zones are likely to follow. Since we don't have a way to ask what is current,
  1285. // just refresh all items.
  1286. m_frame->GetCanvas()->GetView()->UpdateAllItems( KIGFX::REPAINT );
  1287. m_frame->GetCanvas()->Refresh();
  1288. }
  1289. }
  1290. void PCB_NET_INSPECTOR_PANEL::onDeleteSelectedNet()
  1291. {
  1292. if( !m_netsList->HasSelection() )
  1293. return;
  1294. wxDataViewItemArray sel;
  1295. m_netsList->GetSelections( sel );
  1296. auto delete_one = [this]( const LIST_ITEM* i )
  1297. {
  1298. if( i->GetPadCount() == 0
  1299. || IsOK( this, wxString::Format( _( "Net '%s' is in use. Delete anyway?" ),
  1300. i->GetNetName() ) ) )
  1301. {
  1302. // This is a bit hacky, but it will do for now, since this is the only path
  1303. // outside the netlist updater where you can remove a net from a BOARD.
  1304. int removedCode = i->GetNetCode();
  1305. m_frame->GetCanvas()->GetView()->UpdateAllItemsConditionally(
  1306. [removedCode]( KIGFX::VIEW_ITEM* aItem ) -> int
  1307. {
  1308. auto boardItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem );
  1309. if( boardItem && boardItem->GetNetCode() == removedCode )
  1310. return KIGFX::REPAINT;
  1311. EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( aItem );
  1312. if( text && text->HasTextVars() )
  1313. {
  1314. text->ClearRenderCache();
  1315. text->ClearBoundingBoxCache();
  1316. return KIGFX::GEOMETRY | KIGFX::REPAINT;
  1317. }
  1318. return 0;
  1319. } );
  1320. m_board->Remove( i->GetNet() );
  1321. m_frame->OnModify();
  1322. // We'll get an OnBoardItemRemoved callback from this to update our listbox
  1323. }
  1324. };
  1325. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1326. {
  1327. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1328. if( ii->GetIsGroup() )
  1329. {
  1330. if( ii->ChildrenCount() != 0
  1331. && IsOK( this, wxString::Format( _( "Delete all nets in group '%s'?" ),
  1332. ii->GetGroupName() ) ) )
  1333. {
  1334. // we can't be iterating the children container and deleting items from
  1335. // it at the same time. thus take a copy of it first.
  1336. std::vector<const LIST_ITEM*> children;
  1337. children.reserve( ii->ChildrenCount() );
  1338. std::copy( ii->ChildrenBegin(), ii->ChildrenEnd(), std::back_inserter( children ) );
  1339. for( const LIST_ITEM* c : children )
  1340. delete_one( c );
  1341. }
  1342. }
  1343. else
  1344. {
  1345. delete_one( ii );
  1346. }
  1347. }
  1348. }
  1349. /*****************************************************************************************
  1350. *
  1351. * Application-generated event handling
  1352. *
  1353. * ***************************************************************************************/
  1354. void PCB_NET_INSPECTOR_PANEL::OnLanguageChangedImpl()
  1355. {
  1356. SaveSettings();
  1357. buildNetsList( true );
  1358. m_dataModel->updateAllItems();
  1359. }
  1360. void PCB_NET_INSPECTOR_PANEL::onUnitsChanged( wxCommandEvent& event )
  1361. {
  1362. m_dataModel->updateAllItems();
  1363. event.Skip();
  1364. }
  1365. /*****************************************************************************************
  1366. *
  1367. * Settings persistence
  1368. *
  1369. * ***************************************************************************************/
  1370. void PCB_NET_INSPECTOR_PANEL::SaveSettings()
  1371. {
  1372. // Don't save settings if a board has not yet been loaded or the panel hasn't been displayed.
  1373. // Events fire while we set up the panel which overwrite the settings we haven't yet loaded.
  1374. bool displayed = false;
  1375. for( unsigned int ii = 0; ii < m_dataModel->columnCount() && !displayed; ++ii )
  1376. {
  1377. if( m_netsList->GetColumn( ii )->GetWidth() > 0 )
  1378. displayed = true;
  1379. }
  1380. if( !displayed || !m_boardLoaded || m_boardLoading )
  1381. return;
  1382. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  1383. auto& cfg = localSettings.m_NetInspectorPanel;
  1384. // User-defined filters / grouping
  1385. cfg.filter_text = m_searchCtrl->GetValue();
  1386. cfg.filter_by_net_name = m_filterByNetName;
  1387. cfg.filter_by_netclass = m_filterByNetclass;
  1388. cfg.group_by_netclass = m_groupByNetclass;
  1389. cfg.group_by_constraint = m_groupByConstraint;
  1390. cfg.show_zero_pad_nets = m_showZeroPadNets;
  1391. cfg.show_unconnected_nets = m_showUnconnectedNets;
  1392. // Grid sorting
  1393. wxDataViewColumn* sortingCol = m_netsList->GetSortingColumn();
  1394. cfg.sorting_column = sortingCol ? static_cast<int>( sortingCol->GetModelColumn() ) : -1;
  1395. cfg.sort_order_asc = sortingCol ? sortingCol->IsSortOrderAscending() : true;
  1396. // Column arrangement / sizes
  1397. cfg.col_order.resize( m_dataModel->columnCount() );
  1398. cfg.col_widths.resize( m_dataModel->columnCount() );
  1399. cfg.col_hidden.resize( m_dataModel->columnCount() );
  1400. for( unsigned int ii = 0; ii < m_dataModel->columnCount(); ++ii )
  1401. {
  1402. cfg.col_order[ii] = (int) m_netsList->GetColumn( ii )->GetModelColumn();
  1403. cfg.col_widths[ii] = m_netsList->GetColumn( ii )->GetWidth();
  1404. cfg.col_hidden[ii] = m_netsList->GetColumn( ii )->IsHidden();
  1405. }
  1406. // Expanded rows
  1407. cfg.expanded_rows.clear();
  1408. std::vector<std::pair<wxString, wxDataViewItem>> groupItems = m_dataModel->getGroupDataViewItems();
  1409. for( std::pair<wxString, wxDataViewItem>& item : groupItems )
  1410. {
  1411. if( m_netsList->IsExpanded( item.second ) )
  1412. cfg.expanded_rows.push_back( item.first );
  1413. }
  1414. // Customer group rules
  1415. cfg.custom_group_rules.clear();
  1416. for( const std::unique_ptr<EDA_COMBINED_MATCHER>& rule : m_custom_group_rules )
  1417. cfg.custom_group_rules.push_back( rule->GetPattern() );
  1418. }