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