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.

1800 lines
62 KiB

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