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.

1802 lines
60 KiB

1 year ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year 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. m_netsList->Freeze();
  333. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  334. PANEL_NET_INSPECTOR_SETTINGS* cfg = &localSettings.m_NetInspectorPanel;
  335. // Refresh all filtering / grouping settings
  336. m_filterByNetName = cfg->filter_by_net_name;
  337. m_filterByNetclass = cfg->filter_by_netclass;
  338. m_showZeroPadNets = cfg->show_zero_pad_nets;
  339. m_groupByNetclass = cfg->group_by_netclass;
  340. m_groupByConstraint = cfg->group_by_constraint;
  341. // Attempt to keep any expanded groups open
  342. if( m_boardLoaded && !m_boardLoading )
  343. {
  344. cfg->expanded_rows.clear();
  345. DATA_MODEL* model = static_cast<DATA_MODEL*>( m_netsList->GetModel() );
  346. for( const auto& groupItems = model->getGroupDataViewItems();
  347. auto& [groupName, groupItem] : groupItems )
  348. {
  349. if( m_netsList->IsExpanded( groupItem ) )
  350. cfg->expanded_rows.push_back( groupName );
  351. }
  352. }
  353. // When rebuilding the netlist, try to keep the row selection
  354. wxDataViewItemArray sel;
  355. m_netsList->GetSelections( sel );
  356. std::vector<int> prev_selected_netcodes;
  357. prev_selected_netcodes.reserve( sel.GetCount() );
  358. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  359. {
  360. const LIST_ITEM* item = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  361. prev_selected_netcodes.push_back( item->GetNetCode() );
  362. }
  363. int sorting_column_id = cfg->sorting_column;
  364. bool sort_order_asc = cfg->sort_order_asc;
  365. if( wxDataViewColumn* sorting_column = m_netsList->GetSortingColumn() )
  366. {
  367. if( !m_boardLoading )
  368. {
  369. sorting_column_id = static_cast<int>( sorting_column->GetModelColumn() );
  370. sort_order_asc = sorting_column->IsSortOrderAscending();
  371. }
  372. // On GTK, wxDVC will crash if we rebuild with a sorting column set.
  373. sorting_column->UnsetAsSortKey();
  374. }
  375. if( rebuildColumns )
  376. {
  377. m_netsList->ClearColumns();
  378. buildColumns();
  379. }
  380. m_dataModel->deleteAllItems();
  381. m_custom_group_rules.clear();
  382. for( const wxString& rule : cfg->custom_group_rules )
  383. m_custom_group_rules.push_back( std::make_unique<EDA_COMBINED_MATCHER>( rule, CTX_NET ) );
  384. m_dataModel->addCustomGroups();
  385. std::vector<NETINFO_ITEM*> netCodes;
  386. for( NETINFO_ITEM* ni : m_board->GetNetInfo() )
  387. {
  388. if( netFilterMatches( ni, cfg ) )
  389. netCodes.emplace_back( ni );
  390. }
  391. std::ranges::sort( netCodes,
  392. []( const NETINFO_ITEM* a, const NETINFO_ITEM* b )
  393. {
  394. return a->GetNetCode() < b->GetNetCode();
  395. } );
  396. std::vector<std::unique_ptr<LIST_ITEM>> lengths = calculateNets( netCodes, m_showZeroPadNets );
  397. m_dataModel->addItems( lengths );
  398. // Try to re-enable the sorting column
  399. if( !restoreSortColumn( sorting_column_id, sort_order_asc ))
  400. {
  401. // By default, sort by Name column
  402. restoreSortColumn( COLUMN_NAME, true );
  403. }
  404. // Try to restore the expanded groups
  405. if( m_boardLoaded )
  406. {
  407. m_rowExpanding = true;
  408. std::vector<std::pair<wxString, wxDataViewItem>> groupItems = m_dataModel->getGroupDataViewItems();
  409. for( wxString& groupName : cfg->expanded_rows )
  410. {
  411. auto pred =
  412. [&groupName]( const std::pair<wxString, wxDataViewItem>& item )
  413. {
  414. return groupName == item.first;
  415. };
  416. auto tableItem = std::ranges::find_if( groupItems, pred );
  417. if( tableItem != groupItems.end() )
  418. m_netsList->Expand( tableItem->second );
  419. }
  420. m_rowExpanding = false;
  421. }
  422. // Try to restore the selected rows
  423. sel.Clear();
  424. for( const int& nc : prev_selected_netcodes )
  425. {
  426. if( std::optional<LIST_ITEM_ITER> r = m_dataModel->findItem( nc ) )
  427. {
  428. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  429. sel.Add( wxDataViewItem( list_item.get() ) );
  430. }
  431. }
  432. m_netsList->Thaw(); // Must thaw before reselecting to avoid windows selection bug
  433. if( !sel.IsEmpty() )
  434. {
  435. m_netsList->SetSelections( sel );
  436. m_netsList->EnsureVisible( sel.Item( 0 ) );
  437. }
  438. else
  439. {
  440. m_netsList->UnselectAll();
  441. }
  442. m_inBuildNetsList = false;
  443. }
  444. bool PCB_NET_INSPECTOR_PANEL::netFilterMatches( NETINFO_ITEM* aNet,
  445. PANEL_NET_INSPECTOR_SETTINGS* cfg ) const
  446. {
  447. if( cfg == nullptr )
  448. {
  449. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  450. cfg = &localSettings.m_NetInspectorPanel;
  451. }
  452. // Never show an unconnected net
  453. if( aNet->GetNetCode() <= 0 )
  454. return false;
  455. const wxString filterString = UnescapeString( m_searchCtrl->GetValue() ).Upper();
  456. const wxString netName = UnescapeString( aNet->GetNetname() ).Upper();
  457. const NETCLASS* netClass = aNet->GetNetClass();
  458. const wxString netClassName = UnescapeString( netClass->GetName() ).Upper();
  459. bool matched = false;
  460. // No filter - match all
  461. if( filterString.Length() == 0 )
  462. matched = true;
  463. // Search on net class
  464. if( !matched && cfg->filter_by_netclass && netClassName.Find( filterString ) != wxNOT_FOUND )
  465. matched = true;
  466. // Search on net name
  467. if( !matched && cfg->filter_by_net_name && netName.Find( filterString ) != wxNOT_FOUND )
  468. matched = true;
  469. // Remove unconnected nets if required
  470. if( matched )
  471. {
  472. if( !m_showUnconnectedNets )
  473. matched = !netName.StartsWith( wxT( "UNCONNECTED-(" ) );
  474. }
  475. return matched;
  476. }
  477. struct NETCODE_CMP_LESS
  478. {
  479. bool operator()( const CN_ITEM* a, const CN_ITEM* b ) const { return a->Net() < b->Net(); }
  480. bool operator()( const CN_ITEM* a, int b ) const { return a->Net() < b; }
  481. bool operator()( int a, const CN_ITEM* b ) const { return a < b->Net(); }
  482. };
  483. std::vector<CN_ITEM*> PCB_NET_INSPECTOR_PANEL::relevantConnectivityItems() const
  484. {
  485. // Pre-filter the connectivity items and sort them by netcode. This avoids quadratic runtime when building the whole
  486. // net list.
  487. const auto type_bits = std::bitset<MAX_STRUCT_TYPE_ID>()
  488. .set( PCB_TRACE_T )
  489. .set( PCB_ARC_T )
  490. .set( PCB_VIA_T )
  491. .set( PCB_PAD_T );
  492. std::vector<CN_ITEM*> cn_items;
  493. cn_items.reserve( 1024 );
  494. for( CN_ITEM* cn_item : m_board->GetConnectivity()->GetConnectivityAlgo()->ItemList() )
  495. {
  496. if( cn_item->Valid() && type_bits[cn_item->Parent()->Type()] )
  497. cn_items.push_back( cn_item );
  498. }
  499. std::ranges::sort( cn_items, NETCODE_CMP_LESS() );
  500. return cn_items;
  501. }
  502. std::vector<std::unique_ptr<PCB_NET_INSPECTOR_PANEL::LIST_ITEM>>
  503. PCB_NET_INSPECTOR_PANEL::calculateNets( const std::vector<NETINFO_ITEM*>& aNetCodes, bool aIncludeZeroPadNets ) const
  504. {
  505. std::vector<std::unique_ptr<LIST_ITEM>> results;
  506. LENGTH_CALCULATION* calc = m_board->GetLengthCalculation();
  507. const std::vector<CN_ITEM*> conItems = relevantConnectivityItems();
  508. // First assemble the LENGTH_CALCULATION_ITEMs for board items which match the nets we need to recompute
  509. // Precondition: conItems and aNetCodes are sorted in increasing netcode value
  510. // Functionality: This extracts any items from conItems which have a netcode which is present in aNetCodes
  511. std::unordered_map<int, std::vector<LENGTH_CALCULATION_ITEM>> netItemsMap;
  512. std::vector<NETINFO_ITEM*> foundNets;
  513. auto itemItr = conItems.begin();
  514. auto netCodeItr = aNetCodes.begin();
  515. while( itemItr != conItems.end() && netCodeItr != aNetCodes.end() )
  516. {
  517. const int curNetCode = ( *netCodeItr )->GetNetCode();
  518. const int curItemNetCode = ( *itemItr )->Net();
  519. if( curItemNetCode == curNetCode )
  520. {
  521. if( foundNets.empty() || foundNets.back() != *netCodeItr )
  522. foundNets.emplace_back( *netCodeItr );
  523. // Take the item
  524. LENGTH_CALCULATION_ITEM lengthItem = calc->GetLengthCalculationItem( ( *itemItr )->Parent() );
  525. netItemsMap[curItemNetCode].emplace_back( std::move( lengthItem ) );
  526. ++itemItr;
  527. }
  528. else if( curItemNetCode < curNetCode )
  529. {
  530. // Fast-forward through items
  531. while( itemItr != conItems.end() && ( *itemItr )->Net() < curNetCode )
  532. ++itemItr;
  533. }
  534. else if( curItemNetCode > curNetCode )
  535. {
  536. // Fast-forward through required net codes
  537. while( netCodeItr != aNetCodes.end() && curItemNetCode > ( *netCodeItr )->GetNetCode() )
  538. ++netCodeItr;
  539. }
  540. }
  541. // Now calculate the length statistics for each net. This includes potentially expensive path optimisations, so
  542. // parallelize this work.
  543. std::mutex resultsMutex;
  544. thread_pool& tp = GetKiCadThreadPool();
  545. auto resultsFuture = tp.parallelize_loop(
  546. 0, foundNets.size(),
  547. [&, this, calc]( const int start, const int end )
  548. {
  549. for( int i = start; i < end; ++i )
  550. {
  551. int netCode = foundNets[i]->GetNetCode();
  552. constexpr PATH_OPTIMISATIONS opts = { .OptimiseViaLayers = true,
  553. .MergeTracks = true,
  554. .OptimiseTracesInPads = true,
  555. .InferViaInPad = false };
  556. LENGTH_DETAILS lengthDetails =
  557. calc->CalculateLengthDetails( netItemsMap[netCode], opts, nullptr, nullptr, true );
  558. if( aIncludeZeroPadNets || lengthDetails.NumPads > 0 )
  559. {
  560. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( foundNets[i] );
  561. new_item->SetPadCount( lengthDetails.NumPads );
  562. new_item->SetLayerCount( m_board->GetCopperLayerCount() );
  563. new_item->SetPadDieLength( lengthDetails.PadToDieLength );
  564. new_item->SetViaCount( lengthDetails.NumVias );
  565. new_item->SetViaLength( lengthDetails.ViaLength );
  566. new_item->SetLayerWireLengths( *lengthDetails.LayerLengths );
  567. std::scoped_lock lock( resultsMutex );
  568. results.emplace_back( std::move( new_item ) );
  569. }
  570. }
  571. } );
  572. resultsFuture.get();
  573. return results;
  574. }
  575. /*****************************************************************************************
  576. *
  577. * Formatting helpers
  578. *
  579. * ***************************************************************************************/
  580. wxString PCB_NET_INSPECTOR_PANEL::formatNetCode( const NETINFO_ITEM* aNet )
  581. {
  582. return wxString::Format( wxT( "%.3d" ), aNet->GetNetCode() );
  583. }
  584. wxString PCB_NET_INSPECTOR_PANEL::formatNetName( const NETINFO_ITEM* aNet )
  585. {
  586. return UnescapeString( aNet->GetNetname() );
  587. }
  588. wxString PCB_NET_INSPECTOR_PANEL::formatCount( const unsigned int aValue )
  589. {
  590. return wxString::Format( wxT( "%u" ), aValue );
  591. }
  592. wxString PCB_NET_INSPECTOR_PANEL::formatLength( const int64_t aValue ) const
  593. {
  594. return m_frame->MessageTextFromValue( aValue,
  595. // don't include unit label in the string when reporting
  596. !m_inReporting );
  597. }
  598. void PCB_NET_INSPECTOR_PANEL::updateDisplayedRowValues( const std::optional<LIST_ITEM_ITER>& aRow ) const
  599. {
  600. if( !aRow )
  601. return;
  602. wxDataViewItemArray sel;
  603. m_netsList->GetSelections( sel );
  604. m_dataModel->updateItem( aRow );
  605. if( !sel.IsEmpty() )
  606. {
  607. m_netsList->SetSelections( sel );
  608. m_netsList->EnsureVisible( sel.Item( 0 ) );
  609. }
  610. }
  611. /*****************************************************************************************
  612. *
  613. * BOARD_LISTENER event handling
  614. *
  615. * ***************************************************************************************/
  616. void PCB_NET_INSPECTOR_PANEL::OnBoardChanged()
  617. {
  618. m_board = m_frame->GetBoard();
  619. if( m_board )
  620. m_board->AddListener( this );
  621. m_boardLoaded = true;
  622. m_boardLoading = true;
  623. const PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  624. auto& cfg = localSettings.m_NetInspectorPanel;
  625. m_searchCtrl->SetValue( cfg.filter_text );
  626. buildNetsList( true );
  627. m_boardLoading = false;
  628. }
  629. void PCB_NET_INSPECTOR_PANEL::OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  630. {
  631. const std::vector<BOARD_ITEM*> item{ aBoardItem };
  632. updateBoardItems( item );
  633. }
  634. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsAdded( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems )
  635. {
  636. updateBoardItems( aBoardItems );
  637. }
  638. void PCB_NET_INSPECTOR_PANEL::updateBoardItems( const std::vector<BOARD_ITEM*>& aBoardItems )
  639. {
  640. if( !IsShownOnScreen() )
  641. return;
  642. // Rebuild full list for large changes
  643. if( aBoardItems.size()
  644. > static_cast<size_t>( ADVANCED_CFG::GetCfg().m_NetInspectorBulkUpdateOptimisationThreshold ) )
  645. {
  646. buildNetsList();
  647. }
  648. else
  649. {
  650. std::vector<NETINFO_ITEM*> changedNets;
  651. for( BOARD_ITEM* boardItem : aBoardItems )
  652. {
  653. if( NETINFO_ITEM* net = dynamic_cast<NETINFO_ITEM*>( boardItem ) )
  654. {
  655. // A new net has been added to the board. Add it to our list if it passes the netname filter test.
  656. if( netFilterMatches( net ) )
  657. changedNets.emplace_back( net );
  658. }
  659. else if( BOARD_CONNECTED_ITEM* i = dynamic_cast<BOARD_CONNECTED_ITEM*>( boardItem ) )
  660. {
  661. changedNets.emplace_back( i->GetNet() );
  662. }
  663. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( boardItem ) )
  664. {
  665. for( const PAD* pad : footprint->Pads() )
  666. {
  667. if( netFilterMatches( pad->GetNet() ) )
  668. changedNets.emplace_back( pad->GetNet() );
  669. }
  670. }
  671. }
  672. std::ranges::sort( changedNets,
  673. []( const NETINFO_ITEM* a, const NETINFO_ITEM* b )
  674. {
  675. return a->GetNetCode() < b->GetNetCode();
  676. } );
  677. updateNets( changedNets );
  678. }
  679. m_netsList->Refresh();
  680. }
  681. void PCB_NET_INSPECTOR_PANEL::updateNets( const std::vector<NETINFO_ITEM*>& aNets ) const
  682. {
  683. std::vector<NETINFO_ITEM*> netsToUpdate;
  684. std::unordered_set<NETINFO_ITEM*> netsToDelete;
  685. for( NETINFO_ITEM* net : aNets )
  686. {
  687. // Add all nets to the deletion list - we will prune this later to only contain unhandled nets
  688. netsToDelete.insert( net );
  689. // Only calculate nets that match the current filter
  690. if( netFilterMatches( net ) )
  691. netsToUpdate.emplace_back( net );
  692. }
  693. m_netsList->Freeze();
  694. std::vector<std::unique_ptr<LIST_ITEM>> newListItems = calculateNets( aNets, true );
  695. for( std::unique_ptr<LIST_ITEM>& newListItem : newListItems )
  696. {
  697. // Remove the handled net from the deletion list
  698. netsToDelete.erase( newListItem->GetNet() );
  699. std::optional<LIST_ITEM_ITER> curNetRow = m_dataModel->findItem( newListItem->GetNetCode() );
  700. if( !m_showZeroPadNets && newListItem->GetPadCount() == 0 )
  701. {
  702. m_dataModel->deleteItem( curNetRow );
  703. continue;
  704. }
  705. if( !curNetRow )
  706. {
  707. m_dataModel->addItem( std::move( newListItem ) );
  708. continue;
  709. }
  710. const std::unique_ptr<LIST_ITEM>& curListItem = *curNetRow.value();
  711. if( curListItem->GetNetName() != newListItem->GetNetName() )
  712. {
  713. // If the name has changed, it might require re-grouping. It's easier to remove and re-insert it.
  714. m_dataModel->deleteItem( curNetRow );
  715. m_dataModel->addItem( std::move( newListItem ) );
  716. }
  717. else
  718. {
  719. curListItem->SetPadCount( newListItem->GetPadCount() );
  720. curListItem->SetPadDieLength( newListItem->GetPadDieLength() );
  721. curListItem->SetViaCount( newListItem->GetViaCount() );
  722. curListItem->SetViaLength( newListItem->GetViaLength() );
  723. curListItem->SetLayerWireLengths( newListItem->GetLayerWireLengths() );
  724. updateDisplayedRowValues( curNetRow );
  725. }
  726. }
  727. // Delete any nets we have not yet handled
  728. for( const NETINFO_ITEM* netToDelete : netsToDelete )
  729. m_dataModel->deleteItem( m_dataModel->findItem( netToDelete->GetNetCode() ) );
  730. m_netsList->Thaw();
  731. }
  732. void PCB_NET_INSPECTOR_PANEL::OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  733. {
  734. const std::vector<BOARD_ITEM*> item{ aBoardItem };
  735. updateBoardItems( item );
  736. }
  737. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsRemoved( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItems )
  738. {
  739. updateBoardItems( aBoardItems );
  740. }
  741. void PCB_NET_INSPECTOR_PANEL::OnBoardNetSettingsChanged( BOARD& aBoard )
  742. {
  743. if( !IsShownOnScreen() )
  744. return;
  745. buildNetsList();
  746. }
  747. void PCB_NET_INSPECTOR_PANEL::OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  748. {
  749. const std::vector<BOARD_ITEM*> item{ aBoardItem };
  750. updateBoardItems( item );
  751. }
  752. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsChanged( BOARD& aBoard,
  753. std::vector<BOARD_ITEM*>& aBoardItems )
  754. {
  755. updateBoardItems( aBoardItems );
  756. }
  757. void PCB_NET_INSPECTOR_PANEL::OnBoardCompositeUpdate( BOARD& aBoard,
  758. std::vector<BOARD_ITEM*>& aAddedItems,
  759. std::vector<BOARD_ITEM*>& aRemovedItems,
  760. std::vector<BOARD_ITEM*>& aChangedItems )
  761. {
  762. if( !IsShownOnScreen() )
  763. return;
  764. std::vector<BOARD_ITEM*> allItems{ aAddedItems.begin(), aAddedItems.end() };
  765. allItems.insert( allItems.end(), aRemovedItems.begin(), aRemovedItems.end() );
  766. allItems.insert( allItems.end(), aChangedItems.begin(), aChangedItems.end() );
  767. updateBoardItems( allItems );
  768. }
  769. void PCB_NET_INSPECTOR_PANEL::OnBoardHighlightNetChanged( BOARD& aBoard )
  770. {
  771. if( m_highlightingNets || !IsShownOnScreen() )
  772. return;
  773. if( !m_board->IsHighLightNetON() )
  774. {
  775. m_netsList->UnselectAll();
  776. }
  777. else
  778. {
  779. const std::set<int>& selected_codes = m_board->GetHighLightNetCodes();
  780. wxDataViewItemArray new_selection;
  781. new_selection.Alloc( selected_codes.size() );
  782. for( const int code : selected_codes )
  783. {
  784. if( std::optional<LIST_ITEM_ITER> r = m_dataModel->findItem( code ) )
  785. new_selection.Add( wxDataViewItem( &***r ) );
  786. }
  787. m_netsList->SetSelections( new_selection );
  788. if( !new_selection.IsEmpty() )
  789. m_netsList->EnsureVisible( new_selection.Item( 0 ) );
  790. }
  791. }
  792. /*****************************************************************************************
  793. *
  794. * UI-generated event handling
  795. *
  796. * ***************************************************************************************/
  797. void PCB_NET_INSPECTOR_PANEL::OnShowPanel()
  798. {
  799. buildNetsList();
  800. OnBoardHighlightNetChanged( *m_board );
  801. }
  802. void PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu( wxDataViewEvent& event )
  803. {
  804. bool multipleSelections = false;
  805. const LIST_ITEM* selItem = nullptr;
  806. if( m_netsList->GetSelectedItemsCount() == 1 )
  807. {
  808. selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  809. }
  810. else
  811. {
  812. if( m_netsList->GetSelectedItemsCount() > 1 )
  813. multipleSelections = true;
  814. }
  815. wxMenu menu;
  816. // Net edit menu items
  817. wxMenuItem* highlightNet = new wxMenuItem( &menu, ID_HIGHLIGHT_SELECTED_NETS,
  818. _( "Highlight Selected Net" ),
  819. wxEmptyString, wxITEM_NORMAL );
  820. menu.Append( highlightNet );
  821. wxMenuItem* clearHighlighting = new wxMenuItem( &menu, ID_CLEAR_HIGHLIGHTING,
  822. _( "Clear Net Highlighting" ),
  823. wxEmptyString, wxITEM_NORMAL );
  824. menu.Append( clearHighlighting );
  825. RENDER_SETTINGS* renderSettings = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  826. const std::set<int>& selected_codes = renderSettings->GetHighlightNetCodes();
  827. if( selected_codes.size() == 0 )
  828. clearHighlighting->Enable( false );
  829. menu.AppendSeparator();
  830. wxMenuItem* renameNet = new wxMenuItem( &menu, ID_RENAME_NET, _( "Rename Selected Net..." ),
  831. wxEmptyString, wxITEM_NORMAL );
  832. menu.Append( renameNet );
  833. wxMenuItem* deleteNet = new wxMenuItem( &menu, ID_DELETE_NET, _( "Delete Selected Net" ),
  834. wxEmptyString, wxITEM_NORMAL );
  835. menu.Append( deleteNet );
  836. menu.AppendSeparator();
  837. wxMenuItem* addNet = new wxMenuItem( &menu, ID_ADD_NET, _( "Add Net..." ),
  838. wxEmptyString, wxITEM_NORMAL );
  839. menu.Append( addNet );
  840. if( !selItem && !multipleSelections )
  841. {
  842. highlightNet->Enable( false );
  843. deleteNet->Enable( false );
  844. renameNet->Enable( false );
  845. }
  846. else
  847. {
  848. if( multipleSelections || selItem->GetIsGroup() )
  849. {
  850. highlightNet->SetItemLabel( _( "Highlight Selected Nets" ) );
  851. renameNet->Enable( false );
  852. deleteNet->SetItemLabel( _( "Delete Selected Nets" ) );
  853. }
  854. }
  855. menu.AppendSeparator();
  856. wxMenuItem* removeSelectedGroup = new wxMenuItem( &menu, ID_REMOVE_SELECTED_GROUP,
  857. _( "Remove Selected Custom Group" ),
  858. wxEmptyString, wxITEM_NORMAL );
  859. menu.Append( removeSelectedGroup );
  860. if( !selItem || !selItem->GetIsGroup() )
  861. removeSelectedGroup->Enable( false );
  862. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onContextMenuSelection, this );
  863. PopupMenu( &menu );
  864. }
  865. void PCB_NET_INSPECTOR_PANEL::OnSearchTextChanged( wxCommandEvent& event )
  866. {
  867. SaveSettings();
  868. buildNetsList();
  869. }
  870. void PCB_NET_INSPECTOR_PANEL::onAddGroup()
  871. {
  872. wxString newGroupName;
  873. NETNAME_VALIDATOR validator( &newGroupName );
  874. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Group name / pattern:" ), _( "New Group" ), newGroupName );
  875. wxStaticText* help = new wxStaticText( &dlg, wxID_ANY,
  876. _( "(Use /.../ to indicate a regular expression.)" ) );
  877. help->SetFont( KIUI::GetInfoFont( this ).Italic() );
  878. dlg.m_ContentSizer->Add( help, 0, wxALL|wxEXPAND, 5 );
  879. dlg.SetTextValidator( validator );
  880. dlg.GetSizer()->SetSizeHints( &dlg );
  881. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  882. return; //Aborted by user
  883. newGroupName = UnescapeString( dlg.GetValue() );
  884. if( newGroupName == "" )
  885. return;
  886. if( std::ranges::find_if( m_custom_group_rules,
  887. [&]( std::unique_ptr<EDA_COMBINED_MATCHER>& rule )
  888. {
  889. return rule->GetPattern() == newGroupName;
  890. } )
  891. == m_custom_group_rules.end() )
  892. {
  893. m_custom_group_rules.push_back( std::make_unique<EDA_COMBINED_MATCHER>( newGroupName,
  894. CTX_NET ) );
  895. SaveSettings();
  896. }
  897. buildNetsList();
  898. }
  899. void PCB_NET_INSPECTOR_PANEL::onClearHighlighting()
  900. {
  901. m_highlightingNets = true;
  902. m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false );
  903. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  904. m_frame->GetCanvas()->Refresh();
  905. m_highlightingNets = false;
  906. }
  907. void PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow( wxCommandEvent& event )
  908. {
  909. if( !m_rowExpanding )
  910. SaveSettings();
  911. }
  912. void PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu( wxCommandEvent& event )
  913. {
  914. wxMenu menu;
  915. generateShowHideColumnMenu( &menu );
  916. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onContextMenuSelection, this );
  917. PopupMenu( &menu );
  918. }
  919. void PCB_NET_INSPECTOR_PANEL::OnConfigButton( wxCommandEvent& event )
  920. {
  921. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  922. auto& cfg = localSettings.m_NetInspectorPanel;
  923. const LIST_ITEM* selItem = nullptr;
  924. if( m_netsList->GetSelectedItemsCount() == 1 )
  925. {
  926. selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  927. }
  928. wxMenu menu;
  929. // Filtering menu items
  930. wxMenuItem* filterByNetName = new wxMenuItem( &menu, ID_FILTER_BY_NET_NAME,
  931. _( "Filter by Net Name" ),
  932. wxEmptyString, wxITEM_CHECK );
  933. menu.Append( filterByNetName );
  934. filterByNetName->Check( cfg.filter_by_net_name );
  935. wxMenuItem* filterByNetclass = new wxMenuItem( &menu, ID_FILTER_BY_NETCLASS,
  936. _( "Filter by Netclass" ),
  937. wxEmptyString, wxITEM_CHECK );
  938. menu.Append( filterByNetclass );
  939. filterByNetclass->Check( cfg.filter_by_netclass );
  940. menu.AppendSeparator();
  941. // Grouping menu items
  942. //wxMenuItem* groupConstraint =
  943. // new wxMenuItem( &menu, ID_GROUP_BY_CONSTRAINT, _( "Group by DRC Constraint" ),
  944. // wxEmptyString, wxITEM_CHECK );
  945. //groupConstraint->Check( m_group_by_constraint );
  946. //menu.Append( groupConstraint );
  947. wxMenuItem* groupNetclass = new wxMenuItem( &menu, ID_GROUP_BY_NETCLASS,
  948. _( "Group by Netclass" ),
  949. wxEmptyString, wxITEM_CHECK );
  950. menu.Append( groupNetclass );
  951. groupNetclass->Check( m_groupByNetclass );
  952. menu.AppendSeparator();
  953. wxMenuItem* addGroup = new wxMenuItem( &menu, ID_ADD_GROUP, _( "Add Custom Group..." ),
  954. wxEmptyString, wxITEM_NORMAL );
  955. menu.Append( addGroup );
  956. wxMenuItem* removeSelectedGroup = new wxMenuItem( &menu, ID_REMOVE_SELECTED_GROUP,
  957. _( "Remove Selected Custom Group" ),
  958. wxEmptyString, wxITEM_NORMAL );
  959. menu.Append( removeSelectedGroup );
  960. if( !selItem || !selItem->GetIsGroup() )
  961. removeSelectedGroup->Enable( false );
  962. wxMenuItem* removeCustomGroups = new wxMenuItem( &menu, ID_REMOVE_GROUPS,
  963. _( "Remove All Custom Groups" ),
  964. wxEmptyString, wxITEM_NORMAL );
  965. menu.Append( removeCustomGroups );
  966. removeCustomGroups->Enable( m_custom_group_rules.size() != 0 );
  967. menu.AppendSeparator();
  968. wxMenuItem* showZeroNetPads = new wxMenuItem( &menu, ID_SHOW_ZERO_NET_PADS,
  969. _( "Show Zero Pad Nets" ),
  970. wxEmptyString, wxITEM_CHECK );
  971. menu.Append( showZeroNetPads );
  972. showZeroNetPads->Check( m_showZeroPadNets );
  973. wxMenuItem* showUnconnectedNets = new wxMenuItem( &menu, ID_SHOW_UNCONNECTED_NETS,
  974. _( "Show Unconnected Nets" ),
  975. wxEmptyString, wxITEM_CHECK );
  976. menu.Append( showUnconnectedNets );
  977. showUnconnectedNets->Check( m_showUnconnectedNets );
  978. menu.AppendSeparator();
  979. // Report generation
  980. wxMenuItem* generateReport = new wxMenuItem( &menu, ID_GENERATE_REPORT,
  981. _( "Save Net Inspector Report..." ),
  982. wxEmptyString, wxITEM_NORMAL );
  983. menu.Append( generateReport );
  984. menu.AppendSeparator();
  985. // Show / hide columns menu items
  986. wxMenu* colsMenu = new wxMenu();
  987. generateShowHideColumnMenu( colsMenu );
  988. menu.AppendSubMenu( colsMenu, _( "Show / Hide Columns" ) );
  989. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onContextMenuSelection, this );
  990. PopupMenu( &menu );
  991. }
  992. void PCB_NET_INSPECTOR_PANEL::generateShowHideColumnMenu( wxMenu* target )
  993. {
  994. for( int i = 1; i <= COLUMN_LAST_STATIC_COL; ++i )
  995. {
  996. wxMenuItem* opt = new wxMenuItem( target, ID_HIDE_COLUMN + i, m_columns[i].display_name,
  997. wxEmptyString, wxITEM_CHECK );
  998. wxDataViewColumn* col = getDisplayedColumnForModelField( i );
  999. target->Append( opt );
  1000. opt->Check( !col->IsHidden() );
  1001. }
  1002. target->AppendSeparator();
  1003. for( std::size_t i = COLUMN_LAST_STATIC_COL + 1; i < m_columns.size(); ++i )
  1004. {
  1005. wxMenuItem* opt = new wxMenuItem( target, ID_HIDE_COLUMN + i, m_columns[i].display_name,
  1006. wxEmptyString, wxITEM_CHECK );
  1007. wxDataViewColumn* col = getDisplayedColumnForModelField( i );
  1008. target->Append( opt );
  1009. opt->Check( !col->IsHidden() );
  1010. }
  1011. }
  1012. void PCB_NET_INSPECTOR_PANEL::onContextMenuSelection( wxCommandEvent& event )
  1013. {
  1014. bool saveAndRebuild = true;
  1015. switch( event.GetId() )
  1016. {
  1017. case ID_ADD_NET:
  1018. onAddNet();
  1019. break;
  1020. case ID_RENAME_NET:
  1021. onRenameSelectedNet();
  1022. break;
  1023. case ID_DELETE_NET:
  1024. onDeleteSelectedNet();
  1025. break;
  1026. case ID_ADD_GROUP:
  1027. onAddGroup();
  1028. break;
  1029. case ID_GROUP_BY_CONSTRAINT: m_groupByConstraint = !m_groupByConstraint; break;
  1030. case ID_GROUP_BY_NETCLASS: m_groupByNetclass = !m_groupByNetclass; break;
  1031. case ID_FILTER_BY_NET_NAME: m_filterByNetName = !m_filterByNetName; break;
  1032. case ID_FILTER_BY_NETCLASS: m_filterByNetclass = !m_filterByNetclass; break;
  1033. case ID_REMOVE_SELECTED_GROUP:
  1034. onRemoveSelectedGroup();
  1035. break;
  1036. case ID_REMOVE_GROUPS:
  1037. m_custom_group_rules.clear();
  1038. break;
  1039. case ID_SHOW_ZERO_NET_PADS: m_showZeroPadNets = !m_showZeroPadNets; break;
  1040. case ID_SHOW_UNCONNECTED_NETS: m_showUnconnectedNets = !m_showUnconnectedNets; break;
  1041. case ID_GENERATE_REPORT:
  1042. generateReport();
  1043. saveAndRebuild = false;
  1044. break;
  1045. case ID_HIGHLIGHT_SELECTED_NETS:
  1046. highlightSelectedNets();
  1047. saveAndRebuild = false;
  1048. break;
  1049. case ID_CLEAR_HIGHLIGHTING:
  1050. onClearHighlighting();
  1051. saveAndRebuild = false;
  1052. break;
  1053. default:
  1054. if( event.GetId() >= ID_HIDE_COLUMN )
  1055. {
  1056. const int columnId = event.GetId() - ID_HIDE_COLUMN;
  1057. wxDataViewColumn* col = getDisplayedColumnForModelField( columnId );
  1058. // Make sure we end up with something non-zero so we can resize it
  1059. col->SetWidth( std::max( col->GetWidth(), 10 ) );
  1060. col->SetHidden( !col->IsHidden() );
  1061. }
  1062. break;
  1063. }
  1064. if( saveAndRebuild )
  1065. {
  1066. SaveSettings();
  1067. buildNetsList();
  1068. }
  1069. }
  1070. void PCB_NET_INSPECTOR_PANEL::onRemoveSelectedGroup()
  1071. {
  1072. if( m_netsList->GetSelectedItemsCount() == 1 )
  1073. {
  1074. auto* selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1075. if( selItem->GetIsGroup() )
  1076. {
  1077. const wxString groupName = selItem->GetGroupName();
  1078. const auto groupIter = std::ranges::find_if( m_custom_group_rules,
  1079. [&]( std::unique_ptr<EDA_COMBINED_MATCHER>& rule )
  1080. {
  1081. return rule->GetPattern() == groupName;
  1082. } );
  1083. if( groupIter != m_custom_group_rules.end() )
  1084. {
  1085. m_custom_group_rules.erase( groupIter );
  1086. SaveSettings();
  1087. buildNetsList();
  1088. }
  1089. }
  1090. }
  1091. }
  1092. void PCB_NET_INSPECTOR_PANEL::generateReport()
  1093. {
  1094. wxFileDialog dlg( this, _( "Save Net Inspector Report File" ), "", "",
  1095. _( "Report file" ) + AddFileExtListToFilter( { "csv" } ),
  1096. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  1097. if( dlg.ShowModal() == wxID_CANCEL )
  1098. return;
  1099. wxTextFile f( dlg.GetPath() );
  1100. f.Create();
  1101. wxString txt;
  1102. m_inReporting = true;
  1103. // Print Header:
  1104. for( auto&& col : m_columns )
  1105. {
  1106. txt += '"';
  1107. if( col.has_units )
  1108. {
  1109. txt += wxString::Format( _( "%s (%s)" ),
  1110. col.csv_name,
  1111. EDA_UNIT_UTILS::GetLabel( m_frame->GetUserUnits() ) );
  1112. }
  1113. else
  1114. {
  1115. txt += col.csv_name;
  1116. }
  1117. txt += wxT( "\";" );
  1118. }
  1119. f.AddLine( txt );
  1120. // Print list of nets:
  1121. const unsigned int num_rows = m_dataModel->itemCount();
  1122. for( unsigned int row = 0; row < num_rows; row++ )
  1123. {
  1124. auto& i = m_dataModel->itemAt( row );
  1125. if( i.GetIsGroup() || i.GetNetCode() == 0 )
  1126. continue;
  1127. txt = "";
  1128. for( auto&& col : m_columns )
  1129. {
  1130. if( static_cast<int>( col.csv_flags ) & static_cast<int>( CSV_COLUMN_DESC::CSV_QUOTE ) )
  1131. txt += '"' + m_dataModel->valueAt( col.num, row ).GetString() + wxT( "\";" );
  1132. else
  1133. txt += m_dataModel->valueAt( col.num, row ).GetString() + ';';
  1134. }
  1135. f.AddLine( txt );
  1136. }
  1137. m_inReporting = false;
  1138. f.Write();
  1139. f.Close();
  1140. }
  1141. void PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated( wxDataViewEvent& event )
  1142. {
  1143. highlightSelectedNets();
  1144. }
  1145. void PCB_NET_INSPECTOR_PANEL::highlightSelectedNets()
  1146. {
  1147. // ignore selection changes while the whole list is being rebuilt.
  1148. if( m_inBuildNetsList )
  1149. return;
  1150. m_highlightingNets = true;
  1151. RENDER_SETTINGS* renderSettings = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  1152. if( m_netsList->HasSelection() )
  1153. {
  1154. wxDataViewItemArray sel;
  1155. m_netsList->GetSelections( sel );
  1156. renderSettings->SetHighlight( false );
  1157. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1158. {
  1159. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1160. if( ii->GetIsGroup() )
  1161. {
  1162. for( auto c = ii->ChildrenBegin(), end = ii->ChildrenEnd(); c != end; ++c )
  1163. renderSettings->SetHighlight( true, ( *c )->GetNetCode(), true );
  1164. }
  1165. else
  1166. {
  1167. renderSettings->SetHighlight( true, ii->GetNetCode(), true );
  1168. }
  1169. }
  1170. }
  1171. else
  1172. {
  1173. renderSettings->SetHighlight( false );
  1174. }
  1175. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1176. m_frame->GetCanvas()->Refresh();
  1177. m_highlightingNets = false;
  1178. }
  1179. void PCB_NET_INSPECTOR_PANEL::OnColumnSorted( wxDataViewEvent& event )
  1180. {
  1181. if( !m_inBuildNetsList )
  1182. SaveSettings();
  1183. }
  1184. void PCB_NET_INSPECTOR_PANEL::OnParentSetupChanged()
  1185. {
  1186. // Rebuilt the nets list, and force rebuild of columns in case the stackup has changed
  1187. buildNetsList( true );
  1188. }
  1189. void PCB_NET_INSPECTOR_PANEL::onAddNet()
  1190. {
  1191. wxString newNetName;
  1192. NETNAME_VALIDATOR validator( &newNetName );
  1193. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "New Net" ), newNetName );
  1194. dlg.SetTextValidator( validator );
  1195. while( true )
  1196. {
  1197. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  1198. return; //Aborted by user
  1199. newNetName = dlg.GetValue();
  1200. if( m_board->FindNet( newNetName ) )
  1201. {
  1202. DisplayError( this,
  1203. wxString::Format( _( "Net name '%s' is already in use." ), newNetName ) );
  1204. newNetName = wxEmptyString;
  1205. }
  1206. else
  1207. {
  1208. break;
  1209. }
  1210. }
  1211. NETINFO_ITEM* newnet = new NETINFO_ITEM( m_board, dlg.GetValue(), 0 );
  1212. m_board->Add( newnet );
  1213. // We'll get an OnBoardItemAdded callback from this to update our listbox
  1214. m_frame->OnModify();
  1215. }
  1216. void PCB_NET_INSPECTOR_PANEL::onRenameSelectedNet()
  1217. {
  1218. if( m_netsList->GetSelectedItemsCount() == 1 )
  1219. {
  1220. const LIST_ITEM* sel = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1221. if( sel->GetIsGroup() )
  1222. return;
  1223. NETINFO_ITEM* net = sel->GetNet();
  1224. wxString fullNetName = net->GetNetname();
  1225. wxString netPath;
  1226. wxString shortNetName;
  1227. if( fullNetName.Contains( wxT( "/" ) ) )
  1228. {
  1229. netPath = fullNetName.BeforeLast( '/' ) + '/';
  1230. shortNetName = fullNetName.AfterLast( '/' );
  1231. }
  1232. else
  1233. {
  1234. shortNetName = fullNetName;
  1235. }
  1236. wxString unescapedShortName = UnescapeString( shortNetName );
  1237. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "Rename Net" ), unescapedShortName );
  1238. NETNAME_VALIDATOR validator( &unescapedShortName );
  1239. dlg.SetTextValidator( validator );
  1240. while( true )
  1241. {
  1242. if( dlg.ShowModal() != wxID_OK || dlg.GetValue() == unescapedShortName )
  1243. return;
  1244. unescapedShortName = dlg.GetValue();
  1245. if( unescapedShortName.IsEmpty() )
  1246. {
  1247. DisplayError( this, wxString::Format( _( "Net name cannot be empty." ),
  1248. unescapedShortName ) );
  1249. continue;
  1250. }
  1251. shortNetName = EscapeString( unescapedShortName, CTX_NETNAME );
  1252. fullNetName = netPath + shortNetName;
  1253. if( m_board->FindNet( shortNetName ) || m_board->FindNet( fullNetName ) )
  1254. {
  1255. DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ),
  1256. unescapedShortName ) );
  1257. unescapedShortName = wxEmptyString;
  1258. }
  1259. else
  1260. {
  1261. break;
  1262. }
  1263. }
  1264. for( BOARD_CONNECTED_ITEM* boardItem : m_frame->GetBoard()->AllConnectedItems() )
  1265. {
  1266. if( boardItem->GetNet() == net )
  1267. boardItem->SetFlags( CANDIDATE );
  1268. else
  1269. boardItem->ClearFlags( CANDIDATE );
  1270. }
  1271. // the changed name might require re-grouping. remove/re-insert is easier.
  1272. auto removed_item = m_dataModel->deleteItem( m_dataModel->findItem( net ) );
  1273. m_board->Remove( net );
  1274. net->SetNetname( fullNetName );
  1275. m_board->Add( net );
  1276. for( BOARD_CONNECTED_ITEM* boardItem : m_frame->GetBoard()->AllConnectedItems() )
  1277. {
  1278. if( boardItem->GetFlags() & CANDIDATE )
  1279. boardItem->SetNet( net );
  1280. }
  1281. buildNetsList();
  1282. if( std::optional<LIST_ITEM_ITER> r = m_dataModel->findItem( net ) )
  1283. m_netsList->Select( wxDataViewItem( r.value()->get() ) );
  1284. m_frame->OnModify();
  1285. // Currently only tracks and pads have netname annotations and need to be redrawn,
  1286. // but zones are likely to follow. Since we don't have a way to ask what is current,
  1287. // just refresh all items.
  1288. m_frame->GetCanvas()->GetView()->UpdateAllItems( KIGFX::REPAINT );
  1289. m_frame->GetCanvas()->Refresh();
  1290. }
  1291. }
  1292. void PCB_NET_INSPECTOR_PANEL::onDeleteSelectedNet()
  1293. {
  1294. if( !m_netsList->HasSelection() )
  1295. return;
  1296. wxDataViewItemArray sel;
  1297. m_netsList->GetSelections( sel );
  1298. auto delete_one = [this]( const LIST_ITEM* i )
  1299. {
  1300. if( i->GetPadCount() == 0
  1301. || IsOK( this, wxString::Format( _( "Net '%s' is in use. Delete anyway?" ),
  1302. i->GetNetName() ) ) )
  1303. {
  1304. // This is a bit hacky, but it will do for now, since this is the only path
  1305. // outside the netlist updater where you can remove a net from a BOARD.
  1306. int removedCode = i->GetNetCode();
  1307. m_frame->GetCanvas()->GetView()->UpdateAllItemsConditionally(
  1308. [removedCode]( KIGFX::VIEW_ITEM* aItem ) -> int
  1309. {
  1310. auto boardItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem );
  1311. if( boardItem && boardItem->GetNetCode() == removedCode )
  1312. return KIGFX::REPAINT;
  1313. EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( aItem );
  1314. if( text && text->HasTextVars() )
  1315. {
  1316. text->ClearRenderCache();
  1317. text->ClearBoundingBoxCache();
  1318. return KIGFX::GEOMETRY | KIGFX::REPAINT;
  1319. }
  1320. return 0;
  1321. } );
  1322. m_board->Remove( i->GetNet() );
  1323. m_frame->OnModify();
  1324. // We'll get an OnBoardItemRemoved callback from this to update our listbox
  1325. }
  1326. };
  1327. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1328. {
  1329. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1330. if( ii->GetIsGroup() )
  1331. {
  1332. if( ii->ChildrenCount() != 0
  1333. && IsOK( this, wxString::Format( _( "Delete all nets in group '%s'?" ),
  1334. ii->GetGroupName() ) ) )
  1335. {
  1336. // we can't be iterating the children container and deleting items from
  1337. // it at the same time. thus take a copy of it first.
  1338. std::vector<const LIST_ITEM*> children;
  1339. children.reserve( ii->ChildrenCount() );
  1340. std::copy( ii->ChildrenBegin(), ii->ChildrenEnd(), std::back_inserter( children ) );
  1341. for( const LIST_ITEM* c : children )
  1342. delete_one( c );
  1343. }
  1344. }
  1345. else
  1346. {
  1347. delete_one( ii );
  1348. }
  1349. }
  1350. }
  1351. /*****************************************************************************************
  1352. *
  1353. * Application-generated event handling
  1354. *
  1355. * ***************************************************************************************/
  1356. void PCB_NET_INSPECTOR_PANEL::OnLanguageChangedImpl()
  1357. {
  1358. SaveSettings();
  1359. buildNetsList( true );
  1360. m_dataModel->updateAllItems();
  1361. }
  1362. void PCB_NET_INSPECTOR_PANEL::onUnitsChanged( wxCommandEvent& event )
  1363. {
  1364. m_dataModel->updateAllItems();
  1365. event.Skip();
  1366. }
  1367. /*****************************************************************************************
  1368. *
  1369. * Settings persistence
  1370. *
  1371. * ***************************************************************************************/
  1372. void PCB_NET_INSPECTOR_PANEL::SaveSettings()
  1373. {
  1374. // Don't save settings if a board has not yet been loaded or the panel hasn't been displayed.
  1375. // Events fire while we set up the panel which overwrite the settings we haven't yet loaded.
  1376. bool displayed = false;
  1377. for( unsigned int ii = 0; ii < m_dataModel->columnCount() && !displayed; ++ii )
  1378. {
  1379. if( m_netsList->GetColumn( ii )->GetWidth() > 0 )
  1380. displayed = true;
  1381. }
  1382. if( !displayed || !m_boardLoaded || m_boardLoading )
  1383. return;
  1384. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  1385. auto& cfg = localSettings.m_NetInspectorPanel;
  1386. // User-defined filters / grouping
  1387. cfg.filter_text = m_searchCtrl->GetValue();
  1388. cfg.filter_by_net_name = m_filterByNetName;
  1389. cfg.filter_by_netclass = m_filterByNetclass;
  1390. cfg.group_by_netclass = m_groupByNetclass;
  1391. cfg.group_by_constraint = m_groupByConstraint;
  1392. cfg.show_zero_pad_nets = m_showZeroPadNets;
  1393. cfg.show_unconnected_nets = m_showUnconnectedNets;
  1394. // Grid sorting
  1395. wxDataViewColumn* sortingCol = m_netsList->GetSortingColumn();
  1396. cfg.sorting_column = sortingCol ? static_cast<int>( sortingCol->GetModelColumn() ) : -1;
  1397. cfg.sort_order_asc = sortingCol ? sortingCol->IsSortOrderAscending() : true;
  1398. // Column arrangement / sizes
  1399. cfg.col_order.resize( m_dataModel->columnCount() );
  1400. cfg.col_widths.resize( m_dataModel->columnCount() );
  1401. cfg.col_hidden.resize( m_dataModel->columnCount() );
  1402. for( unsigned int ii = 0; ii < m_dataModel->columnCount(); ++ii )
  1403. {
  1404. cfg.col_order[ii] = (int) m_netsList->GetColumn( ii )->GetModelColumn();
  1405. cfg.col_widths[ii] = m_netsList->GetColumn( ii )->GetWidth();
  1406. cfg.col_hidden[ii] = m_netsList->GetColumn( ii )->IsHidden();
  1407. }
  1408. // Expanded rows
  1409. cfg.expanded_rows.clear();
  1410. std::vector<std::pair<wxString, wxDataViewItem>> groupItems = m_dataModel->getGroupDataViewItems();
  1411. for( std::pair<wxString, wxDataViewItem>& item : groupItems )
  1412. {
  1413. if( m_netsList->IsExpanded( item.second ) )
  1414. cfg.expanded_rows.push_back( item.first );
  1415. }
  1416. // Customer group rules
  1417. cfg.custom_group_rules.clear();
  1418. for( const std::unique_ptr<EDA_COMBINED_MATCHER>& rule : m_custom_group_rules )
  1419. cfg.custom_group_rules.push_back( rule->GetPattern() );
  1420. }