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.

1840 lines
70 KiB

6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
5 years ago
5 years ago
6 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
5 years ago
5 years ago
1 year ago
1 year ago
4 years ago
6 years ago
6 years ago
1 year ago
5 years ago
6 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
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 (C) 2004-2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2014 Dick Hollenbeck, dick@softplc.com
  6. * Copyright (C) 2017-2024 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <atomic>
  26. #include <reporter.h>
  27. #include <progress_reporter.h>
  28. #include <string_utils.h>
  29. #include <board_design_settings.h>
  30. #include <drc/drc_engine.h>
  31. #include <drc/drc_rtree.h>
  32. #include <drc/drc_rule_parser.h>
  33. #include <drc/drc_rule.h>
  34. #include <drc/drc_rule_condition.h>
  35. #include <drc/drc_test_provider.h>
  36. #include <drc/drc_item.h>
  37. #include <drc/drc_cache_generator.h>
  38. #include <footprint.h>
  39. #include <pad.h>
  40. #include <pcb_track.h>
  41. #include <core/thread_pool.h>
  42. #include <zone.h>
  43. // wxListBox's performance degrades horrifically with very large datasets. It's not clear
  44. // they're useful to the user anyway.
  45. #define ERROR_LIMIT 199
  46. #define EXTENDED_ERROR_LIMIT 499
  47. void drcPrintDebugMessage( int level, const wxString& msg, const char *function, int line )
  48. {
  49. wxString valueStr;
  50. if( wxGetEnv( wxT( "DRC_DEBUG" ), &valueStr ) )
  51. {
  52. int setLevel = wxAtoi( valueStr );
  53. if( level <= setLevel )
  54. printf( "%-30s:%d | %s\n", function, line, (const char *) msg.c_str() );
  55. }
  56. }
  57. DRC_ENGINE::DRC_ENGINE( BOARD* aBoard, BOARD_DESIGN_SETTINGS *aSettings ) :
  58. UNITS_PROVIDER( pcbIUScale, EDA_UNITS::MILLIMETRES ),
  59. m_designSettings ( aSettings ),
  60. m_board( aBoard ),
  61. m_drawingSheet( nullptr ),
  62. m_schematicNetlist( nullptr ),
  63. m_rulesValid( false ),
  64. m_reportAllTrackErrors( false ),
  65. m_testFootprints( false ),
  66. m_reporter( nullptr ),
  67. m_progressReporter( nullptr )
  68. {
  69. m_errorLimits.resize( DRCE_LAST + 1 );
  70. for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
  71. m_errorLimits[ ii ] = ERROR_LIMIT;
  72. ClearGraphicsHandler();
  73. }
  74. DRC_ENGINE::~DRC_ENGINE()
  75. {
  76. m_rules.clear();
  77. for( std::pair<DRC_CONSTRAINT_T, std::vector<DRC_ENGINE_CONSTRAINT*>*> pair : m_constraintMap )
  78. {
  79. for( DRC_ENGINE_CONSTRAINT* constraint : *pair.second )
  80. delete constraint;
  81. delete pair.second;
  82. }
  83. }
  84. static bool isKeepoutZone( const BOARD_ITEM* aItem, bool aCheckFlags )
  85. {
  86. if( !aItem || aItem->Type() != PCB_ZONE_T )
  87. return false;
  88. const ZONE* zone = static_cast<const ZONE*>( aItem );
  89. if( !zone->GetIsRuleArea() )
  90. return false;
  91. if( !zone->HasKeepoutParametersSet() )
  92. return false;
  93. if( aCheckFlags )
  94. {
  95. if( !zone->GetDoNotAllowTracks()
  96. && !zone->GetDoNotAllowVias()
  97. && !zone->GetDoNotAllowPads()
  98. && !zone->GetDoNotAllowCopperPour()
  99. && !zone->GetDoNotAllowFootprints() )
  100. {
  101. return false;
  102. }
  103. }
  104. return true;
  105. }
  106. std::shared_ptr<DRC_RULE> DRC_ENGINE::createImplicitRule( const wxString& name )
  107. {
  108. std::shared_ptr<DRC_RULE> rule = std::make_shared<DRC_RULE>();
  109. rule->m_Name = name;
  110. rule->m_Implicit = true;
  111. addRule( rule );
  112. return rule;
  113. }
  114. void DRC_ENGINE::loadImplicitRules()
  115. {
  116. ReportAux( wxString::Format( wxT( "Building implicit rules (per-item/class overrides, etc...)" ) ) );
  117. BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
  118. // 1) global defaults
  119. std::shared_ptr<DRC_RULE> rule = createImplicitRule( _( "board setup constraints" ) );
  120. DRC_CONSTRAINT widthConstraint( TRACK_WIDTH_CONSTRAINT );
  121. widthConstraint.Value().SetMin( bds.m_TrackMinWidth );
  122. rule->AddConstraint( widthConstraint );
  123. DRC_CONSTRAINT connectionConstraint( CONNECTION_WIDTH_CONSTRAINT );
  124. connectionConstraint.Value().SetMin( bds.m_MinConn );
  125. rule->AddConstraint( connectionConstraint );
  126. DRC_CONSTRAINT drillConstraint( HOLE_SIZE_CONSTRAINT );
  127. drillConstraint.Value().SetMin( bds.m_MinThroughDrill );
  128. rule->AddConstraint( drillConstraint );
  129. DRC_CONSTRAINT annulusConstraint( ANNULAR_WIDTH_CONSTRAINT );
  130. annulusConstraint.Value().SetMin( bds.m_ViasMinAnnularWidth );
  131. rule->AddConstraint( annulusConstraint );
  132. DRC_CONSTRAINT diameterConstraint( VIA_DIAMETER_CONSTRAINT );
  133. diameterConstraint.Value().SetMin( bds.m_ViasMinSize );
  134. rule->AddConstraint( diameterConstraint );
  135. DRC_CONSTRAINT holeToHoleConstraint( HOLE_TO_HOLE_CONSTRAINT );
  136. holeToHoleConstraint.Value().SetMin( bds.m_HoleToHoleMin );
  137. rule->AddConstraint( holeToHoleConstraint );
  138. rule = createImplicitRule( _( "board setup constraints zone fill strategy" ) );
  139. DRC_CONSTRAINT thermalSpokeCountConstraint( MIN_RESOLVED_SPOKES_CONSTRAINT );
  140. thermalSpokeCountConstraint.Value().SetMin( bds.m_MinResolvedSpokes );
  141. rule->AddConstraint( thermalSpokeCountConstraint );
  142. rule = createImplicitRule( _( "board setup constraints silk" ) );
  143. rule->m_LayerCondition = LSET( { F_SilkS, B_SilkS } );
  144. DRC_CONSTRAINT silkClearanceConstraint( SILK_CLEARANCE_CONSTRAINT );
  145. silkClearanceConstraint.Value().SetMin( bds.m_SilkClearance );
  146. rule->AddConstraint( silkClearanceConstraint );
  147. rule = createImplicitRule( _( "board setup constraints silk text height" ) );
  148. rule->m_LayerCondition = LSET( { F_SilkS, B_SilkS } );
  149. DRC_CONSTRAINT silkTextHeightConstraint( TEXT_HEIGHT_CONSTRAINT );
  150. silkTextHeightConstraint.Value().SetMin( bds.m_MinSilkTextHeight );
  151. rule->AddConstraint( silkTextHeightConstraint );
  152. rule = createImplicitRule( _( "board setup constraints silk text thickness" ) );
  153. rule->m_LayerCondition = LSET( { F_SilkS, B_SilkS } );
  154. DRC_CONSTRAINT silkTextThicknessConstraint( TEXT_THICKNESS_CONSTRAINT );
  155. silkTextThicknessConstraint.Value().SetMin( bds.m_MinSilkTextThickness );
  156. rule->AddConstraint( silkTextThicknessConstraint );
  157. rule = createImplicitRule( _( "board setup constraints hole" ) );
  158. DRC_CONSTRAINT holeClearanceConstraint( HOLE_CLEARANCE_CONSTRAINT );
  159. holeClearanceConstraint.Value().SetMin( bds.m_HoleClearance );
  160. rule->AddConstraint( holeClearanceConstraint );
  161. rule = createImplicitRule( _( "board setup constraints edge" ) );
  162. DRC_CONSTRAINT edgeClearanceConstraint( EDGE_CLEARANCE_CONSTRAINT );
  163. edgeClearanceConstraint.Value().SetMin( bds.m_CopperEdgeClearance );
  164. rule->AddConstraint( edgeClearanceConstraint );
  165. rule = createImplicitRule( _( "board setup constraints courtyard" ) );
  166. DRC_CONSTRAINT courtyardClearanceConstraint( COURTYARD_CLEARANCE_CONSTRAINT );
  167. holeToHoleConstraint.Value().SetMin( 0 );
  168. rule->AddConstraint( courtyardClearanceConstraint );
  169. // 2) micro-via specific defaults (new DRC doesn't treat microvias in any special way)
  170. std::shared_ptr<DRC_RULE> uViaRule = createImplicitRule( _( "board setup micro-via constraints" ) );
  171. uViaRule->m_Condition = new DRC_RULE_CONDITION( wxT( "A.Via_Type == 'Micro'" ) );
  172. DRC_CONSTRAINT uViaDrillConstraint( HOLE_SIZE_CONSTRAINT );
  173. uViaDrillConstraint.Value().SetMin( bds.m_MicroViasMinDrill );
  174. uViaRule->AddConstraint( uViaDrillConstraint );
  175. DRC_CONSTRAINT uViaDiameterConstraint( VIA_DIAMETER_CONSTRAINT );
  176. uViaDiameterConstraint.Value().SetMin( bds.m_MicroViasMinSize );
  177. uViaRule->AddConstraint( uViaDiameterConstraint );
  178. // 3) per-netclass rules
  179. std::vector<std::shared_ptr<DRC_RULE>> netclassClearanceRules;
  180. std::vector<std::shared_ptr<DRC_RULE>> netclassItemSpecificRules;
  181. auto makeNetclassRules =
  182. [&]( const std::shared_ptr<NETCLASS>& nc, bool isDefault )
  183. {
  184. wxString ncName = nc->GetVariableSubstitutionName();
  185. wxString friendlyName = nc->GetName();
  186. wxString expr;
  187. ncName.Replace( "'", "\\'" );
  188. if( nc->HasClearance())
  189. {
  190. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  191. netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
  192. nc->GetClearanceParent()->GetName() );
  193. netclassRule->m_Implicit = true;
  194. expr = wxString::Format( wxT( "A.NetClass == '%s'" ), ncName );
  195. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  196. netclassClearanceRules.push_back( netclassRule );
  197. DRC_CONSTRAINT constraint( CLEARANCE_CONSTRAINT );
  198. constraint.Value().SetMin( nc->GetClearance() );
  199. netclassRule->AddConstraint( constraint );
  200. }
  201. if( nc->HasTrackWidth() )
  202. {
  203. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  204. netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
  205. nc->GetTrackWidthParent()->GetName() );
  206. netclassRule->m_Implicit = true;
  207. expr = wxString::Format( wxT( "A.NetClass == '%s'" ), ncName );
  208. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  209. netclassClearanceRules.push_back( netclassRule );
  210. DRC_CONSTRAINT constraint( TRACK_WIDTH_CONSTRAINT );
  211. constraint.Value().SetMin( bds.m_TrackMinWidth );
  212. constraint.Value().SetOpt( nc->GetTrackWidth() );
  213. netclassRule->AddConstraint( constraint );
  214. }
  215. if( nc->HasDiffPairWidth() )
  216. {
  217. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  218. netclassRule->m_Name = wxString::Format( _( "netclass '%s' (diff pair)" ),
  219. nc->GetDiffPairWidthParent()->GetName() );
  220. netclassRule->m_Implicit = true;
  221. expr = wxString::Format( wxT( "A.NetClass == '%s' && A.inDiffPair('*')" ),
  222. ncName );
  223. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  224. netclassItemSpecificRules.push_back( netclassRule );
  225. DRC_CONSTRAINT constraint( TRACK_WIDTH_CONSTRAINT );
  226. constraint.Value().SetMin( bds.m_TrackMinWidth );
  227. constraint.Value().SetOpt( nc->GetDiffPairWidth() );
  228. netclassRule->AddConstraint( constraint );
  229. }
  230. if( nc->HasDiffPairGap() )
  231. {
  232. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  233. netclassRule->m_Name = wxString::Format( _( "netclass '%s' (diff pair)" ),
  234. nc->GetDiffPairGapParent()->GetName() );
  235. netclassRule->m_Implicit = true;
  236. expr = wxString::Format( wxT( "A.NetClass == '%s'" ), ncName );
  237. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  238. netclassItemSpecificRules.push_back( netclassRule );
  239. DRC_CONSTRAINT constraint( DIFF_PAIR_GAP_CONSTRAINT );
  240. constraint.Value().SetMin( bds.m_MinClearance );
  241. constraint.Value().SetOpt( nc->GetDiffPairGap() );
  242. netclassRule->AddConstraint( constraint );
  243. // A narrower diffpair gap overrides the netclass min clearance
  244. if( nc->GetDiffPairGap() < nc->GetClearance() )
  245. {
  246. netclassRule = std::make_shared<DRC_RULE>();
  247. netclassRule->m_Name = wxString::Format( _( "netclass '%s' (diff pair)" ),
  248. nc->GetDiffPairGapParent()->GetName() );
  249. netclassRule->m_Implicit = true;
  250. expr = wxString::Format( wxT( "A.NetClass == '%s' && AB.isCoupledDiffPair()" ),
  251. ncName );
  252. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  253. netclassItemSpecificRules.push_back( netclassRule );
  254. DRC_CONSTRAINT min_clearanceConstraint( CLEARANCE_CONSTRAINT );
  255. min_clearanceConstraint.Value().SetMin( nc->GetDiffPairGap() );
  256. netclassRule->AddConstraint( min_clearanceConstraint );
  257. }
  258. }
  259. if( nc->HasViaDiameter() )
  260. {
  261. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  262. netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
  263. nc->GetViaDiameterParent()->GetName() );
  264. netclassRule->m_Implicit = true;
  265. expr = wxString::Format( wxT( "A.NetClass == '%s' && A.Via_Type != 'Micro'" ),
  266. ncName );
  267. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  268. netclassItemSpecificRules.push_back( netclassRule );
  269. DRC_CONSTRAINT constraint( VIA_DIAMETER_CONSTRAINT );
  270. constraint.Value().SetMin( bds.m_ViasMinSize );
  271. constraint.Value().SetOpt( nc->GetViaDiameter() );
  272. netclassRule->AddConstraint( constraint );
  273. }
  274. if( nc->HasViaDrill() )
  275. {
  276. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  277. netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
  278. nc->GetViaDrillParent()->GetName() );
  279. netclassRule->m_Implicit = true;
  280. expr = wxString::Format( wxT( "A.NetClass == '%s' && A.Via_Type != 'Micro'" ),
  281. ncName );
  282. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  283. netclassItemSpecificRules.push_back( netclassRule );
  284. DRC_CONSTRAINT constraint( HOLE_SIZE_CONSTRAINT );
  285. constraint.Value().SetMin( bds.m_MinThroughDrill );
  286. constraint.Value().SetOpt( nc->GetViaDrill() );
  287. netclassRule->AddConstraint( constraint );
  288. }
  289. if( nc->HasuViaDiameter() )
  290. {
  291. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  292. netclassRule->m_Name = wxString::Format( _( "netclass '%s' (uvia)" ),
  293. nc->GetuViaDiameterParent()->GetName() );
  294. netclassRule->m_Implicit = true;
  295. expr = wxString::Format( wxT( "A.NetClass == '%s' && A.Via_Type == 'Micro'" ),
  296. ncName );
  297. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  298. netclassItemSpecificRules.push_back( netclassRule );
  299. DRC_CONSTRAINT constraint( VIA_DIAMETER_CONSTRAINT );
  300. constraint.Value().SetMin( bds.m_MicroViasMinSize );
  301. constraint.Value().SetMin( nc->GetuViaDiameter() );
  302. netclassRule->AddConstraint( constraint );
  303. }
  304. if( nc->HasuViaDrill() )
  305. {
  306. std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
  307. netclassRule->m_Name = wxString::Format( _( "netclass '%s' (uvia)" ),
  308. nc->GetuViaDrillParent()->GetName() );
  309. netclassRule->m_Implicit = true;
  310. expr = wxString::Format( wxT( "A.NetClass == '%s' && A.Via_Type == 'Micro'" ),
  311. ncName );
  312. netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
  313. netclassItemSpecificRules.push_back( netclassRule );
  314. DRC_CONSTRAINT constraint( HOLE_SIZE_CONSTRAINT );
  315. constraint.Value().SetMin( bds.m_MicroViasMinDrill );
  316. constraint.Value().SetOpt( nc->GetuViaDrill() );
  317. netclassRule->AddConstraint( constraint );
  318. }
  319. };
  320. m_board->SynchronizeNetsAndNetClasses( false );
  321. makeNetclassRules( bds.m_NetSettings->GetDefaultNetclass(), true );
  322. for( const auto& [name, netclass] : bds.m_NetSettings->GetNetclasses() )
  323. makeNetclassRules( netclass, false );
  324. for( const auto& [name, netclass] : bds.m_NetSettings->GetCompositeNetclasses() )
  325. makeNetclassRules( netclass, false );
  326. // The netclass clearance rules have to be sorted by min clearance so the right one fires
  327. // if 'A' and 'B' belong to two different netclasses.
  328. //
  329. // The item-specific netclass rules are all unary, so there's no 'A' vs 'B' issue.
  330. std::sort( netclassClearanceRules.begin(), netclassClearanceRules.end(),
  331. []( const std::shared_ptr<DRC_RULE>& lhs, const std::shared_ptr<DRC_RULE>& rhs )
  332. {
  333. return lhs->m_Constraints[0].m_Value.Min()
  334. < rhs->m_Constraints[0].m_Value.Min();
  335. } );
  336. for( std::shared_ptr<DRC_RULE>& ncRule : netclassClearanceRules )
  337. addRule( ncRule );
  338. for( std::shared_ptr<DRC_RULE>& ncRule : netclassItemSpecificRules )
  339. addRule( ncRule );
  340. // 3) keepout area rules
  341. std::vector<ZONE*> keepoutZones;
  342. for( ZONE* zone : m_board->Zones() )
  343. {
  344. if( isKeepoutZone( zone, true ) )
  345. keepoutZones.push_back( zone );
  346. }
  347. for( FOOTPRINT* footprint : m_board->Footprints() )
  348. {
  349. for( ZONE* zone : footprint->Zones() )
  350. {
  351. if( isKeepoutZone( zone, true ) )
  352. keepoutZones.push_back( zone );
  353. }
  354. }
  355. for( ZONE* zone : keepoutZones )
  356. {
  357. wxString name = zone->GetZoneName();
  358. if( name.IsEmpty() )
  359. rule = createImplicitRule( _( "keepout area" ) );
  360. else
  361. rule = createImplicitRule( wxString::Format( _( "keepout area '%s'" ), name ) );
  362. rule->m_ImplicitItemId = zone->m_Uuid;
  363. rule->m_Condition = new DRC_RULE_CONDITION( wxString::Format( wxT( "A.intersectsArea('%s')" ),
  364. zone->m_Uuid.AsString() ) );
  365. rule->m_LayerCondition = zone->GetLayerSet();
  366. int disallowFlags = 0;
  367. if( zone->GetDoNotAllowTracks() )
  368. disallowFlags |= DRC_DISALLOW_TRACKS;
  369. if( zone->GetDoNotAllowVias() )
  370. disallowFlags |= DRC_DISALLOW_VIAS;
  371. if( zone->GetDoNotAllowPads() )
  372. disallowFlags |= DRC_DISALLOW_PADS;
  373. if( zone->GetDoNotAllowCopperPour() )
  374. disallowFlags |= DRC_DISALLOW_ZONES;
  375. if( zone->GetDoNotAllowFootprints() )
  376. disallowFlags |= DRC_DISALLOW_FOOTPRINTS;
  377. DRC_CONSTRAINT disallowConstraint( DISALLOW_CONSTRAINT );
  378. disallowConstraint.m_DisallowFlags = disallowFlags;
  379. rule->AddConstraint( disallowConstraint );
  380. }
  381. ReportAux( wxString::Format( wxT( "Building %d implicit netclass rules" ),
  382. (int) netclassClearanceRules.size() ) );
  383. }
  384. void DRC_ENGINE::loadRules( const wxFileName& aPath )
  385. {
  386. if( aPath.FileExists() )
  387. {
  388. std::vector<std::shared_ptr<DRC_RULE>> rules;
  389. FILE* fp = wxFopen( aPath.GetFullPath(), wxT( "rt" ) );
  390. if( fp )
  391. {
  392. DRC_RULES_PARSER parser( fp, aPath.GetFullPath() );
  393. parser.Parse( rules, m_reporter );
  394. }
  395. // Copy the rules into the member variable afterwards so that if Parse() throws then
  396. // the possibly malformed rules won't contaminate the current ruleset.
  397. for( std::shared_ptr<DRC_RULE>& rule : rules )
  398. m_rules.push_back( rule );
  399. }
  400. }
  401. void DRC_ENGINE::compileRules()
  402. {
  403. ReportAux( wxString::Format( wxT( "Compiling Rules (%d rules): " ), (int) m_rules.size() ) );
  404. for( std::shared_ptr<DRC_RULE>& rule : m_rules )
  405. {
  406. DRC_RULE_CONDITION* condition = nullptr;
  407. if( rule->m_Condition && !rule->m_Condition->GetExpression().IsEmpty() )
  408. {
  409. condition = rule->m_Condition;
  410. condition->Compile( nullptr );
  411. }
  412. for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
  413. {
  414. if( !m_constraintMap.count( constraint.m_Type ) )
  415. m_constraintMap[ constraint.m_Type ] = new std::vector<DRC_ENGINE_CONSTRAINT*>();
  416. DRC_ENGINE_CONSTRAINT* engineConstraint = new DRC_ENGINE_CONSTRAINT;
  417. engineConstraint->layerTest = rule->m_LayerCondition;
  418. engineConstraint->condition = condition;
  419. engineConstraint->constraint = constraint;
  420. engineConstraint->parentRule = rule;
  421. m_constraintMap[ constraint.m_Type ]->push_back( engineConstraint );
  422. }
  423. }
  424. }
  425. void DRC_ENGINE::InitEngine( const wxFileName& aRulePath )
  426. {
  427. m_testProviders = DRC_TEST_PROVIDER_REGISTRY::Instance().GetTestProviders();
  428. for( DRC_TEST_PROVIDER* provider : m_testProviders )
  429. {
  430. ReportAux( wxString::Format( wxT( "Create DRC provider: '%s'" ), provider->GetName() ) );
  431. provider->SetDRCEngine( this );
  432. }
  433. m_rules.clear();
  434. m_rulesValid = false;
  435. for( std::pair<DRC_CONSTRAINT_T, std::vector<DRC_ENGINE_CONSTRAINT*>*> pair : m_constraintMap )
  436. {
  437. for( DRC_ENGINE_CONSTRAINT* constraint : *pair.second )
  438. delete constraint;
  439. delete pair.second;
  440. }
  441. m_constraintMap.clear();
  442. m_board->IncrementTimeStamp(); // Clear board-level caches
  443. try // attempt to load full set of rules (implicit + user rules)
  444. {
  445. loadImplicitRules();
  446. loadRules( aRulePath );
  447. compileRules();
  448. }
  449. catch( PARSE_ERROR& original_parse_error )
  450. {
  451. try // try again with just our implicit rules
  452. {
  453. loadImplicitRules();
  454. compileRules();
  455. }
  456. catch( PARSE_ERROR& )
  457. {
  458. wxFAIL_MSG( wxT( "Compiling implicit rules failed." ) );
  459. }
  460. throw original_parse_error;
  461. }
  462. for( int ii = DRCE_FIRST; ii < DRCE_LAST; ++ii )
  463. m_errorLimits[ ii ] = ERROR_LIMIT;
  464. m_rulesValid = true;
  465. }
  466. void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints,
  467. BOARD_COMMIT* aCommit )
  468. {
  469. SetUserUnits( aUnits );
  470. m_reportAllTrackErrors = aReportAllTrackErrors;
  471. m_testFootprints = aTestFootprints;
  472. for( int ii = DRCE_FIRST; ii < DRCE_LAST; ++ii )
  473. {
  474. if( m_designSettings->Ignore( ii ) )
  475. m_errorLimits[ ii ] = 0;
  476. else if( ii == DRCE_CLEARANCE || ii == DRCE_UNCONNECTED_ITEMS )
  477. m_errorLimits[ ii ] = EXTENDED_ERROR_LIMIT;
  478. else
  479. m_errorLimits[ ii ] = ERROR_LIMIT;
  480. }
  481. DRC_TEST_PROVIDER::Init();
  482. m_board->IncrementTimeStamp(); // Invalidate all caches...
  483. DRC_CACHE_GENERATOR cacheGenerator;
  484. cacheGenerator.SetDRCEngine( this );
  485. if( !cacheGenerator.Run() ) // ... and regenerate them.
  486. return;
  487. int timestamp = m_board->GetTimeStamp();
  488. for( DRC_TEST_PROVIDER* provider : m_testProviders )
  489. {
  490. ReportAux( wxString::Format( wxT( "Run DRC provider: '%s'" ), provider->GetName() ) );
  491. provider->SetCommit( aCommit );
  492. if( !provider->RunTests( aUnits ) )
  493. break;
  494. }
  495. // DRC tests are multi-threaded; anything that causes us to attempt to re-generate the
  496. // caches while DRC is running is problematic.
  497. wxASSERT( timestamp == m_board->GetTimeStamp() );
  498. }
  499. #define REPORT( s ) { if( aReporter ) { aReporter->Report( s ); } }
  500. DRC_CONSTRAINT DRC_ENGINE::EvalZoneConnection( const BOARD_ITEM* a, const BOARD_ITEM* b,
  501. PCB_LAYER_ID aLayer, REPORTER* aReporter )
  502. {
  503. DRC_CONSTRAINT constraint = EvalRules( ZONE_CONNECTION_CONSTRAINT, a, b, aLayer, aReporter );
  504. REPORT( "" )
  505. REPORT( wxString::Format( _( "Resolved zone connection type: %s." ),
  506. EscapeHTML( PrintZoneConnection( constraint.m_ZoneConnection ) ) ) )
  507. if( constraint.m_ZoneConnection == ZONE_CONNECTION::THT_THERMAL )
  508. {
  509. const PAD* pad = nullptr;
  510. if( a->Type() == PCB_PAD_T )
  511. pad = static_cast<const PAD*>( a );
  512. else if( b->Type() == PCB_PAD_T )
  513. pad = static_cast<const PAD*>( b );
  514. if( pad && pad->GetAttribute() == PAD_ATTRIB::PTH )
  515. {
  516. constraint.m_ZoneConnection = ZONE_CONNECTION::THERMAL;
  517. }
  518. else
  519. {
  520. REPORT( wxString::Format( _( "Pad is not a through hole pad; connection will be: %s." ),
  521. EscapeHTML( PrintZoneConnection( ZONE_CONNECTION::FULL ) ) ) )
  522. constraint.m_ZoneConnection = ZONE_CONNECTION::FULL;
  523. }
  524. }
  525. return constraint;
  526. }
  527. DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BOARD_ITEM* a,
  528. const BOARD_ITEM* b, PCB_LAYER_ID aLayer,
  529. REPORTER* aReporter )
  530. {
  531. /*
  532. * NOTE: all string manipulation MUST BE KEPT INSIDE the REPORT macro. It absolutely
  533. * kills performance when running bulk DRC tests (where aReporter is nullptr).
  534. */
  535. const BOARD_CONNECTED_ITEM* ac = a && a->IsConnected() ?
  536. static_cast<const BOARD_CONNECTED_ITEM*>( a ) : nullptr;
  537. const BOARD_CONNECTED_ITEM* bc = b && b->IsConnected() ?
  538. static_cast<const BOARD_CONNECTED_ITEM*>( b ) : nullptr;
  539. bool a_is_non_copper = a && ( !a->IsOnCopperLayer() || isKeepoutZone( a, false ) );
  540. bool b_is_non_copper = b && ( !b->IsOnCopperLayer() || isKeepoutZone( b, false ) );
  541. const PAD* pad = nullptr;
  542. const ZONE* zone = nullptr;
  543. const FOOTPRINT* parentFootprint = nullptr;
  544. if( aConstraintType == ZONE_CONNECTION_CONSTRAINT
  545. || aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
  546. || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT )
  547. {
  548. if( a && a->Type() == PCB_PAD_T )
  549. pad = static_cast<const PAD*>( a );
  550. else if( a && a->Type() == PCB_ZONE_T )
  551. zone = static_cast<const ZONE*>( a );
  552. if( b && b->Type() == PCB_PAD_T )
  553. pad = static_cast<const PAD*>( b );
  554. else if( b && b->Type() == PCB_ZONE_T )
  555. zone = static_cast<const ZONE*>( b );
  556. if( pad )
  557. parentFootprint = pad->GetParentFootprint();
  558. }
  559. DRC_CONSTRAINT constraint;
  560. constraint.m_Type = aConstraintType;
  561. auto applyConstraint =
  562. [&]( const DRC_ENGINE_CONSTRAINT* c )
  563. {
  564. if( c->constraint.m_Value.HasMin() )
  565. constraint.m_Value.SetMin( c->constraint.m_Value.Min() );
  566. if( c->constraint.m_Value.HasOpt() )
  567. constraint.m_Value.SetOpt( c->constraint.m_Value.Opt() );
  568. if( c->constraint.m_Value.HasMax() )
  569. constraint .m_Value.SetMax( c->constraint.m_Value.Max() );
  570. // While the expectation would be to OR the disallow flags, we've already
  571. // masked them down to aItem's type -- so we're really only looking for a
  572. // boolean here.
  573. constraint.m_DisallowFlags = c->constraint.m_DisallowFlags;
  574. constraint.m_ZoneConnection = c->constraint.m_ZoneConnection;
  575. constraint.SetParentRule( c->constraint.GetParentRule() );
  576. };
  577. // Local overrides take precedence over everything *except* board min clearance
  578. if( aConstraintType == CLEARANCE_CONSTRAINT || aConstraintType == HOLE_CLEARANCE_CONSTRAINT )
  579. {
  580. int override_val = 0;
  581. std::optional<int> overrideA;
  582. std::optional<int> overrideB;
  583. if( ac && !b_is_non_copper )
  584. overrideA = ac->GetClearanceOverrides( nullptr );
  585. if( bc && !a_is_non_copper )
  586. overrideB = bc->GetClearanceOverrides( nullptr );
  587. if( overrideA.has_value() || overrideB.has_value() )
  588. {
  589. wxString msg;
  590. if( overrideA.has_value() )
  591. {
  592. REPORT( "" )
  593. REPORT( wxString::Format( _( "Local override on %s; clearance: %s." ),
  594. EscapeHTML( a->GetItemDescription( this, true ) ),
  595. MessageTextFromValue( overrideA.value() ) ) )
  596. override_val = ac->GetClearanceOverrides( &msg ).value();
  597. }
  598. if( overrideB.has_value() )
  599. {
  600. REPORT( "" )
  601. REPORT( wxString::Format( _( "Local override on %s; clearance: %s." ),
  602. EscapeHTML( b->GetItemDescription( this, true ) ),
  603. EscapeHTML( MessageTextFromValue( overrideB.value() ) ) ) )
  604. if( overrideB > override_val )
  605. override_val = bc->GetClearanceOverrides( &msg ).value();
  606. }
  607. if( override_val )
  608. {
  609. if( aConstraintType == CLEARANCE_CONSTRAINT )
  610. {
  611. if( override_val < m_designSettings->m_MinClearance )
  612. {
  613. override_val = m_designSettings->m_MinClearance;
  614. msg = _( "board minimum" );
  615. REPORT( "" )
  616. REPORT( wxString::Format( _( "Board minimum clearance: %s." ),
  617. MessageTextFromValue( override_val ) ) )
  618. }
  619. }
  620. else
  621. {
  622. if( override_val < m_designSettings->m_HoleClearance )
  623. {
  624. override_val = m_designSettings->m_HoleClearance;
  625. msg = _( "board minimum hole" );
  626. REPORT( "" )
  627. REPORT( wxString::Format( _( "Board minimum hole clearance: %s." ),
  628. MessageTextFromValue( override_val ) ) )
  629. }
  630. }
  631. constraint.SetName( msg );
  632. constraint.m_Value.SetMin( override_val );
  633. return constraint;
  634. }
  635. }
  636. }
  637. else if( aConstraintType == ZONE_CONNECTION_CONSTRAINT )
  638. {
  639. if( pad && pad->GetLocalZoneConnection() != ZONE_CONNECTION::INHERITED )
  640. {
  641. wxString msg;
  642. ZONE_CONNECTION override = pad->GetZoneConnectionOverrides( &msg );
  643. REPORT( "" )
  644. REPORT( wxString::Format( _( "Local override on %s; zone connection: %s." ),
  645. EscapeHTML( pad->GetItemDescription( this, true ) ),
  646. EscapeHTML( PrintZoneConnection( override ) ) ) )
  647. constraint.SetName( msg );
  648. constraint.m_ZoneConnection = override;
  649. return constraint;
  650. }
  651. }
  652. else if( aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT )
  653. {
  654. if( pad && pad->GetLocalThermalGapOverride( nullptr ) > 0 )
  655. {
  656. wxString msg;
  657. int gap_override = pad->GetLocalThermalGapOverride( &msg );
  658. REPORT( "" )
  659. REPORT( wxString::Format( _( "Local override on %s; thermal relief gap: %s." ),
  660. EscapeHTML( pad->GetItemDescription( this, true ) ),
  661. EscapeHTML( MessageTextFromValue( gap_override ) ) ) )
  662. constraint.SetName( msg );
  663. constraint.m_Value.SetMin( gap_override );
  664. return constraint;
  665. }
  666. }
  667. else if( aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT )
  668. {
  669. if( pad && pad->GetLocalSpokeWidthOverride( nullptr ) > 0 )
  670. {
  671. wxString msg;
  672. int spoke_override = pad->GetLocalSpokeWidthOverride( &msg );
  673. REPORT( "" )
  674. REPORT( wxString::Format( _( "Local override on %s; thermal spoke width: %s." ),
  675. EscapeHTML( pad->GetItemDescription( this, true ) ),
  676. EscapeHTML( MessageTextFromValue( spoke_override ) ) ) )
  677. if( zone && zone->GetMinThickness() > spoke_override )
  678. {
  679. spoke_override = zone->GetMinThickness();
  680. REPORT( "" )
  681. REPORT( wxString::Format( _( "%s min thickness: %s." ),
  682. EscapeHTML( zone->GetItemDescription( this, true ) ),
  683. EscapeHTML( MessageTextFromValue( spoke_override ) ) ) )
  684. }
  685. constraint.SetName( msg );
  686. constraint.m_Value.SetMin( spoke_override );
  687. return constraint;
  688. }
  689. }
  690. auto testAssertion =
  691. [&]( const DRC_ENGINE_CONSTRAINT* c )
  692. {
  693. REPORT( wxString::Format( _( "Checking assertion \"%s\"." ),
  694. EscapeHTML( c->constraint.m_Test->GetExpression() ) ) )
  695. if( c->constraint.m_Test->EvaluateFor( a, b, c->constraint.m_Type, aLayer,
  696. aReporter ) )
  697. {
  698. REPORT( _( "Assertion passed." ) )
  699. }
  700. else
  701. {
  702. REPORT( EscapeHTML( _( "--> Assertion failed. <--" ) ) )
  703. }
  704. };
  705. auto processConstraint =
  706. [&]( const DRC_ENGINE_CONSTRAINT* c )
  707. {
  708. bool implicit = c->parentRule && c->parentRule->m_Implicit;
  709. REPORT( "" )
  710. switch( c->constraint.m_Type )
  711. {
  712. case CLEARANCE_CONSTRAINT:
  713. case COURTYARD_CLEARANCE_CONSTRAINT:
  714. case SILK_CLEARANCE_CONSTRAINT:
  715. case HOLE_CLEARANCE_CONSTRAINT:
  716. case EDGE_CLEARANCE_CONSTRAINT:
  717. case PHYSICAL_CLEARANCE_CONSTRAINT:
  718. case PHYSICAL_HOLE_CLEARANCE_CONSTRAINT:
  719. REPORT( wxString::Format( _( "Checking %s clearance: %s." ),
  720. EscapeHTML( c->constraint.GetName() ),
  721. MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
  722. break;
  723. case CREEPAGE_CONSTRAINT:
  724. REPORT( wxString::Format( _( "Checking %s creepage: %s." ),
  725. EscapeHTML( c->constraint.GetName() ),
  726. MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
  727. break;
  728. case MAX_UNCOUPLED_CONSTRAINT:
  729. REPORT( wxString::Format( _( "Checking %s max uncoupled length: %s." ),
  730. EscapeHTML( c->constraint.GetName() ),
  731. MessageTextFromValue( c->constraint.m_Value.Max() ) ) )
  732. break;
  733. case SKEW_CONSTRAINT:
  734. REPORT( wxString::Format( _( "Checking %s max skew: %s." ),
  735. EscapeHTML( c->constraint.GetName() ),
  736. MessageTextFromValue( c->constraint.m_Value.Max() ) ) )
  737. break;
  738. case THERMAL_RELIEF_GAP_CONSTRAINT:
  739. REPORT( wxString::Format( _( "Checking %s gap: %s." ),
  740. EscapeHTML( c->constraint.GetName() ),
  741. MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
  742. break;
  743. case THERMAL_SPOKE_WIDTH_CONSTRAINT:
  744. REPORT( wxString::Format( _( "Checking %s thermal spoke width: %s." ),
  745. EscapeHTML( c->constraint.GetName() ),
  746. MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
  747. break;
  748. case MIN_RESOLVED_SPOKES_CONSTRAINT:
  749. REPORT( wxString::Format( _( "Checking %s min spoke count: %s." ),
  750. EscapeHTML( c->constraint.GetName() ),
  751. EDA_UNIT_UTILS::UI::MessageTextFromValue( unityScale, EDA_UNITS::UNSCALED,
  752. c->constraint.m_Value.Min() ) ) )
  753. break;
  754. case ZONE_CONNECTION_CONSTRAINT:
  755. REPORT( wxString::Format( _( "Checking %s zone connection: %s." ),
  756. EscapeHTML( c->constraint.GetName() ),
  757. EscapeHTML( PrintZoneConnection( c->constraint.m_ZoneConnection ) ) ) )
  758. break;
  759. case TRACK_WIDTH_CONSTRAINT:
  760. case ANNULAR_WIDTH_CONSTRAINT:
  761. case VIA_DIAMETER_CONSTRAINT:
  762. case HOLE_SIZE_CONSTRAINT:
  763. case TEXT_HEIGHT_CONSTRAINT:
  764. case TEXT_THICKNESS_CONSTRAINT:
  765. case DIFF_PAIR_GAP_CONSTRAINT:
  766. case LENGTH_CONSTRAINT:
  767. case CONNECTION_WIDTH_CONSTRAINT:
  768. case HOLE_TO_HOLE_CONSTRAINT:
  769. {
  770. if( aReporter )
  771. {
  772. wxString min = wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
  773. wxString opt = wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
  774. wxString max = wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" );
  775. if( implicit )
  776. {
  777. min = MessageTextFromValue( c->constraint.m_Value.Min() );
  778. opt = MessageTextFromValue( c->constraint.m_Value.Opt() );
  779. switch( c->constraint.m_Type )
  780. {
  781. case TRACK_WIDTH_CONSTRAINT:
  782. if( c->constraint.m_Value.HasOpt() )
  783. {
  784. REPORT( wxString::Format( _( "Checking %s track width: opt %s." ),
  785. EscapeHTML( c->constraint.GetName() ),
  786. opt ) )
  787. }
  788. else if( c->constraint.m_Value.HasMin() )
  789. {
  790. REPORT( wxString::Format( _( "Checking %s track width: min %s." ),
  791. EscapeHTML( c->constraint.GetName() ),
  792. min ) )
  793. }
  794. break;
  795. case ANNULAR_WIDTH_CONSTRAINT:
  796. REPORT( wxString::Format( _( "Checking %s annular width: min %s." ),
  797. EscapeHTML( c->constraint.GetName() ),
  798. opt ) )
  799. break;
  800. case VIA_DIAMETER_CONSTRAINT:
  801. if( c->constraint.m_Value.HasOpt() )
  802. {
  803. REPORT( wxString::Format( _( "Checking %s via diameter: opt %s." ),
  804. EscapeHTML( c->constraint.GetName() ),
  805. opt ) )
  806. }
  807. else if( c->constraint.m_Value.HasMin() )
  808. {
  809. REPORT( wxString::Format( _( "Checking %s via diameter: min %s." ),
  810. EscapeHTML( c->constraint.GetName() ),
  811. min ) )
  812. }
  813. break;
  814. case HOLE_SIZE_CONSTRAINT:
  815. if( c->constraint.m_Value.HasOpt() )
  816. {
  817. REPORT( wxString::Format( _( "Checking %s hole size: opt %s." ),
  818. EscapeHTML( c->constraint.GetName() ),
  819. opt ) )
  820. }
  821. else if( c->constraint.m_Value.HasMin() )
  822. {
  823. REPORT( wxString::Format( _( "Checking %s hole size: min %s." ),
  824. EscapeHTML( c->constraint.GetName() ),
  825. min ) )
  826. }
  827. break;
  828. case TEXT_HEIGHT_CONSTRAINT:
  829. case TEXT_THICKNESS_CONSTRAINT:
  830. case CONNECTION_WIDTH_CONSTRAINT:
  831. REPORT( wxString::Format( _( "Checking %s: min %s." ),
  832. EscapeHTML( c->constraint.GetName() ),
  833. min ) )
  834. break;
  835. case DIFF_PAIR_GAP_CONSTRAINT:
  836. if( c->constraint.m_Value.HasOpt() )
  837. {
  838. REPORT( wxString::Format( _( "Checking %s diff pair gap: opt %s." ),
  839. EscapeHTML( c->constraint.GetName() ),
  840. opt ) )
  841. }
  842. else if( c->constraint.m_Value.HasMin() )
  843. {
  844. REPORT( wxString::Format( _( "Checking %s clearance: min %s." ),
  845. EscapeHTML( c->constraint.GetName() ),
  846. min ) )
  847. }
  848. break;
  849. case HOLE_TO_HOLE_CONSTRAINT:
  850. REPORT( wxString::Format( _( "Checking %s hole to hole: min %s." ),
  851. EscapeHTML( c->constraint.GetName() ),
  852. min ) )
  853. break;
  854. default:
  855. REPORT( wxString::Format( _( "Checking %s." ),
  856. EscapeHTML( c->constraint.GetName() ) ) )
  857. }
  858. }
  859. else
  860. {
  861. if( c->constraint.m_Value.HasMin() )
  862. min = MessageTextFromValue( c->constraint.m_Value.Min() );
  863. if( c->constraint.m_Value.HasOpt() )
  864. opt = MessageTextFromValue( c->constraint.m_Value.Opt() );
  865. if( c->constraint.m_Value.HasMax() )
  866. max = MessageTextFromValue( c->constraint.m_Value.Max() );
  867. REPORT( wxString::Format( _( "Checking %s: min %s; opt %s; max %s." ),
  868. EscapeHTML( c->constraint.GetName() ),
  869. min,
  870. opt,
  871. max ) )
  872. }
  873. }
  874. break;
  875. }
  876. default:
  877. REPORT( wxString::Format( _( "Checking %s." ),
  878. EscapeHTML( c->constraint.GetName() ) ) )
  879. }
  880. if( c->constraint.m_Type == CLEARANCE_CONSTRAINT )
  881. {
  882. if( a_is_non_copper || b_is_non_copper )
  883. {
  884. if( implicit )
  885. {
  886. REPORT( _( "Netclass clearances apply only between copper items." ) )
  887. }
  888. else if( a_is_non_copper )
  889. {
  890. REPORT( wxString::Format( _( "%s contains no copper. Rule ignored." ),
  891. EscapeHTML( a->GetItemDescription( this, true ) ) ) )
  892. }
  893. else if( b_is_non_copper )
  894. {
  895. REPORT( wxString::Format( _( "%s contains no copper. Rule ignored." ),
  896. EscapeHTML( b->GetItemDescription( this, true ) ) ) )
  897. }
  898. return;
  899. }
  900. }
  901. else if( c->constraint.m_Type == DISALLOW_CONSTRAINT )
  902. {
  903. int mask;
  904. if( a->GetFlags() & HOLE_PROXY )
  905. {
  906. mask = DRC_DISALLOW_HOLES;
  907. }
  908. else if( a->Type() == PCB_VIA_T )
  909. {
  910. mask = DRC_DISALLOW_VIAS;
  911. switch( static_cast<const PCB_VIA*>( a )->GetViaType() )
  912. {
  913. case VIATYPE::BLIND_BURIED: mask |= DRC_DISALLOW_BB_VIAS; break;
  914. case VIATYPE::MICROVIA: mask |= DRC_DISALLOW_MICRO_VIAS; break;
  915. default: break;
  916. }
  917. }
  918. else
  919. {
  920. switch( a->Type() )
  921. {
  922. case PCB_TRACE_T: mask = DRC_DISALLOW_TRACKS; break;
  923. case PCB_ARC_T: mask = DRC_DISALLOW_TRACKS; break;
  924. case PCB_PAD_T: mask = DRC_DISALLOW_PADS; break;
  925. case PCB_FOOTPRINT_T: mask = DRC_DISALLOW_FOOTPRINTS; break;
  926. case PCB_SHAPE_T: mask = DRC_DISALLOW_GRAPHICS; break;
  927. case PCB_FIELD_T: mask = DRC_DISALLOW_TEXTS; break;
  928. case PCB_TEXT_T: mask = DRC_DISALLOW_TEXTS; break;
  929. case PCB_TEXTBOX_T: mask = DRC_DISALLOW_TEXTS; break;
  930. case PCB_TABLE_T: mask = DRC_DISALLOW_TEXTS; break;
  931. case PCB_ZONE_T:
  932. // Treat teardrop areas as tracks for DRC purposes
  933. if( static_cast<const ZONE*>( a )->IsTeardropArea() )
  934. mask = DRC_DISALLOW_TRACKS;
  935. else
  936. mask = DRC_DISALLOW_ZONES;
  937. break;
  938. case PCB_LOCATE_HOLE_T: mask = DRC_DISALLOW_HOLES; break;
  939. default: mask = 0; break;
  940. }
  941. }
  942. if( ( c->constraint.m_DisallowFlags & mask ) == 0 )
  943. {
  944. if( implicit )
  945. REPORT( _( "Keepout constraint not met." ) )
  946. else
  947. REPORT( _( "Disallow constraint not met." ) )
  948. return;
  949. }
  950. LSET itemLayers = a->GetLayerSet();
  951. if( a->Type() == PCB_FOOTPRINT_T )
  952. {
  953. const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( a );
  954. if( !footprint->GetCourtyard( F_CrtYd ).IsEmpty() )
  955. itemLayers |= LSET::FrontMask();
  956. if( !footprint->GetCourtyard( B_CrtYd ).IsEmpty() )
  957. itemLayers |= LSET::BackMask();
  958. }
  959. if( !( c->layerTest & itemLayers ).any() )
  960. {
  961. if( implicit )
  962. {
  963. REPORT( _( "Keepout layer(s) not matched." ) )
  964. }
  965. else if( c->parentRule )
  966. {
  967. REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
  968. EscapeHTML( c->parentRule->m_LayerSource ) ) )
  969. }
  970. else
  971. {
  972. REPORT( _( "Rule layer not matched; rule ignored." ) )
  973. }
  974. return;
  975. }
  976. }
  977. if( ( aLayer != UNDEFINED_LAYER && !c->layerTest.test( aLayer ) )
  978. || ( m_board->GetEnabledLayers() & c->layerTest ).count() == 0 )
  979. {
  980. if( implicit )
  981. {
  982. REPORT( _( "Constraint layer not matched." ) )
  983. }
  984. else if( c->parentRule )
  985. {
  986. REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
  987. EscapeHTML( c->parentRule->m_LayerSource ) ) )
  988. }
  989. else
  990. {
  991. REPORT( _( "Rule layer not matched; rule ignored." ) )
  992. }
  993. }
  994. else if( c->constraint.m_Type == HOLE_TO_HOLE_CONSTRAINT
  995. && ( !a->HasDrilledHole() && !b->HasDrilledHole() ) )
  996. {
  997. // Report non-drilled-holes as an implicit condition
  998. REPORT( wxString::Format( _( "%s is not a drilled hole; rule ignored." ),
  999. a->GetItemDescription( this, true ) ) )
  1000. }
  1001. else if( !c->condition || c->condition->GetExpression().IsEmpty() )
  1002. {
  1003. if( aReporter )
  1004. {
  1005. if( implicit )
  1006. {
  1007. REPORT( _( "Unconditional constraint applied." ) )
  1008. }
  1009. else if( constraint.m_Type == ASSERTION_CONSTRAINT )
  1010. {
  1011. REPORT( _( "Unconditional rule applied." ) )
  1012. testAssertion( c );
  1013. }
  1014. else
  1015. {
  1016. REPORT( _( "Unconditional rule applied; overrides previous constraints." ) )
  1017. }
  1018. }
  1019. applyConstraint( c );
  1020. }
  1021. else
  1022. {
  1023. if( implicit )
  1024. {
  1025. // Don't report on implicit rule conditions; they're synthetic.
  1026. }
  1027. else
  1028. {
  1029. REPORT( wxString::Format( _( "Checking rule condition \"%s\"." ),
  1030. EscapeHTML( c->condition->GetExpression() ) ) )
  1031. }
  1032. if( c->condition->EvaluateFor( a, b, c->constraint.m_Type, aLayer, aReporter ) )
  1033. {
  1034. if( aReporter )
  1035. {
  1036. if( implicit )
  1037. {
  1038. REPORT( _( "Constraint applied." ) )
  1039. }
  1040. else if( constraint.m_Type == ASSERTION_CONSTRAINT )
  1041. {
  1042. REPORT( _( "Rule applied." ) )
  1043. testAssertion( c );
  1044. }
  1045. else
  1046. {
  1047. REPORT( _( "Rule applied; overrides previous constraints." ) )
  1048. }
  1049. }
  1050. applyConstraint( c );
  1051. }
  1052. else
  1053. {
  1054. REPORT( implicit ? _( "Membership not satisfied; constraint ignored." )
  1055. : _( "Condition not satisfied; rule ignored." ) )
  1056. }
  1057. }
  1058. };
  1059. if( m_constraintMap.count( aConstraintType ) )
  1060. {
  1061. std::vector<DRC_ENGINE_CONSTRAINT*>* ruleset = m_constraintMap[ aConstraintType ];
  1062. for( int ii = 0; ii < (int) ruleset->size(); ++ii )
  1063. processConstraint( ruleset->at( ii ) );
  1064. }
  1065. if( constraint.GetParentRule() && !constraint.GetParentRule()->m_Implicit )
  1066. return constraint;
  1067. // Special case for pad zone connections which can iherit from their parent footprints.
  1068. // We've already checked for local overrides, and there were no rules targetting the pad
  1069. // itself, so we know we're inheriting and need to see if there are any rules targetting
  1070. // the parent footprint.
  1071. if( pad && parentFootprint && ( aConstraintType == ZONE_CONNECTION_CONSTRAINT
  1072. || aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
  1073. || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT ) )
  1074. {
  1075. if( a == pad )
  1076. a = parentFootprint;
  1077. else
  1078. b = parentFootprint;
  1079. if( m_constraintMap.count( aConstraintType ) )
  1080. {
  1081. std::vector<DRC_ENGINE_CONSTRAINT*>* ruleset = m_constraintMap[ aConstraintType ];
  1082. for( int ii = 0; ii < (int) ruleset->size(); ++ii )
  1083. processConstraint( ruleset->at( ii ) );
  1084. if( constraint.GetParentRule() && !constraint.GetParentRule()->m_Implicit )
  1085. return constraint;
  1086. }
  1087. }
  1088. // Unfortunately implicit rules don't work for local clearances (such as zones) because
  1089. // they have to be max'ed with netclass values (which are already implicit rules), and our
  1090. // rule selection paradigm is "winner takes all".
  1091. if( aConstraintType == CLEARANCE_CONSTRAINT )
  1092. {
  1093. int global = constraint.m_Value.Min();
  1094. int clearance = global;
  1095. bool needBlankLine = true;
  1096. if( ac && ac->GetLocalClearance().has_value() )
  1097. {
  1098. int localA = ac->GetLocalClearance().value();
  1099. if( needBlankLine )
  1100. {
  1101. REPORT( "" )
  1102. needBlankLine = false;
  1103. }
  1104. REPORT( wxString::Format( _( "Local clearance on %s: %s." ),
  1105. EscapeHTML( a->GetItemDescription( this, true ) ),
  1106. MessageTextFromValue( localA ) ) )
  1107. if( localA > clearance )
  1108. {
  1109. wxString msg;
  1110. clearance = ac->GetLocalClearance( &msg ).value();
  1111. constraint.SetParentRule( nullptr );
  1112. constraint.SetName( msg );
  1113. constraint.m_Value.SetMin( clearance );
  1114. }
  1115. }
  1116. if( bc && bc->GetLocalClearance().has_value() )
  1117. {
  1118. int localB = bc->GetLocalClearance().value();
  1119. if( needBlankLine )
  1120. {
  1121. REPORT( "" )
  1122. needBlankLine = false;
  1123. }
  1124. REPORT( wxString::Format( _( "Local clearance on %s: %s." ),
  1125. EscapeHTML( b->GetItemDescription( this, true ) ),
  1126. MessageTextFromValue( localB ) ) )
  1127. if( localB > clearance )
  1128. {
  1129. wxString msg;
  1130. clearance = bc->GetLocalClearance( &msg ).value();
  1131. constraint.SetParentRule( nullptr );
  1132. constraint.SetName( msg );
  1133. constraint.m_Value.SetMin( clearance );
  1134. }
  1135. }
  1136. if( !a_is_non_copper && !b_is_non_copper )
  1137. {
  1138. if( needBlankLine )
  1139. {
  1140. REPORT( "" )
  1141. needBlankLine = false;
  1142. }
  1143. REPORT( wxString::Format( _( "Board minimum clearance: %s." ),
  1144. MessageTextFromValue( m_designSettings->m_MinClearance ) ) )
  1145. if( clearance < m_designSettings->m_MinClearance )
  1146. {
  1147. constraint.SetParentRule( nullptr );
  1148. constraint.SetName( _( "board minimum" ) );
  1149. constraint.m_Value.SetMin( m_designSettings->m_MinClearance );
  1150. }
  1151. }
  1152. return constraint;
  1153. }
  1154. else if( aConstraintType == DIFF_PAIR_GAP_CONSTRAINT )
  1155. {
  1156. REPORT( "" )
  1157. REPORT( wxString::Format( _( "Board minimum clearance: %s." ),
  1158. MessageTextFromValue( m_designSettings->m_MinClearance ) ) )
  1159. if( constraint.m_Value.Min() < m_designSettings->m_MinClearance )
  1160. {
  1161. constraint.SetParentRule( nullptr );
  1162. constraint.SetName( _( "board minimum" ) );
  1163. constraint.m_Value.SetMin( m_designSettings->m_MinClearance );
  1164. }
  1165. return constraint;
  1166. }
  1167. else if( aConstraintType == ZONE_CONNECTION_CONSTRAINT )
  1168. {
  1169. if( pad && parentFootprint )
  1170. {
  1171. ZONE_CONNECTION local = parentFootprint->GetLocalZoneConnection();
  1172. if( local != ZONE_CONNECTION::INHERITED )
  1173. {
  1174. REPORT( "" )
  1175. REPORT( wxString::Format( _( "%s zone connection: %s." ),
  1176. EscapeHTML( parentFootprint->GetItemDescription( this, true ) ),
  1177. EscapeHTML( PrintZoneConnection( local ) ) ) )
  1178. constraint.SetParentRule( nullptr );
  1179. constraint.SetName( _( "footprint" ) );
  1180. constraint.m_ZoneConnection = local;
  1181. return constraint;
  1182. }
  1183. }
  1184. if( zone )
  1185. {
  1186. ZONE_CONNECTION local = zone->GetPadConnection();
  1187. REPORT( "" )
  1188. REPORT( wxString::Format( _( "%s pad connection: %s." ),
  1189. EscapeHTML( zone->GetItemDescription( this, true ) ),
  1190. EscapeHTML( PrintZoneConnection( local ) ) ) )
  1191. constraint.SetParentRule( nullptr );
  1192. constraint.SetName( _( "zone" ) );
  1193. constraint.m_ZoneConnection = local;
  1194. return constraint;
  1195. }
  1196. }
  1197. else if( aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT )
  1198. {
  1199. if( zone )
  1200. {
  1201. int local = zone->GetThermalReliefGap();
  1202. REPORT( "" )
  1203. REPORT( wxString::Format( _( "%s thermal relief gap: %s." ),
  1204. EscapeHTML( zone->GetItemDescription( this, true ) ),
  1205. EscapeHTML( MessageTextFromValue( local ) ) ) )
  1206. constraint.SetParentRule( nullptr );
  1207. constraint.SetName( _( "zone" ) );
  1208. constraint.m_Value.SetMin( local );
  1209. return constraint;
  1210. }
  1211. }
  1212. else if( aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT )
  1213. {
  1214. if( zone )
  1215. {
  1216. int local = zone->GetThermalReliefSpokeWidth();
  1217. REPORT( "" )
  1218. REPORT( wxString::Format( _( "%s thermal spoke width: %s." ),
  1219. EscapeHTML( zone->GetItemDescription( this, true ) ),
  1220. EscapeHTML( MessageTextFromValue( local ) ) ) )
  1221. constraint.SetParentRule( nullptr );
  1222. constraint.SetName( _( "zone" ) );
  1223. constraint.m_Value.SetMin( local );
  1224. return constraint;
  1225. }
  1226. }
  1227. if( !constraint.GetParentRule() )
  1228. {
  1229. constraint.m_Type = NULL_CONSTRAINT;
  1230. constraint.m_DisallowFlags = 0;
  1231. }
  1232. return constraint;
  1233. }
  1234. void DRC_ENGINE::ProcessAssertions( const BOARD_ITEM* a,
  1235. std::function<void( const DRC_CONSTRAINT* )> aFailureHandler,
  1236. REPORTER* aReporter )
  1237. {
  1238. /*
  1239. * NOTE: all string manipulation MUST BE KEPT INSIDE the REPORT macro. It absolutely
  1240. * kills performance when running bulk DRC tests (where aReporter is nullptr).
  1241. */
  1242. auto testAssertion =
  1243. [&]( const DRC_ENGINE_CONSTRAINT* c )
  1244. {
  1245. REPORT( wxString::Format( _( "Checking rule assertion \"%s\"." ),
  1246. EscapeHTML( c->constraint.m_Test->GetExpression() ) ) )
  1247. if( c->constraint.m_Test->EvaluateFor( a, nullptr, c->constraint.m_Type,
  1248. a->GetLayer(), aReporter ) )
  1249. {
  1250. REPORT( _( "Assertion passed." ) )
  1251. }
  1252. else
  1253. {
  1254. REPORT( EscapeHTML( _( "--> Assertion failed. <--" ) ) )
  1255. aFailureHandler( &c->constraint );
  1256. }
  1257. };
  1258. auto processConstraint =
  1259. [&]( const DRC_ENGINE_CONSTRAINT* c )
  1260. {
  1261. REPORT( "" )
  1262. REPORT( wxString::Format( _( "Checking %s." ), c->constraint.GetName() ) )
  1263. if( !( a->GetLayerSet() & c->layerTest ).any() )
  1264. {
  1265. REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
  1266. EscapeHTML( c->parentRule->m_LayerSource ) ) )
  1267. }
  1268. if( !c->condition || c->condition->GetExpression().IsEmpty() )
  1269. {
  1270. REPORT( _( "Unconditional rule applied." ) )
  1271. testAssertion( c );
  1272. }
  1273. else
  1274. {
  1275. REPORT( wxString::Format( _( "Checking rule condition \"%s\"." ),
  1276. EscapeHTML( c->condition->GetExpression() ) ) )
  1277. if( c->condition->EvaluateFor( a, nullptr, c->constraint.m_Type,
  1278. a->GetLayer(), aReporter ) )
  1279. {
  1280. REPORT( _( "Rule applied." ) )
  1281. testAssertion( c );
  1282. }
  1283. else
  1284. {
  1285. REPORT( _( "Condition not satisfied; rule ignored." ) )
  1286. }
  1287. }
  1288. };
  1289. if( m_constraintMap.count( ASSERTION_CONSTRAINT ) )
  1290. {
  1291. std::vector<DRC_ENGINE_CONSTRAINT*>* ruleset = m_constraintMap[ ASSERTION_CONSTRAINT ];
  1292. for( int ii = 0; ii < (int) ruleset->size(); ++ii )
  1293. processConstraint( ruleset->at( ii ) );
  1294. }
  1295. }
  1296. #undef REPORT
  1297. bool DRC_ENGINE::IsErrorLimitExceeded( int error_code )
  1298. {
  1299. assert( error_code >= 0 && error_code <= DRCE_LAST );
  1300. return m_errorLimits[ error_code ] <= 0;
  1301. }
  1302. void DRC_ENGINE::ReportViolation( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos,
  1303. int aMarkerLayer )
  1304. {
  1305. static std::mutex globalLock;
  1306. m_errorLimits[ aItem->GetErrorCode() ] -= 1;
  1307. if( m_violationHandler )
  1308. {
  1309. std::lock_guard<std::mutex> guard( globalLock );
  1310. m_violationHandler( aItem, aPos, aMarkerLayer );
  1311. }
  1312. if( m_reporter )
  1313. {
  1314. wxString msg = wxString::Format( wxT( "Test '%s': %s (code %d)" ),
  1315. aItem->GetViolatingTest()->GetName(),
  1316. aItem->GetErrorMessage(),
  1317. aItem->GetErrorCode() );
  1318. DRC_RULE* rule = aItem->GetViolatingRule();
  1319. if( rule )
  1320. msg += wxString::Format( wxT( ", violating rule: '%s'" ), rule->m_Name );
  1321. m_reporter->Report( msg );
  1322. wxString violatingItemsStr = wxT( "Violating items: " );
  1323. m_reporter->Report( wxString::Format( wxT( " |- violating position (%d, %d)" ),
  1324. aPos.x,
  1325. aPos.y ) );
  1326. }
  1327. }
  1328. void DRC_ENGINE::ReportAux ( const wxString& aStr )
  1329. {
  1330. if( !m_reporter )
  1331. return;
  1332. m_reporter->Report( aStr, RPT_SEVERITY_INFO );
  1333. }
  1334. bool DRC_ENGINE::KeepRefreshing( bool aWait )
  1335. {
  1336. if( !m_progressReporter )
  1337. return true;
  1338. return m_progressReporter->KeepRefreshing( aWait );
  1339. }
  1340. void DRC_ENGINE::AdvanceProgress()
  1341. {
  1342. if( m_progressReporter )
  1343. m_progressReporter->AdvanceProgress();
  1344. }
  1345. void DRC_ENGINE::SetMaxProgress( int aSize )
  1346. {
  1347. if( m_progressReporter )
  1348. m_progressReporter->SetMaxProgress( aSize );
  1349. }
  1350. bool DRC_ENGINE::ReportProgress( double aProgress )
  1351. {
  1352. if( !m_progressReporter )
  1353. return true;
  1354. m_progressReporter->SetCurrentProgress( aProgress );
  1355. return m_progressReporter->KeepRefreshing( false );
  1356. }
  1357. bool DRC_ENGINE::ReportPhase( const wxString& aMessage )
  1358. {
  1359. if( !m_progressReporter )
  1360. return true;
  1361. m_progressReporter->AdvancePhase( aMessage );
  1362. return m_progressReporter->KeepRefreshing( false );
  1363. }
  1364. bool DRC_ENGINE::IsCancelled() const
  1365. {
  1366. return m_progressReporter && m_progressReporter->IsCancelled();
  1367. }
  1368. bool DRC_ENGINE::HasRulesForConstraintType( DRC_CONSTRAINT_T constraintID )
  1369. {
  1370. //drc_dbg(10,"hascorrect id %d size %d\n", ruleID, m_ruleMap[ruleID]->sortedRules.size( ) );
  1371. if( m_constraintMap.count( constraintID ) )
  1372. return m_constraintMap[ constraintID ]->size() > 0;
  1373. return false;
  1374. }
  1375. bool DRC_ENGINE::QueryWorstConstraint( DRC_CONSTRAINT_T aConstraintId, DRC_CONSTRAINT& aConstraint )
  1376. {
  1377. int worst = 0;
  1378. if( m_constraintMap.count( aConstraintId ) )
  1379. {
  1380. for( DRC_ENGINE_CONSTRAINT* c : *m_constraintMap[aConstraintId] )
  1381. {
  1382. int current = c->constraint.GetValue().Min();
  1383. if( current > worst )
  1384. {
  1385. worst = current;
  1386. aConstraint = c->constraint;
  1387. }
  1388. }
  1389. }
  1390. return worst > 0;
  1391. }
  1392. std::set<int> DRC_ENGINE::QueryDistinctConstraints( DRC_CONSTRAINT_T aConstraintId )
  1393. {
  1394. std::set<int> distinctMinimums;
  1395. if( m_constraintMap.count( aConstraintId ) )
  1396. {
  1397. for( DRC_ENGINE_CONSTRAINT* c : *m_constraintMap[aConstraintId] )
  1398. distinctMinimums.emplace( c->constraint.GetValue().Min() );
  1399. }
  1400. return distinctMinimums;
  1401. }
  1402. // fixme: move two functions below to pcbcommon?
  1403. int DRC_ENGINE::MatchDpSuffix( const wxString& aNetName, wxString& aComplementNet,
  1404. wxString& aBaseDpName )
  1405. {
  1406. int rv = 0;
  1407. int count = 0;
  1408. for( auto it = aNetName.rbegin(); it != aNetName.rend() && rv == 0; ++it, ++count )
  1409. {
  1410. int ch = *it;
  1411. if( ( ch >= '0' && ch <= '9' ) || ch == '_' )
  1412. {
  1413. continue;
  1414. }
  1415. else if( ch == '+' )
  1416. {
  1417. aComplementNet = wxT( "-" );
  1418. rv = 1;
  1419. }
  1420. else if( ch == '-' )
  1421. {
  1422. aComplementNet = wxT( "+" );
  1423. rv = -1;
  1424. }
  1425. else if( ch == 'N' )
  1426. {
  1427. aComplementNet = wxT( "P" );
  1428. rv = -1;
  1429. }
  1430. else if ( ch == 'P' )
  1431. {
  1432. aComplementNet = wxT( "N" );
  1433. rv = 1;
  1434. }
  1435. else
  1436. {
  1437. break;
  1438. }
  1439. }
  1440. if( rv != 0 && count >= 1 )
  1441. {
  1442. aBaseDpName = aNetName.Left( aNetName.Length() - count );
  1443. aComplementNet = wxString( aBaseDpName ) << aComplementNet << aNetName.Right( count - 1 );
  1444. }
  1445. return rv;
  1446. }
  1447. bool DRC_ENGINE::IsNetADiffPair( BOARD* aBoard, NETINFO_ITEM* aNet, int& aNetP, int& aNetN )
  1448. {
  1449. wxString refName = aNet->GetNetname();
  1450. wxString dummy, coupledNetName;
  1451. if( int polarity = MatchDpSuffix( refName, coupledNetName, dummy ) )
  1452. {
  1453. NETINFO_ITEM* net = aBoard->FindNet( coupledNetName );
  1454. if( !net )
  1455. return false;
  1456. if( polarity > 0 )
  1457. {
  1458. aNetP = aNet->GetNetCode();
  1459. aNetN = net->GetNetCode();
  1460. }
  1461. else
  1462. {
  1463. aNetP = net->GetNetCode();
  1464. aNetN = aNet->GetNetCode();
  1465. }
  1466. return true;
  1467. }
  1468. return false;
  1469. }
  1470. /**
  1471. * Check if the given collision between a track and another item occurs during the track's entry
  1472. * into a net-tie pad.
  1473. */
  1474. bool DRC_ENGINE::IsNetTieExclusion( int aTrackNetCode, PCB_LAYER_ID aTrackLayer,
  1475. const VECTOR2I& aCollisionPos, BOARD_ITEM* aCollidingItem )
  1476. {
  1477. FOOTPRINT* parentFootprint = aCollidingItem->GetParentFootprint();
  1478. if( parentFootprint && parentFootprint->IsNetTie() )
  1479. {
  1480. int epsilon = GetDesignSettings()->GetDRCEpsilon();
  1481. std::map<wxString, int> padToNetTieGroupMap = parentFootprint->MapPadNumbersToNetTieGroups();
  1482. for( PAD* pad : parentFootprint->Pads() )
  1483. {
  1484. if( padToNetTieGroupMap[ pad->GetNumber() ] >= 0 && aTrackNetCode == pad->GetNetCode() )
  1485. {
  1486. if( pad->GetEffectiveShape( aTrackLayer )->Collide( aCollisionPos, epsilon ) )
  1487. return true;
  1488. }
  1489. }
  1490. }
  1491. return false;
  1492. }
  1493. DRC_TEST_PROVIDER* DRC_ENGINE::GetTestProvider( const wxString& name ) const
  1494. {
  1495. for( DRC_TEST_PROVIDER* prov : m_testProviders )
  1496. {
  1497. if( name == prov->GetName() )
  1498. return prov;
  1499. }
  1500. return nullptr;
  1501. }