KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_copper_clearance.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 The KiCad Developers.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <common.h>
22#include <footprint.h>
23#include <layer_range.h>
24#include <pcb_shape.h>
25#include <pad.h>
26#include <pcb_track.h>
27#include <thread_pool.h>
28#include <zone.h>
29
30#include <geometry/seg.h>
34
35#include <drc/drc_engine.h>
36#include <drc/drc_rtree.h>
37#include <drc/drc_item.h>
38#include <drc/drc_rule.h>
41#include <pcb_dimension.h>
42
43#include <future>
44
45/*
46 Copper clearance test. Checks all copper items (pads, vias, tracks, drawings, zones) for their
47 electrical clearance.
48
49 Errors generated:
50 - DRCE_CLEARANCE
51 - DRCE_HOLE_CLEARANCE
52 - DRCE_TRACKS_CROSSING
53 - DRCE_ZONES_INTERSECT
54 - DRCE_SHORTING_ITEMS
55*/
56
58{
59public:
64
66
67 virtual bool Run() override;
68
69 virtual const wxString GetName() const override { return wxT( "clearance" ); };
70
71private:
80 bool testSingleLayerItemAgainstItem( BOARD_ITEM* item, SHAPE* itemShape, PCB_LAYER_ID layer,
81 BOARD_ITEM* other );
82
84
85 bool testPadAgainstItem( PAD* pad, SHAPE* padShape, PCB_LAYER_ID layer, BOARD_ITEM* other );
86
87 void testPadClearances();
88
90
91 void testZonesToZones();
92
94
95 void testItemAgainstZone( BOARD_ITEM* aItem, ZONE* aZone, PCB_LAYER_ID aLayer );
96
97 void testKnockoutTextAgainstZone( BOARD_ITEM* aText, NETINFO_ITEM** aInheritedNet, ZONE* aZone );
98
99 int sub_e( int aClearance )
100 {
101 return std::max( 0, aClearance - m_drcEpsilon );
102 };
103
104private:
106};
107
108
110{
111 m_board = m_drcEngine->GetBoard();
112
113 if( m_board->m_DRCMaxClearance <= 0 )
114 {
115 REPORT_AUX( wxT( "No Clearance constraints found. Tests not run." ) );
116 return true; // continue with other tests
117 }
118
119 m_drcEpsilon = m_board->GetDesignSettings().GetDRCEpsilon();
120
121 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ) )
122 {
123 if( !reportPhase( _( "Checking track & via clearances..." ) ) )
124 return false; // DRC cancelled
125
127 }
128 else if( !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE ) )
129 {
130 if( !reportPhase( _( "Checking hole clearances..." ) ) )
131 return false; // DRC cancelled
132
134 }
135
136 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ) )
137 {
138 if( !reportPhase( _( "Checking pad clearances..." ) ) )
139 return false; // DRC cancelled
140
142 }
143 else if( !m_drcEngine->IsErrorLimitExceeded( DRCE_SHORTING_ITEMS )
144 || !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE ) )
145 {
146 if( !reportPhase( _( "Checking pads..." ) ) )
147 return false; // DRC cancelled
148
150 }
151
152 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ) )
153 {
154 if( !reportPhase( _( "Checking copper graphic clearances..." ) ) )
155 return false; // DRC cancelled
156
158 }
159 else if( !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE ) )
160 {
161 if( !reportPhase( _( "Checking copper graphic hole clearances..." ) ) )
162 return false; // DRC cancelled
163
165 }
166
167 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE ) )
168 {
169 if( !reportPhase( _( "Checking copper zone clearances..." ) ) )
170 return false; // DRC cancelled
171
173
174 if( !reportPhase( _( "Checking teardrop clearances..." ) ) )
175 return false; // DRC cancelled
176
178 }
179 else if( !m_drcEngine->IsErrorLimitExceeded( DRCE_ZONES_INTERSECT ) )
180 {
181 if( !reportPhase( _( "Checking zones..." ) ) )
182 return false; // DRC cancelled
183
185 }
186
187 return !m_drcEngine->IsCancelled();
188}
189
190
192 SHAPE* itemShape,
193 PCB_LAYER_ID layer,
194 BOARD_ITEM* other )
195{
196 bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE );
197 bool testShorting = !m_drcEngine->IsErrorLimitExceeded( DRCE_SHORTING_ITEMS );
198 bool testHoles = !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE );
199 DRC_CONSTRAINT constraint;
200 int clearance = -1;
201 int actual;
202 VECTOR2I pos;
203 bool has_error = false;
204 NETINFO_ITEM* itemNet = nullptr;
205 NETINFO_ITEM* otherNet = nullptr;
206
207 if( item->IsConnected() )
208 itemNet = static_cast<BOARD_CONNECTED_ITEM*>( item )->GetNet();
209
210 if( other->IsConnected() )
211 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNet();
212
213 if( itemNet == otherNet )
214 testClearance = testShorting = false;
215
216 std::shared_ptr<SHAPE> otherShape_shared_ptr;
217
218 if( other->Type() == PCB_PAD_T )
219 {
220 PAD* pad = static_cast<PAD*>( other );
221
222 if( !pad->FlashLayer( layer ) )
223 {
224 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
225 testClearance = testShorting = false;
226
227 otherShape_shared_ptr = pad->GetEffectiveHoleShape();
228 }
229 }
230 else if( other->Type() == PCB_VIA_T )
231 {
232 PCB_VIA* via = static_cast<PCB_VIA*>( other );
233
234 if( !via->FlashLayer( layer ) )
235 otherShape_shared_ptr = via->GetEffectiveHoleShape();
236 }
237
238 if( !otherShape_shared_ptr )
239 otherShape_shared_ptr = other->GetEffectiveShape( layer );
240
241 SHAPE* otherShape = otherShape_shared_ptr.get();
242
243 // Collide (and generate violations) based on a well-defined order so that exclusion checking
244 // against previously-generated violations will work.
245 if( item->m_Uuid > other->m_Uuid )
246 {
247 std::swap( item, other );
248 std::swap( itemShape, otherShape );
249 std::swap( itemNet, otherNet );
250 }
251
252 if( testClearance || testShorting )
253 {
254 constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, item, other, layer );
255 clearance = constraint.GetValue().Min();
256 }
257
258 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 )
259 {
260 // Special processing for track:track intersections
261 if( item->Type() == PCB_TRACE_T && other->Type() == PCB_TRACE_T )
262 {
263 PCB_TRACK* track = static_cast<PCB_TRACK*>( item );
264 PCB_TRACK* otherTrack = static_cast<PCB_TRACK*>( other );
265
266 SEG trackSeg( track->GetStart(), track->GetEnd() );
267 SEG otherSeg( otherTrack->GetStart(), otherTrack->GetEnd() );
268
269 if( OPT_VECTOR2I intersection = trackSeg.Intersect( otherSeg ) )
270 {
271 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_TRACKS_CROSSING );
272 drcItem->SetItems( item, other );
273 drcItem->SetViolatingRule( constraint.GetParentRule() );
274 reportTwoPointGeometry( drcItem, *intersection, *intersection, *intersection, layer );
275 return false;
276 }
277 }
278
279 if( itemShape->Collide( otherShape, sub_e( clearance ), &actual, &pos ) )
280 {
281 if( itemNet && m_drcEngine->IsNetTieExclusion( itemNet->GetNetCode(), layer, pos, other ) )
282 {
283 // Collision occurred as track was entering a pad marked as a net-tie. We
284 // allow these.
285 }
286 else if( actual == 0 && otherNet && testShorting )
287 {
288 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SHORTING_ITEMS );
289 drcItem->SetErrorDetail( wxString::Format( _( "(nets %s and %s)" ),
290 itemNet ? itemNet->GetNetname() : _( "<no net>" ),
291 otherNet ? otherNet->GetNetname() : _( "<no net>" ) ) );
292 drcItem->SetItems( item, other );
293 reportTwoPointGeometry( drcItem, pos, pos, pos, layer );
294 has_error = true;
295
296 if( !m_drcEngine->GetReportAllTrackErrors() )
297 return false;
298 }
299 else if( testClearance )
300 {
301 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
302 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
303 constraint.GetName(),
304 clearance,
305 actual ) );
306 drcItem->SetItems( item, other );
307 drcItem->SetViolatingRule( constraint.GetParentRule() );
308 reportTwoShapeGeometry( drcItem, pos, itemShape, otherShape, layer, actual );
309 has_error = true;
310
311 if( !m_drcEngine->GetReportAllTrackErrors() )
312 return false;
313 }
314 }
315 }
316
317 if( testHoles && ( item->HasHole() || other->HasHole() ) )
318 {
319 std::array<BOARD_ITEM*, 2> a{ item, other };
320 std::array<BOARD_ITEM*, 2> b{ other, item };
321 std::array<NETINFO_ITEM*, 2> b_net{ otherNet, itemNet };
322 std::array<SHAPE*, 2> a_shape{ itemShape, otherShape };
323
324 for( size_t ii = 0; ii < 2; ++ii )
325 {
326 std::shared_ptr<SHAPE_SEGMENT> holeShape;
327
328 if( b[ii]->Type() == PCB_VIA_T )
329 {
330 if( b[ii]->GetLayerSet().Contains( layer ) )
331 holeShape = b[ii]->GetEffectiveHoleShape();
332 else
333 continue;
334 }
335 else
336 {
337 if( b[ii]->HasHole() )
338 holeShape = b[ii]->GetEffectiveHoleShape();
339 else
340 continue;
341 }
342
343 int netcode = b_net[ii] ? b_net[ii]->GetNetCode() : 0;
344
345 if( netcode && m_drcEngine->IsNetTieExclusion( netcode, layer, holeShape->Centre(), a[ii] ) )
346 continue;
347
348 constraint = m_drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, b[ii], a[ii], layer );
349 clearance = constraint.GetValue().Min();
350
351 // Test for hole to item clearance even if clearance is 0, because the item cannot be
352 // inside (or intersect) the hole.
353 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE )
354 {
355 if( a_shape[ii]->Collide( holeShape.get(), sub_e( clearance ), &actual, &pos ) )
356 {
357 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE );
358 drcItem->SetErrorDetail( formatMsg( clearance ? _( "(%s clearance %s; actual %s)" )
359 : _( "(%s clearance %s; actual < 0)" ),
360 constraint.GetName(),
361 clearance,
362 actual ) );
363 drcItem->SetItems( a[ii], b[ii] );
364 drcItem->SetViolatingRule( constraint.GetParentRule() );
365 reportTwoShapeGeometry( drcItem, pos, a_shape[ii], holeShape.get(), layer, actual );
366 return false;
367 }
368 }
369 }
370 }
371
372 return !has_error;
373}
374
375
377 PCB_LAYER_ID aLayer )
378{
379 if( !aZone->GetLayerSet().test( aLayer ) )
380 return;
381
382 if( aZone->GetNetCode() && aItem->IsConnected() )
383 {
384 if( aZone->GetNetCode() == static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode() )
385 return;
386 }
387
388 BOX2I itemBBox = aItem->GetBoundingBox();
389 BOX2I worstCaseBBox = itemBBox;
390
391 worstCaseBBox.Inflate( m_board->m_DRCMaxClearance );
392
393 if( !worstCaseBBox.Intersects( aZone->GetBoundingBox() ) )
394 return;
395
396 FOOTPRINT* parentFP = aItem->GetParentFootprint();
397
398 // Ignore graphic items which implement a net-tie to the zone's net on the layer being tested.
399 if( parentFP && parentFP->IsNetTie() && dynamic_cast<PCB_SHAPE*>( aItem ) )
400 {
401 std::set<PAD*> allowedNetTiePads;
402
403 for( PAD* pad : parentFP->Pads() )
404 {
405 if( pad->GetNetCode() == aZone->GetNetCode() && aZone->GetNetCode() != 0 )
406 {
407 if( pad->IsOnLayer( aLayer ) )
408 allowedNetTiePads.insert( pad );
409
410 for( PAD* other : parentFP->GetNetTiePads( pad ) )
411 {
412 if( other->IsOnLayer( aLayer ) )
413 allowedNetTiePads.insert( other );
414 }
415 }
416 }
417
418 if( !allowedNetTiePads.empty() )
419 {
420 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape();
421
422 for( PAD* pad : allowedNetTiePads )
423 {
424 if( pad->GetBoundingBox().Intersects( itemBBox )
425 && pad->GetEffectiveShape( aLayer )->Collide( itemShape.get() ) )
426 {
427 return;
428 }
429 }
430 }
431 }
432
433 bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE );
434 bool testHoles = !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE );
435
436 if( !testClearance && !testHoles )
437 return;
438
439 DRC_RTREE* zoneTree = m_board->m_CopperZoneRTreeCache[ aZone ].get();
440
441 if( !zoneTree )
442 return;
443
444 DRC_CONSTRAINT constraint;
445 int clearance = -1;
446 int actual;
447 VECTOR2I pos;
448
449 if( aItem->Type() == PCB_PAD_T )
450 {
451 PAD* pad = static_cast<PAD*>( aItem );
452 bool flashedPad = pad->FlashLayer( aLayer );
453 bool platedHole = pad->HasHole() && pad->GetAttribute() == PAD_ATTRIB::PTH;
454
455 if( !flashedPad && !platedHole )
456 testClearance = false;
457 }
458
459 if( testClearance )
460 {
461 constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, aItem, aZone, aLayer );
462 clearance = constraint.GetValue().Min();
463 }
464
465 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 )
466 {
467 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aLayer, FLASHING::DEFAULT );
468
469 if( zoneTree->QueryColliding( itemBBox, itemShape.get(), aLayer, sub_e( clearance ), &actual, &pos ) )
470 {
471 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
472 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
473 constraint.GetName(),
474 clearance,
475 actual ) );
476 drcItem->SetItems( aItem, aZone );
477 drcItem->SetViolatingRule( constraint.GetParentRule() );
478 reportTwoItemGeometry( drcItem, pos, aItem, aZone, aLayer, actual );
479 }
480 }
481
482 if( testHoles && aItem->HasHole() )
483 {
484 std::shared_ptr<SHAPE_SEGMENT> holeShape;
485
486 if( aItem->Type() == PCB_VIA_T )
487 {
488 if( aItem->GetLayerSet().Contains( aLayer ) )
489 holeShape = aItem->GetEffectiveHoleShape();
490 }
491 else
492 {
493 holeShape = aItem->GetEffectiveHoleShape();
494 }
495
496 if( holeShape )
497 {
498 constraint = m_drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, aItem, aZone, aLayer );
499 clearance = constraint.GetValue().Min();
500
501 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 )
502 {
503 if( zoneTree->QueryColliding( itemBBox, holeShape.get(), aLayer, sub_e( clearance ), &actual, &pos ) )
504 {
505 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE );
506 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
507 constraint.GetName(),
508 clearance,
509 actual ) );
510 drcItem->SetItems( aItem, aZone );
511 drcItem->SetViolatingRule( constraint.GetParentRule() );
512
513 std::shared_ptr<SHAPE> zoneShape = aZone->GetEffectiveShape( aLayer );
514 reportTwoShapeGeometry( drcItem, pos, holeShape.get(), zoneShape.get(), aLayer, actual );
515 }
516 }
517 }
518 }
519}
520
521
522/*
523 * We have to special-case knockout text as it's most often knocked-out of a zone, so it's
524 * presumed to collide with one. However, if it collides with more than one, and they have
525 * different nets, then we have a short.
526 */
528 NETINFO_ITEM** aInheritedNet,
529 ZONE* aZone )
530{
531 bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE );
532 bool testShorts = !m_drcEngine->IsErrorLimitExceeded( DRCE_SHORTING_ITEMS );
533
534 if( !testClearance && !testShorts )
535 return;
536
537 PCB_LAYER_ID layer = aText->GetLayer();
538
539 if( !aZone->GetLayerSet().test( layer ) )
540 return;
541
542 BOX2I itemBBox = aText->GetBoundingBox();
543 BOX2I worstCaseBBox = itemBBox;
544
545 worstCaseBBox.Inflate( m_board->m_DRCMaxClearance );
546
547 if( !worstCaseBBox.Intersects( aZone->GetBoundingBox() ) )
548 return;
549
550 DRC_RTREE* zoneTree = m_board->m_CopperZoneRTreeCache[ aZone ].get();
551
552 if( !zoneTree )
553 return;
554
555 std::shared_ptr<SHAPE> itemShape = aText->GetEffectiveShape( layer, FLASHING::DEFAULT );
556
557 if( *aInheritedNet == nullptr )
558 {
559 if( zoneTree->QueryColliding( itemBBox, itemShape.get(), layer ) )
560 *aInheritedNet = aZone->GetNet();
561 }
562
563 if( *aInheritedNet == aZone->GetNet() )
564 return;
565
566 DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, aText, aZone, layer );
567 int clearance = constraint.GetValue().Min();
568 int actual;
569 VECTOR2I pos;
570
571 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
572 {
573 if( zoneTree->QueryColliding( itemBBox, itemShape.get(), layer, sub_e( clearance ), &actual, &pos ) )
574 {
575 std::shared_ptr<DRC_ITEM> drcItem;
576
577 if( testShorts && actual == 0 && *aInheritedNet )
578 {
580 drcItem->SetErrorDetail( wxString::Format( _( "(nets %s and %s)" ),
581 ( *aInheritedNet )->GetNetname(),
582 aZone->GetNetname() ) );
583 }
584 else
585 {
586 drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
587 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
588 constraint.GetName(),
589 clearance,
590 actual ) );
591 }
592
593 drcItem->SetItems( aText, aZone );
594 drcItem->SetViolatingRule( constraint.GetParentRule() );
595 reportTwoItemGeometry( drcItem, pos, aText, aZone, layer, actual );
596 }
597 }
598}
599
600
602{
603 std::map<BOARD_ITEM*, int> freePadsUsageMap;
604 std::mutex freePadsUsageMapMutex;
605 std::atomic<size_t> done( 0 );
606 size_t count = m_board->Tracks().size();
607
608 REPORT_AUX( wxString::Format( wxT( "Testing %d tracks & vias..." ), count ) );
609
610 LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
611
612 auto testTrack =
613 [&]( const int trackIdx )
614 {
615 PCB_TRACK* track = m_board->Tracks()[trackIdx];
616
617 for( PCB_LAYER_ID layer : LSET( track->GetLayerSet() & boardCopperLayers ) )
618 {
619 std::shared_ptr<SHAPE> trackShape = track->GetEffectiveShape( layer );
620
621 m_board->m_CopperItemRTreeCache->QueryColliding( track, layer, layer,
622 // Filter:
623 [&]( BOARD_ITEM* other ) -> bool
624 {
625 if( other->IsConnected()
626 && static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode()
627 == track->GetNetCode() )
628 {
629 return false;
630 }
631
632 // For track-vs-track pairs, use pointer ordering to ensure each
633 // pair is tested exactly once across all threads, eliminating the
634 // need for a shared checkedPairs mutex.
635 KICAD_T otherType = other->Type();
636
637 if( ( otherType == PCB_TRACE_T || otherType == PCB_ARC_T
638 || otherType == PCB_VIA_T )
639 && static_cast<void*>( track ) > static_cast<void*>( other ) )
640 {
641 return false;
642 }
643
644 return true;
645 },
646 // Visitor:
647 [&]( BOARD_ITEM* other ) -> bool
648 {
649 if( m_drcEngine->IsCancelled() )
650 return false;
651
652 if( other->Type() == PCB_PAD_T && static_cast<PAD*>( other )->IsFreePad() )
653 {
654 if( other->GetEffectiveShape( layer )->Collide( trackShape.get() ) )
655 {
656 std::lock_guard<std::mutex> lock( freePadsUsageMapMutex );
657 auto it = freePadsUsageMap.find( other );
658
659 if( it == freePadsUsageMap.end() )
660 {
661 freePadsUsageMap[ other ] = track->GetNetCode();
662 return true; // Continue colliding tests
663 }
664 else if( it->second == track->GetNetCode() )
665 {
666 return true; // Continue colliding tests
667 }
668 }
669 }
670
671 if( !testSingleLayerItemAgainstItem( track, trackShape.get(),
672 layer, other ) )
673 {
674 if( !m_drcEngine->GetReportAllTrackErrors() )
675 return false;
676 }
677
678 return !m_drcEngine->IsCancelled();
679 },
680 m_board->m_DRCMaxClearance );
681
682 auto zoneIt = m_board->m_DRCCopperZonesByLayer.find( layer );
683
684 if( zoneIt != m_board->m_DRCCopperZonesByLayer.end() )
685 {
686 for( ZONE* zone : zoneIt->second )
687 {
688 testItemAgainstZone( track, zone, layer );
689
690 if( m_drcEngine->IsCancelled() )
691 break;
692 }
693 }
694 }
695
696 done.fetch_add( 1 );
697 };
698
700
701 auto track_futures = tp.submit_loop( 0, m_board->Tracks().size(), testTrack,
702 m_board->Tracks().size() );
703
704 while( done < count )
705 {
706 reportProgress( done, count );
707
708 if( m_drcEngine->IsCancelled() )
709 {
710 // Wait for the submitted loop tasks to finish
711 track_futures.wait();
712 break;
713 }
714
715 track_futures.wait_for( std::chrono::milliseconds( 250 ) );
716 }
717}
718
719
721 PCB_LAYER_ID aLayer,
722 BOARD_ITEM* other )
723{
724 bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE );
725 bool testShorting = !m_drcEngine->IsErrorLimitExceeded( DRCE_SHORTING_ITEMS );
726 bool testHoles = !m_drcEngine->IsErrorLimitExceeded( DRCE_HOLE_CLEARANCE );
727
728 // Disable some tests for net-tie objects in a footprint
729 if( other->GetParent() == pad->GetParent() )
730 {
731 FOOTPRINT* fp = pad->GetParentFootprint();
732 std::map<wxString, int> padToNetTieGroupMap = fp->MapPadNumbersToNetTieGroups();
733 int padGroupIdx = padToNetTieGroupMap[ pad->GetNumber() ];
734
735 if( other->Type() == PCB_PAD_T )
736 {
737 PAD* otherPad = static_cast<PAD*>( other );
738
739 if( padGroupIdx >= 0 && padGroupIdx == padToNetTieGroupMap[ otherPad->GetNumber() ] )
740 testClearance = testShorting = false;
741
742 if( pad->SameLogicalPadAs( otherPad ) )
743 testHoles = false;
744 }
745
746 if( other->Type() == PCB_SHAPE_T && padGroupIdx >= 0 )
747 testClearance = testShorting = false;
748 }
749
750 BOARD_CONNECTED_ITEM* otherCItem = other->IsConnected()
751 ? static_cast<BOARD_CONNECTED_ITEM*>( other )
752 : nullptr;
753 PAD* otherPad = nullptr;
754 PCB_VIA* otherVia = nullptr;
755
756 if( other->Type() == PCB_PAD_T )
757 otherPad = static_cast<PAD*>( other );
758
759 if( other->Type() == PCB_VIA_T )
760 otherVia = static_cast<PCB_VIA*>( other );
761
762 if( !IsCopperLayer( aLayer ) )
763 testClearance = testShorting = false;
764
765 // A NPTH has no cylinder, but it may still have pads on some layers
766 if( pad->GetAttribute() == PAD_ATTRIB::NPTH && !pad->FlashLayer( aLayer ) )
767 testClearance = testShorting = false;
768
769 if( otherPad && otherPad->GetAttribute() == PAD_ATTRIB::NPTH && !otherPad->FlashLayer( aLayer ) )
770 testClearance = testShorting = false;
771
772 // Track clearances are tested in testTrackClearances()
773 if( other->Type() == PCB_TRACE_T || other->Type() == PCB_ARC_T || other->Type() == PCB_VIA_T )
774 testClearance = testShorting = false;
775
776 // Graphic clearances are tested in testGraphicClearances()
777 if( other->Type() == PCB_SHAPE_T || other->Type() == PCB_TEXTBOX_T )
778 testClearance = testShorting = false;
779
780 int padNet = pad->GetNetCode();
781 int otherNet = otherCItem ? otherCItem->GetNetCode() : 0;
782
783 // Other objects of the same (defined) net get a waiver on clearance and hole tests
784 if( otherNet && otherNet == padNet )
785 {
786 testClearance = testShorting = false;
787 testHoles = false;
788 }
789
790 if( !( pad->GetDrillSize().x > 0 )
791 && !( otherPad && otherPad->GetDrillSize().x > 0 )
792 && !( otherVia && otherVia->GetDrill() > 0 ) )
793 {
794 testHoles = false;
795 }
796
797 if( !testClearance && !testShorting && !testHoles )
798 return true;
799
800 std::shared_ptr<SHAPE> otherShape = other->GetEffectiveShape( aLayer );
801 DRC_CONSTRAINT constraint;
802 int clearance = 0;
803 int actual = 0;
804 VECTOR2I pos;
805 bool has_error = false;
806
807 if( otherPad && pad->SameLogicalPadAs( otherPad ) )
808 {
809 // If pads are equivalent (ie: from the same footprint with the same pad number)...
810 // ... and have "real" nets...
811 // then they must be the same net
812 if( testShorting )
813 {
814 if( pad->GetNetCode() == 0 || pad->GetNetCode() == otherPad->GetNetCode() )
815 return true;
816
817 if( pad->GetShortNetname().StartsWith( wxS( "unconnected-(" ) )
818 && otherPad->GetShortNetname().StartsWith( wxS( "unconnected-(" ) ) )
819 {
820 return true;
821 }
822
823 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SHORTING_ITEMS );
824 drcItem->SetErrorDetail( wxString::Format( _( "(nets %s and %s)" ),
825 pad->GetNetname(),
826 otherPad->GetNetname() ) );
827 drcItem->SetItems( pad, otherPad );
828 reportViolation( drcItem, otherPad->GetPosition(), aLayer );
829 has_error = true;
830 }
831
832 return !has_error;
833 }
834
835 if( testClearance || testShorting )
836 {
837 constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, pad, other, aLayer );
838 clearance = constraint.GetValue().Min();
839
840 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 )
841 {
842 if( padShape->Collide( otherShape.get(), sub_e( clearance ), &actual, &pos ) )
843 {
844 if( m_drcEngine->IsNetTieExclusion( pad->GetNetCode(), aLayer, pos, other ) )
845 {
846 // Pads connected to pads of a net-tie footprint are allowed to collide
847 // with the net-tie footprint's graphics.
848 }
849 else if( actual == 0 && padNet && otherNet && testShorting )
850 {
851 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_SHORTING_ITEMS );
852 drcItem->SetErrorDetail( wxString::Format( _( "(nets %s and %s)" ),
853 pad->GetNetname(),
854 otherCItem->GetNetname() ) );
855 drcItem->SetItems( pad, other );
856 reportTwoPointGeometry( drcItem, pos, pos, pos, aLayer );
857 has_error = true;
858 testHoles = false; // No need for multiple violations
859 }
860 else if( testClearance )
861 {
862 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
863 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
864 constraint.GetName(),
865 clearance,
866 actual ) );
867 drcItem->SetItems( pad, other );
868 drcItem->SetViolatingRule( constraint.GetParentRule() );
869 reportTwoItemGeometry( drcItem, pos, pad, other, aLayer, actual );
870 has_error = true;
871 testHoles = false; // No need for multiple violations
872 }
873 }
874 }
875 }
876
877 auto doTestHole =
878 [&]( BOARD_ITEM* item, SHAPE* shape, BOARD_ITEM* otherItem, SHAPE* aOtherShape, int aClearance )
879 {
880 if( shape->Collide( aOtherShape, sub_e( aClearance ), &actual, &pos ) )
881 {
882 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_HOLE_CLEARANCE );
883 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
884 constraint.GetName(),
885 aClearance,
886 actual ) );
887 drcItem->SetItems( item, otherItem );
888 drcItem->SetViolatingRule( constraint.GetParentRule() );
889 reportTwoShapeGeometry( drcItem, pos, shape, aOtherShape, aLayer, actual );
890 has_error = true;
891 testHoles = false; // No need for multiple violations
892 }
893 };
894
895 if( testHoles )
896 {
897 constraint = m_drcEngine->EvalRules( HOLE_CLEARANCE_CONSTRAINT, pad, other, aLayer );
898
899 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE )
900 testHoles = false;
901 }
902
903 if( testHoles && otherPad && otherPad->HasHole() && pad->FlashLayer( aLayer ) )
904 {
905 clearance = constraint.GetValue().Min();
906
907 if( clearance > 0 )
908 doTestHole( pad, padShape, otherPad, otherPad->GetEffectiveHoleShape().get(), clearance );
909 }
910
911 // Pad pairs are deduplicated by pointer order in testPadClearances.
912 // Run the swapped direction so we don't miss any violations.
913 if( testHoles && pad->HasHole() && otherPad && otherPad->FlashLayer( aLayer ) )
914 {
915 clearance = constraint.GetValue().Min();
916
917 if( clearance > 0 )
918 doTestHole( otherPad, otherShape.get(), pad, pad->GetEffectiveHoleShape().get(), clearance );
919 }
920
921 if( testHoles && otherVia && otherVia->HasHole() )
922 {
923 clearance = constraint.GetValue().Min();
924
925 if( !otherVia->IsOnLayer( aLayer ) )
926 clearance = 0;
927
928 if( clearance > 0 )
929 doTestHole( pad, padShape, otherVia, otherVia->GetEffectiveHoleShape().get(), clearance );
930 }
931
932 return !has_error;
933}
934
935
937{
939 std::atomic<size_t> done( 1 );
940
941 LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
942
943 const auto fp_check =
944 [&]( size_t ii )
945 {
946 FOOTPRINT* footprint = m_board->Footprints()[ ii ];
947
948 for( PAD* pad : footprint->Pads() )
949 {
950 // Through-hole pads are tested per-layer, so overlapping TH pads
951 // may produce one violation per shared copper layer. This is
952 // intentional for parallelized DRC to avoid cross-layer shared state.
953 for( PCB_LAYER_ID layer : LSET( pad->GetLayerSet() & boardCopperLayers ) )
954 {
955 if( m_drcEngine->IsCancelled() )
956 return;
957
958 std::shared_ptr<SHAPE> padShape = pad->GetEffectiveShape( layer );
959
960 m_board->m_CopperItemRTreeCache->QueryColliding( pad, layer, layer,
961 // Filter:
962 [&]( BOARD_ITEM* other ) -> bool
963 {
964 // For pad-vs-pad pairs, use pointer ordering to ensure
965 // each pair is tested only once across all threads.
966 if( other->Type() == PCB_PAD_T
967 && static_cast<void*>( pad )
968 > static_cast<void*>( other ) )
969 {
970 return false;
971 }
972
973 return true;
974 },
975 // Visitor
976 [&]( BOARD_ITEM* other ) -> bool
977 {
978 testPadAgainstItem( pad, padShape.get(), layer, other );
979 return !m_drcEngine->IsCancelled();
980 },
981 m_board->m_DRCMaxClearance );
982
983 auto zoneIt = m_board->m_DRCCopperZonesByLayer.find( layer );
984
985 if( zoneIt != m_board->m_DRCCopperZonesByLayer.end() )
986 {
987 for( ZONE* zone : zoneIt->second )
988 {
989 testItemAgainstZone( pad, zone, layer );
990
991 if( m_drcEngine->IsCancelled() )
992 return;
993 }
994 }
995 }
996 }
997
998 done.fetch_add( 1 );
999 };
1000
1001 size_t numFootprints = m_board->Footprints().size();
1002 auto returns = tp.submit_loop( 0, numFootprints, fp_check, numFootprints );
1003
1004 // Wait for all threads to finish
1005 for( size_t ii = 0; ii < returns.size(); ++ii )
1006 {
1007 while( returns[ii].wait_for( std::chrono::milliseconds( 250 ) ) != std::future_status::ready )
1008 reportProgress( done, numFootprints );
1009 }
1010}
1011
1012
1014{
1016 size_t count = m_board->Drawings().size();
1017 std::atomic<size_t> done( 1 );
1018
1019 for( FOOTPRINT* footprint : m_board->Footprints() )
1020 count += footprint->GraphicalItems().size() + footprint->GetFields().size();
1021
1022 REPORT_AUX( wxString::Format( wxT( "Testing %d graphics..." ), count ) );
1023
1024 auto isKnockoutText =
1025 []( BOARD_ITEM* item )
1026 {
1027 return ( item->Type() == PCB_TEXT_T || item->Type() == PCB_FIELD_T )
1028 && static_cast<PCB_TEXT*>( item )->IsKnockout();
1029 };
1030
1031 auto testGraphicAgainstZone =
1032 [this, isKnockoutText]( BOARD_ITEM* item )
1033 {
1034 if( item->Type() == PCB_REFERENCE_IMAGE_T || isInvisibleText( item ) )
1035 return;
1036
1037 if( !IsCopperLayer( item->GetLayer() ) )
1038 return;
1039
1040 // Knockout text is most often knocked-out of a zone, so it's presumed to
1041 // collide with one. However, if it collides with more than one, and they
1042 // have different nets, then we have a short.
1043 NETINFO_ITEM* inheritedNet = nullptr;
1044
1045 PCB_LAYER_ID layer = item->GetLayer();
1046 auto zoneIt = m_board->m_DRCCopperZonesByLayer.find( layer );
1047
1048 if( zoneIt != m_board->m_DRCCopperZonesByLayer.end() )
1049 {
1050 for( ZONE* zone : zoneIt->second )
1051 {
1052 if( isKnockoutText( item ) )
1053 testKnockoutTextAgainstZone( item, &inheritedNet, zone );
1054 else
1055 testItemAgainstZone( item, zone, layer );
1056
1057 if( m_drcEngine->IsCancelled() )
1058 return;
1059 }
1060 }
1061 };
1062
1063 auto testCopperGraphic =
1064 [this]( BOARD_ITEM* graphic )
1065 {
1066 PCB_LAYER_ID layer = graphic->GetLayer();
1067
1068 m_board->m_CopperItemRTreeCache->QueryColliding( graphic, layer, layer,
1069 // Filter:
1070 [&]( BOARD_ITEM* other ) -> bool
1071 {
1072 // Graphics are often compound shapes so ignore collisions
1073 // between shapes in a single footprint.
1074 if( graphic->Type() == PCB_SHAPE_T && other->Type() == PCB_SHAPE_T
1075 && graphic->GetParentFootprint()
1076 && graphic->GetParentFootprint()
1077 == other->GetParentFootprint() )
1078 {
1079 return false;
1080 }
1081
1082 // Track clearances are tested in testTrackClearances()
1083 if( other->Type() == PCB_TRACE_T || other->Type() == PCB_ARC_T
1084 || other->Type() == PCB_VIA_T )
1085 {
1086 return false;
1087 }
1088
1089 int graphicNet = graphic->IsConnected()
1090 ? static_cast<BOARD_CONNECTED_ITEM*>( graphic )->GetNetCode()
1091 : 0;
1092 int otherNet = other->IsConnected()
1093 ? static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode()
1094 : 0;
1095
1096 if( graphicNet && graphicNet == otherNet )
1097 return false;
1098
1099 // For graphic-graphic pairs, use pointer ordering for dedup
1100 if( ( other->Type() == PCB_SHAPE_T
1101 || other->Type() == PCB_TEXTBOX_T
1102 || other->Type() == PCB_BARCODE_T )
1103 && static_cast<void*>( graphic )
1104 > static_cast<void*>( other ) )
1105 {
1106 return false;
1107 }
1108
1109 return true;
1110 },
1111 // Visitor:
1112 [&]( BOARD_ITEM* other ) -> bool
1113 {
1114 testSingleLayerItemAgainstItem( graphic, graphic->GetEffectiveShape().get(),
1115 layer, other );
1116
1117 return !m_drcEngine->IsCancelled();
1118 },
1119 m_board->m_DRCMaxClearance );
1120 };
1121
1122 for( BOARD_ITEM* item : m_board->Drawings() )
1123 {
1124 (void)tp.submit_task(
1125 [this, item, &done, testGraphicAgainstZone, testCopperGraphic]()
1126 {
1127 if( !m_drcEngine->IsCancelled() )
1128 {
1129 testGraphicAgainstZone( item );
1130
1131 if( ( item->Type() == PCB_SHAPE_T || item->Type() == PCB_BARCODE_T )
1132 && item->IsOnCopperLayer() )
1133 {
1134 testCopperGraphic( static_cast<PCB_SHAPE*>( item ) );
1135 }
1136
1137 done.fetch_add( 1 );
1138 }
1139 } );
1140 }
1141
1142 for( FOOTPRINT* footprint : m_board->Footprints() )
1143 {
1144 (void)tp.submit_task(
1145 [this, footprint, &done, testGraphicAgainstZone, testCopperGraphic]()
1146 {
1147 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1148 {
1149 if( !m_drcEngine->IsCancelled() )
1150 {
1151 testGraphicAgainstZone( item );
1152
1153 if( ( item->Type() == PCB_SHAPE_T || item->Type() == PCB_BARCODE_T )
1154 && item->IsOnCopperLayer() )
1155 {
1156 testCopperGraphic( static_cast<PCB_SHAPE*>( item ) );
1157 }
1158
1159 done.fetch_add( 1 );
1160 }
1161 }
1162
1163 // Fields (reference, value, etc.) live in their own list but render as real
1164 // copper when placed on a copper layer, so they must be tested too.
1165 for( PCB_FIELD* field : footprint->GetFields() )
1166 {
1167 if( !m_drcEngine->IsCancelled() )
1168 {
1169 testGraphicAgainstZone( field );
1170 done.fetch_add( 1 );
1171 }
1172 }
1173 } );
1174 }
1175
1176 while( true )
1177 {
1178 reportProgress( done, count );
1179
1180 if( m_drcEngine->IsCancelled() )
1181 break;
1182
1183 if( tp.wait_for( std::chrono::milliseconds( 250 ) ) )
1184 break;
1185 }
1186}
1187
1188
1190{
1191 LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
1192
1193 for( ZONE* teardrop : m_board->m_DRCCopperZones )
1194 {
1195 if( !teardrop->IsTeardropArea() )
1196 continue;
1197
1198 for( PCB_LAYER_ID layer : LSET( teardrop->GetLayerSet() & boardCopperLayers ) )
1199 {
1200 if( m_drcEngine->IsCancelled() )
1201 return;
1202
1203 auto zoneIt = m_board->m_DRCCopperZonesByLayer.find( layer );
1204
1205 if( zoneIt == m_board->m_DRCCopperZonesByLayer.end() )
1206 continue;
1207
1208 for( ZONE* zone : zoneIt->second )
1209 {
1210 if( zone == teardrop )
1211 continue;
1212
1213 // For teardrop-vs-teardrop pairs, use pointer ordering so each
1214 // pair is tested only once.
1215 if( zone->IsTeardropArea() && zone < teardrop )
1216 continue;
1217
1218 testItemAgainstZone( teardrop, zone, layer );
1219
1220 if( m_drcEngine->IsCancelled() )
1221 return;
1222 }
1223 }
1224 }
1225}
1226
1227
1229{
1230 using SEG_RTREE = KIRTREE::PACKED_RTREE<size_t, int, 2>;
1231
1232 bool testClearance = !m_drcEngine->IsErrorLimitExceeded( DRCE_CLEARANCE );
1233 bool testIntersects = !m_drcEngine->IsErrorLimitExceeded( DRCE_ZONES_INTERSECT );
1234
1235 std::vector<std::map<PCB_LAYER_ID, std::vector<SEG>>> poly_segments( m_board->m_DRCCopperZones.size() );
1236 std::vector<std::map<PCB_LAYER_ID, SEG_RTREE>> seg_rtrees( m_board->m_DRCCopperZones.size() );
1237
1239 std::atomic<size_t> done( 0 );
1240 size_t count = 0;
1241
1242 auto reportZoneZoneViolation =
1243 [this]( ZONE* zoneA, ZONE* zoneB, VECTOR2I& pt, int actual, const DRC_CONSTRAINT& constraint,
1244 PCB_LAYER_ID layer ) -> void
1245 {
1246 std::shared_ptr<DRC_ITEM> drcItem;
1247
1248 if( constraint.IsNull() )
1249 {
1251 drcItem->SetErrorDetail( _( "(intersecting zones must have distinct priorities)" ) );
1252 drcItem->SetItems( zoneA, zoneB );
1253 reportViolation( drcItem, pt, layer );
1254 }
1255 else
1256 {
1257 drcItem = DRC_ITEM::Create( DRCE_CLEARANCE );
1258 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
1259 constraint.GetName(),
1260 constraint.GetValue().Min(),
1261 std::max( actual, 0 ) ) );
1262 drcItem->SetItems( zoneA, zoneB );
1263 drcItem->SetViolatingRule( constraint.GetParentRule() );
1264 reportTwoItemGeometry( drcItem, pt, zoneA, zoneB, layer, actual );
1265 }
1266 };
1267
1268 auto checkZones =
1269 [this, testClearance, testIntersects, reportZoneZoneViolation,
1270 &poly_segments, &seg_rtrees, &done]
1271 ( int zoneA_idx, int zoneB_idx, bool sameNet, PCB_LAYER_ID layer ) -> void
1272 {
1273 ZONE* zoneA = m_board->m_DRCCopperZones[zoneA_idx];
1274 ZONE* zoneB = m_board->m_DRCCopperZones[zoneB_idx];
1275 int actual = 0;
1276 VECTOR2I pt;
1277
1278 if( sameNet && testIntersects )
1279 {
1280 SHAPE_POLY_SET zoneAOutline = zoneA->GetBoardOutline();
1281 SHAPE_POLY_SET zoneBOutline = zoneB->GetBoardOutline();
1282
1283 if( zoneAOutline.Collide( &zoneBOutline, 0, &actual, &pt ) )
1284 {
1285 done.fetch_add( 1 );
1286 reportZoneZoneViolation( zoneA, zoneB, pt, actual, DRC_CONSTRAINT(), layer );
1287 return;
1288 }
1289 }
1290 else if( !sameNet && testClearance )
1291 {
1292 DRC_CONSTRAINT constraint = m_drcEngine->EvalRules( CLEARANCE_CONSTRAINT, zoneA, zoneB, layer );
1293 int clearance = constraint.GetValue().Min();
1294
1295 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance > 0 )
1296 {
1297 std::vector<SEG>& refSegments = poly_segments[zoneA_idx][layer];
1298 std::vector<SEG>& testSegments = poly_segments[zoneB_idx][layer];
1299
1300 auto testIt = seg_rtrees[zoneB_idx].find( layer );
1301
1302 if( testIt != seg_rtrees[zoneB_idx].end() && !testIt->second.empty() )
1303 {
1304 const SEG_RTREE& testTree = testIt->second;
1305
1306 for( SEG& refSegment : refSegments )
1307 {
1308 int minX = std::min( refSegment.A.x, refSegment.B.x ) - clearance;
1309 int minY = std::min( refSegment.A.y, refSegment.B.y ) - clearance;
1310 int maxX = std::max( refSegment.A.x, refSegment.B.x ) + clearance;
1311 int maxY = std::max( refSegment.A.y, refSegment.B.y ) + clearance;
1312 int qmin[2] = { minX, minY };
1313 int qmax[2] = { maxX, maxY };
1314 bool found = false;
1315
1316 auto visitor = [&]( size_t segIdx ) -> bool
1317 {
1318 SEG& testSegment = testSegments[segIdx];
1319 int64_t dist_sq = 0;
1320 VECTOR2I other_pt;
1321
1322 refSegment.NearestPoints( testSegment, pt, other_pt, dist_sq );
1323 actual = std::floor( std::sqrt( dist_sq ) + 0.5 );
1324
1325 if( actual < clearance )
1326 {
1327 found = true;
1328 return false;
1329 }
1330
1331 return true;
1332 };
1333
1334 testTree.Search( qmin, qmax, visitor );
1335
1336 if( found )
1337 {
1338 done.fetch_add( 1 );
1339 reportZoneZoneViolation( zoneA, zoneB, pt, actual, constraint,
1340 layer );
1341 return;
1342 }
1343 }
1344 }
1345 }
1346 }
1347
1348 done.fetch_add( 1 );
1349 };
1350
1351 // Pre-sort zones into layers
1352 std::map<PCB_LAYER_ID, std::vector<size_t>> zone_idx_by_layer;
1353
1354 for ( size_t ii = 0; ii < m_board->m_DRCCopperZones.size(); ii++ )
1355 {
1356 ZONE* zone = m_board->m_DRCCopperZones[ii];
1357
1358 // Teardrop areas are tested as tracks, not zones
1359 if( zone->IsTeardropArea() )
1360 continue;
1361
1362 for( PCB_LAYER_ID layer : zone->GetLayerSet() )
1363 {
1364 if( !IsCopperLayer( layer ) )
1365 continue;
1366
1367 zone_idx_by_layer[layer].push_back( ii );
1368 }
1369 }
1370
1371 for( PCB_LAYER_ID layer : LAYER_RANGE( F_Cu, B_Cu, m_board->GetCopperLayerCount() ) )
1372 {
1373 // Skip over layers not used on the current board
1374 if( !m_board->IsLayerEnabled( layer ) )
1375 continue;
1376
1377 for( size_t ii : zone_idx_by_layer[layer] )
1378 {
1379 if( SHAPE_POLY_SET* poly = m_board->m_DRCCopperZones[ii]->GetFill( layer ) )
1380 {
1381 std::vector<SEG>& zone_layer_poly_segs = poly_segments[ii][layer];
1382 zone_layer_poly_segs.reserve( poly->FullPointCount() );
1383
1384 for( auto it = poly->IterateSegmentsWithHoles(); it; it++ )
1385 {
1386 SEG seg = *it;
1387
1388 if( seg.A.x > seg.B.x )
1389 seg.Reverse();
1390
1391 zone_layer_poly_segs.push_back( seg );
1392 }
1393
1394 SEG_RTREE::Builder builder;
1395 builder.Reserve( zone_layer_poly_segs.size() );
1396
1397 for( size_t si = 0; si < zone_layer_poly_segs.size(); ++si )
1398 {
1399 const SEG& seg = zone_layer_poly_segs[si];
1400 int smin[2] = { std::min( seg.A.x, seg.B.x ), std::min( seg.A.y, seg.B.y ) };
1401 int smax[2] = { std::max( seg.A.x, seg.B.x ), std::max( seg.A.y, seg.B.y ) };
1402 builder.Add( smin, smax, si );
1403 }
1404
1405 seg_rtrees[ii][layer] = builder.Build();
1406 }
1407 }
1408
1409 for( auto it_a = zone_idx_by_layer[layer].begin(); it_a != zone_idx_by_layer[layer].end(); ++it_a )
1410 {
1411 size_t ia = *it_a;
1412 ZONE* zoneA = m_board->m_DRCCopperZones[ia];
1413
1414 for( auto it_a2 = std::next( it_a ); it_a2 != zone_idx_by_layer[layer].end(); ++it_a2 )
1415 {
1416 size_t ia2 = *it_a2;
1417 ZONE* zoneB = m_board->m_DRCCopperZones[ia2];
1418
1419 bool sameNet = zoneA->GetNetCode() == zoneB->GetNetCode() && zoneA->GetNetCode() >= 0;
1420
1421 if( sameNet && zoneA->GetAssignedPriority() != zoneB->GetAssignedPriority() )
1422 continue;
1423
1424 // rule areas may overlap at will
1425 if( zoneA->GetIsRuleArea() || zoneB->GetIsRuleArea() )
1426 continue;
1427
1428 // Examine a candidate zone: compare zoneB to zoneA
1429 SHAPE_POLY_SET zoneAOutline;
1430 SHAPE_POLY_SET zoneBOutline;
1431 SHAPE_POLY_SET* polyA = nullptr;
1432 SHAPE_POLY_SET* polyB = nullptr;
1433
1434 if( sameNet )
1435 {
1436 zoneAOutline = zoneA->GetBoardOutline();
1437 zoneBOutline = zoneB->GetBoardOutline();
1438 zoneAOutline.BuildBBoxCaches();
1439 zoneBOutline.BuildBBoxCaches();
1440 polyA = &zoneAOutline;
1441 polyB = &zoneBOutline;
1442 }
1443 else
1444 {
1445 polyA = zoneA->GetFill( layer );
1446 polyB = zoneB->GetFill( layer );
1447 }
1448
1449 if( !polyA || !polyB
1450 || !polyA->BBoxFromCaches().Intersects( polyB->BBoxFromCaches() ) )
1451 continue;
1452
1453 count++;
1454 (void)tp.submit_task(
1455 [checkZones, ia, ia2, sameNet, layer]()
1456 {
1457 checkZones( ia, ia2, sameNet, layer );
1458 } );
1459 }
1460 }
1461 }
1462
1463 while( true )
1464 {
1465 reportProgress( done, count );
1466
1467 if( m_drcEngine->IsCancelled() )
1468 break;
1469
1470 if( tp.wait_for( std::chrono::milliseconds( 250 ) ) )
1471 break;
1472 }
1473}
1474
1475namespace detail
1476{
1478}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
NETINFO_ITEM * GetNet() const
Return #NET_INFO object for a given item.
const wxString & GetShortNetname() const
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition board_item.h:265
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:155
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
FOOTPRINT * GetParentFootprint() const
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition board_item.h:285
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:231
virtual std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape() const
virtual bool HasHole() const
Definition board_item.h:177
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
wxString GetName() const
Definition drc_rule.h:204
SEVERITY GetSeverity() const
Definition drc_rule.h:217
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:196
DRC_RULE * GetParentRule() const
Definition drc_rule.h:200
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:417
Implement an R-tree for fast spatial and layer indexing of connectable items.
Definition drc_rtree.h:45
int QueryColliding(BOARD_ITEM *aRefItem, PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer, std::function< bool(BOARD_ITEM *)> aFilter=nullptr, std::function< bool(BOARD_ITEM *)> aVisitor=nullptr, int aClearance=0) const
This is a fast test which essentially does bounding-box overlap given a worst-case clearance.
Definition drc_rtree.h:225
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
bool testSingleLayerItemAgainstItem(BOARD_ITEM *item, SHAPE *itemShape, PCB_LAYER_ID layer, BOARD_ITEM *other)
Checks for track/via/hole <-> clearance.
void testItemAgainstZone(BOARD_ITEM *aItem, ZONE *aZone, PCB_LAYER_ID aLayer)
virtual ~DRC_TEST_PROVIDER_COPPER_CLEARANCE()=default
virtual const wxString GetName() const override
bool testPadAgainstItem(PAD *pad, SHAPE *padShape, PCB_LAYER_ID layer, BOARD_ITEM *other)
void testKnockoutTextAgainstZone(BOARD_ITEM *aText, NETINFO_ITEM **aInheritedNet, ZONE *aZone)
virtual bool reportPhase(const wxString &aStageName)
void reportTwoShapeGeometry(std::shared_ptr< DRC_ITEM > &aDrcItem, const VECTOR2I &aMarkerPos, const SHAPE *aShape1, const SHAPE *aShape2, PCB_LAYER_ID aLayer, int aDistance)
void reportTwoItemGeometry(std::shared_ptr< DRC_ITEM > &aDrcItem, const VECTOR2I &aMarkerPos, const BOARD_ITEM *aItem1, const BOARD_ITEM *aItem2, PCB_LAYER_ID aLayer, int aDistance)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
void reportTwoPointGeometry(std::shared_ptr< DRC_ITEM > &aDrcItem, const VECTOR2I &aMarkerPos, const VECTOR2I &ptA, const VECTOR2I &ptB, PCB_LAYER_ID aLayer)
bool isInvisibleText(const BOARD_ITEM *aItem) const
wxString formatMsg(const wxString &aFormatString, const wxString &aSource, double aConstraint, double aActual, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:135
const KIID m_Uuid
Definition eda_item.h:531
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
std::vector< PAD * > GetNetTiePads(PAD *aPad) const
std::map< wxString, int > MapPadNumbersToNetTieGroups() const
std::deque< PAD * > & Pads()
Definition footprint.h:375
bool IsNetTie() const
Definition footprint.h:520
Static (immutable) packed R-tree built via Hilbert-curve bulk loading.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
T Min() const
Definition minoptmax.h:29
Handle the data for a net.
Definition netinfo.h:46
const wxString & GetNetname() const
Definition netinfo.h:100
int GetNetCode() const
Definition netinfo.h:94
Definition pad.h:61
bool FlashLayer(int aLayer, bool aOnlyCheckIfPermitted=false) const
Check to see whether the pad should be flashed on the specific layer.
Definition pad.cpp:650
PAD_ATTRIB GetAttribute() const
Definition pad.h:555
const wxString & GetNumber() const
Definition pad.h:143
VECTOR2I GetPosition() const override
Definition pad.cpp:245
VECTOR2I GetDrillSize() const
Definition pad.h:315
bool IsFreePad() const
Definition pad.cpp:573
bool HasHole() const override
Definition pad.h:113
std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape() const override
Return a SHAPE_SEGMENT object representing the pad's hole.
Definition pad.cpp:1312
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
const VECTOR2I & GetStart() const
Definition pcb_track.h:93
std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT) const override
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
const VECTOR2I & GetEnd() const
Definition pcb_track.h:90
int GetDrill() const
Return the local drill setting for this PCB_VIA.
Definition pcb_track.h:754
std::shared_ptr< SHAPE_SEGMENT > GetEffectiveHoleShape() const override
bool HasHole() const override
Definition pcb_track.h:472
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition seg.cpp:442
void Reverse()
Definition seg.h:364
Represent a set of closed polygons.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
const BOX2I BBoxFromCaches() const
An abstract shape on 2D plane.
Definition shape.h:124
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:179
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:811
const BOX2I GetBoundingBox() const override
Definition zone.cpp:737
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:704
SHAPE_POLY_SET GetBoardOutline() const
Definition zone.cpp:835
bool IsTeardropArea() const
Definition zone.h:786
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
unsigned GetAssignedPriority() const
Definition zone.h:122
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT) const override
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
Definition zone.cpp:1841
The common library.
@ DRCE_HOLE_CLEARANCE
Definition drc_item.h:51
@ DRCE_ZONES_INTERSECT
Definition drc_item.h:44
@ DRCE_CLEARANCE
Definition drc_item.h:40
@ DRCE_SHORTING_ITEMS
Definition drc_item.h:37
@ DRCE_TRACKS_CROSSING
Definition drc_item.h:42
@ CLEARANCE_CONSTRAINT
Definition drc_rule.h:51
@ HOLE_CLEARANCE_CONSTRAINT
Definition drc_rule.h:53
#define REPORT_AUX(s)
#define _(s)
@ DEFAULT
Flashing follows connectivity.
Definition layer_ids.h:181
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:675
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
@ 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
@ RPT_SEVERITY_IGNORE
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
static bool Collide(const SHAPE_CIRCLE &aA, const SHAPE_CIRCLE &aB, int aClearance, int *aActual, VECTOR2I *aLocation, VECTOR2I *aMTV)
VECTOR2I end
int clearance
int actual
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:27
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:71
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:81
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:90
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:86
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:82
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:83
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:94
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:91
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:89
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683