KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_engine.cpp
Go to the documentation of this file.
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, [email protected]
6 * Copyright The 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, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <atomic>
23#include <set>
24#include <wx/log.h>
25#include <reporter.h>
26#include <common.h>
27#include <progress_reporter.h>
28#include <string_utils.h>
33#include <drc/drc_engine.h>
34#include <drc/drc_rtree.h>
35#include <drc/drc_rule_parser.h>
36#include <drc/drc_rule.h>
40#include <drc/drc_item.h>
42#include <board.h>
43#include <pcb_marker.h>
44#include <footprint.h>
45#include <pad.h>
46#include <pcb_track.h>
47#include <pcb_shape.h>
48#include <core/profile.h>
49#include <thread_pool.h>
50#include <zone.h>
55
56
57// wxListBox's performance degrades horrifically with very large datasets. It's not clear
58// they're useful to the user anyway.
59#define ERROR_LIMIT 199
60#define EXTENDED_ERROR_LIMIT 499
61
62
70static const wxChar* traceDrcProfile = wxT( "KICAD_DRC_PROFILE" );
71
72
73void drcPrintDebugMessage( int level, const wxString& msg, const char *function, int line )
74{
75 wxString valueStr;
76
77 if( wxGetEnv( wxT( "DRC_DEBUG" ), &valueStr ) )
78 {
79 int setLevel = wxAtoi( valueStr );
80
81 if( level <= setLevel )
82 printf( "%-30s:%d | %s\n", function, line, (const char *) msg.c_str() );
83 }
84}
85
86
89 m_designSettings ( aSettings ),
90 m_board( aBoard ),
91 m_drawingSheet( nullptr ),
92 m_schematicNetlist( nullptr ),
93 m_rulesValid( false ),
95 m_testFootprints( false ),
96 m_logReporter( nullptr ),
97 m_progressReporter( nullptr )
98{
99 m_errorLimits.resize( DRCE_LAST + 1 );
100
101 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
103}
104
105
107{
108 m_rules.clear();
109
110 for( std::pair<DRC_CONSTRAINT_T, std::vector<DRC_ENGINE_CONSTRAINT*>*> pair : m_constraintMap )
111 {
112 for( DRC_ENGINE_CONSTRAINT* constraint : *pair.second )
113 delete constraint;
114
115 delete pair.second;
116 }
117}
118
119
120static bool isKeepoutZone( const BOARD_ITEM* aItem, bool aCheckFlags )
121{
122 if( !aItem || aItem->Type() != PCB_ZONE_T )
123 return false;
124
125 const ZONE* zone = static_cast<const ZONE*>( aItem );
126
127 if( !zone->GetIsRuleArea() )
128 return false;
129
130 if( !zone->HasKeepoutParametersSet() )
131 return false;
132
133 if( aCheckFlags )
134 {
135 if( !zone->GetDoNotAllowTracks()
136 && !zone->GetDoNotAllowVias()
137 && !zone->GetDoNotAllowPads()
138 && !zone->GetDoNotAllowZoneFills()
139 && !zone->GetDoNotAllowFootprints() )
140 {
141 return false;
142 }
143 }
144
145 return true;
146}
147
148
149std::shared_ptr<DRC_RULE> DRC_ENGINE::createImplicitRule( const wxString& name,
150 const DRC_IMPLICIT_SOURCE aImplicitSource )
151{
152 std::shared_ptr<DRC_RULE> rule = std::make_shared<DRC_RULE>();
153
154 rule->m_Name = name;
155 rule->SetImplicitSource( aImplicitSource );
156
157 addRule( rule );
158
159 return rule;
160}
161
162
164{
165 BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
166 wxString expr, expr2, ncName;
167
168 // 1) global defaults
169
170 std::shared_ptr<DRC_RULE> rule = createImplicitRule( _( "board setup constraints" ),
172
173 DRC_CONSTRAINT widthConstraint( TRACK_WIDTH_CONSTRAINT );
174 widthConstraint.Value().SetMin( bds.m_TrackMinWidth );
175 rule->AddConstraint( widthConstraint );
176
177 DRC_CONSTRAINT connectionConstraint( CONNECTION_WIDTH_CONSTRAINT );
178 connectionConstraint.Value().SetMin( bds.m_MinConn );
179 rule->AddConstraint( connectionConstraint );
180
181 DRC_CONSTRAINT drillConstraint( HOLE_SIZE_CONSTRAINT );
182 drillConstraint.Value().SetMin( bds.m_MinThroughDrill );
183 rule->AddConstraint( drillConstraint );
184
185 DRC_CONSTRAINT annulusConstraint( ANNULAR_WIDTH_CONSTRAINT );
186 annulusConstraint.Value().SetMin( bds.m_ViasMinAnnularWidth );
187 rule->AddConstraint( annulusConstraint );
188
189 DRC_CONSTRAINT diameterConstraint( VIA_DIAMETER_CONSTRAINT );
190 diameterConstraint.Value().SetMin( bds.m_ViasMinSize );
191 rule->AddConstraint( diameterConstraint );
192
193 DRC_CONSTRAINT holeToHoleConstraint( HOLE_TO_HOLE_CONSTRAINT );
194 holeToHoleConstraint.Value().SetMin( bds.m_HoleToHoleMin );
195 rule->AddConstraint( holeToHoleConstraint );
196
197 rule = createImplicitRule( _( "board setup constraints zone fill strategy" ),
199 DRC_CONSTRAINT thermalSpokeCountConstraint( MIN_RESOLVED_SPOKES_CONSTRAINT );
200 thermalSpokeCountConstraint.Value().SetMin( bds.m_MinResolvedSpokes );
201 rule->AddConstraint( thermalSpokeCountConstraint );
202
203 rule = createImplicitRule( _( "board setup constraints silk" ), DRC_IMPLICIT_SOURCE::BOARD_SETUP_CONSTRAINT );
204 rule->m_LayerCondition = LSET( { F_SilkS, B_SilkS } );
205 DRC_CONSTRAINT silkClearanceConstraint( SILK_CLEARANCE_CONSTRAINT );
206 silkClearanceConstraint.Value().SetMin( bds.m_SilkClearance );
207 rule->AddConstraint( silkClearanceConstraint );
208
209 rule = createImplicitRule( _( "board setup constraints silk text height" ),
211 rule->m_LayerCondition = LSET( { F_SilkS, B_SilkS } );
212 DRC_CONSTRAINT silkTextHeightConstraint( TEXT_HEIGHT_CONSTRAINT );
213 silkTextHeightConstraint.Value().SetMin( bds.m_MinSilkTextHeight );
214 rule->AddConstraint( silkTextHeightConstraint );
215
216 rule = createImplicitRule( _( "board setup constraints silk text thickness" ),
218 rule->m_LayerCondition = LSET( { F_SilkS, B_SilkS } );
219 DRC_CONSTRAINT silkTextThicknessConstraint( TEXT_THICKNESS_CONSTRAINT );
220 silkTextThicknessConstraint.Value().SetMin( bds.m_MinSilkTextThickness );
221 rule->AddConstraint( silkTextThicknessConstraint );
222
223 rule = createImplicitRule( _( "board setup constraints hole" ), DRC_IMPLICIT_SOURCE::BOARD_SETUP_CONSTRAINT );
224 DRC_CONSTRAINT holeClearanceConstraint( HOLE_CLEARANCE_CONSTRAINT );
225 holeClearanceConstraint.Value().SetMin( bds.m_HoleClearance );
226 rule->AddConstraint( holeClearanceConstraint );
227
228 rule = createImplicitRule( _( "board setup constraints edge" ), DRC_IMPLICIT_SOURCE::BOARD_SETUP_CONSTRAINT );
229 DRC_CONSTRAINT edgeClearanceConstraint( EDGE_CLEARANCE_CONSTRAINT );
230 edgeClearanceConstraint.Value().SetMin( bds.m_CopperEdgeClearance );
231 rule->AddConstraint( edgeClearanceConstraint );
232
233 rule = createImplicitRule( _( "board setup constraints courtyard" ), DRC_IMPLICIT_SOURCE::BOARD_SETUP_CONSTRAINT );
234 DRC_CONSTRAINT courtyardClearanceConstraint( COURTYARD_CLEARANCE_CONSTRAINT );
235 holeToHoleConstraint.Value().SetMin( 0 );
236 rule->AddConstraint( courtyardClearanceConstraint );
237
238 // 2a) micro-via specific defaults (new DRC doesn't treat microvias in any special way)
239
240 std::shared_ptr<DRC_RULE> uViaRule = createImplicitRule( _( "board setup constraints micro-via" ),
242
243 uViaRule->m_Condition = new DRC_RULE_CONDITION( wxT( "A.Via_Type == 'Micro'" ) );
244
245 DRC_CONSTRAINT uViaDrillConstraint( HOLE_SIZE_CONSTRAINT );
246 uViaDrillConstraint.Value().SetMin( bds.m_MicroViasMinDrill );
247 uViaRule->AddConstraint( uViaDrillConstraint );
248
249 DRC_CONSTRAINT uViaDiameterConstraint( VIA_DIAMETER_CONSTRAINT );
250 uViaDiameterConstraint.Value().SetMin( bds.m_MicroViasMinSize );
251 uViaRule->AddConstraint( uViaDiameterConstraint );
252
253 // 2b) barcode-specific defaults
254
255 std::shared_ptr<DRC_RULE> barcodeRule = createImplicitRule( _( "barcode visual separation default" ),
257 DRC_CONSTRAINT barcodeSeparationConstraint( PHYSICAL_CLEARANCE_CONSTRAINT );
258 barcodeSeparationConstraint.Value().SetMin( GetIuScale().mmToIU( 1.0 ) );
259 barcodeRule->AddConstraint( barcodeSeparationConstraint );
260 barcodeRule->m_Condition = new DRC_RULE_CONDITION( wxT( "A.Type == 'Barcode'" ) );
261
262 // 3) per-netclass rules
263
264 std::vector<std::shared_ptr<DRC_RULE>> netclassClearanceRules;
265 std::vector<std::shared_ptr<DRC_RULE>> netclassItemSpecificRules;
266
267 auto makeNetclassRules =
268 [&]( const std::shared_ptr<NETCLASS>& nc, bool isDefault )
269 {
270 std::optional<wxString> literal = LIBEVAL::EscapeStringLiteral( nc->GetName() );
271
272 // DRC_TEST_PROVIDER_MISC reports the class, since a rule here cannot be compiled
273 if( !literal )
274 return;
275
276 ncName = *literal;
277
278 if( nc->HasClearance() )
279 {
280 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
281 netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
282 nc->GetClearanceParent()->GetHumanReadableName() );
283 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
284
285 expr = wxString::Format( wxT( "A.hasExactNetclass('%s')" ), ncName );
286 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
287 netclassRule->m_ImplicitNetclass = nc->GetName();
288 netclassClearanceRules.push_back( netclassRule );
289
291 constraint.Value().SetMin( nc->GetClearance() );
292 netclassRule->AddConstraint( constraint );
293
294 {
295 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
296 m_netclassClearances[nc->GetName()] = netclassRule.get();
297 }
298 }
299
300 if( nc->HasTrackWidth() )
301 {
302 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
303 netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
304 nc->GetTrackWidthParent()->GetHumanReadableName() );
305 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
306
307 expr = wxString::Format( wxT( "A.hasExactNetclass('%s')" ), ncName );
308 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
309 netclassRule->m_ImplicitNetclass = nc->GetName();
310 netclassClearanceRules.push_back( netclassRule );
311
313 constraint.Value().SetMin( bds.m_TrackMinWidth );
314 constraint.Value().SetOpt( nc->GetTrackWidth() );
315 netclassRule->AddConstraint( constraint );
316 }
317
318 if( nc->HasDiffPairWidth() )
319 {
320 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
321 netclassRule->m_Name = wxString::Format( _( "netclass '%s' diff pair" ),
322 nc->GetDiffPairWidthParent()->GetHumanReadableName() );
323 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
324
325 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.inDiffPair('*')" ), ncName );
326 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
327 netclassRule->m_ImplicitNetclass = nc->GetName();
328 netclassItemSpecificRules.push_back( netclassRule );
329
331 constraint.Value().SetMin( bds.m_TrackMinWidth );
332 constraint.Value().SetOpt( nc->GetDiffPairWidth() );
333 netclassRule->AddConstraint( constraint );
334 }
335
336 if( nc->HasDiffPairGap() )
337 {
338 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
339 netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
340 nc->GetDiffPairGapParent()->GetHumanReadableName() );
341 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
342
343 expr = wxString::Format( wxT( "A.hasExactNetclass('%s')" ), ncName );
344 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
345 netclassRule->m_ImplicitNetclass = nc->GetName();
346 netclassItemSpecificRules.push_back( netclassRule );
347
349 constraint.Value().SetMin( bds.m_MinClearance );
350 constraint.Value().SetOpt( nc->GetDiffPairGap() );
351 netclassRule->AddConstraint( constraint );
352
353 // A narrower diffpair gap overrides the netclass min clearance
354 if( nc->HasClearance() && nc->GetDiffPairGap() < nc->GetClearance() )
355 {
356 netclassRule = std::make_shared<DRC_RULE>();
357 netclassRule->m_Name = wxString::Format( _( "netclass '%s' diff pair" ),
358 nc->GetDiffPairGapParent()->GetHumanReadableName() );
359 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
360
361 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && AB.isCoupledDiffPair()" ), ncName );
362 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
363 netclassRule->m_ImplicitNetclass = nc->GetName();
364 netclassItemSpecificRules.push_back( netclassRule );
365
366 DRC_CONSTRAINT min_clearanceConstraint( CLEARANCE_CONSTRAINT );
367 min_clearanceConstraint.Value().SetMin( nc->GetDiffPairGap() );
368 netclassRule->AddConstraint( min_clearanceConstraint );
369
371 }
372 }
373
374 if( nc->HasViaDiameter() )
375 {
376 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
377 netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
378 nc->GetViaDiameterParent()->GetHumanReadableName() );
379 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
380
381 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Via_Type != 'Micro'" ), ncName );
382 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
383 netclassRule->m_ImplicitNetclass = nc->GetName();
384 netclassItemSpecificRules.push_back( netclassRule );
385
387 constraint.Value().SetMin( bds.m_ViasMinSize );
388 constraint.Value().SetOpt( nc->GetViaDiameter() );
389 netclassRule->AddConstraint( constraint );
390 }
391
392 if( nc->HasViaDrill() )
393 {
394 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
395 netclassRule->m_Name = wxString::Format( _( "netclass '%s'" ),
396 nc->GetViaDrillParent()->GetHumanReadableName() );
397 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
398
399 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Via_Type != 'Micro'" ), ncName );
400 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
401 netclassRule->m_ImplicitNetclass = nc->GetName();
402 netclassItemSpecificRules.push_back( netclassRule );
403
405 constraint.Value().SetMin( bds.m_MinThroughDrill );
406 constraint.Value().SetOpt( nc->GetViaDrill() );
407 netclassRule->AddConstraint( constraint );
408 }
409
410 if( nc->HasuViaDiameter() )
411 {
412 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
413 netclassRule->m_Name = wxString::Format( _( "netclass '%s' uvia" ),
414 nc->GetuViaDiameterParent()->GetHumanReadableName() );
415 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
416
417 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Via_Type == 'Micro'" ), ncName );
418 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
419 netclassRule->m_ImplicitNetclass = nc->GetName();
420 netclassItemSpecificRules.push_back( netclassRule );
421
423 constraint.Value().SetMin( bds.m_MicroViasMinSize );
424 constraint.Value().SetOpt( nc->GetuViaDiameter() );
425 netclassRule->AddConstraint( constraint );
426 }
427
428 if( nc->HasuViaDrill() )
429 {
430 std::shared_ptr<DRC_RULE> netclassRule = std::make_shared<DRC_RULE>();
431 netclassRule->m_Name = wxString::Format( _( "netclass '%s' uvia" ),
432 nc->GetuViaDrillParent()->GetHumanReadableName() );
433 netclassRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::NET_CLASS );
434
435 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Via_Type == 'Micro'" ), ncName );
436 netclassRule->m_Condition = new DRC_RULE_CONDITION( expr );
437 netclassRule->m_ImplicitNetclass = nc->GetName();
438 netclassItemSpecificRules.push_back( netclassRule );
439
441 constraint.Value().SetMin( bds.m_MicroViasMinDrill );
442 constraint.Value().SetOpt( nc->GetuViaDrill() );
443 netclassRule->AddConstraint( constraint );
444 }
445 };
446
447 m_board->SynchronizeNetsAndNetClasses( false );
448 makeNetclassRules( bds.m_NetSettings->GetDefaultNetclass(), true );
449
450 for( const auto& [name, netclass] : bds.m_NetSettings->GetNetclasses() )
451 makeNetclassRules( netclass, false );
452
453 for( const auto& [name, netclass] : bds.m_NetSettings->GetCompositeNetclasses() )
454 makeNetclassRules( netclass, false );
455
456 // The netclass clearance rules have to be sorted by min clearance so the right one fires
457 // if 'A' and 'B' belong to two different netclasses.
458 //
459 // The item-specific netclass rules are all unary, so there's no 'A' vs 'B' issue.
460
461 std::sort( netclassClearanceRules.begin(), netclassClearanceRules.end(),
462 []( const std::shared_ptr<DRC_RULE>& lhs, const std::shared_ptr<DRC_RULE>& rhs )
463 {
464 return lhs->m_Constraints[0].m_Value.Min() < rhs->m_Constraints[0].m_Value.Min();
465 } );
466
467 for( std::shared_ptr<DRC_RULE>& ncRule : netclassClearanceRules )
468 addRule( ncRule );
469
470 for( std::shared_ptr<DRC_RULE>& ncRule : netclassItemSpecificRules )
471 addRule( ncRule );
472
473 // 4) tuning profile rules
474 auto addTuningSingleRule =
475 [&]( const DELAY_PROFILE_TRACK_PROPAGATION_ENTRY& aLayerEntry, const wxString& aProfileName,
476 const wxString& aNetclassName )
477 {
478 if( aLayerEntry.GetWidth() <= 0 )
479 return;
480
481 std::shared_ptr<DRC_RULE> tuningRule = std::make_shared<DRC_RULE>();
482 tuningRule->m_Severity = bds.m_DRCSeverities[DRCE_TUNING_PROFILE_IMPLICIT_RULES];
483 tuningRule->m_Name = wxString::Format( _( "tuning profile '%s'" ), aProfileName );
484 tuningRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::TUNING_PROFILE );
485
486 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Layer == '%s'" ),
487 aNetclassName,
488 LSET::Name( aLayerEntry.GetSignalLayer() ) );
489 tuningRule->m_Condition = new DRC_RULE_CONDITION( expr );
490
492 constraint.Value().SetMin( std::max( bds.m_TrackMinWidth, aLayerEntry.GetWidth() ) );
493 constraint.Value().SetOpt( aLayerEntry.GetWidth() );
494 constraint.Value().SetMax( aLayerEntry.GetWidth() );
495 tuningRule->AddConstraint( constraint );
496
497 addRule( tuningRule );
498 };
499
500 auto addTuningDifferentialRules =
501 [&]( const DELAY_PROFILE_TRACK_PROPAGATION_ENTRY& aLayerEntry, const wxString& aProfileName,
502 const NETCLASS* aNetclass, const wxString& aNetclassName )
503 {
504 if( aLayerEntry.GetWidth() <= 0 || aLayerEntry.GetDiffPairGap() <= 0 )
505 return;
506
507 std::shared_ptr<DRC_RULE> tuningRule = std::make_shared<DRC_RULE>();
508 tuningRule->m_Severity = bds.m_DRCSeverities[DRCE_TUNING_PROFILE_IMPLICIT_RULES];
509 tuningRule->m_Name = wxString::Format( _( "tuning profile '%s'" ), aProfileName );
510 tuningRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::TUNING_PROFILE );
511
512 expr = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Layer == '%s' && A.inDiffPair('*')" ),
513 aNetclassName,
514 LSET::Name( aLayerEntry.GetSignalLayer() ) );
515 tuningRule->m_Condition = new DRC_RULE_CONDITION( expr );
516
518 constraint.Value().SetMin( std::max( bds.m_TrackMinWidth, aLayerEntry.GetWidth() ) );
519 constraint.Value().SetOpt( aLayerEntry.GetWidth() );
520 constraint.Value().SetMax( aLayerEntry.GetWidth() );
521 tuningRule->AddConstraint( constraint );
522
523 addRule( tuningRule );
524
525 std::shared_ptr<DRC_RULE> tuningRule2 = std::make_shared<DRC_RULE>();
526 tuningRule2->m_Severity = bds.m_DRCSeverities[DRCE_TUNING_PROFILE_IMPLICIT_RULES];
527 tuningRule2->m_Name = wxString::Format( _( "tuning profile '%s'" ), aProfileName );
528 tuningRule2->SetImplicitSource( DRC_IMPLICIT_SOURCE::TUNING_PROFILE );
529
530 expr2 = wxString::Format( wxT( "A.hasExactNetclass('%s') && A.Layer == '%s' && A.inDiffPair('*')" ),
531 aNetclassName,
532 LSET::Name( aLayerEntry.GetSignalLayer() ) );
533 tuningRule2->m_Condition = new DRC_RULE_CONDITION( expr2 );
534
536 constraint2.Value().SetMin( std::max( bds.m_MinClearance, aLayerEntry.GetDiffPairGap() ) );
537 constraint2.Value().SetOpt( aLayerEntry.GetDiffPairGap() );
538 constraint2.Value().SetMax( aLayerEntry.GetDiffPairGap() );
539 tuningRule2->AddConstraint( constraint2 );
540
541 addRule( tuningRule2 );
542
543 // A narrower diffpair gap overrides the netclass min clearance
544 if( aNetclass->HasClearance() && aLayerEntry.GetDiffPairGap() < aNetclass->GetClearance() )
545 {
546 std::shared_ptr<DRC_RULE> diffPairClearanceRule = std::make_shared<DRC_RULE>();
547 diffPairClearanceRule->m_Severity = bds.m_DRCSeverities[DRCE_TUNING_PROFILE_IMPLICIT_RULES];
548 diffPairClearanceRule->m_Name = wxString::Format( _( "tuning profile '%s'" ), aProfileName );
549 diffPairClearanceRule->SetImplicitSource( DRC_IMPLICIT_SOURCE::TUNING_PROFILE );
550
551 expr = wxString::Format(
552 wxT( "A.hasExactNetclass('%s') && A.Layer == '%s' && AB.isCoupledDiffPair()" ),
553 aNetclassName, LSET::Name( aLayerEntry.GetSignalLayer() ) );
554 diffPairClearanceRule->m_Condition = new DRC_RULE_CONDITION( expr );
555
556 DRC_CONSTRAINT min_clearanceConstraint( CLEARANCE_CONSTRAINT );
557 min_clearanceConstraint.Value().SetMin( aLayerEntry.GetDiffPairGap() );
558 diffPairClearanceRule->AddConstraint( min_clearanceConstraint );
559
560 addRule( diffPairClearanceRule );
561 }
562 };
563
564 if( PROJECT* project = m_board->GetProject() )
565 {
566 std::shared_ptr<TUNING_PROFILES> tuningParams = project->GetProjectFile().TuningProfileParameters();
567
568 auto addNetclassTuningProfileRules =
569 [&tuningParams, &addTuningSingleRule, &addTuningDifferentialRules]( NETCLASS* aNetclass )
570 {
571 std::optional<wxString> literal = LIBEVAL::EscapeStringLiteral( aNetclass->GetName() );
572
573 if( aNetclass->HasTuningProfile() && literal )
574 {
575 const wxString delayProfileName = aNetclass->GetTuningProfile();
576 const TUNING_PROFILE& profile = tuningParams->GetTuningProfile( delayProfileName );
577
579 {
580 if( entry.GetWidth() <= 0 )
581 continue;
582
584 addTuningSingleRule( entry, delayProfileName, *literal );
585 else
586 addTuningDifferentialRules( entry, delayProfileName, aNetclass, *literal );
587 }
588 }
589 };
590
591 addNetclassTuningProfileRules( bds.m_NetSettings->GetDefaultNetclass().get() );
592
593 for( const auto& [netclassName, netclass] : bds.m_NetSettings->GetNetclasses() )
594 addNetclassTuningProfileRules( netclass.get() );
595
596 for( const auto& [netclassName, netclass] : bds.m_NetSettings->GetCompositeNetclasses() )
597 addNetclassTuningProfileRules( netclass.get() );
598 }
599
600 // 5) keepout area rules
601 auto addKeepoutZoneRule =
602 [&]( ZONE* zone, FOOTPRINT* parentFP )
603 {
604 const wxString& name = zone->GetZoneName();
605
606 if( name.IsEmpty() )
607 {
608 if( parentFP )
609 {
610 rule = createImplicitRule(
611 wxString::Format( _( "keepout area of %s" ), DescribeRef( parentFP->GetReference() ) ),
613 }
614 else
615 {
616 rule = createImplicitRule( _( "keepout area" ), DRC_IMPLICIT_SOURCE::KEEPOUT );
617 }
618
619 }
620 else
621 {
622 if( parentFP )
623 {
624 rule = createImplicitRule( wxString::Format( _( "keepout area '%s' of %s" ), name,
625 DescribeRef( parentFP->GetReference() ) ),
627 }
628 else
629 {
630 rule = createImplicitRule( wxString::Format( _( "keepout area '%s'" ), name ),
632 }
633 }
634
635 rule->m_ImplicitItemId = zone->m_Uuid;
636 rule->m_ImplicitItem = zone;
637
638 rule->m_Condition = new DRC_RULE_CONDITION( wxString::Format( wxT( "A.intersectsKeepout('%s')" ),
639 zone->m_Uuid.AsString() ) );
640
641 rule->m_LayerCondition = zone->GetLayerSet();
642
643 int disallowFlags = 0;
644
645 if( zone->GetDoNotAllowTracks() )
646 disallowFlags |= DRC_DISALLOW_TRACKS;
647
648 if( zone->GetDoNotAllowVias() )
649 disallowFlags |= DRC_DISALLOW_VIAS;
650
651 if( zone->GetDoNotAllowPads() )
652 disallowFlags |= DRC_DISALLOW_PADS;
653
654 if( zone->GetDoNotAllowZoneFills() )
655 disallowFlags |= DRC_DISALLOW_ZONES;
656
657 if( zone->GetDoNotAllowFootprints() )
658 disallowFlags |= DRC_DISALLOW_FOOTPRINTS;
659
660 DRC_CONSTRAINT disallowConstraint( DISALLOW_CONSTRAINT );
661 disallowConstraint.m_DisallowFlags = disallowFlags;
662 rule->AddConstraint( disallowConstraint );
663 };
664
665 for( ZONE* zone : m_board->Zones() )
666 {
667 if( isKeepoutZone( zone, true ) )
668 addKeepoutZoneRule( zone, nullptr );
669 }
670
671 for( FOOTPRINT* footprint : m_board->Footprints() )
672 {
673 for( ZONE* zone : footprint->Zones() )
674 {
675 if( isKeepoutZone( zone, true ) )
676 addKeepoutZoneRule( zone, footprint );
677 }
678 }
679}
680
681
682void DRC_ENGINE::loadRules( const wxFileName& aPath )
683{
684 if( m_board && aPath.FileExists() )
685 {
686 std::vector<std::shared_ptr<DRC_RULE>> rules;
687
688 if( FILE* fp = wxFopen( aPath.GetFullPath(), wxT( "rt" ) ) )
689 {
690 FILE_LINE_READER lineReader( fp, aPath.GetFullPath() ); // Will close rules file
691 wxString rulesText;
692
693 std::function<bool( wxString* )> resolver =
694 [&]( wxString* token ) -> bool
695 {
696 if( IsComponentClassSelector( *token ) )
697 return false;
698
699 return m_board->ResolveTextVar( token, 0 );
700 };
701
702 while( char* line = lineReader.ReadLine() )
703 {
704 wxString str( line );
705 str = m_board->ConvertCrossReferencesToKIIDs( str );
706 str = ExpandTextVars( str, &resolver, INTERNAL );
707
708 rulesText << str << '\n';
709 }
710
711 DRC_RULES_PARSER parser( rulesText, aPath.GetFullPath() );
712 parser.Parse( rules, m_logReporter );
713 }
714
715 // Copy the rules into the member variable afterwards so that if Parse() throws then
716 // the possibly malformed rules won't contaminate the current ruleset.
717
718 for( std::shared_ptr<DRC_RULE>& rule : rules )
719 m_rules.push_back( rule );
720 }
721}
722
723
725{
726 if( m_logReporter )
727 m_logReporter->Report( wxT( "Compiling Rules" ) );
728
729 REPORTER error_semaphore;
730
731 for( std::shared_ptr<DRC_RULE>& rule : m_rules )
732 {
733 DRC_RULE_CONDITION* condition = nullptr;
734
735 if( rule->m_Condition && !rule->m_Condition->GetExpression().IsEmpty() )
736 {
737 condition = rule->m_Condition;
738 condition->Compile( &error_semaphore );
739 }
740
741 if( error_semaphore.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
742 THROW_PARSE_ERROR( wxT( "Parse error" ), rule->m_Name, TO_UTF8( rule->m_Condition->GetExpression() ), 0, 0 );
743
744 for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
745 {
746 auto& ruleVec = m_constraintMap[ constraint.m_Type ];
747
748 if( !ruleVec )
749 ruleVec = new std::vector<DRC_ENGINE_CONSTRAINT*>();
750
751 DRC_ENGINE_CONSTRAINT* engineConstraint = new DRC_ENGINE_CONSTRAINT;
752
753 engineConstraint->layerTest = rule->m_LayerCondition;
754 engineConstraint->condition = condition;
755 engineConstraint->constraint = constraint;
756 engineConstraint->parentRule = rule;
757
758 if( rule->IsImplicit() && constraint.m_Type == DISALLOW_CONSTRAINT
759 && rule->m_ImplicitItem && rule->m_ImplicitItem->Type() == PCB_ZONE_T )
760 {
761 // Duplicate zone UUIDs make an item-by-id lookup return the wrong same-UUID
762 // zone and defeat self-exclusion, so use the rule's own zone pointer
763 engineConstraint->implicitKeepoutZone = static_cast<ZONE*>( rule->m_ImplicitItem );
764 }
765
766 ruleVec->push_back( engineConstraint );
767 }
768 }
769
772 m_explicitConstraints.clear();
773
774 for( auto& [constraintType, ruleList] : m_constraintMap )
775 {
776 for( DRC_ENGINE_CONSTRAINT* c : *ruleList )
777 {
778 if( c->parentRule && !c->parentRule->IsImplicit() )
779 {
780 m_explicitConstraints[constraintType].push_back( c );
781
782 if( constraintType == CLEARANCE_CONSTRAINT )
784
786 && c->condition
787 && c->condition->HasGeometryDependentFunctions() )
788 {
790 }
791 }
792 }
793 }
794}
795
796
797void DRC_ENGINE::InitEngine( const std::shared_ptr<DRC_RULE>& rule )
798{
800
801 for( DRC_TEST_PROVIDER* provider : m_testProviders )
802 {
803 if( m_logReporter )
804 m_logReporter->Report( wxString::Format( wxT( "Create DRC provider: '%s'" ), provider->GetName() ) );
805
806 provider->SetDRCEngine( this );
807 }
808
809 // Existing markers may hold raw pointers to DRC_RULEs we're about to destroy.
810 // Null them out so GetSeverity() falls back to the board design settings.
811 if( m_board )
812 {
813 for( PCB_MARKER* marker : m_board->Markers() )
814 {
815 if( DRC_ITEM* drcItem = static_cast<DRC_ITEM*>( marker->GetRCItem().get() ) )
816 drcItem->SetViolatingRule( nullptr );
817 }
818 }
819
820 m_rules.clear();
821 m_rulesValid = false;
822
823 for( std::pair<DRC_CONSTRAINT_T, std::vector<DRC_ENGINE_CONSTRAINT*>*> pair : m_constraintMap )
824 {
825 for( DRC_ENGINE_CONSTRAINT* constraint : *pair.second )
826 delete constraint;
827
828 delete pair.second;
829 }
830
831 m_constraintMap.clear();
832
833 // The netclass clearance cache points at the rules just destroyed
834 {
835 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
836 m_netclassClearances.clear();
837 }
838
839 m_board->IncrementTimeStamp(); // Clear board-level caches
840
841 try
842 {
843 m_rules.push_back( rule );
844 compileRules();
845 }
846 catch( PARSE_ERROR& original_parse_error )
847 {
848 throw original_parse_error;
849 }
850
851 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
853
854 m_rulesValid = true;
855}
856
857
858void DRC_ENGINE::InitEngine( const wxFileName& aRulePath )
859{
861
862 for( DRC_TEST_PROVIDER* provider : m_testProviders )
863 {
864 if( m_logReporter )
865 m_logReporter->Report( wxString::Format( wxT( "Create DRC provider: '%s'" ), provider->GetName() ) );
866
867 provider->SetDRCEngine( this );
868 }
869
870 // Existing markers may hold raw pointers to DRC_RULEs we're about to destroy.
871 // Null them out so GetSeverity() falls back to the board design settings.
872 if( m_board )
873 {
874 for( PCB_MARKER* marker : m_board->Markers() )
875 {
876 if( DRC_ITEM* drcItem = static_cast<DRC_ITEM*>( marker->GetRCItem().get() ) )
877 drcItem->SetViolatingRule( nullptr );
878 }
879 }
880
881 m_rules.clear();
882 m_rulesValid = false;
883
884 for( std::pair<DRC_CONSTRAINT_T, std::vector<DRC_ENGINE_CONSTRAINT*>*> pair : m_constraintMap )
885 {
886 for( DRC_ENGINE_CONSTRAINT* constraint : *pair.second )
887 delete constraint;
888
889 delete pair.second;
890 }
891
892 m_constraintMap.clear();
893
894 {
895 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
896 m_ownClearanceCache.clear();
897 m_netclassClearances.clear();
898 }
899
902 m_explicitConstraints.clear();
903
904 m_board->IncrementTimeStamp(); // Clear board-level caches
905
906 try // attempt to load full set of rules (implicit + user rules)
907 {
909 loadRules( aRulePath );
910 compileRules();
911 }
912 catch( PARSE_ERROR& original_parse_error )
913 {
914 m_rules.clear();
915
916 try // try again with just our implicit rules
917 {
919 compileRules();
920 }
921 catch( PARSE_ERROR& )
922 {
923 wxFAIL_MSG( wxT( "Compiling implicit rules failed." ) );
924 }
925
926 throw original_parse_error;
927 }
928
929 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
931
932 m_rulesValid = true;
933}
934
935
936void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints,
937 BOARD_COMMIT* aCommit )
938{
939 PROF_TIMER timer;
940
941 SetUserUnits( aUnits );
942
943 m_reportAllTrackErrors = aReportAllTrackErrors;
944 m_testFootprints = aTestFootprints;
945
946 for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
947 {
948 if( m_designSettings->Ignore( ii ) )
949 m_errorLimits[ ii ] = 0;
950 else if( ii == DRCE_CLEARANCE || ii == DRCE_UNCONNECTED_ITEMS )
952 else
954 }
955
957
958 m_board->IncrementTimeStamp(); // Invalidate all caches...
959
960 DRC_CACHE_GENERATOR cacheGenerator;
961 cacheGenerator.SetDRCEngine( this );
962
963 if( !cacheGenerator.Run() ) // ... and regenerate them.
964 return;
965
966 // Recompute component classes
967 m_board->GetComponentClassManager().ForceComponentClassRecalculation();
968
969 int timestamp = m_board->GetTimeStamp();
970
971 for( DRC_TEST_PROVIDER* provider : m_testProviders )
972 {
973 if( m_logReporter )
974 m_logReporter->Report( wxString::Format( wxT( "Run DRC provider: '%s'" ), provider->GetName() ) );
975
976 PROF_TIMER providerTimer;
977
978 if( !provider->RunTests( aUnits ) )
979 break;
980
981 providerTimer.Stop();
982 wxLogTrace( traceDrcProfile, "DRC provider '%s' took %0.3f ms", provider->GetName(), providerTimer.msecs() );
983 }
984
985 timer.Stop();
986 wxLogTrace( traceDrcProfile, "DRC took %0.3f ms", timer.msecs() );
987
988 // DRC tests are multi-threaded; anything that causes us to attempt to re-generate the
989 // caches while DRC is running is problematic.
990 wxASSERT( timestamp == m_board->GetTimeStamp() );
991}
992
993
994#define REPORT( s ) { if( aReporter ) { aReporter->Report( s ); } }
995
997 PCB_LAYER_ID aLayer, REPORTER* aReporter )
998{
999 DRC_CONSTRAINT constraint = EvalRules( ZONE_CONNECTION_CONSTRAINT, a, b, aLayer, aReporter );
1000
1001 REPORT( "" )
1002 REPORT( wxString::Format( _( "Resolved zone connection type: %s." ),
1003 PrintZoneConnection( constraint.m_ZoneConnection ) ) )
1004
1006 {
1007 const PAD* pad = nullptr;
1008
1009 if( a->Type() == PCB_PAD_T )
1010 pad = static_cast<const PAD*>( a );
1011 else if( b->Type() == PCB_PAD_T )
1012 pad = static_cast<const PAD*>( b );
1013
1014 if( pad && pad->GetAttribute() == PAD_ATTRIB::PTH )
1015 {
1017 }
1018 else
1019 {
1020 REPORT( wxString::Format( _( "Pad is not a through hole pad; connection will be: %s." ),
1023 }
1024 }
1025
1026 return constraint;
1027}
1028
1029
1031 PCB_LAYER_ID aLayer, REPORTER* aReporter )
1032{
1033 /*
1034 * NOTE: all string manipulation MUST BE KEPT INSIDE the REPORT macro. It absolutely
1035 * kills performance when running bulk DRC tests (where aReporter is nullptr).
1036 */
1037
1038 const BOARD_CONNECTED_ITEM* ac = a && a->IsConnected() ? static_cast<const BOARD_CONNECTED_ITEM*>( a ) : nullptr;
1039 const BOARD_CONNECTED_ITEM* bc = b && b->IsConnected() ? static_cast<const BOARD_CONNECTED_ITEM*>( b ) : nullptr;
1040 const NETCLASS* acNetclass = ac ? ac->GetEffectiveNetClass() : nullptr;
1041 const NETCLASS* bcNetclass = bc ? bc->GetEffectiveNetClass() : nullptr;
1042
1043 bool a_is_non_copper = a && ( !a->IsOnCopperLayer() || isKeepoutZone( a, false ) );
1044 bool b_is_non_copper = b && ( !b->IsOnCopperLayer() || isKeepoutZone( b, false ) );
1045
1046 const PAD* pad = nullptr;
1047 const ZONE* zone = nullptr;
1048 const FOOTPRINT* parentFootprint = nullptr;
1049
1050 if( aConstraintType == ZONE_CONNECTION_CONSTRAINT
1051 || aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
1052 || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT
1053 || aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT
1054 || aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT
1055 || aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT )
1056 {
1057 if( a && a->Type() == PCB_PAD_T )
1058 pad = static_cast<const PAD*>( a );
1059 else if( a && a->Type() == PCB_ZONE_T )
1060 zone = static_cast<const ZONE*>( a );
1061
1062 if( b && b->Type() == PCB_PAD_T )
1063 pad = static_cast<const PAD*>( b );
1064 else if( b && b->Type() == PCB_ZONE_T )
1065 zone = static_cast<const ZONE*>( b );
1066
1067 if( pad )
1068 parentFootprint = pad->GetParentFootprint();
1069 }
1070
1071 DRC_CONSTRAINT constraint;
1072 constraint.m_Type = aConstraintType;
1073
1074 auto applyConstraint =
1075 [&]( const DRC_ENGINE_CONSTRAINT* c )
1076 {
1077 DRC_RULE* rule = c->constraint.GetParentRule();
1078 DRC_RULE* minRule = constraint.GetMinRule();
1079 DRC_RULE* optRule = constraint.GetOptRule();
1080 DRC_RULE* maxRule = constraint.GetMaxRule();
1081
1082 if( c->constraint.m_Value.HasMin() )
1083 {
1084 if( c->parentRule && c->parentRule->IsImplicit() )
1085 constraint.m_ImplicitMin = true;
1086 else
1087 constraint.m_ImplicitMin = false;
1088
1089 constraint.m_Value.SetMin( c->constraint.m_Value.Min() );
1090 minRule = rule;
1091 }
1092
1093 if( c->constraint.m_Value.HasOpt() )
1094 {
1095 constraint.m_Value.SetOpt( c->constraint.m_Value.Opt() );
1096 optRule = rule;
1097 }
1098
1099 if( c->constraint.m_Value.HasMax() )
1100 {
1101 constraint.m_Value.SetMax( c->constraint.m_Value.Max() );
1102 maxRule = rule;
1103 }
1104
1105 switch( c->constraint.m_Type )
1106 {
1114 if( constraint.m_Value.Min() > MAXIMUM_CLEARANCE )
1115 constraint.m_Value.SetMin( MAXIMUM_CLEARANCE );
1116
1117 break;
1118
1119 default:
1120 break;
1121 }
1122
1123 // While the expectation would be to OR the disallow flags, we've already
1124 // masked them down to aItem's type -- so we're really only looking for a
1125 // boolean here.
1126 constraint.m_DisallowFlags = c->constraint.m_DisallowFlags;
1127
1128 constraint.m_ZoneConnection = c->constraint.m_ZoneConnection;
1129
1130 constraint.SetParentRule( rule );
1131 constraint.SetValueRules( minRule, optRule, maxRule );
1132
1133 constraint.SetOptionsFromOther( c->constraint );
1134 };
1135
1136 const FOOTPRINT* footprints[2] = { a ? a->GetParentFootprint() : nullptr,
1137 b ? b->GetParentFootprint() : nullptr };
1138
1139 // Handle Footprint net ties, which will zero out the clearance for footprint objects
1140 if( aConstraintType == CLEARANCE_CONSTRAINT // Only zero clearance, other constraints still apply
1141 && ( ( ( !ac ) ^ ( !bc ) )// Only apply to cases where we are comparing a connected item to a non-connected item
1142 // and not both connected. Connected items of different nets still need to be checked
1143 // for their standard clearance value
1144 || ( ( footprints[0] == footprints[1] ) // Unless both items are in the same footprint
1145 && footprints[0] ) ) // And that footprint exists
1146 && !a_is_non_copper // Also, both elements need to be on copper layers
1147 && !b_is_non_copper )
1148 {
1149 const BOARD_ITEM* child_items[2] = {a, b};
1150
1151 // These are the items being compared against, so the order is reversed
1152 const BOARD_CONNECTED_ITEM* alt_items[2] = {bc, ac};
1153
1154 for( int ii = 0; ii < 2; ++ii )
1155 {
1156 // We need both a footprint item and a connected item to check for a net tie
1157 if( !footprints[ii] || !alt_items[ii] )
1158 continue;
1159
1160 const std::set<int>& netcodes = footprints[ii]->GetNetTieCache( child_items[ii] );
1161
1162 auto it = netcodes.find( alt_items[ii]->GetNetCode() );
1163
1164 if( it != netcodes.end() )
1165 {
1166 REPORT( "" )
1167 REPORT( wxString::Format( _( "Net tie on %s; clearance: 0." ),
1168 EscapeHTML( footprints[ii]->GetItemDescription( this, true ) ) ) )
1169
1170 constraint.SetName( _( "net tie" ) );
1171 constraint.m_Value.SetMin( 0 );
1172 return constraint;
1173 }
1174 }
1175 }
1176
1177 // Local overrides take precedence over everything *except* board min clearance
1178 if( aConstraintType == CLEARANCE_CONSTRAINT || aConstraintType == HOLE_CLEARANCE_CONSTRAINT )
1179 {
1180 int override_val = 0;
1181 std::optional<int> overrideA;
1182 std::optional<int> overrideB;
1183
1184 if( ac && !b_is_non_copper )
1185 overrideA = ac->GetClearanceOverrides( nullptr );
1186
1187 if( bc && !a_is_non_copper )
1188 overrideB = bc->GetClearanceOverrides( nullptr );
1189
1190 if( overrideA.has_value() || overrideB.has_value() )
1191 {
1192 wxString msg;
1193
1194 if( overrideA.has_value() )
1195 {
1196 REPORT( "" )
1197 REPORT( wxString::Format( _( "Local override on %s; clearance: %s." ),
1198 EscapeHTML( a->GetItemDescription( this, true ) ),
1199 MessageTextFromValue( overrideA.value() ) ) )
1200
1201 override_val = ac->GetClearanceOverrides( &msg ).value();
1202 }
1203
1204 if( overrideB.has_value() )
1205 {
1206 REPORT( "" )
1207 REPORT( wxString::Format( _( "Local override on %s; clearance: %s." ),
1208 EscapeHTML( b->GetItemDescription( this, true ) ),
1209 MessageTextFromValue( overrideB.value() ) ) )
1210
1211 if( overrideB > override_val )
1212 override_val = bc->GetClearanceOverrides( &msg ).value();
1213 }
1214
1215 if( override_val )
1216 {
1217 if( aConstraintType == CLEARANCE_CONSTRAINT )
1218 {
1219 if( override_val < m_designSettings->m_MinClearance )
1220 {
1221 override_val = m_designSettings->m_MinClearance;
1222 msg = _( "board minimum" );
1223
1224 REPORT( "" )
1225 REPORT( wxString::Format( _( "Board minimum clearance: %s." ),
1226 MessageTextFromValue( override_val ) ) )
1227 }
1228 }
1229 else
1230 {
1231 if( override_val < m_designSettings->m_HoleClearance )
1232 {
1233 override_val = m_designSettings->m_HoleClearance;
1234 msg = _( "board minimum hole" );
1235
1236 REPORT( "" )
1237 REPORT( wxString::Format( _( "Board minimum hole clearance: %s." ),
1238 MessageTextFromValue( override_val ) ) )
1239 }
1240 }
1241
1242 constraint.SetName( msg );
1243 constraint.m_Value.SetMin( override_val );
1244 return constraint;
1245 }
1246 }
1247 }
1248 else if( aConstraintType == ZONE_CONNECTION_CONSTRAINT )
1249 {
1250 if( pad && pad->GetLocalZoneConnection() != ZONE_CONNECTION::INHERITED )
1251 {
1252 wxString msg;
1253 ZONE_CONNECTION override = pad->GetZoneConnectionOverrides( &msg );
1254
1255 REPORT( "" )
1256 REPORT( wxString::Format( _( "Local override on %s; zone connection: %s." ),
1257 EscapeHTML( pad->GetItemDescription( this, true ) ),
1258 PrintZoneConnection( override ) ) )
1259
1260 constraint.SetName( msg );
1261 constraint.m_ZoneConnection = override;
1262 return constraint;
1263 }
1264 }
1265 else if( aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT )
1266 {
1267 if( pad && pad->GetLocalThermalGapOverride( nullptr ) > 0 )
1268 {
1269 wxString msg;
1270 int gap_override = pad->GetLocalThermalGapOverride( &msg );
1271
1272 REPORT( "" )
1273 REPORT( wxString::Format( _( "Local override on %s; thermal relief gap: %s." ),
1274 EscapeHTML( pad->GetItemDescription( this, true ) ),
1275 MessageTextFromValue( gap_override ) ) )
1276
1277 constraint.SetName( msg );
1278 constraint.m_Value.SetMin( gap_override );
1279 return constraint;
1280 }
1281 }
1282 else if( aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT )
1283 {
1284 if( pad && pad->GetLocalSpokeWidthOverride( nullptr ) > 0 )
1285 {
1286 wxString msg;
1287 int spoke_override = pad->GetLocalSpokeWidthOverride( &msg );
1288
1289 REPORT( "" )
1290 REPORT( wxString::Format( _( "Local override on %s; thermal spoke width: %s." ),
1291 EscapeHTML( pad->GetItemDescription( this, true ) ),
1292 MessageTextFromValue( spoke_override ) ) )
1293
1294 if( zone && zone->GetMinThickness() > spoke_override )
1295 {
1296 spoke_override = zone->GetMinThickness();
1297
1298 REPORT( "" )
1299 REPORT( wxString::Format( _( "%s min thickness: %s." ),
1300 EscapeHTML( zone->GetItemDescription( this, true ) ),
1301 MessageTextFromValue( spoke_override ) ) )
1302 }
1303
1304 constraint.SetName( msg );
1305 constraint.m_Value.SetMin( spoke_override );
1306 return constraint;
1307 }
1308 }
1309 else if( aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT )
1310 {
1311 std::optional<int> override;
1312 const BOARD_ITEM* overrideItem = a;
1313
1314 if( pad )
1315 override = pad->GetLocalSolderMaskMargin();
1316 else if( a->Type() == PCB_SHAPE_T )
1317 override = static_cast<const PCB_SHAPE*>( a )->GetLocalSolderMaskMargin();
1318 else if( const PCB_TRACK* track = dynamic_cast<const PCB_TRACK*>( a ) )
1319 override = track->GetLocalSolderMaskMargin();
1320
1321 if( !override.has_value() && pad )
1322 {
1323 if( FOOTPRINT* overrideFootprint = pad->GetParentFootprint() )
1324 {
1325 override = overrideFootprint->GetLocalSolderMaskMargin();
1326 overrideItem = overrideFootprint;
1327 }
1328 }
1329
1330 if( override )
1331 {
1332 REPORT( "" )
1333 REPORT( wxString::Format( _( "Local override on %s; solder mask expansion: %s." ),
1334 EscapeHTML( overrideItem->GetItemDescription( this, true ) ),
1335 MessageTextFromValue( override.value() ) ) )
1336
1337 constraint.m_Value.SetOpt( override.value() );
1338 return constraint;
1339 }
1340 }
1341 else if( aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT )
1342 {
1343 std::optional<int> override;
1344 const BOARD_ITEM* overrideItem = a;
1345
1346 if( pad )
1347 override = pad->GetLocalSolderPasteMargin();
1348
1349 if( !override.has_value() && pad )
1350 {
1351 if( FOOTPRINT* overrideFootprint = pad->GetParentFootprint() )
1352 {
1353 override = overrideFootprint->GetLocalSolderPasteMargin();
1354 overrideItem = overrideFootprint;
1355 }
1356 }
1357
1358 if( override )
1359 {
1360 REPORT( "" )
1361 REPORT( wxString::Format( _( "Local override on %s; solder paste absolute clearance: %s." ),
1362 EscapeHTML( overrideItem->GetItemDescription( this, true ) ),
1363 MessageTextFromValue( override.value() ) ) )
1364
1365 constraint.m_Value.SetOpt( override.value_or( 0 ) );
1366 return constraint;
1367 }
1368 }
1369 else if( aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT )
1370 {
1371 std::optional<double> overrideRatio;
1372 const BOARD_ITEM* overrideItem = a;
1373
1374 if( pad )
1375 overrideRatio = pad->GetLocalSolderPasteMarginRatio();
1376
1377 if( !overrideRatio.has_value() && pad )
1378 {
1379 if( FOOTPRINT* overrideFootprint = pad->GetParentFootprint() )
1380 {
1381 overrideRatio = overrideFootprint->GetLocalSolderPasteMarginRatio();
1382 overrideItem = overrideFootprint;
1383 }
1384 }
1385
1386 if( overrideRatio )
1387 {
1388 REPORT( "" )
1389 REPORT( wxString::Format( _( "Local override on %s; solder paste relative clearance: %s." ),
1390 EscapeHTML( overrideItem->GetItemDescription( this, true ) ),
1391 MessageTextFromValue( overrideRatio.value() * 100.0 ) ) )
1392
1393 constraint.m_Value.SetOpt( KiROUND( overrideRatio.value_or( 0 ) * 1000 ) );
1394 return constraint;
1395 }
1396 }
1397
1398 auto testAssertion =
1399 [&]( const DRC_ENGINE_CONSTRAINT* c )
1400 {
1401 REPORT( wxString::Format( _( "Checking assertion '%s'." ),
1402 EscapeHTML( c->constraint.m_Test->GetExpression() ) ) )
1403
1404 if( c->constraint.m_Test->EvaluateFor( a, b, c->constraint.m_Type, aLayer, aReporter ) )
1405 REPORT( _( "Assertion passed." ) )
1406 else
1407 REPORT( EscapeHTML( _( "--> Assertion failed. <--" ) ) )
1408 };
1409
1410 // Within a single EvalRules call a, b, aLayer and the constraint type are fixed, so a rule
1411 // condition's result depends only on its expression. Large rule sets contain many rules with
1412 // identical conditions; caching by expression collapses those duplicates into one evaluation
1413 // each and avoids re-running the (allocation-heavy) expression VM. Only used on the bulk path
1414 // with no reporter, since the reporter path has reporting side effects.
1415 std::unordered_map<wxString, bool> conditionCache;
1416
1417 auto reportConstraintHeader =
1418 [&]( const DRC_ENGINE_CONSTRAINT* c )
1419 {
1420 REPORT( "" )
1421
1422 bool implicit = c->parentRule && c->parentRule->IsImplicit();
1423
1424 switch( c->constraint.m_Type )
1425 {
1433 REPORT( wxString::Format( _( "Checking %s clearance: %s." ),
1434 EscapeHTML( c->constraint.GetName() ),
1435 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1436 break;
1438 REPORT( wxString::Format( _( "Checking %s creepage: %s." ),
1439 EscapeHTML( c->constraint.GetName() ),
1440 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1441 break;
1443 REPORT( wxString::Format( _( "Checking %s max uncoupled length: %s." ),
1444 EscapeHTML( c->constraint.GetName() ),
1445 MessageTextFromValue( c->constraint.m_Value.Max() ) ) )
1446 break;
1447
1448 case SKEW_CONSTRAINT:
1449 REPORT( wxString::Format( _( "Checking %s max skew: %s." ),
1450 EscapeHTML( c->constraint.GetName() ),
1451 MessageTextFromValue( c->constraint.m_Value.Max() ) ) )
1452 break;
1453
1455 REPORT( wxString::Format( _( "Checking %s gap: %s." ),
1456 EscapeHTML( c->constraint.GetName() ),
1457 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1458 break;
1459
1461 REPORT( wxString::Format( _( "Checking %s thermal spoke width: %s." ),
1462 EscapeHTML( c->constraint.GetName() ),
1463 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1464 break;
1465
1467 REPORT( wxString::Format( _( "Checking %s solder mask expansion: %s." ),
1468 EscapeHTML( c->constraint.GetName() ),
1469 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1470 break;
1471
1473 REPORT( wxString::Format( _( "Checking %s solder paste absolute clearance: %s." ),
1474 EscapeHTML( c->constraint.GetName() ),
1475 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1476 break;
1477
1479 REPORT( wxString::Format( _( "Checking %s solder paste relative clearance: %s." ),
1480 EscapeHTML( c->constraint.GetName() ),
1481 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1482 break;
1483
1485 REPORT( wxString::Format( _( "Checking %s min spoke count: %s." ),
1486 EscapeHTML( c->constraint.GetName() ),
1487 MessageTextFromUnscaledValue( c->constraint.m_Value.Min() ) ) )
1488 break;
1489
1491 REPORT( wxString::Format( _( "Checking %s max microvia stack depth: %s." ),
1492 EscapeHTML( c->constraint.GetName() ),
1493 MessageTextFromUnscaledValue( c->constraint.m_Value.Max() ) ) )
1494 break;
1495
1497 REPORT( wxString::Format( _( "Checking %s max hole aspect ratio: %.3f." ),
1498 EscapeHTML( c->constraint.GetName() ),
1499 c->constraint.m_Value.Max() / 1000.0 ) )
1500 break;
1501
1503 REPORT( wxString::Format( _( "Checking %s zone connection: %s." ),
1504 EscapeHTML( c->constraint.GetName() ),
1505 PrintZoneConnection( c->constraint.m_ZoneConnection ) ) )
1506 break;
1507
1515 case LENGTH_CONSTRAINT:
1521 {
1522 if( implicit )
1523 {
1524 switch( c->constraint.m_Type )
1525 {
1527 if( c->constraint.m_Value.HasOpt() )
1528 {
1529 REPORT( wxString::Format( _( "Checking %s track width: opt %s." ),
1530 EscapeHTML( c->constraint.GetName() ),
1531 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1532 }
1533 else if( c->constraint.m_Value.HasMin() )
1534 {
1535 REPORT( wxString::Format( _( "Checking %s track width: min %s." ),
1536 EscapeHTML( c->constraint.GetName() ),
1537 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1538 }
1539
1540 break;
1541
1543 REPORT( wxString::Format( _( "Checking %s annular width: min %s." ),
1544 EscapeHTML( c->constraint.GetName() ),
1545 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1546 break;
1547
1549 if( c->constraint.m_Value.HasOpt() )
1550 {
1551 REPORT( wxString::Format( _( "Checking %s via diameter: opt %s." ),
1552 EscapeHTML( c->constraint.GetName() ),
1553 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1554 }
1555 else if( c->constraint.m_Value.HasMin() )
1556 {
1557 REPORT( wxString::Format( _( "Checking %s via diameter: min %s." ),
1558 EscapeHTML( c->constraint.GetName() ),
1559 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1560 }
1561 break;
1562
1564 if( c->constraint.m_Value.HasOpt() )
1565 {
1566 REPORT( wxString::Format( _( "Checking %s hole size: opt %s." ),
1567 EscapeHTML( c->constraint.GetName() ),
1568 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1569 }
1570 else if( c->constraint.m_Value.HasMin() )
1571 {
1572 REPORT( wxString::Format( _( "Checking %s hole size: min %s." ),
1573 EscapeHTML( c->constraint.GetName() ),
1574 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1575 }
1576
1577 break;
1578
1582 REPORT( wxString::Format( _( "Checking %s: min %s." ),
1583 EscapeHTML( c->constraint.GetName() ),
1584 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1585 break;
1586
1588 if( c->constraint.m_Value.HasOpt() )
1589 {
1590 REPORT( wxString::Format( _( "Checking %s diff pair gap: opt %s." ),
1591 EscapeHTML( c->constraint.GetName() ),
1592 MessageTextFromValue( c->constraint.m_Value.Opt() ) ) )
1593 }
1594 else if( c->constraint.m_Value.HasMin() )
1595 {
1596 REPORT( wxString::Format( _( "Checking %s clearance: min %s." ),
1597 EscapeHTML( c->constraint.GetName() ),
1598 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1599 }
1600
1601 break;
1602
1604 REPORT( wxString::Format( _( "Checking %s hole to hole: min %s." ),
1605 EscapeHTML( c->constraint.GetName() ),
1606 MessageTextFromValue( c->constraint.m_Value.Min() ) ) )
1607 break;
1608
1609 default:
1610 REPORT( wxString::Format( _( "Checking %s." ),
1611 EscapeHTML( c->constraint.GetName() ) ) )
1612 }
1613 }
1614 else
1615 {
1616 REPORT( wxString::Format( _( "Checking %s: min %s; opt %s; max %s." ),
1617 EscapeHTML( c->constraint.GetName() ),
1618 c->constraint.m_Value.HasMin()
1619 ? MessageTextFromValue( c->constraint.m_Value.Min() )
1620 : wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" ),
1621 c->constraint.m_Value.HasOpt()
1622 ? MessageTextFromValue( c->constraint.m_Value.Opt() )
1623 : wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" ),
1624 c->constraint.m_Value.HasMax()
1625 ? MessageTextFromValue( c->constraint.m_Value.Max() )
1626 : wxT( "<i>" ) + _( "undefined" ) + wxT( "</i>" ) ) )
1627 }
1628 break;
1629 }
1630
1631 default:
1632 REPORT( wxString::Format( _( "Checking %s." ), EscapeHTML( c->constraint.GetName() ) ) )
1633 }
1634 };
1635
1636 auto checkCondition =
1637 [&]( const DRC_ENGINE_CONSTRAINT* c, REPORTER* r )
1638 {
1639 bool condMatched = false;
1640
1641 if( r )
1642 {
1643 condMatched = c->condition->EvaluateFor( a, b, c->constraint.m_Type, aLayer, r );
1644 }
1645 else
1646 {
1647 const wxString& expr = c->condition->GetExpression();
1648 auto it = conditionCache.find( expr );
1649
1650 if( it != conditionCache.end() )
1651 {
1652 condMatched = it->second;
1653 }
1654 else
1655 {
1656 condMatched = c->condition->EvaluateFor( a, b, c->constraint.m_Type, aLayer, r );
1657 conditionCache[expr] = condMatched;
1658 }
1659 }
1660
1661 return condMatched;
1662 };
1663
1664 auto processConstraint =
1665 [&]( const DRC_ENGINE_CONSTRAINT* c )
1666 {
1667 bool implicit = c->parentRule && c->parentRule->IsImplicit();
1668
1670 {
1672 {
1673 if( a_is_non_copper || b_is_non_copper )
1674 {
1675 reportConstraintHeader( c );
1676 REPORT( _( "Netclass clearances apply only between copper items." ) )
1677 return;
1678 }
1679 }
1680
1681 // The condition starts with A.hasExactNetclass( tag ) and is tried both ways round, so
1682 // it cannot match unless one of the items is in that netclass
1683 const wxString& netclassName = c->parentRule->m_ImplicitNetclass;
1684
1685 if( !netclassName.empty()
1686 && !( acNetclass && acNetclass->NameEquals( netclassName ) )
1687 && !( bcNetclass && bcNetclass->NameEquals( netclassName ) ) )
1688 {
1689 return;
1690 }
1691
1692 if( !checkCondition( c, nullptr ) )
1693 return;
1694 }
1695
1696 reportConstraintHeader( c );
1697
1699 {
1700 if( a_is_non_copper )
1701 {
1702 REPORT( wxString::Format( _( "%s contains no copper. Constraint ignored." ),
1703 EscapeHTML( a->GetItemDescription( this, true ) ) ) )
1704 return;
1705 }
1706 else if( b_is_non_copper )
1707 {
1708 REPORT( wxString::Format( _( "%s contains no copper. Constraint ignored." ),
1709 EscapeHTML( b->GetItemDescription( this, true ) ) ) )
1710 return;
1711 }
1712 }
1713 else if( c->constraint.m_Type == DISALLOW_CONSTRAINT )
1714 {
1715 // A footprint's own keepout never applies to that footprint; decide
1716 // ownership from the rule's zone so a hijacked UUID cache can't defeat it
1718 {
1719 REPORT( _( "Keepout belongs to the footprint under test; constraint ignored." ) )
1720 return;
1721 }
1722
1723 int mask;
1724
1725 if( a->GetFlags() & HOLE_PROXY )
1726 {
1727 mask = DRC_DISALLOW_HOLES;
1728 }
1729 else if( a->Type() == PCB_VIA_T )
1730 {
1731 const PCB_VIA* via = static_cast<const PCB_VIA*>( a );
1732
1733 if( via->IsMicroVia() )
1735 else if( via->IsBlindVia() )
1737 else if( via->IsBuriedVia() )
1739 else
1741 }
1742 else
1743 {
1744 switch( a->Type() )
1745 {
1746 case PCB_TRACE_T: mask = DRC_DISALLOW_TRACKS; break;
1747 case PCB_ARC_T: mask = DRC_DISALLOW_TRACKS; break;
1748 case PCB_PAD_T: mask = DRC_DISALLOW_PADS; break;
1749 case PCB_FOOTPRINT_T: mask = DRC_DISALLOW_FOOTPRINTS; break;
1750 case PCB_SHAPE_T: mask = DRC_DISALLOW_GRAPHICS; break;
1751 case PCB_BARCODE_T: mask = DRC_DISALLOW_GRAPHICS; break;
1752 case PCB_FIELD_T: mask = DRC_DISALLOW_TEXTS; break;
1753 case PCB_TEXT_T: mask = DRC_DISALLOW_TEXTS; break;
1754 case PCB_TEXTBOX_T: mask = DRC_DISALLOW_TEXTS; break;
1755 case PCB_TABLE_T:
1756 case PCB_DRILL_CHART_T: mask = DRC_DISALLOW_TEXTS; break;
1757
1758 case PCB_ZONE_T:
1759 // Treat teardrop areas as tracks for DRC purposes
1760 if( static_cast<const ZONE*>( a )->IsTeardropArea() )
1761 mask = DRC_DISALLOW_TRACKS;
1762 else
1763 mask = DRC_DISALLOW_ZONES;
1764
1765 break;
1766
1767 case PCB_LOCATE_HOLE_T: mask = DRC_DISALLOW_HOLES; break;
1768 default: mask = 0; break;
1769 }
1770 }
1771
1772 if( ( c->constraint.m_DisallowFlags & mask ) == 0 )
1773 {
1774 if( implicit )
1775 REPORT( _( "Keepout constraint not met." ) )
1776 else
1777 REPORT( _( "Disallow constraint not met." ) )
1778
1779 return;
1780 }
1781
1782 LSET itemLayers = a->GetLayerSet();
1783
1784 if( a->Type() == PCB_FOOTPRINT_T )
1785 {
1786 const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( a );
1787
1788 if( !footprint->GetCourtyard( F_CrtYd ).IsEmpty() )
1789 itemLayers |= LSET::FrontMask();
1790
1791 if( !footprint->GetCourtyard( B_CrtYd ).IsEmpty() )
1792 itemLayers |= LSET::BackMask();
1793 }
1794
1795 if( !( c->layerTest & itemLayers ).any() )
1796 {
1797 if( implicit )
1798 {
1799 REPORT( _( "Keepout layer(s) not matched." ) )
1800 }
1801 else if( c->parentRule )
1802 {
1803 REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
1805 }
1806 else
1807 {
1808 REPORT( _( "Rule layer not matched; rule ignored." ) )
1809 }
1810
1811 return;
1812 }
1813 }
1814
1815 if( ( IsPcbLayer( aLayer ) && !c->layerTest.test( aLayer ) )
1816 || ( m_board->GetEnabledLayers() & c->layerTest ).count() == 0 )
1817 {
1818 if( implicit )
1819 {
1820 REPORT( _( "Constraint layer not matched." ) )
1821 }
1822 else if( c->parentRule )
1823 {
1824 REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
1826 }
1827 else
1828 {
1829 REPORT( _( "Rule layer not matched; rule ignored." ) )
1830 }
1831 }
1833 && ( !a->HasHole() || !b->HasHole() ) )
1834 {
1835 // Hole-to-hole only applies between two mechanical holes; this covers both
1836 // round drilled holes and milled (oval) slots, but not a hole paired with
1837 // a non-hole item.
1838 const BOARD_ITEM* nonHole = a->HasHole() ? b : a;
1839
1840 REPORT( wxString::Format( _( "%s does not have a hole; rule ignored." ),
1841 nonHole->GetItemDescription( this, true ) ) )
1842 }
1843 else if( !c->condition || c->condition->GetExpression().IsEmpty() )
1844 {
1845 if( aReporter )
1846 {
1847 if( implicit )
1848 {
1849 REPORT( _( "Unconditional constraint applied." ) )
1850 }
1851 else if( constraint.m_Type == ASSERTION_CONSTRAINT )
1852 {
1853 REPORT( _( "Unconditional rule applied." ) )
1854 testAssertion( c );
1855 }
1856 else
1857 {
1858 REPORT( _( "Unconditional rule applied; overrides previous constraints." ) )
1859 }
1860 }
1861
1862 applyConstraint( c );
1863 }
1864 else
1865 {
1866 // For implicit keepout rules with a pre-resolved zone pointer, skip the
1867 // expensive expression evaluation when the item doesn't overlap the
1868 // zone's bounding box. This avoids UUID string parsing and cache lock
1869 // contention for the vast majority of item-keepout pairs.
1870 if( c->implicitKeepoutZone && !aReporter )
1871 {
1872 BOX2I itemBBox = a->GetBoundingBox();
1873 BOX2I zoneBBox = c->implicitKeepoutZone->GetBoundingBox();
1874
1875 if( !itemBBox.Intersects( zoneBBox ) )
1876 return;
1877 }
1878
1879 if( implicit )
1880 {
1881 // Don't report on implicit rule conditions; they're synthetic.
1882 }
1883 else
1884 {
1885 REPORT( wxString::Format( _( "Checking rule condition '%s'." ),
1887 }
1888
1889 bool condMatched;
1890
1892 condMatched = true;
1893 else
1894 condMatched = checkCondition( c, aReporter );
1895
1896 if( condMatched )
1897 {
1898 if( aReporter )
1899 {
1900 if( implicit )
1901 {
1902 REPORT( _( "Constraint applied." ) )
1903 }
1904 else if( constraint.m_Type == ASSERTION_CONSTRAINT )
1905 {
1906 REPORT( _( "Rule applied." ) )
1907 testAssertion( c );
1908 }
1909 else
1910 {
1911 REPORT( _( "Rule applied; overrides previous constraints." ) )
1912 }
1913 }
1914
1915 applyConstraint( c );
1916 }
1917 else
1918 {
1919 REPORT( implicit ? _( "Membership not satisfied; constraint ignored." )
1920 : _( "Condition not satisfied; rule ignored." ) )
1921 }
1922 }
1923 };
1924
1925 // Fast-path for netclass clearance when no explicit or diff pair override rules exist
1926 if( aConstraintType == CLEARANCE_CONSTRAINT
1929 && !aReporter
1930 && !a_is_non_copper
1931 && ( !b || !b_is_non_copper ) )
1932 {
1933 DRC_RULE* netclassRule = nullptr;
1934 int clearance = 0;
1935
1936 // Get netclass names outside of the lock to minimize critical section
1937 wxString ncNameA;
1938 wxString ncNameB;
1939
1940 if( ac )
1941 {
1942 NETCLASS* ncA = ac->GetEffectiveNetClass();
1943
1944 if( ncA )
1945 ncNameA = ncA->GetName();
1946 }
1947
1948 if( bc )
1949 {
1950 NETCLASS* ncB = bc->GetEffectiveNetClass();
1951
1952 if( ncB )
1953 ncNameB = ncB->GetName();
1954 }
1955
1956 // Look up clearances with shared lock protection
1957 if( !ncNameA.empty() || !ncNameB.empty() )
1958 {
1959 std::shared_lock<std::shared_mutex> readLock( m_clearanceCacheMutex );
1960
1961 if( !ncNameA.empty() )
1962 {
1963 auto it = m_netclassClearances.find( ncNameA );
1964
1965 if( it != m_netclassClearances.end() )
1966 {
1967 netclassRule = it->second;
1968 clearance = netclassRule->m_Constraints[0].m_Value.Min();
1969 }
1970 }
1971
1972 if( !ncNameB.empty() )
1973 {
1974 auto it = m_netclassClearances.find( ncNameB );
1975
1976 if( it != m_netclassClearances.end() && it->second->m_Constraints[0].m_Value.Min() > clearance )
1977 {
1978 netclassRule = it->second;
1979 clearance = netclassRule->m_Constraints[0].m_Value.Min();
1980 }
1981 }
1982 }
1983
1984 if( clearance > 0 )
1985 {
1986 constraint.m_Value.SetMin( clearance );
1987 constraint.m_ImplicitMin = true;
1988 constraint.SetParentRule( netclassRule );
1989 }
1990 }
1991 else
1992 {
1993 auto it = m_constraintMap.find( aConstraintType );
1994
1995 if( it != m_constraintMap.end() )
1996 {
1997 for( DRC_ENGINE_CONSTRAINT* rule : *it->second )
1998 processConstraint( rule );
1999 }
2000
2001 // DIFF_PAIR_GAP_CONSTRAINT must also respect CLEARANCE_CONSTRAINTs when called with two items
2002 if( aConstraintType == DIFF_PAIR_GAP_CONSTRAINT && b != nullptr )
2003 {
2004 DRC_CONSTRAINT clearanceConstraint = EvalRules( CLEARANCE_CONSTRAINT, a, b, aLayer, nullptr );
2005
2006 REPORT( "" )
2007 REPORT( wxString::Format( _( "Resolved minimum clearance: %s." ),
2008 MessageTextFromValue( clearanceConstraint.m_Value.Min() ) ) )
2009
2010 if( constraint.m_Value.Min() < clearanceConstraint.m_Value.Min() )
2011 {
2012 constraint.m_Value.SetMin( clearanceConstraint.m_Value.Min() );
2013 constraint.SetValueRules( clearanceConstraint.GetMinRule(), constraint.GetOptRule(),
2014 constraint.GetMaxRule() );
2015 }
2016
2017 return constraint;
2018 }
2019 }
2020
2021 if( constraint.GetParentRule() && !constraint.GetParentRule()->IsImplicit() )
2022 return constraint;
2023
2024 // Special case for properties which can be inherited from parent footprints. We've already
2025 // checked for local overrides, and there were no rules targetting the item itself, so we know
2026 // we're inheriting and need to see if there are any rules targetting the parent footprint.
2027 if( pad && parentFootprint && ( aConstraintType == ZONE_CONNECTION_CONSTRAINT
2028 || aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT
2029 || aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT
2030 || aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT
2031 || aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT
2032 || aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT ) )
2033 {
2034 REPORT( "" )
2035 REPORT( wxString::Format( _( "Inheriting from parent: %s." ),
2036 EscapeHTML( parentFootprint->GetItemDescription( this, true ) ) ) )
2037
2038 if( a == pad )
2039 a = parentFootprint;
2040 else
2041 b = parentFootprint;
2042
2043 // a/b just changed, so conditions evaluated against the pad must not be reused for the
2044 // parent footprint.
2045 conditionCache.clear();
2046
2047 auto it = m_constraintMap.find( aConstraintType );
2048
2049 if( it != m_constraintMap.end() )
2050 {
2051 for( DRC_ENGINE_CONSTRAINT* rule : *it->second )
2052 processConstraint( rule );
2053
2054 if( constraint.GetParentRule() && !constraint.GetParentRule()->IsImplicit() )
2055 return constraint;
2056 }
2057
2058 // Found nothing again? Return the defaults.
2059 if( aConstraintType == SOLDER_MASK_EXPANSION_CONSTRAINT )
2060 {
2061 constraint.SetParentRule( nullptr );
2062 constraint.SetName( _( "board setup" ) );
2063 constraint.m_Value.SetOpt( m_designSettings->m_SolderMaskExpansion );
2064 return constraint;
2065 }
2066 else if( aConstraintType == SOLDER_PASTE_ABS_MARGIN_CONSTRAINT )
2067 {
2068 constraint.SetParentRule( nullptr );
2069 constraint.SetName( _( "board setup" ) );
2070 constraint.m_Value.SetOpt( m_designSettings->m_SolderPasteMargin );
2071 return constraint;
2072 }
2073 else if( aConstraintType == SOLDER_PASTE_REL_MARGIN_CONSTRAINT )
2074 {
2075 constraint.SetParentRule( nullptr );
2076 constraint.SetName( _( "board setup" ) );
2077 constraint.m_Value.SetOpt( KiROUND( m_designSettings->m_SolderPasteMarginRatio * 1000 ) );
2078 return constraint;
2079 }
2080 }
2081
2082 // Unfortunately implicit rules don't work for local clearances (such as zones) because
2083 // they have to be max'ed with netclass values (which are already implicit rules), and our
2084 // rule selection paradigm is "winner takes all".
2085 if( aConstraintType == CLEARANCE_CONSTRAINT )
2086 {
2087 int global = constraint.m_Value.Min();
2088 int clearance = global;
2089 bool needBlankLine = true;
2090
2091 if( ac && ac->GetLocalClearance().has_value() )
2092 {
2093 int localA = ac->GetLocalClearance().value();
2094
2095 if( needBlankLine )
2096 {
2097 REPORT( "" )
2098 needBlankLine = false;
2099 }
2100
2101 REPORT( wxString::Format( _( "Local clearance on %s: %s." ),
2102 EscapeHTML( a->GetItemDescription( this, true ) ),
2103 MessageTextFromValue( localA ) ) )
2104
2105 if( localA > clearance )
2106 {
2107 wxString msg;
2108 clearance = ac->GetLocalClearance( &msg ).value();
2109 constraint.SetParentRule( nullptr );
2110 constraint.SetName( msg );
2111 constraint.m_Value.SetMin( clearance );
2112 }
2113 }
2114
2115 if( bc && bc->GetLocalClearance().has_value() )
2116 {
2117 int localB = bc->GetLocalClearance().value();
2118
2119 if( needBlankLine )
2120 {
2121 REPORT( "" )
2122 needBlankLine = false;
2123 }
2124
2125 REPORT( wxString::Format( _( "Local clearance on %s: %s." ),
2126 EscapeHTML( b->GetItemDescription( this, true ) ),
2127 MessageTextFromValue( localB ) ) )
2128
2129 if( localB > clearance )
2130 {
2131 wxString msg;
2132 clearance = bc->GetLocalClearance( &msg ).value();
2133 constraint.SetParentRule( nullptr );
2134 constraint.SetName( msg );
2135 constraint.m_Value.SetMin( clearance );
2136 }
2137 }
2138
2139 if( !a_is_non_copper && !b_is_non_copper )
2140 {
2141 if( needBlankLine )
2142 {
2143 REPORT( "" )
2144 needBlankLine = false;
2145 }
2146
2147 REPORT( wxString::Format( _( "Board minimum clearance: %s." ),
2148 MessageTextFromValue( m_designSettings->m_MinClearance ) ) )
2149
2150 if( clearance < m_designSettings->m_MinClearance )
2151 {
2152 constraint.SetParentRule( nullptr );
2153 constraint.SetName( _( "board minimum" ) );
2154 constraint.m_Value.SetMin( m_designSettings->m_MinClearance );
2155 }
2156 }
2157
2158 return constraint;
2159 }
2160 else if( aConstraintType == ZONE_CONNECTION_CONSTRAINT )
2161 {
2162 if( pad && parentFootprint )
2163 {
2164 ZONE_CONNECTION local = parentFootprint->GetLocalZoneConnection();
2165
2166 if( local != ZONE_CONNECTION::INHERITED )
2167 {
2168 REPORT( "" )
2169 REPORT( wxString::Format( _( "%s zone connection: %s." ),
2170 EscapeHTML( parentFootprint->GetItemDescription( this, true ) ),
2171 PrintZoneConnection( local ) ) )
2172
2173 constraint.SetParentRule( nullptr );
2174 constraint.SetName( _( "footprint" ) );
2175 constraint.m_ZoneConnection = local;
2176 return constraint;
2177 }
2178 }
2179
2180 if( zone )
2181 {
2182 ZONE_CONNECTION local = zone->GetPadConnection();
2183
2184 REPORT( "" )
2185 REPORT( wxString::Format( _( "%s pad connection: %s." ),
2186 EscapeHTML( zone->GetItemDescription( this, true ) ),
2187 PrintZoneConnection( local ) ) )
2188
2189 constraint.SetParentRule( nullptr );
2190 constraint.SetName( _( "zone" ) );
2191 constraint.m_ZoneConnection = local;
2192 return constraint;
2193 }
2194 }
2195 else if( aConstraintType == THERMAL_RELIEF_GAP_CONSTRAINT )
2196 {
2197 if( zone )
2198 {
2199 int local = zone->GetThermalReliefGap();
2200
2201 REPORT( "" )
2202 REPORT( wxString::Format( _( "%s thermal relief gap: %s." ),
2203 EscapeHTML( zone->GetItemDescription( this, true ) ),
2204 MessageTextFromValue( local ) ) )
2205
2206 constraint.SetParentRule( nullptr );
2207 constraint.SetName( _( "zone" ) );
2208 constraint.m_Value.SetMin( local );
2209 return constraint;
2210 }
2211 }
2212 else if( aConstraintType == THERMAL_SPOKE_WIDTH_CONSTRAINT )
2213 {
2214 if( zone )
2215 {
2216 int local = zone->GetThermalReliefSpokeWidth();
2217
2218 REPORT( "" )
2219 REPORT( wxString::Format( _( "%s thermal spoke width: %s." ),
2220 EscapeHTML( zone->GetItemDescription( this, true ) ),
2221 MessageTextFromValue( local ) ) )
2222
2223 constraint.SetParentRule( nullptr );
2224 constraint.SetName( _( "zone" ) );
2225 constraint.m_Value.SetMin( local );
2226 return constraint;
2227 }
2228 }
2229
2230 if( !constraint.GetParentRule() )
2231 {
2232 constraint.m_Type = NULL_CONSTRAINT;
2233 constraint.m_DisallowFlags = 0;
2234 }
2235
2236 return constraint;
2237}
2238
2239
2241 PCB_LAYER_ID aLayer )
2242{
2245
2246 c = EvalRules( CLEARANCE_CONSTRAINT, a, b, aLayer );
2247
2248 if( c.m_Value.HasMin() )
2249 result.clearance = c.m_Value.Min();
2250
2251 c = EvalRules( HOLE_CLEARANCE_CONSTRAINT, a, b, aLayer );
2252
2253 if( c.m_Value.HasMin() )
2254 result.holeClearance = c.m_Value.Min();
2255
2256 c = EvalRules( HOLE_TO_HOLE_CONSTRAINT, a, b, aLayer );
2257
2258 if( c.m_Value.HasMin() )
2259 result.holeToHole = c.m_Value.Min();
2260
2261 c = EvalRules( EDGE_CLEARANCE_CONSTRAINT, a, b, aLayer );
2262
2263 if( c.m_Value.HasMin() )
2264 result.edgeClearance = c.m_Value.Min();
2265
2266 c = EvalRules( PHYSICAL_CLEARANCE_CONSTRAINT, a, b, aLayer );
2267
2268 if( c.m_Value.HasMin() )
2269 result.physicalClearance = c.m_Value.Min();
2270
2271 return result;
2272}
2273
2274
2276 std::function<void( const DRC_CONSTRAINT* )> aFailureHandler,
2277 REPORTER* aReporter )
2278{
2279 /*
2280 * NOTE: all string manipulation MUST BE KEPT INSIDE the REPORT macro. It absolutely
2281 * kills performance when running bulk DRC tests (where aReporter is nullptr).
2282 */
2283
2284 auto testAssertion =
2285 [&]( const DRC_ENGINE_CONSTRAINT* c )
2286 {
2287 REPORT( wxString::Format( _( "Checking rule assertion '%s'." ),
2288 EscapeHTML( c->constraint.m_Test->GetExpression() ) ) )
2289
2290 if( c->constraint.m_Test->EvaluateFor( a, nullptr, c->constraint.m_Type,
2291 a->GetLayer(), aReporter ) )
2292 {
2293 REPORT( _( "Assertion passed." ) )
2294 }
2295 else
2296 {
2297 REPORT( EscapeHTML( _( "--> Assertion failed. <--" ) ) )
2298 aFailureHandler( &c->constraint );
2299 }
2300 };
2301
2302 auto processConstraint =
2303 [&]( const DRC_ENGINE_CONSTRAINT* c )
2304 {
2305 REPORT( "" )
2306 REPORT( wxString::Format( _( "Checking %s." ), c->constraint.GetName() ) )
2307
2308 if( !( a->GetLayerSet() & c->layerTest ).any() )
2309 {
2310 REPORT( wxString::Format( _( "Rule layer '%s' not matched; rule ignored." ),
2311 EscapeHTML( c->parentRule->m_LayerSource ) ) )
2312 }
2313
2314 if( !c->condition || c->condition->GetExpression().IsEmpty() )
2315 {
2316 REPORT( _( "Unconditional rule applied." ) )
2317 testAssertion( c );
2318 }
2319 else
2320 {
2321 REPORT( wxString::Format( _( "Checking rule condition '%s'." ),
2322 EscapeHTML( c->condition->GetExpression() ) ) )
2323
2324 if( c->condition->EvaluateFor( a, nullptr, c->constraint.m_Type,
2325 a->GetLayer(), aReporter ) )
2326 {
2327 REPORT( _( "Rule applied." ) )
2328 testAssertion( c );
2329 }
2330 else
2331 {
2332 REPORT( _( "Condition not satisfied; rule ignored." ) )
2333 }
2334 }
2335 };
2336
2337 auto it = m_constraintMap.find( ASSERTION_CONSTRAINT );
2338
2339 if( it != m_constraintMap.end() )
2340 {
2341 for( int ii = 0; ii < (int) it->second->size(); ++ii )
2342 processConstraint( it->second->at( ii ) );
2343 }
2344}
2345
2346
2347#undef REPORT
2348
2349
2351{
2352 assert( error_code >= 0 && error_code <= DRCE_LAST );
2353 std::lock_guard<std::mutex> lock( m_errorLimitsMutex );
2354 return m_errorLimits[ error_code ] <= 0;
2355}
2356
2357
2358int DRC_ENGINE::GetErrorLimit( int error_code )
2359{
2360 assert( error_code >= 0 && error_code <= DRCE_LAST );
2361 std::lock_guard<std::mutex> lock( m_errorLimitsMutex );
2362 return std::max( 0, m_errorLimits[ error_code ] );
2363}
2364
2365
2366void DRC_ENGINE::ReportViolation( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos,
2367 int aMarkerLayer, const std::function<void( PCB_MARKER* )>& aPathGenerator )
2368{
2369 {
2370 std::lock_guard<std::mutex> lock( m_errorLimitsMutex );
2371
2372 // Providers pre-check IsErrorLimitExceeded(), but that check is racy when items
2373 // are processed in parallel, letting many threads slip past the cap and overshoot
2374 // it. Enforce the per-code limit atomically here so the reported count is exact
2375 // and reproducible regardless of worker scheduling.
2376 if( m_errorLimits[ aItem->GetErrorCode() ] <= 0 )
2377 return;
2378
2379 m_errorLimits[ aItem->GetErrorCode() ] -= 1;
2380 }
2381
2382 if( m_violationHandler )
2383 {
2384 static std::mutex handlerLock;
2385 std::lock_guard<std::mutex> guard( handlerLock );
2386 m_violationHandler( aItem, aPos, aMarkerLayer, aPathGenerator );
2387 }
2388
2389 if( m_logReporter )
2390 {
2391 wxString msg = wxString::Format( wxT( "Test '%s': %s (code %d)" ),
2392 aItem->GetViolatingTest()->GetName(),
2393 aItem->GetErrorMessage( false ),
2394 aItem->GetErrorCode() );
2395
2396 DRC_RULE* rule = aItem->GetViolatingRule();
2397
2398 if( rule )
2399 msg += wxString::Format( wxT( ", violating rule: '%s'" ), rule->m_Name );
2400
2401 m_logReporter->Report( msg );
2402
2403 wxString violatingItemsStr = wxT( "Violating items: " );
2404
2405 m_logReporter->Report( wxString::Format( wxT( " |- violating position (%d, %d)" ),
2406 aPos.x,
2407 aPos.y ) );
2408 }
2409}
2410
2411
2413{
2414 if( !m_progressReporter )
2415 return true;
2416
2417 return m_progressReporter->KeepRefreshing( aWait );
2418}
2419
2420
2422{
2423 if( m_progressReporter )
2424 m_progressReporter->AdvanceProgress();
2425}
2426
2427
2429{
2430 if( m_progressReporter )
2431 m_progressReporter->SetMaxProgress( aSize );
2432}
2433
2434
2435bool DRC_ENGINE::ReportProgress( double aProgress )
2436{
2437 if( !m_progressReporter )
2438 return true;
2439
2440 m_progressReporter->SetCurrentProgress( aProgress );
2441 return m_progressReporter->KeepRefreshing( false );
2442}
2443
2444
2445bool DRC_ENGINE::ReportPhase( const wxString& aMessage )
2446{
2447 if( !m_progressReporter )
2448 return true;
2449
2450 m_progressReporter->AdvancePhase( aMessage );
2451 return m_progressReporter->KeepRefreshing( false );
2452}
2453
2454
2456{
2457 return m_progressReporter && m_progressReporter->IsCancelled();
2458}
2459
2460
2462{
2463 auto it = m_constraintMap.find( constraintID );
2464 return it != m_constraintMap.end() && !it->second->empty();
2465}
2466
2467
2469 bool aUnconditionalOnly )
2470{
2471 int worst = 0;
2472 auto it = m_constraintMap.find( aConstraintId );
2473
2474 if( it != m_constraintMap.end() )
2475 {
2476 for( DRC_ENGINE_CONSTRAINT* c : *it->second )
2477 {
2478 if( aUnconditionalOnly && c->condition )
2479 continue;
2480
2481 int current = c->constraint.GetValue().Min();
2482
2483 if( current > worst )
2484 {
2485 worst = current;
2486 aConstraint = c->constraint;
2487 }
2488 }
2489 }
2490
2491 return worst > 0;
2492}
2493
2494
2496{
2497 auto it = m_constraintMap.find( aConstraintId );
2498
2499 if( it != m_constraintMap.end() )
2500 {
2501 for( DRC_ENGINE_CONSTRAINT* c : *it->second )
2502 {
2503 if( c->condition && c->parentRule && !c->parentRule->IsImplicit() )
2504 return true;
2505 }
2506 }
2507
2508 return false;
2509}
2510
2511
2513{
2514 auto it = m_constraintMap.find( aConstraintId );
2515
2516 if( it != m_constraintMap.end() )
2517 {
2518 for( DRC_ENGINE_CONSTRAINT* c : *it->second )
2519 {
2520 if( c->parentRule && !c->parentRule->IsImplicit() )
2521 return true;
2522 }
2523 }
2524
2525 return false;
2526}
2527
2528
2534
2535
2537{
2538 std::set<int> distinctMinimums;
2539 auto it = m_constraintMap.find( aConstraintId );
2540
2541 if( it != m_constraintMap.end() )
2542 {
2543 for( DRC_ENGINE_CONSTRAINT* c : *it->second )
2544 distinctMinimums.emplace( c->constraint.GetValue().Min() );
2545 }
2546
2547 return distinctMinimums;
2548}
2549
2550
2551// fixme: move two functions below to pcbcommon?
2552int DRC_ENGINE::MatchDpSuffix( const wxString& aNetName, wxString& aComplementNet, wxString& aBaseDpName )
2553{
2554 int rv = 0;
2555 int count = 0;
2556
2557 for( auto it = aNetName.rbegin(); it != aNetName.rend() && rv == 0; ++it, ++count )
2558 {
2559 int ch = *it;
2560
2561 if( ( ch >= '0' && ch <= '9' ) || ch == '_' )
2562 {
2563 continue;
2564 }
2565 else if( ch == '+' )
2566 {
2567 aComplementNet = wxT( "-" );
2568 rv = 1;
2569 }
2570 else if( ch == '-' )
2571 {
2572 aComplementNet = wxT( "+" );
2573 rv = -1;
2574 }
2575 else if( ch == 'N' )
2576 {
2577 aComplementNet = wxT( "P" );
2578 rv = -1;
2579 }
2580 else if ( ch == 'P' )
2581 {
2582 aComplementNet = wxT( "N" );
2583 rv = 1;
2584 }
2585 else
2586 {
2587 break;
2588 }
2589 }
2590
2591 if( rv != 0 && count >= 1 )
2592 {
2593 aBaseDpName = aNetName.Left( aNetName.Length() - count );
2594 aComplementNet = wxString( aBaseDpName ) << aComplementNet << aNetName.Right( count - 1 );
2595 }
2596
2597 return rv;
2598}
2599
2600
2601bool DRC_ENGINE::IsNetADiffPair( BOARD* aBoard, const NETINFO_ITEM* aNet, int& aNetP, int& aNetN )
2602{
2603 const wxString& refName = aNet->GetNetname();
2604 wxString dummy, coupledNetName;
2605
2606 if( int polarity = MatchDpSuffix( refName, coupledNetName, dummy ) )
2607 {
2608 NETINFO_ITEM* net = aBoard->FindNet( coupledNetName );
2609
2610 if( !net )
2611 return false;
2612
2613 if( polarity > 0 )
2614 {
2615 aNetP = aNet->GetNetCode();
2616 aNetN = net->GetNetCode();
2617 }
2618 else
2619 {
2620 aNetP = net->GetNetCode();
2621 aNetN = aNet->GetNetCode();
2622 }
2623
2624 return true;
2625 }
2626
2627 return false;
2628}
2629
2630
2635bool DRC_ENGINE::IsNetTieExclusion( int aTrackNetCode, PCB_LAYER_ID aTrackLayer,
2636 const VECTOR2I& aCollisionPos, BOARD_ITEM* aCollidingItem )
2637{
2638 FOOTPRINT* parentFootprint = aCollidingItem->GetParentFootprint();
2639
2640 if( parentFootprint && parentFootprint->IsNetTie() )
2641 {
2643 std::map<wxString, int> padToNetTieGroupMap = parentFootprint->MapPadNumbersToNetTieGroups();
2644
2645 for( PAD* pad : parentFootprint->Pads() )
2646 {
2647 if( padToNetTieGroupMap[ pad->GetNumber() ] >= 0 && aTrackNetCode == pad->GetNetCode() )
2648 {
2649 if( pad->GetEffectiveShape( aTrackLayer )->Collide( aCollisionPos, epsilon ) )
2650 return true;
2651 }
2652 }
2653 }
2654
2655 return false;
2656}
2657
2658
2659namespace
2660{
2661enum class SHOWMATCH_DOMAIN
2662{
2663 ALL_ITEMS,
2664 COPPER_ITEMS,
2665 EDGE_ITEMS,
2666 FOOTPRINTS,
2667 HOLE_ITEMS,
2668 MASK_EXPANSION_ITEMS,
2669 MASK_ITEMS,
2670 PADS,
2671 PADS_AND_VIAS,
2672 PASTE_ITEMS,
2673 ROUTING_ITEMS,
2674 SILK_ITEMS,
2675 SILK_TARGET_ITEMS,
2676 TEXT_ITEMS,
2677 VIAS
2678};
2679
2680
2681struct SHOWMATCH_DOMAIN_SPEC
2682{
2683 SHOWMATCH_DOMAIN primary;
2684 SHOWMATCH_DOMAIN secondary = SHOWMATCH_DOMAIN::ALL_ITEMS;
2685 bool hasSecondary = false;
2686 bool secondaryUnary = false;
2687};
2688
2689
2690bool isShowMatchSkippable( const BOARD_ITEM* aItem )
2691{
2692 switch( aItem->Type() )
2693 {
2694 case PCB_NETINFO_T:
2695 case PCB_GENERATOR_T:
2696 case PCB_GROUP_T: return true;
2697
2698 default: return false;
2699 }
2700}
2701
2702
2703bool matchesShowMatchDomain( const BOARD_ITEM* aItem, SHOWMATCH_DOMAIN aDomain )
2704{
2705 if( !aItem || isShowMatchSkippable( aItem ) )
2706 return false;
2707
2708 switch( aDomain )
2709 {
2710 case SHOWMATCH_DOMAIN::ALL_ITEMS: return true;
2711
2712 case SHOWMATCH_DOMAIN::COPPER_ITEMS: return aItem->IsOnCopperLayer();
2713
2714 case SHOWMATCH_DOMAIN::EDGE_ITEMS:
2715 if( aItem->IsOnLayer( Edge_Cuts ) || aItem->IsOnLayer( Margin ) )
2716 return true;
2717
2718 if( aItem->Type() == PCB_PAD_T )
2719 {
2720 const PAD* pad = static_cast<const PAD*>( aItem );
2721 return pad->GetAttribute() == PAD_ATTRIB::NPTH && pad->HasHole();
2722 }
2723
2724 return false;
2725
2726 case SHOWMATCH_DOMAIN::FOOTPRINTS: return aItem->Type() == PCB_FOOTPRINT_T;
2727
2728 case SHOWMATCH_DOMAIN::HOLE_ITEMS: return aItem->HasHole();
2729
2730 case SHOWMATCH_DOMAIN::MASK_EXPANSION_ITEMS:
2731 switch( aItem->Type() )
2732 {
2733 case PCB_PAD_T:
2734 case PCB_TRACE_T:
2735 case PCB_ARC_T:
2736 case PCB_VIA_T:
2737 case PCB_SHAPE_T:
2738 case PCB_ZONE_T: return true;
2739
2740 default: return false;
2741 }
2742
2743 case SHOWMATCH_DOMAIN::MASK_ITEMS: return aItem->IsOnLayer( F_Mask ) || aItem->IsOnLayer( B_Mask );
2744
2745 case SHOWMATCH_DOMAIN::PADS: return aItem->Type() == PCB_PAD_T;
2746
2747 case SHOWMATCH_DOMAIN::PADS_AND_VIAS: return aItem->Type() == PCB_PAD_T || aItem->Type() == PCB_VIA_T;
2748
2749 case SHOWMATCH_DOMAIN::PASTE_ITEMS: return aItem->IsOnLayer( F_Paste ) || aItem->IsOnLayer( B_Paste );
2750
2751 case SHOWMATCH_DOMAIN::ROUTING_ITEMS:
2752 switch( aItem->Type() )
2753 {
2754 case PCB_TRACE_T:
2755 case PCB_ARC_T:
2756 case PCB_VIA_T:
2757 case PCB_PAD_T: return true;
2758
2759 default: return false;
2760 }
2761
2762 case SHOWMATCH_DOMAIN::SILK_ITEMS: return aItem->IsOnLayer( F_SilkS ) || aItem->IsOnLayer( B_SilkS );
2763
2764 case SHOWMATCH_DOMAIN::SILK_TARGET_ITEMS:
2765 return aItem->IsOnLayer( F_SilkS ) || aItem->IsOnLayer( B_SilkS ) || aItem->IsOnLayer( F_Mask )
2766 || aItem->IsOnLayer( B_Mask ) || aItem->IsOnLayer( F_Adhes ) || aItem->IsOnLayer( B_Adhes )
2767 || aItem->IsOnLayer( F_Paste ) || aItem->IsOnLayer( B_Paste ) || aItem->IsOnLayer( F_CrtYd )
2768 || aItem->IsOnLayer( B_CrtYd ) || aItem->IsOnLayer( F_Fab ) || aItem->IsOnLayer( B_Fab )
2769 || aItem->IsOnCopperLayer() || aItem->IsOnLayer( Edge_Cuts ) || aItem->IsOnLayer( Margin );
2770
2771 case SHOWMATCH_DOMAIN::TEXT_ITEMS:
2772 return aItem->Type() == PCB_FIELD_T || aItem->Type() == PCB_TEXT_T || aItem->Type() == PCB_TEXTBOX_T
2773 || aItem->Type() == PCB_TABLECELL_T || BaseType( aItem->Type() ) == PCB_DIMENSION_T;
2774
2775 case SHOWMATCH_DOMAIN::VIAS: return aItem->Type() == PCB_VIA_T;
2776 }
2777
2778 return false;
2779}
2780
2781
2782SHOWMATCH_DOMAIN_SPEC getShowMatchDomainSpec( DRC_CONSTRAINT_T aConstraint )
2783{
2784 switch( aConstraint )
2785 {
2786 case CLEARANCE_CONSTRAINT: return { SHOWMATCH_DOMAIN::COPPER_ITEMS };
2787
2788 case EDGE_CLEARANCE_CONSTRAINT: return { SHOWMATCH_DOMAIN::COPPER_ITEMS, SHOWMATCH_DOMAIN::EDGE_ITEMS, true, true };
2789
2790 case HOLE_CLEARANCE_CONSTRAINT: return { SHOWMATCH_DOMAIN::HOLE_ITEMS, SHOWMATCH_DOMAIN::ALL_ITEMS, true, false };
2791
2792 case HOLE_TO_HOLE_CONSTRAINT: return { SHOWMATCH_DOMAIN::HOLE_ITEMS };
2793
2794 case COURTYARD_CLEARANCE_CONSTRAINT: return { SHOWMATCH_DOMAIN::FOOTPRINTS };
2795
2798 case CREEPAGE_CONSTRAINT: return { SHOWMATCH_DOMAIN::ALL_ITEMS };
2799
2801 return { SHOWMATCH_DOMAIN::SILK_ITEMS, SHOWMATCH_DOMAIN::SILK_TARGET_ITEMS, true, false };
2802
2803 case SOLDER_MASK_SLIVER_CONSTRAINT: return { SHOWMATCH_DOMAIN::MASK_ITEMS };
2804
2811 case LENGTH_CONSTRAINT:
2812 case SKEW_CONSTRAINT: return { SHOWMATCH_DOMAIN::ROUTING_ITEMS };
2813
2817 case HOLE_ASPECT_RATIO_CONSTRAINT: return { SHOWMATCH_DOMAIN::HOLE_ITEMS };
2818
2819 case HOLE_SIZE_CONSTRAINT: return { SHOWMATCH_DOMAIN::HOLE_ITEMS };
2820
2821 case ANNULAR_WIDTH_CONSTRAINT: return { SHOWMATCH_DOMAIN::PADS_AND_VIAS };
2822
2823 case MIN_RESOLVED_SPOKES_CONSTRAINT: return { SHOWMATCH_DOMAIN::PADS };
2824
2826 case TEXT_THICKNESS_CONSTRAINT: return { SHOWMATCH_DOMAIN::TEXT_ITEMS };
2827
2828 case SOLDER_MASK_EXPANSION_CONSTRAINT: return { SHOWMATCH_DOMAIN::MASK_EXPANSION_ITEMS };
2829
2831 case SOLDER_PASTE_REL_MARGIN_CONSTRAINT: return { SHOWMATCH_DOMAIN::PASTE_ITEMS };
2832
2835 default: return { SHOWMATCH_DOMAIN::ALL_ITEMS };
2836 }
2837}
2838
2839
2840std::vector<BOARD_ITEM*> collectShowMatchCandidates( BOARD* aBoard, SHOWMATCH_DOMAIN aDomain )
2841{
2842 std::vector<BOARD_ITEM*> items;
2843
2844 if( !aBoard )
2845 return items;
2846
2847 aBoard->RunOnChildren(
2848 [&]( BOARD_ITEM* aItem )
2849 {
2850 if( matchesShowMatchDomain( aItem, aDomain ) )
2851 items.push_back( aItem );
2852 },
2854
2855 return items;
2856}
2857
2858
2859std::vector<PCB_LAYER_ID> getShowMatchLayers( const BOARD_ITEM* aItem )
2860{
2861 std::vector<PCB_LAYER_ID> layers;
2862
2863 switch( aItem->Type() )
2864 {
2865 case PCB_PAD_T: layers = static_cast<const PAD*>( aItem )->Padstack().UniqueLayers(); break;
2866
2867 case PCB_VIA_T: layers = static_cast<const PCB_VIA*>( aItem )->Padstack().UniqueLayers(); break;
2868
2869 default:
2870 for( PCB_LAYER_ID layer : aItem->GetLayerSet() )
2871 layers.push_back( layer );
2872
2873 break;
2874 }
2875
2876 if( layers.empty() )
2877 layers.push_back( UNDEFINED_LAYER );
2878
2879 return layers;
2880}
2881
2882
2883bool ruleMatchesUnary( const DRC_RULE& aRule, const BOARD_ITEM* aItem, DRC_CONSTRAINT_T aConstraint,
2884 REPORTER* aReporter )
2885{
2886 bool testedLayer = false;
2887
2888 for( PCB_LAYER_ID layer : getShowMatchLayers( aItem ) )
2889 {
2890 if( layer != UNDEFINED_LAYER && !aRule.m_LayerCondition.test( layer ) )
2891 continue;
2892
2893 testedLayer = true;
2894
2895 if( !aRule.m_Condition
2896 || aRule.m_Condition->EvaluateFor( aItem, nullptr, static_cast<int>( aConstraint ), layer, aReporter ) )
2897 {
2898 return true;
2899 }
2900 }
2901
2902 if( !testedLayer && aItem->GetLayerSet().none() )
2903 {
2904 return !aRule.m_Condition
2905 || aRule.m_Condition->EvaluateFor( aItem, nullptr, static_cast<int>( aConstraint ), UNDEFINED_LAYER,
2906 aReporter );
2907 }
2908
2909 return false;
2910}
2911
2912
2913std::vector<PCB_LAYER_ID> getShowMatchPairLayers( const DRC_RULE& aRule, const BOARD_ITEM* aItemA,
2914 const BOARD_ITEM* aItemB, DRC_CONSTRAINT_T aConstraint )
2915{
2916 std::vector<PCB_LAYER_ID> layers;
2917 std::set<int> seenLayers;
2918
2919 auto addLayer =
2920 [&]( PCB_LAYER_ID aLayer )
2921 {
2922 if( aLayer != UNDEFINED_LAYER && !aRule.m_LayerCondition.test( aLayer ) )
2923 return;
2924
2925 if( seenLayers.insert( static_cast<int>( aLayer ) ).second )
2926 layers.push_back( aLayer );
2927 };
2928
2929 switch( aConstraint )
2930 {
2932 for( PCB_LAYER_ID layer : getShowMatchLayers( aItemA ) )
2933 addLayer( layer );
2934
2935 break;
2936
2937 case COURTYARD_CLEARANCE_CONSTRAINT: addLayer( UNDEFINED_LAYER ); break;
2938
2939 default:
2940 for( PCB_LAYER_ID layer : getShowMatchLayers( aItemA ) )
2941 addLayer( layer );
2942
2943 for( PCB_LAYER_ID layer : getShowMatchLayers( aItemB ) )
2944 addLayer( layer );
2945
2946 break;
2947 }
2948
2949 if( layers.empty() )
2950 layers.push_back( UNDEFINED_LAYER );
2951
2952 return layers;
2953}
2954
2955
2956bool ruleMatchesPair( const DRC_RULE& aRule, const BOARD_ITEM* aItemA, const BOARD_ITEM* aItemB,
2957 DRC_CONSTRAINT_T aConstraint, REPORTER* aReporter )
2958{
2959 for( PCB_LAYER_ID layer : getShowMatchPairLayers( aRule, aItemA, aItemB, aConstraint ) )
2960 {
2961 if( !aRule.m_Condition
2962 || aRule.m_Condition->EvaluateFor( aItemA, aItemB, static_cast<int>( aConstraint ), layer, aReporter ) )
2963 {
2964 return true;
2965 }
2966 }
2967
2968 return false;
2969}
2970} // namespace
2971
2972
2974{
2975 for( DRC_TEST_PROVIDER* prov : m_testProviders )
2976 {
2977 if( name == prov->GetName() )
2978 return prov;
2979 }
2980
2981 return nullptr;
2982}
2983
2984
2985std::vector<BOARD_ITEM*> DRC_ENGINE::GetItemsMatchingCondition( const wxString& aExpression,
2986 DRC_CONSTRAINT_T aConstraint,
2987 REPORTER* aReporter )
2988{
2989 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] engine enter: expr='%s', constraint=%d" ),
2990 aExpression, (int) aConstraint );
2991
2992 std::vector<BOARD_ITEM*> matches;
2993
2994 if( !m_board )
2995 return matches;
2996
2997 DRC_RULE_CONDITION condition( aExpression );
2998
2999 if( !condition.Compile( aReporter ? aReporter : m_logReporter ) )
3000 {
3001 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] engine: compile failed" ) );
3002 return matches;
3003 }
3004
3005 // Rebuild the from-to cache so that fromTo() expressions can be evaluated.
3006 // This cache requires explicit rebuilding before use since it depends on the full
3007 // connectivity graph being available.
3008 if( std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity() )
3009 {
3010 if( std::shared_ptr<FROM_TO_CACHE> ftCache = connectivity->GetFromToCache() )
3011 ftCache->Rebuild( m_board );
3012 }
3013
3014 size_t totalItems = 0;
3015 size_t skippedItems = 0;
3016 size_t noLayerItems = 0;
3017 size_t checkedItems = 0;
3018
3019 const auto applyConditionToItemFunc =
3020 [&]( BOARD_ITEM* aItem )
3021 {
3022 totalItems++;
3023
3024 // Skip items that don't have visible geometry or can't be meaningfully matched
3025 switch( aItem->Type() )
3026 {
3027 case PCB_NETINFO_T:
3028 case PCB_GENERATOR_T:
3029 case PCB_GROUP_T:
3030 case PCB_CONSTRAINT_T:
3031 skippedItems++;
3032 return;
3033
3034 default:
3035 break;
3036 }
3037
3038 LSET itemLayers = aItem->GetLayerSet();
3039
3040 if( itemLayers.none() )
3041 {
3042 noLayerItems++;
3043 return;
3044 }
3045
3046 checkedItems++;
3047 bool matched = false;
3048
3049 for( PCB_LAYER_ID layer : itemLayers )
3050 {
3051 if( condition.EvaluateFor( aItem, nullptr, static_cast<int>( aConstraint ), layer,
3052 aReporter ? aReporter : m_logReporter ) )
3053 {
3054 matches.push_back( aItem );
3055 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
3056 wxS( "[ShowMatches] engine: match type=%d kiid=%s layer=%d" ),
3057 (int) aItem->Type(), aItem->m_Uuid.AsString(), (int) layer );
3058 matched = true;
3059 break; // No need to check other layers
3060 }
3061 }
3062
3063 // Log a few non-matching items to help debug condition issues
3064 if( !matched && matches.size() == 0 && checkedItems <= 5 )
3065 {
3066 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
3067 wxS( "[ShowMatches] engine: no-match sample type=%d kiid=%s layers=%s" ),
3068 (int) aItem->Type(), aItem->m_Uuid.AsString(), itemLayers.FmtHex() );
3069 }
3070 };
3071
3072 m_board->RunOnChildren( applyConditionToItemFunc, RECURSE_MODE::RECURSE );
3073
3074 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ),
3075 wxS( "[ShowMatches] engine stats: total=%zu skipped=%zu noLayer=%zu checked=%zu" ),
3076 totalItems, skippedItems, noLayerItems, checkedItems );
3077
3078 wxLogTrace( wxS( "KI_TRACE_DRC_RULE_EDITOR" ), wxS( "[ShowMatches] engine exit: total=%zu" ), matches.size() );
3079 return matches;
3080}
3081
3082
3083std::vector<BOARD_ITEM*> DRC_ENGINE::GetItemsMatchingRule( const std::shared_ptr<DRC_RULE>& aRule, REPORTER* aReporter )
3084{
3085 std::vector<BOARD_ITEM*> matches;
3086
3087 if( !m_board || !aRule )
3088 return matches;
3089
3090 const bool requiresPairwise = aRule->m_Condition && aRule->m_Condition->RequiresPairItems();
3091 std::set<BOARD_ITEM*> matchedItems;
3092
3093 if( std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity() )
3094 {
3095 if( std::shared_ptr<FROM_TO_CACHE> ftCache = connectivity->GetFromToCache() )
3096 ftCache->Rebuild( m_board );
3097 }
3098
3099 for( const DRC_CONSTRAINT& constraint : aRule->m_Constraints )
3100 {
3101 if( constraint.m_Type == NULL_CONSTRAINT )
3102 continue;
3103
3104 SHOWMATCH_DOMAIN_SPEC domainSpec = getShowMatchDomainSpec( constraint.m_Type );
3105 std::vector<BOARD_ITEM*> primaryItems = collectShowMatchCandidates( m_board, domainSpec.primary );
3106 std::vector<BOARD_ITEM*> secondaryItems;
3107
3108 if( domainSpec.hasSecondary )
3109 secondaryItems = collectShowMatchCandidates( m_board, domainSpec.secondary );
3110
3111 if( requiresPairwise )
3112 {
3113 if( secondaryItems.empty() )
3114 {
3115 for( size_t ii = 0; ii < primaryItems.size(); ++ii )
3116 {
3117 BOARD_ITEM* itemA = primaryItems[ii];
3118
3119 for( size_t jj = ii + 1; jj < primaryItems.size(); ++jj )
3120 {
3121 BOARD_ITEM* itemB = primaryItems[jj];
3122
3123 if( ruleMatchesPair( *aRule, itemA, itemB, constraint.m_Type,
3124 aReporter ? aReporter : m_logReporter ) )
3125 {
3126 matchedItems.insert( itemA );
3127 matchedItems.insert( itemB );
3128 }
3129 }
3130 }
3131 }
3132 else
3133 {
3134 for( BOARD_ITEM* itemA : primaryItems )
3135 {
3136 for( BOARD_ITEM* itemB : secondaryItems )
3137 {
3138 if( itemA == itemB )
3139 continue;
3140
3141 if( ruleMatchesPair( *aRule, itemA, itemB, constraint.m_Type,
3142 aReporter ? aReporter : m_logReporter ) )
3143 {
3144 matchedItems.insert( itemA );
3145 matchedItems.insert( itemB );
3146 }
3147 }
3148 }
3149 }
3150 }
3151 else
3152 {
3153 for( BOARD_ITEM* item : primaryItems )
3154 {
3155 if( ruleMatchesUnary( *aRule, item, constraint.m_Type, aReporter ? aReporter : m_logReporter ) )
3156 {
3157 matchedItems.insert( item );
3158 }
3159 }
3160
3161 if( domainSpec.hasSecondary && domainSpec.secondaryUnary )
3162 {
3163 for( BOARD_ITEM* item : secondaryItems )
3164 {
3165 if( ruleMatchesUnary( *aRule, item, constraint.m_Type, aReporter ? aReporter : m_logReporter ) )
3166 {
3167 matchedItems.insert( item );
3168 }
3169 }
3170 }
3171 }
3172 }
3173
3174 matches.assign( matchedItems.begin(), matchedItems.end() );
3175
3176 return matches;
3177}
3178
3179
3181 wxString* aSource )
3182{
3183 DRC_OWN_CLEARANCE_CACHE_KEY key{ aItem->m_Uuid, aLayer };
3184
3185 // Fast path: check cache with shared (read) lock
3186 {
3187 std::shared_lock<std::shared_mutex> readLock( m_clearanceCacheMutex );
3188
3189 auto it = m_ownClearanceCache.find( key );
3190
3191 if( it != m_ownClearanceCache.end() )
3192 {
3193 // Cache hit. We don't cache the source string since it's rarely requested
3194 // and caching it would add complexity.
3195 return it->second;
3196 }
3197 }
3198
3199 // Cache miss - evaluate the constraint (outside lock to avoid blocking other threads)
3200 DRC_CONSTRAINT_T constraintType = CLEARANCE_CONSTRAINT;
3201
3202 if( aItem->Type() == PCB_PAD_T )
3203 {
3204 const PAD* pad = static_cast<const PAD*>( aItem );
3205
3206 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
3207 constraintType = HOLE_CLEARANCE_CONSTRAINT;
3208 }
3209
3210 DRC_CONSTRAINT constraint = EvalRules( constraintType, aItem, nullptr, aLayer );
3211
3212 int clearance = 0;
3213
3214 if( constraint.Value().HasMin() )
3215 {
3216 clearance = constraint.Value().Min();
3217
3218 if( aSource )
3219 *aSource = constraint.GetName();
3220 }
3221
3222 // Store in cache with exclusive (write) lock using double-checked locking
3223 {
3224 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
3225
3226 auto it = m_ownClearanceCache.find( key );
3227
3228 if( it == m_ownClearanceCache.end() )
3230 }
3231
3232 return clearance;
3233}
3234
3235
3237{
3238 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
3239
3240 if( m_board )
3241 {
3242 LSET copperLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
3243
3244 for( PCB_LAYER_ID layer : copperLayers.Seq() )
3245 m_ownClearanceCache.erase( DRC_OWN_CLEARANCE_CACHE_KEY{ aUuid, layer } );
3246 }
3247 else
3248 {
3249 std::erase_if( m_ownClearanceCache, [&aUuid]( const auto& entry ) { return entry.first.m_uuid == aUuid; } );
3250 }
3251}
3252
3253
3255{
3256 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
3257 m_ownClearanceCache.clear();
3258}
3259
3260
3262{
3263 if( !m_board )
3264 return;
3265
3266 // Pre-populate the cache for all connected items to avoid delays during first render.
3267 // We only need to cache copper layers since clearance outlines are only drawn on copper.
3268
3269 LSET copperLayers = m_board->GetEnabledLayers() & LSET::AllCuMask();
3270
3271 using CLEARANCE_MAP = std::unordered_map<DRC_OWN_CLEARANCE_CACHE_KEY, int>;
3272
3273 // Build flat list of (item, layer) pairs to process.
3274 // Estimate size based on tracks + pads * average layers (2 for typical TH pads).
3275 std::vector<std::pair<const BOARD_ITEM*, PCB_LAYER_ID>> itemsToProcess;
3276 size_t estimatedPads = 0;
3277
3278 for( FOOTPRINT* footprint : m_board->Footprints() )
3279 estimatedPads += footprint->Pads().size();
3280
3281 itemsToProcess.reserve( m_board->Tracks().size() + estimatedPads * 2 );
3282
3283 for( PCB_TRACK* track : m_board->Tracks() )
3284 {
3285 if( track->Type() == PCB_VIA_T )
3286 {
3287 for( PCB_LAYER_ID layer : LSET( track->GetLayerSet() & copperLayers ).Seq() )
3288 itemsToProcess.emplace_back( track, layer );
3289 }
3290 else
3291 {
3292 itemsToProcess.emplace_back( track, track->GetLayer() );
3293 }
3294 }
3295
3296 for( FOOTPRINT* footprint : m_board->Footprints() )
3297 {
3298 for( PAD* pad : footprint->Pads() )
3299 {
3300 for( PCB_LAYER_ID layer : LSET( pad->GetLayerSet() & copperLayers ).Seq() )
3301 itemsToProcess.emplace_back( pad, layer );
3302 }
3303 }
3304
3305 if( itemsToProcess.empty() )
3306 return;
3307
3308 {
3309 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
3310 m_ownClearanceCache.reserve( itemsToProcess.size() );
3311 }
3312
3314
3315 auto processItems =
3316 [this]( size_t aStart, size_t aEnd, const std::vector<std::pair<const BOARD_ITEM*, PCB_LAYER_ID>>& aItems )
3317 -> CLEARANCE_MAP
3318 {
3319 CLEARANCE_MAP localCache;
3320
3321 for( size_t i = aStart; i < aEnd; ++i )
3322 {
3323 const BOARD_ITEM* item = aItems[i].first;
3324 PCB_LAYER_ID layer = aItems[i].second;
3325
3326 DRC_CONSTRAINT_T constraintType = CLEARANCE_CONSTRAINT;
3327
3328 if( item->Type() == PCB_PAD_T )
3329 {
3330 const PAD* pad = static_cast<const PAD*>( item );
3331
3332 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
3333 constraintType = HOLE_CLEARANCE_CONSTRAINT;
3334 }
3335
3336 DRC_CONSTRAINT constraint = EvalRules( constraintType, item, nullptr, layer );
3337
3338 int clearance = 0;
3339
3340 if( constraint.Value().HasMin() )
3341 clearance = constraint.Value().Min();
3342
3343 localCache[{ item->m_Uuid, layer }] = clearance;
3344 }
3345
3346 return localCache;
3347 };
3348
3349 auto results = tp.submit_blocks( 0, itemsToProcess.size(),
3350 [&]( size_t aStart, size_t aEnd ) -> CLEARANCE_MAP
3351 {
3352 return processItems( aStart, aEnd, itemsToProcess );
3353 } );
3354
3355 // Collect all results first WITHOUT holding the lock to avoid deadlock.
3356 // Worker threads call EvalRules() which needs a read lock on m_clearanceCacheMutex.
3357 // If we held a write lock while calling .get(), workers would block on the read lock
3358 // while we block waiting for them to complete.
3359 std::vector<CLEARANCE_MAP> collectedResults;
3360 collectedResults.reserve( results.size() );
3361
3362 for( size_t i = 0; i < results.size(); ++i )
3363 {
3364 if( results[i].valid() )
3365 collectedResults.push_back( results[i].get() );
3366 }
3367
3368 // Now merge with write lock held, but no blocking on futures
3369 {
3370 std::unique_lock<std::shared_mutex> writeLock( m_clearanceCacheMutex );
3371
3372 for( const auto& localCache : collectedResults )
3373 m_ownClearanceCache.insert( localCache.begin(), localCache.end() );
3374 }
3375}
const char * name
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
#define MAXIMUM_CLEARANCE
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
std::string FmtHex() const
Return a hex string showing contents of this set.
Definition base_set.h:312
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual std::optional< int > GetClearanceOverrides(wxString *aSource) const
Return any clearance overrides set in the "classic" (ie: pre-rule) system.
virtual NETCLASS * GetEffectiveNetClass() const
Return the NETCLASS for this item.
virtual std::optional< int > GetLocalClearance() const
Return any local clearances set in the "classic" (ie: pre-rule) system.
Container for design settings for a BOARD object.
std::shared_ptr< NET_SETTINGS > m_NetSettings
std::map< int, SEVERITY > m_DRCSeverities
int GetDRCEpsilon() const
Return an epsilon which accounts for rounding errors, etc.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:173
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:418
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:355
virtual bool IsOnCopperLayer() const
Definition board_item.h:190
virtual bool HasHole() const
Definition board_item.h:208
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
NETINFO_ITEM * FindNet(int aNetcode) const
Search for a net with the given netcode.
Definition board.cpp:2797
void RunOnChildren(const std::function< void(BOARD_ITEM *)> &aFunction, RECURSE_MODE aMode) const override
Invoke a function on all children.
Definition board.cpp:837
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
Represents a single line in a time domain profile track propagation setup.
int GetDiffPairGap() const
int GetWidth() const
PCB_LAYER_ID GetSignalLayer() const
virtual bool Run() override
Discard the board's run-time DRC caches and regenerate them from scratch.
DRC_RULE * GetOptRule() const
Definition drc_rule.h:157
wxString GetName() const
Definition drc_rule.h:162
int m_DisallowFlags
Definition drc_rule.h:205
DRC_RULE * GetMinRule() const
Definition drc_rule.h:156
void SetParentRule(DRC_RULE *aParentRule)
Attribute the constraint, including each min, opt and max value it has, to aParentRule.
Definition drc_rule.h:135
MINOPTMAX< int > & Value()
Definition drc_rule.h:130
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:129
ZONE_CONNECTION m_ZoneConnection
Definition drc_rule.h:206
void SetName(const wxString &aName)
Definition drc_rule.h:160
MINOPTMAX< int > m_Value
Definition drc_rule.h:204
DRC_CONSTRAINT_T m_Type
Definition drc_rule.h:203
void SetOptionsFromOther(const DRC_CONSTRAINT &aOther)
Definition drc_rule.h:198
DRC_RULE * GetMaxRule() const
Definition drc_rule.h:158
void SetValueRules(DRC_RULE *aMinRule, DRC_RULE *aOptRule, DRC_RULE *aMaxRule)
Definition drc_rule.h:149
DRC_RULE * GetParentRule() const
Definition drc_rule.h:147
bool m_ImplicitMin
Definition drc_rule.h:208
std::map< DRC_CONSTRAINT_T, std::vector< DRC_ENGINE_CONSTRAINT * > * > m_constraintMap
Definition drc_engine.h:386
DRC_CLEARANCE_BATCH EvalClearanceBatch(const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer)
Evaluate all clearance-related constraints in a single batch call.
void AdvanceProgress()
std::shared_ptr< DRC_RULE > createImplicitRule(const wxString &name, DRC_IMPLICIT_SOURCE aImplicitSource)
void RunTests(EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, BOARD_COMMIT *aCommit=nullptr)
Run the DRC tests.
std::unordered_map< DRC_OWN_CLEARANCE_CACHE_KEY, int > m_ownClearanceCache
Definition drc_engine.h:397
bool m_hasDiffPairClearanceOverrides
Definition drc_engine.h:409
bool m_testFootprints
Definition drc_engine.h:383
void addRule(std::shared_ptr< DRC_RULE > &rule)
Definition drc_engine.h:340
PROGRESS_REPORTER * m_progressReporter
Definition drc_engine.h:390
void ClearClearanceCache()
Clear the entire clearance cache.
void loadRules(const wxFileName &aPath)
Load and parse a rule set from an sexpr text file.
std::vector< DRC_TEST_PROVIDER * > m_testProviders
Definition drc_engine.h:378
REPORTER * m_logReporter
Definition drc_engine.h:389
std::map< DRC_CONSTRAINT_T, std::vector< DRC_ENGINE_CONSTRAINT * > > m_explicitConstraints
Definition drc_engine.h:410
int GetErrorLimit(int error_code)
Return the number of violations of error_code that may still be reported.
void compileRules()
std::unordered_map< wxString, DRC_RULE * > m_netclassClearances
Definition drc_engine.h:402
std::set< int > QueryDistinctConstraints(DRC_CONSTRAINT_T aConstraintId)
bool m_hasGeometryDependentRules
Definition drc_engine.h:408
DS_PROXY_VIEW_ITEM * m_drawingSheet
Definition drc_engine.h:373
NETLIST * m_schematicNetlist
Definition drc_engine.h:374
std::shared_mutex m_clearanceCacheMutex
Definition drc_engine.h:406
bool KeepRefreshing(bool aWait=false)
std::vector< BOARD_ITEM * > GetItemsMatchingRule(const std::shared_ptr< DRC_RULE > &aRule, REPORTER *aReporter=nullptr)
bool HasUserDefinedConstraint(DRC_CONSTRAINT_T aConstraintId)
BOARD * m_board
Definition drc_engine.h:372
bool HasConditionalConstraint(DRC_CONSTRAINT_T aConstraintId)
int GetCachedOwnClearance(const BOARD_ITEM *aItem, PCB_LAYER_ID aLayer, wxString *aSource=nullptr)
Get the cached own clearance for an item on a specific layer.
bool m_reportAllTrackErrors
Definition drc_engine.h:382
bool ReportProgress(double aProgress)
DRC_TEST_PROVIDER * GetTestProvider(const wxString &name) const
bool HasRulesForConstraintType(DRC_CONSTRAINT_T constraintID)
BOARD_DESIGN_SETTINGS * GetDesignSettings() const
Definition drc_engine.h:146
void SetMaxProgress(int aSize)
DRC_ENGINE(BOARD *aBoard=nullptr, BOARD_DESIGN_SETTINGS *aSettings=nullptr)
bool m_hasExplicitClearanceRules
Definition drc_engine.h:407
std::vector< int > m_errorLimits
Definition drc_engine.h:380
bool IsErrorLimitExceeded(int error_code)
void ProcessAssertions(const BOARD_ITEM *a, std::function< void(const DRC_CONSTRAINT *)> aFailureHandler, REPORTER *aReporter=nullptr)
void loadImplicitRules()
DRC_VIOLATION_HANDLER m_violationHandler
Definition drc_engine.h:388
DRC_CONSTRAINT EvalRules(DRC_CONSTRAINT_T aConstraintType, const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
std::vector< std::shared_ptr< DRC_RULE > > m_rules
Definition drc_engine.h:376
bool QueryWorstConstraint(DRC_CONSTRAINT_T aRuleId, DRC_CONSTRAINT &aConstraint, bool aUnconditionalOnly=false)
bool IsCancelled() const
bool IsNetTieExclusion(int aTrackNetCode, PCB_LAYER_ID aTrackLayer, const VECTOR2I &aCollisionPos, BOARD_ITEM *aCollidingItem)
Check if the given collision between a track and another item occurs during the track's entry into a ...
virtual ~DRC_ENGINE()
std::vector< BOARD_ITEM * > GetItemsMatchingCondition(const wxString &aExpression, DRC_CONSTRAINT_T aConstraint=ASSERTION_CONSTRAINT, REPORTER *aReporter=nullptr)
Evaluate a DRC condition against all board items and return matches.
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
DRC_CONSTRAINT EvalZoneConnection(const BOARD_ITEM *a, const BOARD_ITEM *b, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
static int MatchDpSuffix(const wxString &aNetName, wxString &aComplementNet, wxString &aBaseDpName)
Check if the given net is a diff pair, returning its polarity and complement if so.
bool ReportPhase(const wxString &aMessage)
static bool IsNetADiffPair(BOARD *aBoard, const NETINFO_ITEM *aNet, int &aNetP, int &aNetN)
void InitializeClearanceCache()
Initialize the clearance cache for all items on the board.
bool m_rulesValid
Definition drc_engine.h:377
void InvalidateClearanceCache(const KIID &aUuid)
Invalidate the clearance cache for a specific item.
BOARD_DESIGN_SETTINGS * m_designSettings
Definition drc_engine.h:371
bool HasUserDefinedPhysicalConstraint()
void ReportViolation(const std::shared_ptr< DRC_ITEM > &aItem, const VECTOR2I &aPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator={})
std::mutex m_errorLimitsMutex
Definition drc_engine.h:381
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
bool Compile(REPORTER *aReporter, int aSourceLine=0, int aSourceOffset=0)
wxString GetExpression() const
bool EvaluateFor(const BOARD_ITEM *aItemA, const BOARD_ITEM *aItemB, int aConstraint, PCB_LAYER_ID aLayer, REPORTER *aReporter=nullptr)
DRC_RULE_CONDITION * m_Condition
Definition drc_rule.h:84
bool IsImplicit() const
Definition drc_rule.h:70
LSET m_LayerCondition
Definition drc_rule.h:83
std::vector< DRC_CONSTRAINT > m_Constraints
Definition drc_rule.h:85
wxString m_Name
Definition drc_rule.h:80
wxString m_LayerSource
Definition drc_rule.h:82
wxString m_ImplicitNetclass
Netclass named by an implicit netclass rule's condition.
Definition drc_rule.h:81
DRC_IMPLICIT_SOURCE GetImplicitSource() const
Definition drc_rule.h:74
std::vector< DRC_TEST_PROVIDER * > GetShowMatchesProviders() const
static DRC_SHOWMATCHES_PROVIDER_REGISTRY & Instance()
std::vector< DRC_TEST_PROVIDER * > GetTestProviders() const
static DRC_TEST_PROVIDER_REGISTRY & Instance()
Represent a DRC "provider" which runs some DRC functions over a BOARD and spits out DRC_ITEM and posi...
void SetDRCEngine(DRC_ENGINE *engine)
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
virtual wxString GetItemDescription(UNITS_PROVIDER *aUnitsProvider, bool aFull) const
Return a user-visible description string of this item.
Definition eda_item.cpp:304
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:167
A LINE_READER that reads from an open file.
Definition richio.h:157
char * ReadLine() override
Read a line of text into the buffer and increments the line number counter.
Definition richio.cpp:202
ZONE_CONNECTION GetLocalZoneConnection() const
Definition footprint.h:491
std::map< wxString, int > MapPadNumbersToNetTieGroups() const
std::deque< PAD * > & Pads()
Definition footprint.h:355
const std::set< int > & GetNetTieCache(const BOARD_ITEM *aItem) const
Get the set of net codes that are allowed to connect to a footprint item.
Definition footprint.h:682
bool IsNetTie() const
Definition footprint.h:525
wxString GetItemDescription(UNITS_PROVIDER *aUnitsProvider, bool aFull) const override
Return a user-visible description string of this item.
const SHAPE_POLY_SET & GetCourtyard(PCB_LAYER_ID aLayer) const
Used in DRC to test the courtyard area (a complex polygon).
Definition kiid.h:46
wxString AsString() const
Definition kiid.cpp:264
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & FrontMask()
Return a mask holding all technical layers and the external CU layer on front side.
Definition lset.cpp:718
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
static const LSET & BackMask()
Return a mask holding all technical layers and the external CU layer on back side.
Definition lset.cpp:725
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:309
static wxString Name(PCB_LAYER_ID aLayerId)
Return the fixed name association with aLayerId.
Definition lset.cpp:184
T Min() const
Definition minoptmax.h:29
void SetMin(T v)
Definition minoptmax.h:41
void SetOpt(T v)
Definition minoptmax.h:43
bool HasMin() const
Definition minoptmax.h:37
void SetMax(T v)
Definition minoptmax.h:42
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:44
const wxString GetName() const
Gets the name of this (maybe aggregate) netclass in a format for internal usage or for export to exte...
Definition netclass.cpp:380
bool NameEquals(const wxString &aName) const
Compares a name with GetName() without constructing the aggregate name.
Definition netclass.cpp:407
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetname() const
Definition netinfo.h:110
int GetNetCode() const
Definition netinfo.h:104
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetCompositeNetclasses() const
Gets all composite (multiple assignment / missing defaults) netclasses.
const std::map< wxString, std::shared_ptr< NETCLASS > > & GetNetclasses() const
Gets all netclasses.
std::shared_ptr< NETCLASS > GetDefaultNetclass() const
Gets the default netclass for the project.
Definition pad.h:61
std::optional< int > GetLocalSolderMaskMargin() const
Definition pcb_shape.h:350
A small class to help profiling.
Definition profile.h:46
void Stop()
Save the time when this function was called, and set the counter stane to stop.
Definition profile.h:86
double msecs(bool aSinceLast=false)
Definition profile.h:148
Container for project specific data.
Definition project.h:63
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:73
virtual bool HasMessageOfSeverity(int aSeverityMask) const
Returns true if the reporter has one or more messages matching the specified severity mask.
Definition reporter.h:152
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
const EDA_IU_SCALE & GetIuScale() const
wxString MessageTextFromUnscaledValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
UNITS_PROVIDER(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits)
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
void SetUserUnits(EDA_UNITS aUnits)
Handle a list of polygons defining a copper zone.
Definition zone.h:70
wxString GetItemDescription(UNITS_PROVIDER *aUnitsProvider, bool aFull) const override
Return a user-visible description string of this item.
Definition zone.cpp:1516
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:832
bool GetDoNotAllowVias() const
Definition zone.h:850
bool GetDoNotAllowPads() const
Definition zone.h:852
const BOX2I GetBoundingBox() const override
Definition zone.cpp:822
bool GetDoNotAllowTracks() const
Definition zone.h:851
const wxString & GetZoneName() const
Definition zone.h:160
int GetMinThickness() const
Definition zone.h:315
ZONE_CONNECTION GetPadConnection() const
Definition zone.h:312
int GetThermalReliefSpokeWidth() const
Definition zone.h:259
bool GetDoNotAllowFootprints() const
Definition zone.h:853
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
bool HasKeepoutParametersSet() const
Accessor to determine if any keepout parameters are set.
Definition zone.h:823
bool GetDoNotAllowZoneFills() const
Definition zone.h:849
int GetThermalReliefGap() const
Definition zone.h:248
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
wxString DescribeRef(const wxString &aRef)
Returns a user-visible HTML string describing a footprint reference designator.
Definition common.cpp:508
@ INTERNAL
Definition common.h:96
DRC_IMPLICIT_SOURCE
@ DRC_DISALLOW_PADS
@ DRC_DISALLOW_BURIED_VIAS
@ DRC_DISALLOW_BLIND_VIAS
@ DRC_DISALLOW_TEXTS
@ DRC_DISALLOW_ZONES
@ DRC_DISALLOW_HOLES
@ DRC_DISALLOW_GRAPHICS
@ DRC_DISALLOW_THROUGH_VIAS
@ DRC_DISALLOW_FOOTPRINTS
@ DRC_DISALLOW_TRACKS
@ DRC_DISALLOW_MICRO_VIAS
@ ANNULAR_WIDTH_CONSTRAINT
@ COURTYARD_CLEARANCE_CONSTRAINT
@ VIA_DIAMETER_CONSTRAINT
@ ZONE_CONNECTION_CONSTRAINT
@ DIFF_PAIR_GAP_CONSTRAINT
@ NET_CHAIN_LENGTH_CONSTRAINT
@ SOLDER_MASK_SLIVER_CONSTRAINT
@ NET_CHAIN_STUB_LENGTH_CONSTRAINT
@ DISALLOW_CONSTRAINT
@ TRACK_WIDTH_CONSTRAINT
@ SILK_CLEARANCE_CONSTRAINT
@ EDGE_CLEARANCE_CONSTRAINT
@ MIN_RESOLVED_SPOKES_CONSTRAINT
@ TRACK_SEGMENT_LENGTH_CONSTRAINT
@ HOLE_ASPECT_RATIO_CONSTRAINT
@ TEXT_THICKNESS_CONSTRAINT
@ LENGTH_CONSTRAINT
@ VIA_COUNT_CONSTRAINT
@ NET_CHAIN_RETURN_PATH_CONSTRAINT
@ PHYSICAL_HOLE_CLEARANCE_CONSTRAINT
@ CLEARANCE_CONSTRAINT
@ NULL_CONSTRAINT
@ THERMAL_SPOKE_WIDTH_CONSTRAINT
@ CONNECTION_WIDTH_CONSTRAINT
@ THERMAL_RELIEF_GAP_CONSTRAINT
@ MAX_UNCOUPLED_CONSTRAINT
@ ASSERTION_CONSTRAINT
@ SKEW_CONSTRAINT
@ MICROVIA_STACK_DEPTH_CONSTRAINT
@ HOLE_CLEARANCE_CONSTRAINT
@ SOLDER_PASTE_ABS_MARGIN_CONSTRAINT
@ SOLDER_MASK_EXPANSION_CONSTRAINT
@ TRACK_ANGLE_CONSTRAINT
@ HOLE_SIZE_CONSTRAINT
@ TEXT_HEIGHT_CONSTRAINT
@ CREEPAGE_CONSTRAINT
@ PHYSICAL_CLEARANCE_CONSTRAINT
@ SOLDER_PASTE_REL_MARGIN_CONSTRAINT
@ HOLE_TO_HOLE_CONSTRAINT
constexpr int DRC_DISALLOW_VIAS
void drcPrintDebugMessage(int level, const wxString &msg, const char *function, int line)
#define EXTENDED_ERROR_LIMIT
static bool isKeepoutZone(const BOARD_ITEM *aItem, bool aCheckFlags)
#define ERROR_LIMIT
@ DRCE_TUNING_PROFILE_IMPLICIT_RULES
Definition drc_item.h:131
@ DRCE_UNCONNECTED_ITEMS
Definition drc_item.h:48
@ DRCE_CLEARANCE
Definition drc_item.h:52
@ DRCE_FIRST
Definition drc_item.h:47
@ DRCE_LAST
Definition drc_item.h:144
bool IsComponentClassSelector(const wxString &aToken)
${Class:X} inside a DRC rule is a component-class selector consumed by testFootprintSelector(),...
Definition drc_rule.cpp:32
#define _(s)
@ RECURSE
Definition eda_item.h:51
#define HOLE_PROXY
Indicates the BOARD_ITEM is a proxy for its hole.
EDA_UNITS
Definition eda_units.h:44
#define THROW_PARSE_ERROR(aProblem, aSource, aInputLine, aLineNumber, aByteIndex)
static const wxChar * traceDrcProfile
Flag to enable DRC profile timing logging.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
bool IsPcbLayer(int aLayer)
Test whether a layer is a valid layer for Pcbnew.
Definition layer_ids.h:730
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ B_Adhes
Definition layer_ids.h:99
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Paste
Definition layer_ids.h:100
@ F_Adhes
Definition layer_ids.h:98
@ B_Mask
Definition layer_ids.h:94
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_Fab
Definition layer_ids.h:115
@ Margin
Definition layer_ids.h:109
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ B_SilkS
Definition layer_ids.h:97
@ B_Fab
Definition layer_ids.h:114
#define REPORT(msg)
std::optional< wxString > EscapeStringLiteral(const wxString &aText)
Escape text for use between the quotes of an expression string.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
@ PTH
Plated through hole pad.
Definition padstack.h:97
std::deque< PAD * > PADS
std::deque< FOOTPRINT * > FOOTPRINTS
@ RPT_SEVERITY_ERROR
const double epsilon
std::vector< FAB_LAYER_COLOR > dummy
wxString EscapeHTML(const wxString &aString)
Return a new wxString escaped for embedding in HTML.
Batch result for clearance-related constraints to reduce per-query overhead during PNS routing.
Definition drc_engine.h:109
std::shared_ptr< DRC_RULE > parentRule
Definition drc_engine.h:358
DRC_RULE_CONDITION * condition
Definition drc_engine.h:357
Cache key for own clearance lookups, combining item UUID and layer.
Definition drc_engine.h:39
A filename or source description, a problem input line, a line number, a byte offset,...
Represents a single line in the tuning profile configuration grid.
PROFILE_TYPE m_Type
std::vector< DELAY_PROFILE_TRACK_PROPAGATION_ENTRY > m_TrackPropagationEntries
static const long long MM
int clearance
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
constexpr KICAD_T BaseType(const KICAD_T aType)
Return the underlying type of the given type.
Definition typeinfo.h:260
@ PCB_CONSTRAINT_T
a geometric constraint between board items
Definition typeinfo.h:237
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:92
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86
@ PCB_NETINFO_T
class NETINFO_ITEM, a description of a net
Definition typeinfo.h:102
@ PCB_LOCATE_HOLE_T
Definition typeinfo.h:123
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
@ PCB_DRILL_CHART_T
class PCB_DRILL_CHART, a live drill chart derived from PCB_TABLE
Definition typeinfo.h:239
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
wxString PrintZoneConnection(ZONE_CONNECTION aConnection)
Definition zones.h:52
ZONE_CONNECTION
How pads are covered by copper in zone.
Definition zones.h:43
@ THERMAL
Use thermal relief for pads.
Definition zones.h:46
@ THT_THERMAL
Thermal relief only for THT pads.
Definition zones.h:48
@ FULL
pads are covered by copper
Definition zones.h:47