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.

2165 lines
73 KiB

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