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.

290 lines
10 KiB

5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2004-2022 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 <atomic>
  24. #include <common.h>
  25. #include <board_design_settings.h>
  26. #include <drc/drc_rtree.h>
  27. #include <drc/drc_engine.h>
  28. #include <drc/drc_item.h>
  29. #include <drc/drc_rule.h>
  30. #include <drc/drc_test_provider.h>
  31. #include <pad.h>
  32. #include <progress_reporter.h>
  33. #include <thread_pool.h>
  34. #include <zone.h>
  35. /*
  36. "Disallow" test. Goes through all items, matching types/conditions drop errors.
  37. Errors generated:
  38. - DRCE_ALLOWED_ITEMS
  39. - DRCE_TEXT_ON_EDGECUTS
  40. */
  41. class DRC_TEST_PROVIDER_DISALLOW : public DRC_TEST_PROVIDER
  42. {
  43. public:
  44. DRC_TEST_PROVIDER_DISALLOW()
  45. {
  46. }
  47. virtual ~DRC_TEST_PROVIDER_DISALLOW()
  48. {
  49. }
  50. virtual bool Run() override;
  51. virtual const wxString GetName() const override
  52. {
  53. return wxT( "disallow" );
  54. };
  55. virtual const wxString GetDescription() const override
  56. {
  57. return wxT( "Tests for disallowed items (e.g. keepouts)" );
  58. }
  59. };
  60. bool DRC_TEST_PROVIDER_DISALLOW::Run()
  61. {
  62. if( !reportPhase( _( "Checking keepouts & disallow constraints..." ) ) )
  63. return false; // DRC cancelled
  64. BOARD* board = m_drcEngine->GetBoard();
  65. int epsilon = board->GetDesignSettings().GetDRCEpsilon();
  66. // First build out the board's cache of copper-keepout to copper-zone caches. This is where
  67. // the bulk of the time is spent, and we can do this in parallel.
  68. //
  69. std::vector<ZONE*> antiCopperKeepouts;
  70. std::vector<ZONE*> copperZones;
  71. std::vector<std::pair<ZONE*, ZONE*>> toCache;
  72. std::atomic<size_t> done( 1 );
  73. int totalCount = 0;
  74. forEachGeometryItem( {}, LSET::AllLayersMask(),
  75. [&]( BOARD_ITEM* item ) -> bool
  76. {
  77. ZONE* zone = dynamic_cast<ZONE*>( item );
  78. if( zone && zone->GetIsRuleArea() && zone->GetDoNotAllowCopperPour() )
  79. antiCopperKeepouts.push_back( zone );
  80. else if( zone && zone->IsOnCopperLayer() )
  81. copperZones.push_back( zone );
  82. totalCount++;
  83. return true;
  84. } );
  85. for( ZONE* ruleArea : antiCopperKeepouts )
  86. {
  87. for( ZONE* copperZone : copperZones )
  88. {
  89. toCache.push_back( { ruleArea, copperZone } );
  90. totalCount++;
  91. }
  92. }
  93. auto query_areas =
  94. [&]( std::pair<ZONE* /* rule area */, ZONE* /* copper zone */> areaZonePair ) -> size_t
  95. {
  96. if( m_drcEngine->IsCancelled() )
  97. return 0;
  98. ZONE* ruleArea = areaZonePair.first;
  99. ZONE* copperZone = areaZonePair.second;
  100. BOX2I areaBBox = ruleArea->GetBoundingBox();
  101. BOX2I copperBBox = copperZone->GetBoundingBox();
  102. bool isInside = false;
  103. if( copperZone->IsFilled() && areaBBox.Intersects( copperBBox ) )
  104. {
  105. // Collisions include touching, so we need to deflate outline by enough to
  106. // exclude it. This is particularly important for detecting copper fills as
  107. // they will be exactly touching along the entire exclusion border.
  108. SHAPE_POLY_SET areaPoly = ruleArea->Outline()->CloneDropTriangulation();
  109. areaPoly.Fracture( SHAPE_POLY_SET::PM_FAST );
  110. areaPoly.Deflate( epsilon, SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS, ARC_LOW_DEF );
  111. DRC_RTREE* zoneRTree = board->m_CopperZoneRTreeCache[ copperZone ].get();
  112. if( zoneRTree )
  113. {
  114. for( PCB_LAYER_ID layer : ruleArea->GetLayerSet().Seq() )
  115. {
  116. if( zoneRTree->QueryColliding( areaBBox, &areaPoly, layer ) )
  117. {
  118. isInside = true;
  119. break;
  120. }
  121. if( m_drcEngine->IsCancelled() )
  122. return 0;
  123. }
  124. }
  125. }
  126. if( m_drcEngine->IsCancelled() )
  127. return 0;
  128. PTR_PTR_LAYER_CACHE_KEY key = { ruleArea, copperZone, UNDEFINED_LAYER };
  129. {
  130. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  131. board->m_IntersectsAreaCache[ key ] = isInside;
  132. }
  133. done.fetch_add( 1 );
  134. return 1;
  135. };
  136. thread_pool& tp = GetKiCadThreadPool();
  137. std::vector<std::future<size_t>> returns;
  138. returns.reserve( toCache.size() );
  139. for( const std::pair<ZONE*, ZONE*>& areaZonePair : toCache )
  140. returns.emplace_back( tp.submit( query_areas, areaZonePair ) );
  141. for( const std::future<size_t>& ret : returns )
  142. {
  143. std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
  144. while( status != std::future_status::ready )
  145. {
  146. m_drcEngine->ReportProgress( static_cast<double>( done ) / toCache.size() );
  147. status = ret.wait_for( std::chrono::milliseconds( 250 ) );
  148. }
  149. }
  150. if( m_drcEngine->IsCancelled() )
  151. return false;
  152. // Now go through all the board objects calling the DRC_ENGINE to run the actual disallow
  153. // tests. These should be reasonably quick using the caches generated above.
  154. //
  155. const int progressDelta = 250;
  156. int ii = static_cast<int>( toCache.size() );
  157. auto checkTextOnEdgeCuts =
  158. [&]( BOARD_ITEM* item )
  159. {
  160. if( item->Type() == PCB_FIELD_T || item->Type() == PCB_TEXT_T || item->Type() == PCB_TEXTBOX_T
  161. || BaseType( item->Type() ) == PCB_DIMENSION_T )
  162. {
  163. if( item->GetLayer() == Edge_Cuts )
  164. {
  165. std::shared_ptr<DRC_ITEM> drc = DRC_ITEM::Create( DRCE_TEXT_ON_EDGECUTS );
  166. drc->SetItems( item );
  167. reportViolation( drc, item->GetPosition(), Edge_Cuts );
  168. }
  169. }
  170. };
  171. auto checkDisallow =
  172. [&]( BOARD_ITEM* item )
  173. {
  174. DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( DISALLOW_CONSTRAINT, item,
  175. nullptr, UNDEFINED_LAYER );
  176. if( constraint.m_DisallowFlags && constraint.GetSeverity() != RPT_SEVERITY_IGNORE )
  177. {
  178. std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ALLOWED_ITEMS );
  179. DRC_RULE* rule = constraint.GetParentRule();
  180. VECTOR2I pos = item->GetPosition();
  181. PCB_LAYER_ID layer = UNDEFINED_LAYER;
  182. wxString msg;
  183. msg.Printf( drcItem->GetErrorText() + wxS( " (%s)" ), constraint.GetName() );
  184. drcItem->SetErrorMessage( msg );
  185. drcItem->SetItems( item );
  186. drcItem->SetViolatingRule( rule );
  187. if( item->GetLayerSet().count() )
  188. layer = item->GetLayerSet().Seq().front();
  189. if( rule->m_Implicit )
  190. {
  191. // Provide a better location for keepout area collisions.
  192. BOARD_ITEM* ruleItem = board->GetItem( rule->m_ImplicitItemId );
  193. if( ZONE* keepout = dynamic_cast<ZONE*>( ruleItem ) )
  194. {
  195. std::shared_ptr<SHAPE> shape = item->GetEffectiveShape( layer );
  196. int dummyActual;
  197. keepout->Outline()->Collide( shape.get(), board->m_DRCMaxClearance,
  198. &dummyActual, &pos );
  199. }
  200. }
  201. reportViolation( drcItem, pos, layer );
  202. }
  203. };
  204. forEachGeometryItem( {}, LSET::AllLayersMask(),
  205. [&]( BOARD_ITEM* item ) -> bool
  206. {
  207. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_TEXT_ON_EDGECUTS ) )
  208. checkTextOnEdgeCuts( item );
  209. if( !m_drcEngine->IsErrorLimitExceeded( DRCE_ALLOWED_ITEMS ) )
  210. {
  211. ZONE* zone = dynamic_cast<ZONE*>( item );
  212. if( zone && zone->GetIsRuleArea() )
  213. return true;
  214. item->ClearFlags( HOLE_PROXY ); // Just in case
  215. checkDisallow( item );
  216. if( item->HasHole() )
  217. {
  218. item->SetFlags( HOLE_PROXY );
  219. checkDisallow( item );
  220. item->ClearFlags( HOLE_PROXY );
  221. }
  222. }
  223. if( !reportProgress( ii++, totalCount, progressDelta ) )
  224. return false;
  225. return true;
  226. } );
  227. reportRuleStatistics();
  228. return !m_drcEngine->IsCancelled();
  229. }
  230. namespace detail
  231. {
  232. static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_DISALLOW> dummy;
  233. }