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.

472 lines
16 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers.
  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 <board_design_settings.h>
  24. #include <drc/drc_engine.h>
  25. #include <drc/drc_item.h>
  26. #include <drc/drc_rule.h>
  27. #include <drc/drc_rule_condition.h>
  28. #include <drc/drc_test_provider.h>
  29. #include <pad.h>
  30. #include <pcb_track.h>
  31. #include <drawing_sheet/ds_draw_item.h>
  32. #include <drawing_sheet/ds_proxy_view_item.h>
  33. /*
  34. Miscellaneous tests:
  35. - DRCE_DISABLED_LAYER_ITEM, ///< item on a disabled layer
  36. - DRCE_INVALID_OUTLINE, ///< invalid board outline
  37. - DRCE_UNRESOLVED_VARIABLE,
  38. - DRCE_ASSERTION_FAILURE, ///< user-defined assertions
  39. - DRCE_GENERIC_WARNING ///< user-defined warnings
  40. - DRCE_GENERIC_ERROR ///< user-defined errors
  41. */
  42. class DRC_TEST_PROVIDER_MISC : public DRC_TEST_PROVIDER
  43. {
  44. public:
  45. DRC_TEST_PROVIDER_MISC() :
  46. m_board( nullptr )
  47. {
  48. m_isRuleDriven = false;
  49. }
  50. virtual ~DRC_TEST_PROVIDER_MISC()
  51. {
  52. }
  53. virtual bool Run() override;
  54. virtual const wxString GetName() const override
  55. {
  56. return wxT( "miscellaneous" );
  57. };
  58. virtual const wxString GetDescription() const override
  59. {
  60. return wxT( "Misc checks (board outline, missing textvars)" );
  61. }
  62. private:
  63. void testOutline();
  64. void testDisabledLayers();
  65. void testTextVars();
  66. void testAssertions();
  67. BOARD* m_board;
  68. };
  69. void DRC_TEST_PROVIDER_MISC::testOutline()
  70. {
  71. SHAPE_POLY_SET dummyOutline;
  72. bool errorHandled = false;
  73. OUTLINE_ERROR_HANDLER errorHandler =
  74. [&]( const wxString& msg, BOARD_ITEM* itemA, BOARD_ITEM* itemB, const VECTOR2I& pt )
  75. {
  76. errorHandled = true;
  77. if( m_drcEngine->IsErrorLimitExceeded( DRCE_INVALID_OUTLINE ) )
  78. return;
  79. if( !itemA ) // If we only have a single item, make sure it's A
  80. std::swap( itemA, itemB );
  81. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE );
  82. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  83. drcItem->SetItems( itemA, itemB );
  84. reportViolation( drcItem, pt, Edge_Cuts );
  85. };
  86. // Test for very small graphic items (a few nm size) that can create issues
  87. // when trying to build the board outlines, and they are not easy to locate onn screen.
  88. const int minSizeForValideGraphics = pcbIUScale.mmToIU( 0.001 );
  89. if( !TestBoardOutlinesGraphicItems(m_board, minSizeForValideGraphics, &errorHandler ) )
  90. {
  91. if( errorHandled )
  92. {
  93. // if there are invalid items on Edge.Cuts, they are already reported
  94. }
  95. else
  96. {
  97. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE );
  98. wxString msg;
  99. msg.Printf( _( "(Suspicious items found on Edge.Cuts layer)" ) );
  100. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  101. drcItem->SetItems( m_board );
  102. reportViolation( drcItem, m_board->GetBoundingBox().Centre(), Edge_Cuts );
  103. }
  104. }
  105. // Use the standard chaining epsilon here so that we report errors that might affect
  106. // other tools (such as 3D viewer).
  107. int chainingEpsilon = m_board->GetOutlinesChainingEpsilon();
  108. if( !BuildBoardPolygonOutlines( m_board, dummyOutline, m_board->GetDesignSettings().m_MaxError,
  109. chainingEpsilon, &errorHandler ) )
  110. {
  111. if( errorHandled )
  112. {
  113. // if there is an invalid outline, then there must be an outline
  114. }
  115. else
  116. {
  117. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE );
  118. wxString msg;
  119. msg.Printf( _( "(no edges found on Edge.Cuts layer)" ) );
  120. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  121. drcItem->SetItems( m_board );
  122. reportViolation( drcItem, m_board->GetBoundingBox().Centre(), Edge_Cuts );
  123. }
  124. }
  125. }
  126. void DRC_TEST_PROVIDER_MISC::testDisabledLayers()
  127. {
  128. const int progressDelta = 2000;
  129. int ii = 0;
  130. int items = 0;
  131. auto countItems =
  132. [&]( BOARD_ITEM* item ) -> bool
  133. {
  134. ++items;
  135. return true;
  136. };
  137. LSET disabledLayers = LSET( m_board->GetEnabledLayers() ).flip();
  138. // Perform the test only for copper layers
  139. disabledLayers &= LSET::AllCuMask();
  140. auto checkDisabledLayers =
  141. [&]( BOARD_ITEM* item ) -> bool
  142. {
  143. if( m_drcEngine->IsErrorLimitExceeded( DRCE_DISABLED_LAYER_ITEM ) )
  144. return false;
  145. if( !reportProgress( ii++, items, progressDelta ) )
  146. return false;
  147. PCB_LAYER_ID badLayer = UNDEFINED_LAYER;
  148. if( item->Type() == PCB_PAD_T )
  149. {
  150. PAD* pad = static_cast<PAD*>( item );
  151. if( pad->GetAttribute() == PAD_ATTRIB::SMD
  152. || pad->GetAttribute() == PAD_ATTRIB::CONN )
  153. {
  154. if( disabledLayers.test( pad->GetPrincipalLayer() ) )
  155. badLayer = item->GetLayer();
  156. }
  157. else
  158. {
  159. // Through hole pad pierces all physical layers.
  160. }
  161. }
  162. else if( item->Type() == PCB_VIA_T )
  163. {
  164. PCB_VIA* via = static_cast<PCB_VIA*>( item );
  165. PCB_LAYER_ID top;
  166. PCB_LAYER_ID bottom;
  167. via->LayerPair( &top, &bottom );
  168. if( disabledLayers.test( top ) )
  169. badLayer = top;
  170. else if( disabledLayers.test( bottom ) )
  171. badLayer = bottom;
  172. }
  173. else if( item->Type() == PCB_ZONE_T )
  174. {
  175. // Footprint zones just get a top/bottom/inner setting, so they're on
  176. // whatever inner layers there are.
  177. }
  178. else
  179. {
  180. LSET badLayers = disabledLayers & item->GetLayerSet();
  181. if( badLayers.any() )
  182. badLayer = badLayers.Seq().front();
  183. }
  184. if( badLayer != UNDEFINED_LAYER )
  185. {
  186. auto drcItem = DRC_ITEM::Create( DRCE_DISABLED_LAYER_ITEM );
  187. wxString msg;
  188. msg.Printf( _( "(layer %s)" ), LayerName( badLayer ) );
  189. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
  190. drcItem->SetItems( item );
  191. reportViolation( drcItem, item->GetPosition(), UNDEFINED_LAYER );
  192. }
  193. return true;
  194. };
  195. forEachGeometryItem( s_allBasicItems, LSET::AllLayersMask(), countItems );
  196. forEachGeometryItem( s_allBasicItems, LSET::AllLayersMask(), checkDisabledLayers );
  197. }
  198. void DRC_TEST_PROVIDER_MISC::testAssertions()
  199. {
  200. const int progressDelta = 2000;
  201. int ii = 0;
  202. int items = 0;
  203. auto countItems =
  204. [&]( BOARD_ITEM* item ) -> bool
  205. {
  206. ++items;
  207. return true;
  208. };
  209. auto checkAssertions =
  210. [&]( BOARD_ITEM* item ) -> bool
  211. {
  212. if( !reportProgress( ii++, items, progressDelta ) )
  213. return false;
  214. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_ASSERTION_FAILURE ) )
  215. {
  216. m_drcEngine->ProcessAssertions( item,
  217. [&]( const DRC_CONSTRAINT* c )
  218. {
  219. auto drcItem = DRC_ITEM::Create( DRCE_ASSERTION_FAILURE );
  220. drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " (" )
  221. + c->GetName() + wxS( ")" ) );
  222. drcItem->SetItems( item );
  223. drcItem->SetViolatingRule( c->GetParentRule() );
  224. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  225. } );
  226. }
  227. return true;
  228. };
  229. forEachGeometryItem( {}, LSET::AllLayersMask(), countItems );
  230. forEachGeometryItem( {}, LSET::AllLayersMask(), checkAssertions );
  231. }
  232. void DRC_TEST_PROVIDER_MISC::testTextVars()
  233. {
  234. const int progressDelta = 2000;
  235. int ii = 0;
  236. int items = 0;
  237. static const std::vector<KICAD_T> itemTypes = {
  238. PCB_FIELD_T,
  239. PCB_TEXT_T, PCB_TEXTBOX_T, PCB_TABLECELL_T,
  240. PCB_DIMENSION_T
  241. };
  242. auto testAssertion =
  243. [&]( BOARD_ITEM* item, const wxString& text, const VECTOR2I& pos, int layer )
  244. {
  245. static wxRegEx warningExpr( wxS( "^\\$\\{DRC_WARNING\\s*([^}]*)\\}(.*)$" ) );
  246. static wxRegEx errorExpr( wxS( "^\\$\\{DRC_ERROR\\s*([^}]*)\\}(.*)$" ) );
  247. if( warningExpr.Matches( text ) )
  248. {
  249. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_GENERIC_WARNING ) )
  250. {
  251. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_GENERIC_WARNING );
  252. wxString drcText = warningExpr.GetMatch( text, 1 );
  253. if( item )
  254. drcItem->SetItems( item );
  255. else
  256. drcText += _( " (in drawing sheet)" );
  257. drcItem->SetErrorMessage( drcText );
  258. reportViolation( drcItem, pos, layer );
  259. }
  260. return true;
  261. }
  262. if( errorExpr.Matches( text ) )
  263. {
  264. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_GENERIC_ERROR ) )
  265. {
  266. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_GENERIC_ERROR );
  267. wxString drcText = errorExpr.GetMatch( text, 1 );
  268. if( item )
  269. drcItem->SetItems( item );
  270. else
  271. drcText += _( " (in drawing sheet)" );
  272. drcItem->SetErrorMessage( drcText );
  273. reportViolation( drcItem, pos, layer );
  274. }
  275. return true;
  276. }
  277. return false;
  278. };
  279. forEachGeometryItem( itemTypes, LSET::AllLayersMask(),
  280. [&]( BOARD_ITEM* item ) -> bool
  281. {
  282. ++items;
  283. return true;
  284. } );
  285. forEachGeometryItem( itemTypes, LSET::AllLayersMask(),
  286. [&]( BOARD_ITEM* item ) -> bool
  287. {
  288. if( m_drcEngine->IsErrorLimitExceeded( DRCE_UNRESOLVED_VARIABLE ) )
  289. return false;
  290. if( !reportProgress( ii++, items, progressDelta ) )
  291. return false;
  292. if( EDA_TEXT* textItem = dynamic_cast<EDA_TEXT*>( item ) )
  293. {
  294. wxString result = ExpandEnvVarSubstitutions( textItem->GetShownText( true ),
  295. nullptr /*project already done*/ );
  296. if( result.Matches( wxT( "*${*}*" ) ) )
  297. {
  298. auto drcItem = DRC_ITEM::Create( DRCE_UNRESOLVED_VARIABLE );
  299. drcItem->SetItems( item );
  300. reportViolation( drcItem, item->GetPosition(), item->GetLayer() );
  301. }
  302. testAssertion( item, textItem->GetText(), item->GetPosition(), item->GetLayer() );
  303. }
  304. return true;
  305. } );
  306. DS_PROXY_VIEW_ITEM* drawingSheet = m_drcEngine->GetDrawingSheet();
  307. DS_DRAW_ITEM_LIST drawItems( pcbIUScale, FOR_ERC_DRC );
  308. if( !drawingSheet || m_drcEngine->IsErrorLimitExceeded( DRCE_UNRESOLVED_VARIABLE ) )
  309. return;
  310. drawItems.SetPageNumber( wxT( "1" ) );
  311. drawItems.SetSheetCount( 1 );
  312. drawItems.SetFileName( wxT( "dummyFilename" ) );
  313. drawItems.SetSheetName( wxT( "dummySheet" ) );
  314. drawItems.SetSheetLayer( wxT( "dummyLayer" ) );
  315. drawItems.SetProject( m_board->GetProject() );
  316. drawItems.BuildDrawItemsList( drawingSheet->GetPageInfo(), drawingSheet->GetTitleBlock() );
  317. for( DS_DRAW_ITEM_BASE* item = drawItems.GetFirst(); item; item = drawItems.GetNext() )
  318. {
  319. if( m_drcEngine->IsErrorLimitExceeded( DRCE_UNRESOLVED_VARIABLE ) )
  320. break;
  321. if( m_drcEngine->IsCancelled() )
  322. return;
  323. if( DS_DRAW_ITEM_TEXT* text = dynamic_cast<DS_DRAW_ITEM_TEXT*>( item ) )
  324. {
  325. if( testAssertion( nullptr, text->GetText(), text->GetPosition(), LAYER_DRAWINGSHEET ) )
  326. {
  327. // Don't run unresolved test
  328. }
  329. else if( text->GetShownText( true ).Matches( wxT( "*${*}*" ) ) )
  330. {
  331. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_UNRESOLVED_VARIABLE );
  332. drcItem->SetItems( drawingSheet );
  333. reportViolation( drcItem, text->GetPosition(), LAYER_DRAWINGSHEET );
  334. }
  335. }
  336. }
  337. }
  338. bool DRC_TEST_PROVIDER_MISC::Run()
  339. {
  340. m_board = m_drcEngine->GetBoard();
  341. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_INVALID_OUTLINE ) )
  342. {
  343. if( !reportPhase( _( "Checking board outline..." ) ) )
  344. return false; // DRC cancelled
  345. testOutline();
  346. }
  347. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_DISABLED_LAYER_ITEM ) )
  348. {
  349. if( !reportPhase( _( "Checking disabled layers..." ) ) )
  350. return false; // DRC cancelled
  351. testDisabledLayers();
  352. }
  353. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_UNRESOLVED_VARIABLE ) )
  354. {
  355. if( !reportPhase( _( "Checking text variables..." ) ) )
  356. return false; // DRC cancelled
  357. testTextVars();
  358. }
  359. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_ASSERTION_FAILURE )
  360. || !m_drcEngine->IsErrorLimitExceeded( DRCE_GENERIC_WARNING )
  361. || !m_drcEngine->IsErrorLimitExceeded( DRCE_GENERIC_ERROR ) )
  362. {
  363. if( !reportPhase( _( "Checking assertions..." ) ) )
  364. return false; // DRC cancelled
  365. testAssertions();
  366. }
  367. return !m_drcEngine->IsCancelled();
  368. }
  369. namespace detail
  370. {
  371. static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_MISC> dummy;
  372. }