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