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.

374 lines
14 KiB

5 years ago
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.
  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 <common.h>
  24. #include <pcb_shape.h>
  25. #include <footprint.h>
  26. #include <geometry/seg.h>
  27. #include <geometry/shape_segment.h>
  28. #include <drc/drc_engine.h>
  29. #include <drc/drc_item.h>
  30. #include <drc/drc_rule.h>
  31. #include <drc/drc_test_provider_clearance_base.h>
  32. #include "drc_rtree.h"
  33. /*
  34. Board edge clearance test. Checks all items for their mechanical clearances against the board
  35. edge.
  36. Errors generated:
  37. - DRCE_EDGE_CLEARANCE
  38. - DRCE_SILK_EDGE_CLEARANCE
  39. */
  40. class DRC_TEST_PROVIDER_EDGE_CLEARANCE : public DRC_TEST_PROVIDER_CLEARANCE_BASE
  41. {
  42. public:
  43. DRC_TEST_PROVIDER_EDGE_CLEARANCE () :
  44. DRC_TEST_PROVIDER_CLEARANCE_BASE(),
  45. m_largestEdgeClearance( 0 )
  46. {
  47. }
  48. virtual ~DRC_TEST_PROVIDER_EDGE_CLEARANCE()
  49. {
  50. }
  51. virtual bool Run() override;
  52. virtual const wxString GetName() const override
  53. {
  54. return wxT( "edge_clearance" );
  55. }
  56. virtual const wxString GetDescription() const override
  57. {
  58. return wxT( "Tests items vs board edge clearance" );
  59. }
  60. private:
  61. bool testAgainstEdge( BOARD_ITEM* item, SHAPE* itemShape, BOARD_ITEM* other,
  62. DRC_CONSTRAINT_T aConstraintType, PCB_DRC_CODE aErrorCode );
  63. private:
  64. std::vector<PAD*> m_castellatedPads;
  65. int m_largestEdgeClearance;
  66. };
  67. bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::testAgainstEdge( BOARD_ITEM* item, SHAPE* itemShape,
  68. BOARD_ITEM* edge,
  69. DRC_CONSTRAINT_T aConstraintType,
  70. PCB_DRC_CODE aErrorCode )
  71. {
  72. std::shared_ptr<SHAPE> shape;
  73. if( edge->Type() == PCB_PAD_T )
  74. shape = edge->GetEffectiveHoleShape();
  75. else
  76. shape = edge->GetEffectiveShape( Edge_Cuts );
  77. auto constraint = m_drcEngine->EvalRules( aConstraintType, edge, item, UNDEFINED_LAYER );
  78. int minClearance = constraint.GetValue().Min();
  79. int actual;
  80. VECTOR2I pos;
  81. if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && minClearance >= 0 )
  82. {
  83. if( itemShape->Collide( shape.get(), minClearance, &actual, &pos ) )
  84. {
  85. // Exact clearance is allowed
  86. if( minClearance > 0 && actual == minClearance )
  87. return true;
  88. if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
  89. {
  90. // Edge collisions are allowed inside the holes of castellated pads
  91. for( PAD* castellatedPad : m_castellatedPads )
  92. {
  93. if( castellatedPad->GetEffectiveHoleShape()->Collide( pos ) )
  94. return true;
  95. }
  96. }
  97. std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( aErrorCode );
  98. // Only report clearance info if there is any; otherwise it's just a straight collision
  99. if( minClearance > 0 )
  100. {
  101. wxString msg = formatMsg( _( "(%s clearance %s; actual %s)" ),
  102. constraint.GetName(),
  103. minClearance,
  104. actual );
  105. drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
  106. }
  107. drce->SetItems( edge->m_Uuid, item->m_Uuid );
  108. drce->SetViolatingRule( constraint.GetParentRule() );
  109. reportViolation( drce, pos, Edge_Cuts );
  110. if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
  111. {
  112. return m_drcEngine->GetReportAllTrackErrors();
  113. }
  114. else
  115. {
  116. return false; // don't report violations with multiple edges; one is enough
  117. }
  118. }
  119. }
  120. return true;
  121. }
  122. bool DRC_TEST_PROVIDER_EDGE_CLEARANCE::Run()
  123. {
  124. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_EDGE_CLEARANCE ) )
  125. {
  126. if( !reportPhase( _( "Checking copper to board edge clearances..." ) ) )
  127. return false; // DRC cancelled
  128. }
  129. else if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_EDGE_CLEARANCE ) )
  130. {
  131. if( !reportPhase( _( "Checking silk to board edge clearances..." ) ) )
  132. return false; // DRC cancelled
  133. }
  134. else
  135. {
  136. reportAux( wxT( "Edge clearance violations ignored. Tests not run." ) );
  137. return true; // continue with other tests
  138. }
  139. m_board = m_drcEngine->GetBoard();
  140. m_castellatedPads.clear();
  141. DRC_CONSTRAINT worstClearanceConstraint;
  142. if( m_drcEngine->QueryWorstConstraint( EDGE_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
  143. m_largestEdgeClearance = worstClearanceConstraint.GetValue().Min();
  144. reportAux( wxT( "Worst clearance : %d nm" ), m_largestEdgeClearance );
  145. /*
  146. * Build an RTree of the various edges (including NPTH holes) and margins found on the board.
  147. */
  148. std::vector<std::unique_ptr<PCB_SHAPE>> edges;
  149. DRC_RTREE edgesTree;
  150. forEachGeometryItem( { PCB_SHAPE_T }, LSET( { Edge_Cuts, Margin } ),
  151. [&]( BOARD_ITEM *item ) -> bool
  152. {
  153. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  154. STROKE_PARAMS stroke = shape->GetStroke();
  155. if( item->IsOnLayer( Edge_Cuts ) )
  156. stroke.SetWidth( 0 );
  157. if( shape->GetShape() == SHAPE_T::RECTANGLE && !shape->IsSolidFill() )
  158. {
  159. // A single rectangle for the board would make the RTree useless, so convert
  160. // to 4 edges
  161. edges.emplace_back( static_cast<PCB_SHAPE*>( shape->Clone() ) );
  162. edges.back()->SetShape( SHAPE_T::SEGMENT );
  163. edges.back()->SetEndX( shape->GetStartX() );
  164. edges.back()->SetStroke( stroke );
  165. edges.back()->SetParentGroup( nullptr );
  166. edges.emplace_back( static_cast<PCB_SHAPE*>( shape->Clone() ) );
  167. edges.back()->SetShape( SHAPE_T::SEGMENT );
  168. edges.back()->SetEndY( shape->GetStartY() );
  169. edges.back()->SetStroke( stroke );
  170. edges.back()->SetParentGroup( nullptr );
  171. edges.emplace_back( static_cast<PCB_SHAPE*>( shape->Clone() ) );
  172. edges.back()->SetShape( SHAPE_T::SEGMENT );
  173. edges.back()->SetStartX( shape->GetEndX() );
  174. edges.back()->SetStroke( stroke );
  175. edges.back()->SetParentGroup( nullptr );
  176. edges.emplace_back( static_cast<PCB_SHAPE*>( shape->Clone() ) );
  177. edges.back()->SetShape( SHAPE_T::SEGMENT );
  178. edges.back()->SetStartY( shape->GetEndY() );
  179. edges.back()->SetStroke( stroke );
  180. edges.back()->SetParentGroup( nullptr );
  181. }
  182. else if( shape->GetShape() == SHAPE_T::POLY && !shape->IsSolidFill() )
  183. {
  184. // A single polygon for the board would make the RTree useless, so convert
  185. // to n edges.
  186. SHAPE_LINE_CHAIN poly = shape->GetPolyShape().Outline( 0 );
  187. for( size_t ii = 0; ii < poly.GetSegmentCount(); ++ii )
  188. {
  189. SEG seg = poly.CSegment( ii );
  190. edges.emplace_back( static_cast<PCB_SHAPE*>( shape->Clone() ) );
  191. edges.back()->SetShape( SHAPE_T::SEGMENT );
  192. edges.back()->SetStart( seg.A );
  193. edges.back()->SetEnd( seg.B );
  194. edges.back()->SetStroke( stroke );
  195. edges.back()->SetParentGroup( nullptr );
  196. }
  197. }
  198. else
  199. {
  200. edges.emplace_back( static_cast<PCB_SHAPE*>( shape->Clone() ) );
  201. edges.back()->SetStroke( stroke );
  202. edges.back()->SetParentGroup( nullptr );
  203. }
  204. return true;
  205. } );
  206. for( const std::unique_ptr<PCB_SHAPE>& edge : edges )
  207. {
  208. for( PCB_LAYER_ID layer : { Edge_Cuts, Margin } )
  209. {
  210. if( edge->IsOnLayer( layer ) )
  211. edgesTree.Insert( edge.get(), layer, m_largestEdgeClearance );
  212. }
  213. }
  214. for( FOOTPRINT* footprint : m_board->Footprints() )
  215. {
  216. for( PAD* pad : footprint->Pads() )
  217. {
  218. if( pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole() )
  219. {
  220. // edge-clearances are for milling tolerances (drilling tolerances are handled
  221. // by hole-clearances)
  222. if( pad->GetDrillSizeX() != pad->GetDrillSizeY() )
  223. edgesTree.Insert( pad, Edge_Cuts, m_largestEdgeClearance );
  224. }
  225. if( pad->GetProperty() == PAD_PROP::CASTELLATED )
  226. m_castellatedPads.push_back( pad );
  227. }
  228. }
  229. /*
  230. * Test copper and silk items against the set of edges.
  231. */
  232. const int progressDelta = 200;
  233. int count = 0;
  234. int ii = 0;
  235. forEachGeometryItem( s_allBasicItemsButZones, LSET::AllLayersMask(),
  236. [&]( BOARD_ITEM *item ) -> bool
  237. {
  238. count++;
  239. return true;
  240. } );
  241. forEachGeometryItem( s_allBasicItemsButZones, LSET::AllLayersMask(),
  242. [&]( BOARD_ITEM *item ) -> bool
  243. {
  244. bool testCopper = !m_drcEngine->IsErrorLimitExceeded( DRCE_EDGE_CLEARANCE );
  245. bool testSilk = !m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_EDGE_CLEARANCE );
  246. if( !testCopper && !testSilk )
  247. return false; // All limits exceeded; we're done
  248. if( !reportProgress( ii++, count, progressDelta ) )
  249. return false; // DRC cancelled; we're done
  250. if( isInvisibleText( item ) )
  251. return true; // Continue with other items
  252. if( item->Type() == PCB_PAD_T )
  253. {
  254. PAD* pad = static_cast<PAD*>( item );
  255. if( pad->GetProperty() == PAD_PROP::CASTELLATED
  256. || pad->GetAttribute() == PAD_ATTRIB::CONN )
  257. {
  258. return true; // Continue with other items
  259. }
  260. }
  261. std::vector<PCB_LAYER_ID> layersToTest;
  262. switch( item->Type() )
  263. {
  264. case PCB_PAD_T:
  265. layersToTest = static_cast<PAD*>( item )->Padstack().UniqueLayers();
  266. break;
  267. case PCB_VIA_T:
  268. layersToTest = static_cast<PCB_VIA*>( item )->Padstack().UniqueLayers();
  269. break;
  270. default:
  271. layersToTest = { UNDEFINED_LAYER };
  272. }
  273. for( PCB_LAYER_ID shapeLayer : layersToTest )
  274. {
  275. const std::shared_ptr<SHAPE>& itemShape = item->GetEffectiveShape( shapeLayer );
  276. for( PCB_LAYER_ID testLayer : { Edge_Cuts, Margin } )
  277. {
  278. if( testCopper && item->IsOnCopperLayer() )
  279. {
  280. edgesTree.QueryColliding( item, shapeLayer, testLayer, nullptr,
  281. [&]( BOARD_ITEM* edge ) -> bool
  282. {
  283. return testAgainstEdge( item, itemShape.get(), edge,
  284. EDGE_CLEARANCE_CONSTRAINT,
  285. DRCE_EDGE_CLEARANCE );
  286. },
  287. m_largestEdgeClearance );
  288. }
  289. if( testSilk && ( item->IsOnLayer( F_SilkS ) || item->IsOnLayer( B_SilkS ) ) )
  290. {
  291. if( edgesTree.QueryColliding( item, shapeLayer, testLayer, nullptr,
  292. [&]( BOARD_ITEM* edge ) -> bool
  293. {
  294. return testAgainstEdge( item, itemShape.get(), edge,
  295. SILK_CLEARANCE_CONSTRAINT,
  296. DRCE_SILK_EDGE_CLEARANCE );
  297. },
  298. m_largestEdgeClearance ) )
  299. {
  300. // violations reported during QueryColliding
  301. }
  302. else
  303. {
  304. // TODO: check postion being outside board boundary
  305. }
  306. }
  307. }
  308. }
  309. return true;
  310. } );
  311. reportRuleStatistics();
  312. return !m_drcEngine->IsCancelled();
  313. }
  314. namespace detail
  315. {
  316. static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_EDGE_CLEARANCE> dummy;
  317. }