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.

1857 lines
64 KiB

5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019-2022 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <bitmaps.h>
  24. #include <pcb_group.h>
  25. #include <tool/tool_manager.h>
  26. #include <tools/pcb_selection_tool.h>
  27. #include <tools/pcb_picker_tool.h>
  28. #include <tools/edit_tool.h>
  29. #include <pcb_painter.h>
  30. #include <connectivity/connectivity_data.h>
  31. #include <drc/drc_engine.h>
  32. #include <dialogs/dialog_board_statistics.h>
  33. #include <dialogs/dialog_constraints_reporter.h>
  34. #include <dialogs/panel_setup_rules_base.h>
  35. #include <string_utils.h>
  36. #include "board_inspection_tool.h"
  37. #include <pcbnew_settings.h>
  38. #include <widgets/appearance_controls.h>
  39. #include <widgets/wx_html_report_box.h>
  40. #include <drc/drc_item.h>
  41. #include <pad.h>
  42. BOARD_INSPECTION_TOOL::BOARD_INSPECTION_TOOL() :
  43. PCB_TOOL_BASE( "pcbnew.InspectionTool" ),
  44. m_frame( nullptr )
  45. {
  46. m_dynamicData = nullptr;
  47. }
  48. class NET_CONTEXT_MENU : public ACTION_MENU
  49. {
  50. public:
  51. NET_CONTEXT_MENU() : ACTION_MENU( true )
  52. {
  53. SetIcon( BITMAPS::show_ratsnest );
  54. SetTitle( _( "Net Inspection Tools" ) );
  55. Add( PCB_ACTIONS::showNetInRatsnest );
  56. Add( PCB_ACTIONS::hideNetInRatsnest );
  57. AppendSeparator();
  58. Add( PCB_ACTIONS::highlightNetSelection );
  59. Add( PCB_ACTIONS::clearHighlight );
  60. }
  61. private:
  62. ACTION_MENU* create() const override
  63. {
  64. return new NET_CONTEXT_MENU();
  65. }
  66. };
  67. bool BOARD_INSPECTION_TOOL::Init()
  68. {
  69. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  70. std::shared_ptr<NET_CONTEXT_MENU> netSubMenu = std::make_shared<NET_CONTEXT_MENU>();
  71. netSubMenu->SetTool( this );
  72. static std::vector<KICAD_T> connectedTypes = { PCB_TRACE_T,
  73. PCB_VIA_T,
  74. PCB_ARC_T,
  75. PCB_PAD_T,
  76. PCB_ZONE_T };
  77. CONDITIONAL_MENU& menu = selectionTool->GetToolMenu().GetMenu();
  78. selectionTool->GetToolMenu().RegisterSubMenu( netSubMenu );
  79. menu.AddMenu( netSubMenu.get(), SELECTION_CONDITIONS::OnlyTypes( connectedTypes ), 100 );
  80. return true;
  81. }
  82. void BOARD_INSPECTION_TOOL::Reset( RESET_REASON aReason )
  83. {
  84. m_frame = getEditFrame<PCB_EDIT_FRAME>();
  85. }
  86. int BOARD_INSPECTION_TOOL::ShowBoardStatistics( const TOOL_EVENT& aEvent )
  87. {
  88. DIALOG_BOARD_STATISTICS dialog( m_frame );
  89. dialog.ShowModal();
  90. return 0;
  91. }
  92. DRC_ENGINE BOARD_INSPECTION_TOOL::makeDRCEngine( bool* aCompileError, bool* aCourtyardError )
  93. {
  94. DRC_ENGINE engine( m_frame->GetBoard(), &m_frame->GetBoard()->GetDesignSettings() );
  95. try
  96. {
  97. engine.InitEngine( m_frame->GetDesignRulesPath() );
  98. }
  99. catch( PARSE_ERROR& )
  100. {
  101. if( aCompileError )
  102. *aCompileError = true;
  103. }
  104. for( ZONE* zone : m_frame->GetBoard()->Zones() )
  105. zone->CacheBoundingBox();
  106. for( FOOTPRINT* footprint : m_frame->GetBoard()->Footprints() )
  107. {
  108. for( ZONE* zone : footprint->Zones() )
  109. zone->CacheBoundingBox();
  110. footprint->BuildCourtyardCaches();
  111. if( aCourtyardError && ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 )
  112. *aCourtyardError = true;
  113. }
  114. return engine;
  115. }
  116. bool isNPTHPad( BOARD_ITEM* aItem )
  117. {
  118. return aItem->Type() == PCB_PAD_T
  119. && static_cast<PAD*>( aItem )->GetAttribute() == PAD_ATTRIB::NPTH;
  120. }
  121. wxString BOARD_INSPECTION_TOOL::getItemDescription( BOARD_ITEM* aItem )
  122. {
  123. // Null items have no description
  124. if( !aItem )
  125. return wxString();
  126. wxString msg = aItem->GetSelectMenuText( m_frame );
  127. if( aItem->IsConnected() && !isNPTHPad( aItem ) )
  128. {
  129. BOARD_CONNECTED_ITEM* cItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
  130. msg += wxS( " " ) + wxString::Format( _( "[netclass %s]" ),
  131. cItem->GetEffectiveNetClass()->GetName() );
  132. }
  133. return msg;
  134. };
  135. void BOARD_INSPECTION_TOOL::reportCompileError( REPORTER* r )
  136. {
  137. r->Report( "" );
  138. r->Report( _( "Report incomplete: could not compile custom design rules. " )
  139. + wxT( "<a href='boardsetup'>" ) + _( "Show design rules." ) + wxT( "</a>" ) );
  140. }
  141. void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, REPORTER* r )
  142. {
  143. r->Report( wxT( "<h7>" ) + EscapeHTML( aTitle ) + wxT( "</h7>" ) );
  144. r->Report( wxT( "<ul><li>" ) + EscapeHTML( getItemDescription( a ) ) + wxT( "</li></ul>" ) );
  145. }
  146. void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, BOARD_ITEM* b,
  147. REPORTER* r )
  148. {
  149. r->Report( wxT( "<h7>" ) + EscapeHTML( aTitle ) + wxT( "</h7>" ) );
  150. r->Report( wxT( "<ul><li>" ) + EscapeHTML( getItemDescription( a ) ) + wxT( "</li>" )
  151. + wxT( "<li>" ) + EscapeHTML( getItemDescription( b ) ) + wxT( "</li></ul>" ) );
  152. }
  153. void BOARD_INSPECTION_TOOL::reportHeader( const wxString& aTitle, BOARD_ITEM* a, BOARD_ITEM* b,
  154. PCB_LAYER_ID aLayer, REPORTER* r )
  155. {
  156. wxString layerStr = _( "Layer" ) + wxS( " " ) + m_frame->GetBoard()->GetLayerName( aLayer );
  157. r->Report( wxT( "<h7>" ) + EscapeHTML( aTitle ) + wxT( "</h7>" ) );
  158. r->Report( wxT( "<ul><li>" ) + EscapeHTML( layerStr ) + wxT( "</li>" )
  159. + wxT( "<li>" ) + EscapeHTML( getItemDescription( a ) ) + wxT( "</li>" )
  160. + wxT( "<li>" ) + EscapeHTML( getItemDescription( b ) ) + wxT( "</li></ul>" ) );
  161. }
  162. wxString reportMin( PCB_BASE_FRAME* aFrame, DRC_CONSTRAINT& aConstraint )
  163. {
  164. if( aConstraint.m_Value.HasMin() )
  165. return aFrame->StringFromValue( aConstraint.m_Value.Min(), true );
  166. else
  167. return wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
  168. }
  169. wxString reportOpt( PCB_BASE_FRAME* aFrame, DRC_CONSTRAINT& aConstraint )
  170. {
  171. if( aConstraint.m_Value.HasOpt() )
  172. return aFrame->StringFromValue( aConstraint.m_Value.Opt(), true );
  173. else
  174. return wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
  175. }
  176. wxString reportMax( PCB_BASE_FRAME* aFrame, DRC_CONSTRAINT& aConstraint )
  177. {
  178. if( aConstraint.m_Value.HasMax() )
  179. return aFrame->StringFromValue( aConstraint.m_Value.Max(), true );
  180. else
  181. return wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
  182. }
  183. void BOARD_INSPECTION_TOOL::InspectDRCError( const std::shared_ptr<RC_ITEM>& aDRCItem )
  184. {
  185. BOARD_ITEM* a = m_frame->GetBoard()->GetItem( aDRCItem->GetMainItemID() );
  186. BOARD_ITEM* b = m_frame->GetBoard()->GetItem( aDRCItem->GetAuxItemID() );
  187. BOARD_CONNECTED_ITEM* ac = dynamic_cast<BOARD_CONNECTED_ITEM*>( a );
  188. BOARD_CONNECTED_ITEM* bc = dynamic_cast<BOARD_CONNECTED_ITEM*>( b );
  189. PCB_LAYER_ID layer = m_frame->GetActiveLayer();
  190. if( m_inspectClearanceDialog == nullptr )
  191. {
  192. m_inspectClearanceDialog = std::make_unique<DIALOG_CONSTRAINTS_REPORTER>( m_frame );
  193. m_inspectClearanceDialog->SetTitle( _( "Violation Report" ) );
  194. m_inspectClearanceDialog->Connect( wxEVT_CLOSE_WINDOW,
  195. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onInspectClearanceDialogClosed ),
  196. nullptr, this );
  197. }
  198. WX_HTML_REPORT_BOX* r = nullptr;
  199. bool compileError = false;
  200. DRC_ENGINE drcEngine = makeDRCEngine( &compileError );
  201. DRC_CONSTRAINT constraint;
  202. int clearance = 0;
  203. wxString clearanceStr;
  204. switch( aDRCItem->GetErrorCode() )
  205. {
  206. case DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG:
  207. {
  208. for( KIID id : aDRCItem->GetIDs() )
  209. {
  210. bc = dynamic_cast<BOARD_CONNECTED_ITEM*>( m_frame->GetBoard()->GetItem( id ) );
  211. if( ac && bc && ac->GetNetCode() != bc->GetNetCode() )
  212. break;
  213. }
  214. r = m_inspectClearanceDialog->AddPage( _( "Uncoupled Length" ) );
  215. reportHeader( _( "Diff pair uncoupled length resolution for:" ), ac, bc, r );
  216. if( compileError )
  217. reportCompileError( r );
  218. constraint = drcEngine.EvalRules( DIFF_PAIR_MAX_UNCOUPLED_CONSTRAINT, a, b, layer, r );
  219. r->Report( "" );
  220. r->Report( wxString::Format( _( "Resolved max uncoupled length: %s." ),
  221. reportMax( m_frame, constraint ) ) );
  222. break;
  223. }
  224. case DRCE_TEXT_HEIGHT:
  225. r = m_inspectClearanceDialog->AddPage( _( "Text Height" ) );
  226. reportHeader( _( "Text height resolution for:" ), a, r );
  227. if( compileError )
  228. reportCompileError( r );
  229. constraint = drcEngine.EvalRules( TEXT_HEIGHT_CONSTRAINT, a, b, layer, r );
  230. r->Report( "" );
  231. r->Report( wxString::Format( _( "Resolved height constraints: min %s; max %s." ),
  232. reportMin( m_frame, constraint ),
  233. reportMax( m_frame, constraint ) ) );
  234. break;
  235. case DRCE_TEXT_THICKNESS:
  236. r = m_inspectClearanceDialog->AddPage( _( "Text Thickness" ) );
  237. reportHeader( _( "Text thickness resolution for:" ), a, r );
  238. if( compileError )
  239. reportCompileError( r );
  240. constraint = drcEngine.EvalRules( TEXT_THICKNESS_CONSTRAINT, a, b, layer, r );
  241. r->Report( "" );
  242. r->Report( wxString::Format( _( "Resolved thickness constraints: min %s; max %s." ),
  243. reportMin( m_frame, constraint ),
  244. reportMax( m_frame, constraint ) ) );
  245. break;
  246. case DRCE_TRACK_WIDTH:
  247. r = m_inspectClearanceDialog->AddPage( _( "Track Width" ) );
  248. reportHeader( _( "Track width resolution for:" ), a, r );
  249. if( compileError )
  250. reportCompileError( r );
  251. constraint = drcEngine.EvalRules( TRACK_WIDTH_CONSTRAINT, a, b, layer, r );
  252. r->Report( "" );
  253. r->Report( wxString::Format( _( "Resolved width constraints: min %s; max %s." ),
  254. reportMin( m_frame, constraint ),
  255. reportMax( m_frame, constraint ) ) );
  256. break;
  257. case DRCE_CONNECTION_WIDTH:
  258. r = m_inspectClearanceDialog->AddPage( _( "Connection Width" ) );
  259. reportHeader( _( "Connection width resolution for:" ), a, b, r );
  260. if( compileError )
  261. reportCompileError( r );
  262. constraint = drcEngine.EvalRules( CONNECTION_WIDTH_CONSTRAINT, a, b, layer, r );
  263. r->Report( "" );
  264. r->Report( wxString::Format( _( "Resolved min connection width constraint: %s." ),
  265. reportMin( m_frame, constraint ) ) );
  266. break;
  267. case DRCE_VIA_DIAMETER:
  268. r = m_inspectClearanceDialog->AddPage( _( "Via Diameter" ) );
  269. reportHeader( _( "Via diameter resolution for:" ), a, r );
  270. if( compileError )
  271. reportCompileError( r );
  272. constraint = drcEngine.EvalRules( VIA_DIAMETER_CONSTRAINT, a, b, layer, r );
  273. r->Report( "" );
  274. r->Report( wxString::Format( _( "Resolved diameter constraints: min %s; max %s." ),
  275. reportMin( m_frame, constraint ),
  276. reportMax( m_frame, constraint ) ) );
  277. break;
  278. case DRCE_ANNULAR_WIDTH:
  279. r = m_inspectClearanceDialog->AddPage( _( "Via Annulus" ) );
  280. reportHeader( _( "Via annular width resolution for:" ), a, r );
  281. if( compileError )
  282. reportCompileError( r );
  283. constraint = drcEngine.EvalRules( ANNULAR_WIDTH_CONSTRAINT, a, b, layer, r );
  284. r->Report( "" );
  285. r->Report( wxString::Format( _( "Resolved annular width constraints: min %s; max %s." ),
  286. reportMin( m_frame, constraint ),
  287. reportMax( m_frame, constraint ) ) );
  288. break;
  289. case DRCE_DRILL_OUT_OF_RANGE:
  290. case DRCE_MICROVIA_DRILL_OUT_OF_RANGE:
  291. r = m_inspectClearanceDialog->AddPage( _( "Hole Size" ) );
  292. reportHeader( _( "Hole diameter resolution for:" ), a, r );
  293. if( compileError )
  294. reportCompileError( r );
  295. constraint = drcEngine.EvalRules( HOLE_SIZE_CONSTRAINT, a, b, layer, r );
  296. r->Report( "" );
  297. r->Report( wxString::Format( _( "Resolved diameter constraints: min %s; max %s." ),
  298. reportMin( m_frame, constraint ),
  299. reportMax( m_frame, constraint ) ) );
  300. break;
  301. case DRCE_HOLE_CLEARANCE:
  302. r = m_inspectClearanceDialog->AddPage( _( "Hole Clearance" ) );
  303. reportHeader( _( "Hole clearance resolution for:" ), a, b, r );
  304. if( compileError )
  305. reportCompileError( r );
  306. if( ac && bc && ac->GetNetCode() == bc->GetNetCode() )
  307. {
  308. r->Report( "" );
  309. r->Report( _( "Items belong to the same net. Clearance is 0." ) );
  310. }
  311. else
  312. {
  313. constraint = drcEngine.EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r );
  314. clearance = constraint.m_Value.Min();
  315. clearanceStr = m_frame->StringFromValue( clearance, true );
  316. r->Report( "" );
  317. r->Report( wxString::Format( _( "Resolved clearance: %s." ), clearanceStr ) );
  318. }
  319. r->Report( "" );
  320. r->Report( "" );
  321. r->Report( "" );
  322. reportHeader( _( "Physical hole clearance resolution for:" ), a, b, layer, r );
  323. constraint = drcEngine.EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r );
  324. clearance = constraint.m_Value.Min();
  325. clearanceStr = m_frame->StringFromValue( clearance, true );
  326. if( !drcEngine.HasRulesForConstraintType( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT ) )
  327. {
  328. r->Report( "" );
  329. r->Report( _( "No 'physical_hole_clearance' constraints defined." ) );
  330. }
  331. else
  332. {
  333. r->Report( "" );
  334. r->Report( wxString::Format( _( "Resolved clearance: %s." ), clearanceStr ) );
  335. }
  336. break;
  337. case DRCE_DRILLED_HOLES_TOO_CLOSE:
  338. r = m_inspectClearanceDialog->AddPage( _( "Hole to Hole" ) );
  339. reportHeader( _( "Hole to hole clearance resolution for:" ), a, b, r );
  340. if( compileError )
  341. reportCompileError( r );
  342. constraint = drcEngine.EvalRules( HOLE_TO_HOLE_CONSTRAINT, a, b, UNDEFINED_LAYER, r );
  343. clearance = constraint.m_Value.Min();
  344. clearanceStr = m_frame->StringFromValue( clearance, true );
  345. r->Report( "" );
  346. r->Report( wxString::Format( _( "Resolved clearance: %s." ), clearanceStr ) );
  347. break;
  348. case DRCE_EDGE_CLEARANCE:
  349. r = m_inspectClearanceDialog->AddPage( _( "Edge Clearance" ) );
  350. reportHeader( _( "Edge clearance resolution for:" ), a, b, r );
  351. if( compileError )
  352. reportCompileError( r );
  353. constraint = drcEngine.EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, layer, r );
  354. clearance = constraint.m_Value.Min();
  355. clearanceStr = m_frame->StringFromValue( clearance, true );
  356. r->Report( "" );
  357. r->Report( wxString::Format( _( "Resolved clearance: %s." ), clearanceStr ) );
  358. break;
  359. case DRCE_CLEARANCE:
  360. if( a->Type() == PCB_TRACE_T || a->Type() == PCB_ARC_T )
  361. {
  362. layer = a->GetLayer();
  363. }
  364. else if( b->Type() == PCB_TRACE_T || b->Type() == PCB_ARC_T )
  365. {
  366. layer = b->GetLayer();
  367. }
  368. else if( a->Type() == PCB_PAD_T && static_cast<PAD*>( a )->GetAttribute() == PAD_ATTRIB::SMD )
  369. {
  370. PAD* pad = static_cast<PAD*>( a );
  371. if( pad->IsOnLayer( F_Cu ) )
  372. layer = F_Cu;
  373. else
  374. layer = B_Cu;
  375. }
  376. else if( b->Type() == PCB_PAD_T && static_cast<PAD*>( a )->GetAttribute() == PAD_ATTRIB::SMD )
  377. {
  378. PAD* pad = static_cast<PAD*>( b );
  379. if( pad->IsOnLayer( F_Cu ) )
  380. layer = F_Cu;
  381. else
  382. layer = B_Cu;
  383. }
  384. r = m_inspectClearanceDialog->AddPage( _( "Clearance" ) );
  385. reportHeader( _( "Clearance resolution for:" ), a, b, layer, r );
  386. if( compileError )
  387. reportCompileError( r );
  388. if( ac && bc && ac->GetNetCode() == bc->GetNetCode() )
  389. {
  390. r->Report( "" );
  391. r->Report( _( "Items belong to the same net. Clearance is 0." ) );
  392. }
  393. else
  394. {
  395. constraint = drcEngine.EvalRules( CLEARANCE_CONSTRAINT, a, a, layer, r );
  396. clearance = constraint.m_Value.Min();
  397. clearanceStr = m_frame->StringFromValue( clearance, true );
  398. r->Report( "" );
  399. r->Report( wxString::Format( _( "Resolved clearance: %s." ), clearanceStr ) );
  400. }
  401. r->Report( "" );
  402. r->Report( "" );
  403. r->Report( "" );
  404. reportHeader( _( "Physical clearance resolution for:" ), a, b, layer, r );
  405. constraint = drcEngine.EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, a, b, layer, r );
  406. clearance = constraint.m_Value.Min();
  407. clearanceStr = m_frame->StringFromValue( clearance, true );
  408. if( !drcEngine.HasRulesForConstraintType( PHYSICAL_CLEARANCE_CONSTRAINT ) )
  409. {
  410. r->Report( "" );
  411. r->Report( _( "No 'physical_clearance' constraints defined." ) );
  412. }
  413. else
  414. {
  415. r->Report( "" );
  416. r->Report( wxString::Format( _( "Resolved clearance: %s." ), clearanceStr ) );
  417. }
  418. break;
  419. default:
  420. return;
  421. }
  422. r->Flush();
  423. m_inspectClearanceDialog->Raise();
  424. m_inspectClearanceDialog->Show( true );
  425. }
  426. int BOARD_INSPECTION_TOOL::InspectClearance( const TOOL_EVENT& aEvent )
  427. {
  428. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  429. const PCB_SELECTION& selection = selTool->GetSelection();
  430. if( selection.Size() != 2 )
  431. {
  432. m_frame->ShowInfoBarError( _( "Select two items for a clearance resolution report." ) );
  433. return 0;
  434. }
  435. BOARD_ITEM* a = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) );
  436. BOARD_ITEM* b = static_cast<BOARD_ITEM*>( selection.GetItem( 1 ) );
  437. wxCHECK( a && b, 0 );
  438. if( a->Type() == PCB_GROUP_T )
  439. {
  440. PCB_GROUP* ag = static_cast<PCB_GROUP*>( a );
  441. if( ag->GetItems().empty() )
  442. {
  443. m_frame->ShowInfoBarError( _( "Cannot generate clearance report on empty group." ) );
  444. return 0;
  445. }
  446. a = *ag->GetItems().begin();
  447. }
  448. if( b->Type() == PCB_GROUP_T )
  449. {
  450. PCB_GROUP* bg = static_cast<PCB_GROUP*>( b );
  451. if( bg->GetItems().empty() )
  452. {
  453. m_frame->ShowInfoBarError( _( "Cannot generate clearance report on empty group." ) );
  454. return 0;
  455. }
  456. b = *bg->GetItems().begin();
  457. }
  458. // a and b could be null after group tests above.
  459. wxCHECK( a && b, 0 );
  460. if( m_inspectClearanceDialog == nullptr )
  461. {
  462. m_inspectClearanceDialog = std::make_unique<DIALOG_CONSTRAINTS_REPORTER>( m_frame );
  463. m_inspectClearanceDialog->SetTitle( _( "Clearance Report" ) );
  464. m_inspectClearanceDialog->Connect( wxEVT_CLOSE_WINDOW,
  465. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onInspectClearanceDialogClosed ),
  466. nullptr, this );
  467. }
  468. m_inspectClearanceDialog->DeleteAllPages();
  469. if( a->Type() != PCB_ZONE_T && b->Type() == PCB_ZONE_T )
  470. std::swap( a, b );
  471. else if( !a->IsConnected() && b->IsConnected() )
  472. std::swap( a, b );
  473. WX_HTML_REPORT_BOX* r = nullptr;
  474. PCB_LAYER_ID active = m_frame->GetActiveLayer();
  475. LSET layerIntersection = a->GetLayerSet() & b->GetLayerSet();
  476. LSET copperIntersection = layerIntersection & LSET::AllCuMask();
  477. BOARD_CONNECTED_ITEM* ac = dynamic_cast<BOARD_CONNECTED_ITEM*>( a );
  478. BOARD_CONNECTED_ITEM* bc = dynamic_cast<BOARD_CONNECTED_ITEM*>( b );
  479. ZONE* zone = dynamic_cast<ZONE*>( a );
  480. PAD* pad = dynamic_cast<PAD*>( b );
  481. FOOTPRINT* aFP = dynamic_cast<FOOTPRINT*>( a );
  482. FOOTPRINT* bFP = dynamic_cast<FOOTPRINT*>( b );
  483. bool compileError = false;
  484. DRC_ENGINE drcEngine = makeDRCEngine( &compileError );
  485. DRC_CONSTRAINT constraint;
  486. int clearance = 0;
  487. if( copperIntersection.any() && zone && pad && zone->GetNetCode() == pad->GetNetCode() )
  488. {
  489. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  490. if( zone->IsOnLayer( active ) )
  491. layer = active;
  492. else if( zone->GetLayerSet().count() > 0 )
  493. layer = zone->GetLayerSet().Seq().front();
  494. r = m_inspectClearanceDialog->AddPage( _( "Zone" ) );
  495. reportHeader( _( "Zone connection resolution for:" ), a, b, layer, r );
  496. constraint = drcEngine.EvalZoneConnection( pad, zone, layer, r );
  497. if( constraint.m_ZoneConnection == ZONE_CONNECTION::THERMAL )
  498. {
  499. r->Report( "" );
  500. r->Report( "" );
  501. reportHeader( _( "Thermal relief gap resolution for:" ), a, b, layer, r );
  502. constraint = drcEngine.EvalRules( THERMAL_RELIEF_GAP_CONSTRAINT, pad, zone, layer, r );
  503. int gap = constraint.m_Value.Min();
  504. if( compileError )
  505. reportCompileError( r );
  506. r->Report( "" );
  507. r->Report( wxString::Format( _( "Resolved thermal relief gap: %s." ),
  508. m_frame->StringFromValue( gap, true ) ) );
  509. r->Report( "" );
  510. r->Report( "" );
  511. reportHeader( _( "Spoke width resolution for:" ), a, b, layer, r );
  512. constraint = drcEngine.EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, zone, layer, r );
  513. int width = constraint.m_Value.Opt();
  514. if( compileError )
  515. reportCompileError( r );
  516. r->Report( "" );
  517. r->Report( wxString::Format( _( "Resolved thermal relief spoke width: %s." ),
  518. m_frame->StringFromValue( width, true ) ) );
  519. r->Report( "" );
  520. r->Report( "" );
  521. reportHeader( _( "Spoke count resolution for:" ), a, b, layer, r );
  522. constraint = drcEngine.EvalRules( MIN_RESOLVED_SPOKES_CONSTRAINT, pad, zone, layer, r );
  523. int minSpokes = constraint.m_Value.Min();
  524. if( compileError )
  525. reportCompileError( r );
  526. r->Report( "" );
  527. r->Report( wxString::Format( _( "Resolved min thermal relief spoke count: %d." ),
  528. minSpokes ) );
  529. std::shared_ptr<CONNECTIVITY_DATA> connectivity = pad->GetBoard()->GetConnectivity();
  530. }
  531. else if( constraint.m_ZoneConnection == ZONE_CONNECTION::NONE )
  532. {
  533. r->Report( "" );
  534. r->Report( "" );
  535. reportHeader( _( "Zone clearance resolution for:" ), a, b, layer, r );
  536. clearance = zone->GetLocalClearance();
  537. r->Report( "" );
  538. r->Report( wxString::Format( _( "Zone clearance: %s." ),
  539. m_frame->StringFromValue( clearance, true ) ) );
  540. constraint = drcEngine.EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, pad, zone, layer, r );
  541. if( constraint.m_Value.Min() > clearance )
  542. {
  543. clearance = constraint.m_Value.Min();
  544. r->Report( "" );
  545. r->Report( wxString::Format( _( "Overridden by larger physical clearance from %s;"
  546. "clearance: %s." ),
  547. EscapeHTML( constraint.GetName() ),
  548. m_frame->StringFromValue( clearance, true ) ) );
  549. }
  550. if( !pad->FlashLayer( layer ) )
  551. {
  552. constraint = drcEngine.EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, pad, zone,
  553. layer, r );
  554. if( constraint.m_Value.Min() > clearance )
  555. {
  556. clearance = constraint.m_Value.Min();
  557. r->Report( "" );
  558. r->Report( wxString::Format( _( "Overridden by larger physical hole clearance from %s;"
  559. "clearance: %s." ),
  560. EscapeHTML( constraint.GetName() ),
  561. m_frame->StringFromValue( clearance, true ) ) );
  562. }
  563. }
  564. if( compileError )
  565. reportCompileError( r );
  566. r->Report( "" );
  567. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  568. m_frame->StringFromValue( clearance, true ) ) );
  569. }
  570. else
  571. {
  572. r->Report( "" );
  573. r->Report( "" );
  574. reportHeader( _( "Zone clearance resolution for:" ), a, b, layer, r );
  575. if( compileError )
  576. reportCompileError( r );
  577. // Report a 0 clearance for solid connections
  578. r->Report( "" );
  579. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  580. m_frame->StringFromValue( 0, true ) ) );
  581. }
  582. r->Flush();
  583. }
  584. else if( copperIntersection.any() && !aFP && !bFP )
  585. {
  586. PCB_LAYER_ID layer = active;
  587. if( !copperIntersection.test( layer ) )
  588. layer = copperIntersection.Seq().front();
  589. r = m_inspectClearanceDialog->AddPage( m_frame->GetBoard()->GetLayerName( layer ) );
  590. reportHeader( _( "Clearance resolution for:" ), a, b, layer, r );
  591. if( ac && bc && ac->GetNetCode() > 0 && ac->GetNetCode() == bc->GetNetCode() )
  592. {
  593. // Same nets....
  594. r->Report( _( "Items belong to the same net. Clearance is 0." ) );
  595. }
  596. else
  597. {
  598. // Different nets (or one or both unconnected)....
  599. constraint = drcEngine.EvalRules( CLEARANCE_CONSTRAINT, a, b, layer, r );
  600. clearance = constraint.m_Value.Min();
  601. if( compileError )
  602. reportCompileError( r );
  603. r->Report( "" );
  604. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  605. m_frame->StringFromValue( clearance, true ) ) );
  606. }
  607. r->Flush();
  608. }
  609. if( ac && bc )
  610. {
  611. NETINFO_ITEM* refNet = ac->GetNet();
  612. wxString coupledNet;
  613. wxString dummy;
  614. if( DRC_ENGINE::MatchDpSuffix( refNet->GetNetname(), coupledNet, dummy )
  615. && bc->GetNetname() == coupledNet )
  616. {
  617. r = m_inspectClearanceDialog->AddPage( _( "Diff Pair" ) );
  618. reportHeader( _( "Diff pair gap resolution for:" ), ac, bc, active, r );
  619. constraint = drcEngine.EvalRules( DIFF_PAIR_GAP_CONSTRAINT, ac, bc, active, r );
  620. r->Report( "" );
  621. r->Report( wxString::Format( _( "Resolved gap constraints: min %s; opt %s; max %s." ),
  622. reportMin( m_frame, constraint ),
  623. reportOpt( m_frame, constraint ),
  624. reportMax( m_frame, constraint ) ) );
  625. r->Report( "" );
  626. r->Report( "" );
  627. r->Report( "" );
  628. reportHeader( _( "Diff pair max uncoupled length resolution for:" ), ac, bc, active, r );
  629. if( !drcEngine.HasRulesForConstraintType( DIFF_PAIR_MAX_UNCOUPLED_CONSTRAINT ) )
  630. {
  631. r->Report( "" );
  632. r->Report( _( "No 'diff_pair_uncoupled' constraints defined." ) );
  633. }
  634. else
  635. {
  636. constraint = drcEngine.EvalRules( DIFF_PAIR_MAX_UNCOUPLED_CONSTRAINT, ac, bc,
  637. active, r );
  638. r->Report( "" );
  639. r->Report( wxString::Format( _( "Resolved max uncoupled length: %s." ),
  640. reportMax( m_frame, constraint ) ) );
  641. }
  642. r->Flush();
  643. }
  644. }
  645. auto isOnCorrespondingLayer=
  646. [&]( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, wxString* aWarning )
  647. {
  648. if( aItem->IsOnLayer( aLayer ) )
  649. return true;
  650. PCB_LAYER_ID correspondingMask = IsFrontLayer( aLayer ) ? F_Mask : B_Mask;
  651. PCB_LAYER_ID correspondingCopper = IsFrontLayer( aLayer ) ? F_Cu : B_Cu;
  652. if( aItem->IsOnLayer( aLayer ) )
  653. return true;
  654. if( aItem->IsOnLayer( correspondingMask ) )
  655. return true;
  656. if( aItem->IsTented() && aItem->IsOnLayer( correspondingCopper ) )
  657. {
  658. *aWarning = wxString::Format( _( "Note: %s is tented; clearance will only be "
  659. "applied to holes." ),
  660. getItemDescription( aItem ) );
  661. return true;
  662. }
  663. return false;
  664. };
  665. for( PCB_LAYER_ID layer : { F_SilkS, B_SilkS } )
  666. {
  667. wxString warning;
  668. if( ( a->IsOnLayer( layer ) && isOnCorrespondingLayer( b, layer, &warning ) )
  669. || ( b->IsOnLayer( layer ) && isOnCorrespondingLayer( a, layer, &warning ) ) )
  670. {
  671. r = m_inspectClearanceDialog->AddPage( m_frame->GetBoard()->GetLayerName( layer ) );
  672. reportHeader( _( "Silkscreen clearance resolution for:" ), a, b, layer, r );
  673. constraint = drcEngine.EvalRules( SILK_CLEARANCE_CONSTRAINT, a, b, layer, r );
  674. clearance = constraint.m_Value.Min();
  675. if( compileError )
  676. reportCompileError( r );
  677. r->Report( "" );
  678. if( !warning.IsEmpty() )
  679. r->Report( warning );
  680. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  681. m_frame->StringFromValue( clearance, true ) ) );
  682. r->Flush();
  683. }
  684. }
  685. for( PCB_LAYER_ID layer : { F_CrtYd, B_CrtYd } )
  686. {
  687. bool aCourtyard = aFP && !aFP->GetCourtyard( layer ).IsEmpty();
  688. bool bCourtyard = bFP && !bFP->GetCourtyard( layer ).IsEmpty();
  689. if( aCourtyard && bCourtyard )
  690. {
  691. r = m_inspectClearanceDialog->AddPage( m_frame->GetBoard()->GetLayerName( layer ) );
  692. reportHeader( _( "Courtyard clearance resolution for:" ), a, b, layer, r );
  693. constraint = drcEngine.EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, a, b, layer, r );
  694. clearance = constraint.m_Value.Min();
  695. if( compileError )
  696. reportCompileError( r );
  697. r->Report( "" );
  698. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  699. m_frame->StringFromValue( clearance, true ) ) );
  700. r->Flush();
  701. }
  702. }
  703. if( a->HasHole() || b->HasHole() )
  704. {
  705. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  706. bool pageAdded = false;
  707. if( a->HasHole() && b->IsOnLayer( active ) && IsCopperLayer( active ) )
  708. layer = active;
  709. else if( b->HasHole() && a->IsOnLayer( active ) && IsCopperLayer( active ) )
  710. layer = active;
  711. else if( a->HasHole() && b->IsOnCopperLayer() )
  712. layer = b->GetLayer();
  713. else if( b->HasHole() && b->IsOnCopperLayer() )
  714. layer = a->GetLayer();
  715. if( layer >= 0 )
  716. {
  717. if( !pageAdded )
  718. {
  719. r = m_inspectClearanceDialog->AddPage( _( "Hole" ) );
  720. pageAdded = true;
  721. }
  722. else
  723. {
  724. r->Report( "" );
  725. r->Report( "" );
  726. r->Report( "" );
  727. }
  728. reportHeader( _( "Hole clearance resolution for:" ), a, b, layer, r );
  729. constraint = drcEngine.EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r );
  730. clearance = constraint.m_Value.Min();
  731. if( compileError )
  732. reportCompileError( r );
  733. r->Report( "" );
  734. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  735. m_frame->StringFromValue( clearance, true ) ) );
  736. r->Flush();
  737. }
  738. if( a->HasHole() && b->HasHole() )
  739. {
  740. if( !pageAdded )
  741. {
  742. r = m_inspectClearanceDialog->AddPage( _( "Hole" ) );
  743. pageAdded = true;
  744. }
  745. else
  746. {
  747. r->Report( "" );
  748. r->Report( "" );
  749. r->Report( "" );
  750. }
  751. reportHeader( _( "Hole to hole clearance resolution for:" ), a, b, r );
  752. constraint = drcEngine.EvalRules( HOLE_TO_HOLE_CONSTRAINT, a, b, UNDEFINED_LAYER, r );
  753. clearance = constraint.m_Value.Min();
  754. if( compileError )
  755. reportCompileError( r );
  756. r->Report( "" );
  757. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  758. m_frame->StringFromValue( clearance, true ) ) );
  759. r->Flush();
  760. }
  761. }
  762. for( PCB_LAYER_ID edgeLayer : { Edge_Cuts, Margin } )
  763. {
  764. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  765. if( a->IsOnLayer( edgeLayer ) && b->Type() != PCB_FOOTPRINT_T )
  766. {
  767. if( b->IsOnLayer( active ) && IsCopperLayer( active ) )
  768. layer = active;
  769. else if( IsCopperLayer( b->GetLayer() ) )
  770. layer = b->GetLayer();
  771. }
  772. else if( b->IsOnLayer( edgeLayer ) && a->Type() != PCB_FOOTPRINT_T )
  773. {
  774. if( a->IsOnLayer( active ) && IsCopperLayer( active ) )
  775. layer = active;
  776. else if( IsCopperLayer( a->GetLayer() ) )
  777. layer = a->GetLayer();
  778. }
  779. if( layer >= 0 )
  780. {
  781. wxString layerName = m_frame->GetBoard()->GetLayerName( edgeLayer );
  782. r = m_inspectClearanceDialog->AddPage( layerName + wxS( " " ) + _( "Clearance" ) );
  783. reportHeader( _( "Edge clearance resolution for:" ), a, b, layer, r );
  784. constraint = drcEngine.EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, layer, r );
  785. clearance = constraint.m_Value.Min();
  786. if( compileError )
  787. reportCompileError( r );
  788. r->Report( "" );
  789. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  790. m_frame->StringFromValue( clearance, true ) ) );
  791. r->Flush();
  792. }
  793. }
  794. r = m_inspectClearanceDialog->AddPage( _( "Physical Clearances" ) );
  795. auto reportPhysicalClearance =
  796. [&]( PCB_LAYER_ID aLayer )
  797. {
  798. reportHeader( _( "Physical clearance resolution for:" ), a, b, aLayer, r );
  799. constraint = drcEngine.EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, a, b, aLayer, r );
  800. clearance = constraint.m_Value.Min();
  801. if( compileError )
  802. {
  803. reportCompileError( r );
  804. }
  805. else if( !drcEngine.HasRulesForConstraintType( PHYSICAL_CLEARANCE_CONSTRAINT ) )
  806. {
  807. r->Report( "" );
  808. r->Report( _( "No 'physical_clearance' constraints defined." ) );
  809. }
  810. else
  811. {
  812. r->Report( "" );
  813. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  814. m_frame->StringFromValue( clearance, true ) ) );
  815. }
  816. r->Report( "" );
  817. r->Report( "" );
  818. r->Report( "" );
  819. };
  820. if( layerIntersection.any() )
  821. {
  822. PCB_LAYER_ID layer = active;
  823. if( !layerIntersection.test( layer ) )
  824. layer = layerIntersection.Seq().front();
  825. reportPhysicalClearance( layer );
  826. }
  827. if( aFP && b->IsOnLayer( Edge_Cuts ) )
  828. {
  829. if( !aFP->GetCourtyard( F_CrtYd ).IsEmpty() )
  830. reportPhysicalClearance( F_CrtYd );
  831. if( !aFP->GetCourtyard( B_CrtYd ).IsEmpty() )
  832. reportPhysicalClearance( B_CrtYd );
  833. }
  834. else if( bFP && a->IsOnLayer( Edge_Cuts ) )
  835. {
  836. if( !bFP->GetCourtyard( F_CrtYd ).IsEmpty() )
  837. reportPhysicalClearance( F_CrtYd );
  838. if( !bFP->GetCourtyard( B_CrtYd ).IsEmpty() )
  839. reportPhysicalClearance( B_CrtYd );
  840. }
  841. if( a->HasHole() || b->HasHole() )
  842. {
  843. PCB_LAYER_ID layer;
  844. if( a->HasHole() && b->IsOnLayer( active ) )
  845. layer = active;
  846. else if( b->HasHole() && a->IsOnLayer( active ) )
  847. layer = active;
  848. else if( a->HasHole() )
  849. layer = b->GetLayer();
  850. else
  851. layer = a->GetLayer();
  852. reportHeader( _( "Physical hole clearance resolution for:" ), a, b, layer, r );
  853. constraint = drcEngine.EvalRules( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT, a, b, layer, r );
  854. clearance = constraint.m_Value.Min();
  855. if( compileError )
  856. {
  857. reportCompileError( r );
  858. }
  859. else if( !drcEngine.HasRulesForConstraintType( PHYSICAL_HOLE_CLEARANCE_CONSTRAINT ) )
  860. {
  861. r->Report( "" );
  862. r->Report( _( "No 'physical_hole_clearance' constraints defined." ) );
  863. }
  864. else
  865. {
  866. r->Report( "" );
  867. r->Report( wxString::Format( _( "Resolved clearance: %s." ),
  868. m_frame->StringFromValue( clearance, true ) ) );
  869. }
  870. }
  871. r->Flush();
  872. m_inspectClearanceDialog->Raise();
  873. m_inspectClearanceDialog->Show( true );
  874. return 0;
  875. }
  876. int BOARD_INSPECTION_TOOL::InspectConstraints( const TOOL_EVENT& aEvent )
  877. {
  878. #define EVAL_RULES( constraint, a, b, layer, r ) drcEngine.EvalRules( constraint, a, b, layer, r )
  879. PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  880. const PCB_SELECTION& selection = selTool->GetSelection();
  881. if( selection.Size() != 1 )
  882. {
  883. m_frame->ShowInfoBarError( _( "Select an item for a constraints resolution report." ) );
  884. return 0;
  885. }
  886. if( m_inspectConstraintsDialog == nullptr )
  887. {
  888. m_inspectConstraintsDialog = std::make_unique<DIALOG_CONSTRAINTS_REPORTER>( m_frame );
  889. m_inspectConstraintsDialog->SetTitle( _( "Constraints Report" ) );
  890. m_inspectConstraintsDialog->Connect( wxEVT_CLOSE_WINDOW,
  891. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onInspectConstraintsDialogClosed ),
  892. nullptr, this );
  893. }
  894. m_inspectConstraintsDialog->DeleteAllPages();
  895. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( selection.GetItem( 0 ) );
  896. bool compileError = false;
  897. bool courtyardError = false;
  898. DRC_ENGINE drcEngine = makeDRCEngine( &compileError, &courtyardError );
  899. DRC_CONSTRAINT constraint;
  900. WX_HTML_REPORT_BOX* r = nullptr;
  901. if( item->Type() == PCB_TRACE_T )
  902. {
  903. r = m_inspectConstraintsDialog->AddPage( _( "Track Width" ) );
  904. reportHeader( _( "Track width resolution for:" ), item, r );
  905. constraint = EVAL_RULES( TRACK_WIDTH_CONSTRAINT, item, nullptr, item->GetLayer(), r );
  906. if( compileError )
  907. reportCompileError( r );
  908. r->Report( "" );
  909. r->Report( wxString::Format( _( "Width constraints: min %s; opt %s; max %s." ),
  910. reportMin( m_frame, constraint ),
  911. reportOpt( m_frame, constraint ),
  912. reportMax( m_frame, constraint ) ) );
  913. r->Flush();
  914. }
  915. if( item->Type() == PCB_VIA_T )
  916. {
  917. r = m_inspectConstraintsDialog->AddPage( _( "Via Diameter" ) );
  918. reportHeader( _( "Via diameter resolution for:" ), item, r );
  919. // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer....
  920. constraint = EVAL_RULES( VIA_DIAMETER_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
  921. if( compileError )
  922. reportCompileError( r );
  923. r->Report( "" );
  924. r->Report( wxString::Format( _( "Diameter constraints: min %s; opt %s; max %s." ),
  925. reportMin( m_frame, constraint ),
  926. reportOpt( m_frame, constraint ),
  927. reportMax( m_frame, constraint ) ) );
  928. r->Flush();
  929. r = m_inspectConstraintsDialog->AddPage( _( "Via Annular Width" ) );
  930. reportHeader( _( "Via annular width resolution for:" ), item, r );
  931. // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer....
  932. constraint = EVAL_RULES( ANNULAR_WIDTH_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
  933. if( compileError )
  934. reportCompileError( r );
  935. r->Report( "" );
  936. r->Report( wxString::Format( _( "Annular width constraints: min %s; opt %s; max %s." ),
  937. reportMin( m_frame, constraint ),
  938. reportOpt( m_frame, constraint ),
  939. reportMax( m_frame, constraint ) ) );
  940. r->Flush();
  941. }
  942. if( ( item->Type() == PCB_PAD_T && static_cast<PAD*>( item )->GetDrillSize().x > 0 )
  943. || item->Type() == PCB_VIA_T )
  944. {
  945. r = m_inspectConstraintsDialog->AddPage( _( "Hole Size" ) );
  946. reportHeader( _( "Hole diameter resolution for:" ), item, r );
  947. // PADSTACKS TODO: once we have padstacks we'll need to run this per-layer....
  948. constraint = EVAL_RULES( HOLE_SIZE_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
  949. if( compileError )
  950. reportCompileError( r );
  951. r->Report( "" );
  952. r->Report( wxString::Format( _( "Diameter constraints: min %s; opt %s; max %s." ),
  953. reportMin( m_frame, constraint ),
  954. reportOpt( m_frame, constraint ),
  955. reportMax( m_frame, constraint ) ) );
  956. r->Flush();
  957. }
  958. if( item->Type() == PCB_TEXT_T
  959. || item->Type() == PCB_TEXTBOX_T
  960. || item->Type() == PCB_FP_TEXT_T )
  961. {
  962. r = m_inspectConstraintsDialog->AddPage( _( "Text Size" ) );
  963. reportHeader( _( "Text height resolution for:" ), item, r );
  964. constraint = EVAL_RULES( TEXT_HEIGHT_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
  965. if( compileError )
  966. reportCompileError( r );
  967. r->Report( "" );
  968. r->Report( wxString::Format( _( "Text height constraints: min %s; opt %s; max %s." ),
  969. reportMin( m_frame, constraint ),
  970. reportOpt( m_frame, constraint ),
  971. reportMax( m_frame, constraint ) ) );
  972. r->Report( "" );
  973. r->Report( "" );
  974. r->Report( "" );
  975. reportHeader( _( "Text thickness resolution for:" ), item, r );
  976. constraint = EVAL_RULES( TEXT_THICKNESS_CONSTRAINT, item, nullptr, UNDEFINED_LAYER, r );
  977. if( compileError )
  978. reportCompileError( r );
  979. r->Report( "" );
  980. r->Report( wxString::Format( _( "Text thickness constraints: min %s; opt %s; max %s." ),
  981. reportMin( m_frame, constraint ),
  982. reportOpt( m_frame, constraint ),
  983. reportMax( m_frame, constraint ) ) );
  984. r->Flush();
  985. }
  986. r = m_inspectConstraintsDialog->AddPage( _( "Keepouts" ) );
  987. reportHeader( _( "Keepout resolution for:" ), item, r );
  988. constraint = EVAL_RULES( DISALLOW_CONSTRAINT, item, nullptr, item->GetLayer(), r );
  989. if( compileError )
  990. reportCompileError( r );
  991. if( courtyardError )
  992. {
  993. r->Report( "" );
  994. r->Report( _( "Report may be incomplete: some footprint courtyards are malformed." )
  995. + wxT( " <a href='drc'>" ) + _( "Run DRC for a full analysis." ) + wxT( "</a>" ) );
  996. }
  997. r->Report( "" );
  998. if( constraint.m_DisallowFlags )
  999. r->Report( _( "Item <b>disallowed</b> at current location." ) );
  1000. else
  1001. r->Report( _( "Item allowed at current location." ) );
  1002. r->Flush();
  1003. r = m_inspectConstraintsDialog->AddPage( _( "Assertions" ) );
  1004. reportHeader( _( "Assertions for:" ), item, r );
  1005. if( compileError )
  1006. reportCompileError( r );
  1007. if( courtyardError )
  1008. {
  1009. r->Report( "" );
  1010. r->Report( _( "Report may be incomplete: some footprint courtyards are malformed." )
  1011. + wxT( " <a href='drc'>" ) + _( "Run DRC for a full analysis." ) + wxT( "</a>" ) );
  1012. }
  1013. drcEngine.ProcessAssertions( item, []( const DRC_CONSTRAINT* c ){}, r );
  1014. r->Flush();
  1015. m_inspectConstraintsDialog->FinishInitialization();
  1016. m_inspectConstraintsDialog->Raise();
  1017. m_inspectConstraintsDialog->Show( true );
  1018. return 0;
  1019. }
  1020. int BOARD_INSPECTION_TOOL::HighlightItem( const TOOL_EVENT& aEvent )
  1021. {
  1022. BOARD_ITEM* item = aEvent.Parameter<BOARD_ITEM*>();
  1023. m_frame->m_probingSchToPcb = true; // recursion guard
  1024. {
  1025. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  1026. if( item )
  1027. m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, (void*) item );
  1028. }
  1029. m_frame->m_probingSchToPcb = false;
  1030. bool request3DviewRedraw = frame()->GetPcbNewSettings()->m_Display.m_Live3DRefresh;
  1031. if( item && item->Type() != PCB_FOOTPRINT_T )
  1032. request3DviewRedraw = false;
  1033. // Update 3D viewer highlighting
  1034. if( request3DviewRedraw )
  1035. m_frame->Update3DView( false, true );
  1036. return 0;
  1037. }
  1038. bool BOARD_INSPECTION_TOOL::highlightNet( const VECTOR2D& aPosition, bool aUseSelection )
  1039. {
  1040. BOARD* board = static_cast<BOARD*>( m_toolMgr->GetModel() );
  1041. KIGFX::RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
  1042. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1043. int net = -1;
  1044. bool enableHighlight = false;
  1045. if( aUseSelection )
  1046. {
  1047. const PCB_SELECTION& selection = selectionTool->GetSelection();
  1048. std::set<int> netcodes;
  1049. for( EDA_ITEM* item : selection )
  1050. {
  1051. if( BOARD_CONNECTED_ITEM* ci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
  1052. netcodes.insert( ci->GetNetCode() );
  1053. }
  1054. enableHighlight = !netcodes.empty();
  1055. if( enableHighlight && netcodes.size() > 1 )
  1056. {
  1057. // If we are doing a multi-highlight, cross-probing back and other stuff is not
  1058. // yet supported
  1059. settings->SetHighlight( netcodes );
  1060. board->ResetNetHighLight();
  1061. for( int multiNet : netcodes )
  1062. board->SetHighLightNet( multiNet, true );
  1063. board->HighLightON();
  1064. m_toolMgr->GetView()->UpdateAllLayersColor();
  1065. m_currentlyHighlighted = netcodes;
  1066. return true;
  1067. }
  1068. else if( enableHighlight )
  1069. {
  1070. net = *netcodes.begin();
  1071. }
  1072. }
  1073. // If we didn't get a net to highlight from the selection, use the cursor
  1074. if( net < 0 )
  1075. {
  1076. GENERAL_COLLECTORS_GUIDE guide = m_frame->GetCollectorsGuide();
  1077. guide.SetIgnoreZoneFills( false );
  1078. PCB_LAYER_ID activeLayer = static_cast<PCB_LAYER_ID>( view()->GetTopLayer() );
  1079. guide.SetPreferredLayer( activeLayer );
  1080. GENERAL_COLLECTOR collector;
  1081. collector.Collect( board, { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T }, aPosition,
  1082. guide );
  1083. if( collector.GetCount() == 0 )
  1084. collector.Collect( board, { PCB_ZONE_T, PCB_FP_ZONE_T }, aPosition, guide );
  1085. // Apply the active selection filter, except we want to allow picking locked items for
  1086. // highlighting even if the user has disabled them for selection
  1087. SELECTION_FILTER_OPTIONS& filter = selectionTool->GetFilter();
  1088. bool saved = filter.lockedItems;
  1089. filter.lockedItems = true;
  1090. selectionTool->FilterCollectedItems( collector, true );
  1091. filter.lockedItems = saved;
  1092. // Clear the previous highlight
  1093. //m_frame->SendMessageToEESCHEMA( nullptr );
  1094. bool highContrast = settings->GetHighContrast();
  1095. PCB_LAYER_ID contrastLayer = settings->GetPrimaryHighContrastLayer();
  1096. for( int i = collector.GetCount() - 1; i >= 0; i-- )
  1097. {
  1098. LSET itemLayers = collector[i]->GetLayerSet();
  1099. if( ( itemLayers & LSET::AllCuMask() ).none() ||
  1100. ( highContrast && !itemLayers.Contains( contrastLayer ) ) )
  1101. {
  1102. collector.Remove( i );
  1103. continue;
  1104. }
  1105. }
  1106. enableHighlight = ( collector.GetCount() > 0 );
  1107. // Obtain net code for the clicked item
  1108. if( enableHighlight )
  1109. {
  1110. BOARD_CONNECTED_ITEM* targetItem = static_cast<BOARD_CONNECTED_ITEM*>( collector[0] );
  1111. if( targetItem->Type() == PCB_PAD_T )
  1112. m_frame->SendCrossProbeItem( targetItem );
  1113. net = targetItem->GetNetCode();
  1114. }
  1115. }
  1116. const std::set<int>& netcodes = settings->GetHighlightNetCodes();
  1117. // Toggle highlight when the same net was picked
  1118. if( netcodes.count( net ) )
  1119. enableHighlight = !settings->IsHighlightEnabled();
  1120. if( enableHighlight != settings->IsHighlightEnabled() || !netcodes.count( net ) )
  1121. {
  1122. if( !netcodes.empty() )
  1123. m_lastHighlighted = netcodes;
  1124. settings->SetHighlight( enableHighlight, net );
  1125. m_toolMgr->GetView()->UpdateAllLayersColor();
  1126. }
  1127. // Store the highlighted netcode in the current board (for dialogs for instance)
  1128. if( enableHighlight && net >= 0 )
  1129. {
  1130. m_currentlyHighlighted = netcodes;
  1131. board->SetHighLightNet( net );
  1132. board->HighLightON();
  1133. NETINFO_ITEM* netinfo = board->FindNet( net );
  1134. if( netinfo )
  1135. {
  1136. std::vector<MSG_PANEL_ITEM> items;
  1137. netinfo->GetMsgPanelInfo( m_frame, items );
  1138. m_frame->SetMsgPanel( items );
  1139. m_frame->SendCrossProbeNetName( netinfo->GetNetname() );
  1140. }
  1141. }
  1142. else
  1143. {
  1144. m_currentlyHighlighted.clear();
  1145. board->ResetNetHighLight();
  1146. m_frame->SetMsgPanel( board );
  1147. m_frame->SendCrossProbeNetName( "" );
  1148. }
  1149. return true;
  1150. }
  1151. int BOARD_INSPECTION_TOOL::HighlightNet( const TOOL_EVENT& aEvent )
  1152. {
  1153. int netcode = aEvent.Parameter<intptr_t>();
  1154. KIGFX::RENDER_SETTINGS* settings = m_toolMgr->GetView()->GetPainter()->GetSettings();
  1155. const std::set<int>& highlighted = settings->GetHighlightNetCodes();
  1156. if( netcode > 0 )
  1157. {
  1158. m_lastHighlighted = highlighted;
  1159. settings->SetHighlight( true, netcode );
  1160. m_toolMgr->GetView()->UpdateAllLayersColor();
  1161. m_currentlyHighlighted.clear();
  1162. m_currentlyHighlighted.insert( netcode );
  1163. }
  1164. else if( aEvent.IsAction( &PCB_ACTIONS::highlightNetSelection ) )
  1165. {
  1166. // Highlight selection (cursor position will be ignored)
  1167. highlightNet( getViewControls()->GetMousePosition(), true );
  1168. }
  1169. else if( aEvent.IsAction( &PCB_ACTIONS::toggleLastNetHighlight ) )
  1170. {
  1171. std::set<int> temp = highlighted;
  1172. settings->SetHighlight( m_lastHighlighted );
  1173. m_toolMgr->GetView()->UpdateAllLayersColor();
  1174. m_currentlyHighlighted = m_lastHighlighted;
  1175. m_lastHighlighted = temp;
  1176. }
  1177. else if( aEvent.IsAction( &PCB_ACTIONS::toggleNetHighlight ) )
  1178. {
  1179. bool turnOn = highlighted.empty() && !m_currentlyHighlighted.empty();
  1180. settings->SetHighlight( m_currentlyHighlighted, turnOn );
  1181. m_toolMgr->GetView()->UpdateAllLayersColor();
  1182. }
  1183. else // Highlight the net belonging to the item under the cursor
  1184. {
  1185. highlightNet( getViewControls()->GetMousePosition(), false );
  1186. }
  1187. return 0;
  1188. }
  1189. int BOARD_INSPECTION_TOOL::ClearHighlight( const TOOL_EVENT& aEvent )
  1190. {
  1191. BOARD* board = static_cast<BOARD*>( m_toolMgr->GetModel() );
  1192. KIGFX::RENDER_SETTINGS* settings = m_toolMgr->GetView()->GetPainter()->GetSettings();
  1193. m_currentlyHighlighted.clear();
  1194. m_lastHighlighted.clear();
  1195. board->ResetNetHighLight();
  1196. settings->SetHighlight( false );
  1197. m_toolMgr->GetView()->UpdateAllLayersColor();
  1198. m_frame->SetMsgPanel( board );
  1199. m_frame->SendCrossProbeNetName( "" );
  1200. return 0;
  1201. }
  1202. int BOARD_INSPECTION_TOOL::LocalRatsnestTool( const TOOL_EVENT& aEvent )
  1203. {
  1204. PCB_PICKER_TOOL* picker = m_toolMgr->GetTool<PCB_PICKER_TOOL>();
  1205. BOARD* board = getModel<BOARD>();
  1206. // Deactivate other tools; particularly important if another PICKER is currently running
  1207. Activate();
  1208. picker->SetCursor( KICURSOR::BULLSEYE );
  1209. picker->SetClickHandler(
  1210. [this, board]( const VECTOR2D& pt ) -> bool
  1211. {
  1212. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1213. m_toolMgr->RunAction( PCB_ACTIONS::selectionClear, true );
  1214. m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true, EDIT_TOOL::PadFilter );
  1215. PCB_SELECTION& selection = selectionTool->GetSelection();
  1216. if( selection.Empty() )
  1217. {
  1218. m_toolMgr->RunAction( PCB_ACTIONS::selectionCursor, true,
  1219. EDIT_TOOL::FootprintFilter );
  1220. selection = selectionTool->GetSelection();
  1221. }
  1222. if( selection.Empty() )
  1223. {
  1224. // Clear the previous local ratsnest if we click off all items
  1225. for( FOOTPRINT* fp : board->Footprints() )
  1226. {
  1227. for( PAD* pad : fp->Pads() )
  1228. pad->SetLocalRatsnestVisible( displayOptions().m_ShowGlobalRatsnest );
  1229. }
  1230. }
  1231. else
  1232. {
  1233. for( EDA_ITEM* item : selection )
  1234. {
  1235. if( PAD* pad = dyn_cast<PAD*>( item) )
  1236. {
  1237. pad->SetLocalRatsnestVisible( !pad->GetLocalRatsnestVisible() );
  1238. }
  1239. else if( FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item) )
  1240. {
  1241. if( !fp->Pads().empty() )
  1242. {
  1243. bool enable = !fp->Pads()[0]->GetLocalRatsnestVisible();
  1244. for( PAD* childPad : fp->Pads() )
  1245. childPad->SetLocalRatsnestVisible( enable );
  1246. }
  1247. }
  1248. }
  1249. }
  1250. m_toolMgr->GetView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
  1251. return true;
  1252. } );
  1253. picker->SetFinalizeHandler(
  1254. [this, board]( int aCondition )
  1255. {
  1256. if( aCondition != PCB_PICKER_TOOL::END_ACTIVATE )
  1257. {
  1258. for( FOOTPRINT* fp : board->Footprints() )
  1259. {
  1260. for( PAD* pad : fp->Pads() )
  1261. pad->SetLocalRatsnestVisible( displayOptions().m_ShowGlobalRatsnest );
  1262. }
  1263. }
  1264. } );
  1265. m_toolMgr->RunAction( ACTIONS::pickerTool, true );
  1266. return 0;
  1267. }
  1268. int BOARD_INSPECTION_TOOL::UpdateLocalRatsnest( const TOOL_EVENT& aEvent )
  1269. {
  1270. VECTOR2I delta;
  1271. // If we have passed the simple move vector, we can update without recalculation
  1272. if( aEvent.Parameter<VECTOR2I*>() )
  1273. {
  1274. delta = *aEvent.Parameter<VECTOR2I*>();
  1275. delete aEvent.Parameter<VECTOR2I*>();
  1276. }
  1277. else
  1278. {
  1279. // We can delete the existing map to force a recalculation
  1280. delete m_dynamicData;
  1281. m_dynamicData = nullptr;
  1282. }
  1283. auto selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1284. auto& selection = selectionTool->GetSelection();
  1285. auto connectivity = getModel<BOARD>()->GetConnectivity();
  1286. if( selection.Empty() )
  1287. {
  1288. connectivity->ClearLocalRatsnest();
  1289. delete m_dynamicData;
  1290. m_dynamicData = nullptr;
  1291. }
  1292. else
  1293. {
  1294. calculateSelectionRatsnest( delta );
  1295. }
  1296. return 0;
  1297. }
  1298. int BOARD_INSPECTION_TOOL::HideLocalRatsnest( const TOOL_EVENT& aEvent )
  1299. {
  1300. getModel<BOARD>()->GetConnectivity()->ClearLocalRatsnest();
  1301. delete m_dynamicData;
  1302. m_dynamicData = nullptr;
  1303. return 0;
  1304. }
  1305. void BOARD_INSPECTION_TOOL::calculateSelectionRatsnest( const VECTOR2I& aDelta )
  1306. {
  1307. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1308. SELECTION& selection = selectionTool->GetSelection();
  1309. std::shared_ptr<CONNECTIVITY_DATA> connectivity = board()->GetConnectivity();
  1310. std::vector<BOARD_ITEM*> items;
  1311. std::deque<EDA_ITEM*> queued_items( selection.begin(), selection.end() );
  1312. for( std::size_t i = 0; i < queued_items.size(); ++i )
  1313. {
  1314. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( queued_items[i] );
  1315. if( item->Type() == PCB_FOOTPRINT_T )
  1316. {
  1317. for( PAD* pad : static_cast<FOOTPRINT*>( item )->Pads() )
  1318. {
  1319. if( pad->GetLocalRatsnestVisible() || displayOptions().m_ShowModuleRatsnest )
  1320. items.push_back( pad );
  1321. }
  1322. }
  1323. else if( item->Type() == PCB_GROUP_T )
  1324. {
  1325. PCB_GROUP *group = static_cast<PCB_GROUP*>( item );
  1326. group->RunOnDescendants( [ &queued_items ]( BOARD_ITEM *aItem )
  1327. {
  1328. queued_items.push_back( aItem );
  1329. } );
  1330. }
  1331. else if( BOARD_CONNECTED_ITEM* boardItem = dyn_cast<BOARD_CONNECTED_ITEM*>( item ) )
  1332. {
  1333. if( boardItem->GetLocalRatsnestVisible() || displayOptions().m_ShowModuleRatsnest )
  1334. items.push_back( boardItem );
  1335. }
  1336. }
  1337. if( items.empty() || std::none_of( items.begin(), items.end(),
  1338. []( const BOARD_ITEM* aItem )
  1339. {
  1340. return( aItem->Type() == PCB_TRACE_T
  1341. || aItem->Type() == PCB_PAD_T
  1342. || aItem->Type() == PCB_ARC_T
  1343. || aItem->Type() == PCB_ZONE_T
  1344. || aItem->Type() == PCB_FOOTPRINT_T
  1345. || aItem->Type() == PCB_VIA_T );
  1346. } ) )
  1347. {
  1348. return;
  1349. }
  1350. if( !m_dynamicData )
  1351. {
  1352. m_dynamicData = new CONNECTIVITY_DATA( items, true );
  1353. connectivity->BlockRatsnestItems( items );
  1354. }
  1355. else
  1356. {
  1357. m_dynamicData->Move( aDelta );
  1358. }
  1359. connectivity->ComputeLocalRatsnest( items, m_dynamicData );
  1360. }
  1361. int BOARD_INSPECTION_TOOL::ListNets( const TOOL_EVENT& aEvent )
  1362. {
  1363. if( m_listNetsDialog == nullptr )
  1364. {
  1365. m_listNetsDialog =
  1366. std::make_unique<DIALOG_NET_INSPECTOR>( m_frame, m_listNetsDialogSettings );
  1367. m_listNetsDialog->Connect( wxEVT_CLOSE_WINDOW,
  1368. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onListNetsDialogClosed ), nullptr,
  1369. this );
  1370. m_listNetsDialog->Connect( wxEVT_BUTTON,
  1371. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onListNetsDialogClosed ), nullptr,
  1372. this );
  1373. }
  1374. m_listNetsDialog->Raise();
  1375. m_listNetsDialog->Show( true );
  1376. return 0;
  1377. }
  1378. void BOARD_INSPECTION_TOOL::onListNetsDialogClosed( wxCommandEvent& event )
  1379. {
  1380. m_listNetsDialogSettings = m_listNetsDialog->Settings();
  1381. m_listNetsDialog->Disconnect( wxEVT_CLOSE_WINDOW,
  1382. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onListNetsDialogClosed ), nullptr, this );
  1383. m_listNetsDialog->Disconnect( wxEVT_BUTTON,
  1384. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onListNetsDialogClosed ), nullptr, this );
  1385. m_listNetsDialog->Destroy();
  1386. m_listNetsDialog.release();
  1387. }
  1388. void BOARD_INSPECTION_TOOL::onInspectClearanceDialogClosed( wxCommandEvent& event )
  1389. {
  1390. m_inspectClearanceDialog->Disconnect( wxEVT_CLOSE_WINDOW,
  1391. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onInspectClearanceDialogClosed ),
  1392. nullptr, this );
  1393. m_inspectClearanceDialog->Destroy();
  1394. m_inspectClearanceDialog.release();
  1395. }
  1396. void BOARD_INSPECTION_TOOL::onInspectConstraintsDialogClosed( wxCommandEvent& event )
  1397. {
  1398. m_inspectConstraintsDialog->Disconnect( wxEVT_CLOSE_WINDOW,
  1399. wxCommandEventHandler( BOARD_INSPECTION_TOOL::onInspectConstraintsDialogClosed ),
  1400. nullptr, this );
  1401. m_inspectConstraintsDialog->Destroy();
  1402. m_inspectConstraintsDialog.release();
  1403. }
  1404. int BOARD_INSPECTION_TOOL::HideNetInRatsnest( const TOOL_EVENT& aEvent )
  1405. {
  1406. doHideRatsnestNet( aEvent.Parameter<intptr_t>(), true );
  1407. return 0;
  1408. }
  1409. int BOARD_INSPECTION_TOOL::ShowNetInRatsnest( const TOOL_EVENT& aEvent )
  1410. {
  1411. doHideRatsnestNet( aEvent.Parameter<intptr_t>(), false );
  1412. return 0;
  1413. }
  1414. void BOARD_INSPECTION_TOOL::doHideRatsnestNet( int aNetCode, bool aHide )
  1415. {
  1416. KIGFX::PCB_RENDER_SETTINGS* rs = static_cast<KIGFX::PCB_RENDER_SETTINGS*>(
  1417. m_toolMgr->GetView()->GetPainter()->GetSettings() );
  1418. PCB_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<PCB_SELECTION_TOOL>();
  1419. SELECTION& selection = selectionTool->GetSelection();
  1420. if( aNetCode <= 0 && !selection.Empty() )
  1421. {
  1422. for( EDA_ITEM* item : selection )
  1423. {
  1424. if( BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( item ) )
  1425. {
  1426. if( bci->GetNetCode() > 0 )
  1427. doHideRatsnestNet( bci->GetNetCode(), aHide );
  1428. }
  1429. }
  1430. return;
  1431. }
  1432. if( aHide )
  1433. rs->GetHiddenNets().insert( aNetCode );
  1434. else
  1435. rs->GetHiddenNets().erase( aNetCode );
  1436. m_frame->GetCanvas()->RedrawRatsnest();
  1437. m_frame->GetCanvas()->Refresh();
  1438. m_frame->GetAppearancePanel()->OnNetVisibilityChanged( aNetCode, !aHide );
  1439. }
  1440. void BOARD_INSPECTION_TOOL::setTransitions()
  1441. {
  1442. Go( &BOARD_INSPECTION_TOOL::LocalRatsnestTool, PCB_ACTIONS::localRatsnestTool.MakeEvent() );
  1443. Go( &BOARD_INSPECTION_TOOL::HideLocalRatsnest, PCB_ACTIONS::hideLocalRatsnest.MakeEvent() );
  1444. Go( &BOARD_INSPECTION_TOOL::UpdateLocalRatsnest, PCB_ACTIONS::updateLocalRatsnest.MakeEvent() );
  1445. Go( &BOARD_INSPECTION_TOOL::ListNets, PCB_ACTIONS::listNets.MakeEvent() );
  1446. Go( &BOARD_INSPECTION_TOOL::ShowBoardStatistics, PCB_ACTIONS::boardStatistics.MakeEvent() );
  1447. Go( &BOARD_INSPECTION_TOOL::InspectClearance, PCB_ACTIONS::inspectClearance.MakeEvent() );
  1448. Go( &BOARD_INSPECTION_TOOL::InspectConstraints, PCB_ACTIONS::inspectConstraints.MakeEvent() );
  1449. Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::highlightNet.MakeEvent() );
  1450. Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::highlightNetSelection.MakeEvent() );
  1451. Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::toggleLastNetHighlight.MakeEvent() );
  1452. Go( &BOARD_INSPECTION_TOOL::ClearHighlight, PCB_ACTIONS::clearHighlight.MakeEvent() );
  1453. Go( &BOARD_INSPECTION_TOOL::HighlightNet, PCB_ACTIONS::toggleNetHighlight.MakeEvent() );
  1454. Go( &BOARD_INSPECTION_TOOL::HighlightItem, PCB_ACTIONS::highlightItem.MakeEvent() );
  1455. Go( &BOARD_INSPECTION_TOOL::HideNetInRatsnest, PCB_ACTIONS::hideNetInRatsnest.MakeEvent() );
  1456. Go( &BOARD_INSPECTION_TOOL::ShowNetInRatsnest, PCB_ACTIONS::showNetInRatsnest.MakeEvent() );
  1457. }