KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_solder_mask.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>
23#include <footprint.h>
24#include <pad.h>
25#include <pcb_track.h>
26#include <pcb_text.h>
27#include <thread_pool.h>
28#include <zone.h>
29#include <geometry/seg.h>
30#include <drc/drc_engine.h>
31#include <drc/drc_item.h>
32#include <drc/drc_rule.h>
34#include <drc/drc_rtree.h>
35
36#include <set>
37
38/*
39 Solder mask tests. Checks for silkscreen which is clipped by mask openings and for bridges
40 between mask apertures with different nets.
41 Errors generated:
42 - DRCE_SILK_MASK_CLEARANCE
43 - DRCE_SOLDERMASK_BRIDGE
44*/
45
47{
48public:
50 m_board( nullptr ),
51 m_webWidth( 0 ),
52 m_maxError( 0 ),
54 {
55 m_bridgeRule.m_Name = _( "board setup solder mask min width" );
56 }
57
58 virtual ~DRC_TEST_PROVIDER_SOLDER_MASK() = default;
59
60 virtual bool Run() override;
61
62 virtual const wxString GetName() const override { return wxT( "solder_mask_issues" ); };
63
64private:
65 void addItemToRTrees( BOARD_ITEM* aItem );
66 void buildRTrees();
67
69 void testMaskBridges();
70
71 void testItemAgainstItems( BOARD_ITEM* aItem, const BOX2I& aItemBBox,
72 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer );
73 void testMaskItemAgainstZones( BOARD_ITEM* item, const BOX2I& itemBBox,
74 PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer );
75
76 bool checkMaskAperture( BOARD_ITEM* aMaskItem, BOARD_ITEM* aTestItem, PCB_LAYER_ID aTestLayer,
77 int aTestNet, BOARD_ITEM** aCollidingItem );
78
79 bool checkItemMask( BOARD_ITEM* aItem, int aTestNet );
80
81private:
83
88
89 std::unique_ptr<DRC_RTREE> m_fullSolderMaskRTree;
90 std::unique_ptr<DRC_RTREE> m_itemTree;
91
93 std::unordered_map<PTR_PTR_CACHE_KEY, LSET> m_checkedPairs;
94
95 // Shapes used to define solder mask apertures don't have nets, so we assign them the
96 // first object+net that bridges their aperture (after which any other nets will generate
97 // violations).
98 //
99 // When "report all track errors" is enabled, we store all items per net so we can report
100 // violations for each pair of items from different nets.
101 std::mutex m_netMapMutex;
102 std::unordered_map<PTR_LAYER_CACHE_KEY, std::pair<BOARD_ITEM*, int>> m_maskApertureNetMap;
103
104 // Extended storage for "report all track errors" mode: stores all items per net per aperture
105 std::unordered_map<PTR_LAYER_CACHE_KEY, std::vector<std::pair<BOARD_ITEM*, int>>> m_maskApertureNetMapAll;
106
107 // Pending collision info for deferred violation reporting (avoids race condition).
108 // Stores info about each mask aperture that bridges different nets.
117
119 std::vector<MASK_APERTURE_COLLISION> m_pendingCollisions;
120};
121
122
124{
125 // Rule areas are purely logical: no copper, no mask, no silk. Skip them entirely
126 // so they cannot contribute to solder-mask bridge or silk-to-mask collisions.
127 if( aItem->Type() == PCB_ZONE_T && static_cast<ZONE*>( aItem )->GetIsRuleArea() )
128 return;
129
130 for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
131 {
132 if( !aItem->IsOnLayer( layer ) )
133 continue;
134
135 SHAPE_POLY_SET* solderMask = m_board->m_SolderMaskBridges->GetFill( layer );
136
137 if( aItem->Type() == PCB_ZONE_T )
138 {
139 ZONE* zone = static_cast<ZONE*>( aItem );
140
141 solderMask->BooleanAdd( *zone->GetFilledPolysList( layer ) );
142 }
143 else
144 {
145 int clearance = m_webWidth / 2;
146
147 if( aItem->Type() == PCB_PAD_T )
148 clearance += static_cast<PAD*>( aItem )->GetSolderMaskExpansion( layer );
149 else if( aItem->Type() == PCB_VIA_T )
150 clearance += static_cast<PCB_VIA*>( aItem )->GetSolderMaskExpansion();
151 else if( aItem->Type() == PCB_SHAPE_T )
152 clearance += static_cast<PCB_SHAPE*>( aItem )->GetSolderMaskExpansion();
153
154 if( aItem->Type() == PCB_FIELD_T || aItem->Type() == PCB_TEXT_T )
155 {
156 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
157
158 text->TransformTextToPolySet( *solderMask, clearance, m_maxError, ERROR_OUTSIDE );
159 }
160 else
161 {
162 aItem->TransformShapeToPolygon( *solderMask, layer, clearance, m_maxError, ERROR_OUTSIDE );
163 }
164
165 m_itemTree->Insert( aItem, layer, m_largestClearance );
166 }
167 }
168}
169
170
172{
173 ZONE* solderMask = m_board->m_SolderMaskBridges;
174 LSET layers( { F_Mask, B_Mask, F_Cu, B_Cu } );
175
176 const size_t progressDelta = 500;
177 int count = 0;
178 int ii = 0;
179
180 solderMask->GetFill( F_Mask )->RemoveAllContours();
181 solderMask->GetFill( B_Mask )->RemoveAllContours();
182
183 m_fullSolderMaskRTree = std::make_unique<DRC_RTREE>();
184 m_itemTree = std::make_unique<DRC_RTREE>();
185
187 [&]( BOARD_ITEM* item ) -> bool
188 {
189 ++count;
190 return true;
191 } );
192
194 [&]( BOARD_ITEM* item ) -> bool
195 {
196 if( !reportProgress( ii++, count, progressDelta ) )
197 return false;
198
199 addItemToRTrees( item );
200 return true;
201 } );
202
203 solderMask->GetFill( F_Mask )->Simplify();
204 solderMask->GetFill( B_Mask )->Simplify();
205
206 if( m_webWidth > 0 )
207 {
210 }
211
212 solderMask->SetFillFlag( F_Mask, true );
213 solderMask->SetFillFlag( B_Mask, true );
214 solderMask->SetIsFilled( true );
215
216 solderMask->CacheTriangulation();
217
218 m_fullSolderMaskRTree->Insert( solderMask, F_Mask );
219 m_fullSolderMaskRTree->Insert( solderMask, B_Mask );
220 m_fullSolderMaskRTree->Build();
221
222 m_itemTree->Build();
223
224 m_checkedPairs.clear();
225}
226
227
229{
230 LSET silkLayers( { F_SilkS, B_SilkS } );
231
232 // If we have no minimum web width then we delegate to the silk checker which does object-to-object
233 // testing (instead of object-to-solder-mask-zone-fill checking that we do here).
234 if( m_webWidth <= 0 )
235 return;
236
237 const size_t progressDelta = 250;
238 int count = 0;
239 int ii = 0;
240
242 [&]( BOARD_ITEM* item ) -> bool
243 {
244 ++count;
245 return true;
246 } );
247
249 [&]( BOARD_ITEM* item ) -> bool
250 {
251 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE ) )
252 return false;
253
254 if( !reportProgress( ii++, count, progressDelta ) )
255 return false;
256
257 if( isInvisibleText( item ) )
258 return true;
259
260 for( PCB_LAYER_ID layer : silkLayers )
261 {
262 if( !item->IsOnLayer( layer ) )
263 continue;
264
265 PCB_LAYER_ID maskLayer = layer == F_SilkS ? F_Mask : B_Mask;
266 BOX2I itemBBox = item->GetBoundingBox();
268 item, nullptr, maskLayer );
269 int clearance = constraint.GetValue().Min();
270 int actual;
271 VECTOR2I pos;
272
273 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance < 0 )
274 return true;
275
276 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( layer );
277
278 if( m_fullSolderMaskRTree->QueryColliding( itemBBox, itemShape.get(), maskLayer,
279 clearance, &actual, &pos ) )
280 {
281 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SILK_MASK_CLEARANCE );
282
283 if( clearance > 0 )
284 {
285 drce->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
286 constraint.GetName(),
287 clearance,
288 actual ) );
289 }
290
291 drce->SetItems( item );
292 drce->SetViolatingRule( constraint.GetParentRule() );
293
294 reportViolation( drce, pos, layer );
295 }
296 }
297
298 return true;
299 } );
300}
301
302
304{
305 if( aItem->Type() == PCB_PAD_T )
306 return static_cast<PAD*>( aItem )->IsNPTHWithNoCopper();
307
308 return false;
309}
310
311
312// Simple mask apertures aren't associated with copper items, so they only constitute a bridge
313// when they expose other copper items having at least two distinct nets. We use a map to record
314// the first net exposed by each mask aperture (on each copper layer).
315//
316// Note that this algorithm is also used for free pads.
317
319{
320 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->IsFreePad() )
321 return true;
322
323 static const LSET saved( { F_Mask, B_Mask } );
324
325 LSET maskLayers = aItem->GetLayerSet() & saved;
326 LSET copperLayers = ( aItem->GetLayerSet() & ~saved ) & LSET::AllCuMask();
327
328 return maskLayers.count() > 0 && copperLayers.count() == 0;
329}
330
331
333 PCB_LAYER_ID aTestLayer, int aTestNet,
334 BOARD_ITEM** aCollidingItem )
335{
336 if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) )
337 return false;
338
339 if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) )
340 return false;
341
342 PCB_LAYER_ID maskLayer = IsFrontLayer( aTestLayer ) ? F_Mask : B_Mask;
343
344 FOOTPRINT* fp = aMaskItem->GetParentFootprint();
345
346 // Mask apertures in footprints which allow soldermask bridges are ignored entirely.
347 if( fp && fp->AllowSolderMaskBridges() )
348 return false;
349
350 PTR_LAYER_CACHE_KEY key = { aMaskItem, maskLayer };
351 BOARD_ITEM* alreadyEncounteredItem = nullptr;
352 int encounteredItemNet = -1;
353
354 {
355 std::lock_guard<std::mutex> lock( m_netMapMutex );
356 auto ii = m_maskApertureNetMap.find( key );
357
358 if( ii == m_maskApertureNetMap.end() )
359 {
360 m_maskApertureNetMap[ key ] = { aTestItem, aTestNet };
361 m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
362
363 // First net; no bridge yet....
364 return false;
365 }
366
367 alreadyEncounteredItem = ii->second.first;
368 encounteredItemNet = ii->second.second;
369
370 // Always store the item in the full list for complete violation reporting.
371 // This ensures all items are available when we generate violations in post-processing,
372 // avoiding race conditions from parallel thread execution.
373 m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
374
375 if( encounteredItemNet == aTestNet )
376 return false;
377
378 // Net code <= 0 is no net (NPTH, <no net> items). Cannot bridge.
379 if( aTestNet <= 0 )
380 return false;
381
382 if( encounteredItemNet <= 0 )
383 {
384 // Replace the no-net placeholder with this real net.
385 m_maskApertureNetMap[key] = { aTestItem, aTestNet };
386 return false;
387 }
388 }
389
390 if( fp && aTestItem->GetParentFootprint() == fp )
391 {
392 std::map<wxString, int> padToNetTieGroupMap = fp->MapPadNumbersToNetTieGroups();
393 PAD* padA = nullptr;
394 PAD* padB = nullptr;
395
396 if( alreadyEncounteredItem->Type() == PCB_PAD_T )
397 padA = static_cast<PAD*>( alreadyEncounteredItem );
398
399 if( aTestItem->Type() == PCB_PAD_T )
400 padB = static_cast<PAD*>( aTestItem );
401
402 if( padA && padB && ( padA->SameLogicalPadAs( padB ) || padA->SharesNetTieGroup( padB ) ) )
403 {
404 return false;
405 }
406 else if( padA && aTestItem->Type() == PCB_SHAPE_T )
407 {
408 if( padToNetTieGroupMap.contains( padA->GetNumber() ) )
409 return false;
410 }
411 else if( padB && alreadyEncounteredItem->Type() == PCB_SHAPE_T )
412 {
413 if( padToNetTieGroupMap.contains( padB->GetNumber() ) )
414 return false;
415 }
416 }
417
418 *aCollidingItem = alreadyEncounteredItem;
419 return true;
420}
421
422
424{
425 if( FOOTPRINT* fp = aItem->GetParentFootprint() )
426 {
427 // If we're allowing bridges then we're allowing bridges. Nothing to check.
428 if( fp->AllowSolderMaskBridges() )
429 return false;
430
431 // Items belonging to a net-tie may share the mask aperture of pads in the same group.
432 if( aItem->Type() == PCB_PAD_T && fp->IsNetTie() )
433 {
434 PAD* pad = static_cast<PAD*>( aItem );
435 std::map<wxString, int> padNumberToGroupIdxMap = fp->MapPadNumbersToNetTieGroups();
436 int groupIdx = padNumberToGroupIdxMap[ pad->GetNumber() ];
437
438 if( groupIdx >= 0 )
439 {
440 if( aTestNet < 0 )
441 return false;
442
443 if( pad->GetNetCode() == aTestNet )
444 return false;
445
446 for( PAD* other : fp->GetNetTiePads( pad ) )
447 {
448 if( other->GetNetCode() == aTestNet )
449 return false;
450 }
451 }
452 }
453 }
454
455 return true;
456}
457
458
460 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer )
461{
462 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
463 PCB_VIA* via = aItem->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( aItem ) : nullptr;
464 PCB_SHAPE* shape = aItem->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( aItem ) : nullptr;
465 int itemNet = -1;
466
467 std::optional<DRC_CONSTRAINT> itemConstraint;
468 DRC_CONSTRAINT otherConstraint;
469
470 if( aItem->IsConnected() )
471 itemNet = static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode();
472
473 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aRefLayer );
474
475 m_itemTree->QueryColliding( aItem, aRefLayer, aTargetLayer,
476 // Filter:
477 [&]( BOARD_ITEM* other ) -> bool
478 {
479 FOOTPRINT* itemFP = aItem->GetParentFootprint();
480 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
481 int otherNet = -1;
482
483 if( other->IsConnected() )
484 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
485
486 if( otherNet > 0 && otherNet == itemNet )
487 return false;
488
489 if( isNPTHPadWithNoCopper( other ) )
490 return false;
491
492 if( itemFP && itemFP == other->GetParentFootprint() )
493 {
494 // Board-wide exclusion
495 if( BOARD* board = itemFP->GetBoard() )
496 {
497 if( board->GetDesignSettings().m_AllowSoldermaskBridgesInFPs )
498 return false;
499 }
500
501 // Footprint-specific exclusion
502 if( itemFP->AllowSolderMaskBridges() )
503 return false;
504 }
505
506 if( pad && otherPad && ( pad->SameLogicalPadAs( otherPad )
507 || pad->SharesNetTieGroup( otherPad ) ) )
508 {
509 return false;
510 }
511
512 if( itemFP && itemFP->IsNetTie() )
513 {
514 const std::set<int>& nets = itemFP->GetNetTieCache( aItem );
515
516 if( otherNet < 0 || nets.count( otherNet ) )
517 return false;
518 }
519
520 if( FOOTPRINT* otherFP = other->GetParentFootprint(); otherFP && otherFP->IsNetTie() )
521 {
522 const std::set<int>& nets = otherFP->GetNetTieCache( other );
523
524 if( itemNet < 0 || nets.count( itemNet ) )
525 return false;
526 }
527
528 BOARD_ITEM* a = aItem;
529 BOARD_ITEM* b = other;
530
531 // store canonical order so we don't collide in both directions (a:b and b:a)
532 if( static_cast<void*>( a ) > static_cast<void*>( b ) )
533 std::swap( a, b );
534
535 {
536 std::lock_guard<std::mutex> lock( m_checkedPairsMutex );
537 auto it = m_checkedPairs.find( { a, b } );
538
539 if( it != m_checkedPairs.end() && it->second.test( aTargetLayer ) )
540 {
541 return false;
542 }
543 else
544 {
545 m_checkedPairs[{ a, b }].set( aTargetLayer );
546 return true;
547 }
548 }
549 },
550 // Visitor:
551 [&]( BOARD_ITEM* other ) -> bool
552 {
553 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
554 PCB_VIA* otherVia = other->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( other ) : nullptr;
555 PCB_SHAPE* otherShape = other->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( other ) : nullptr;
556 auto otherItemShape = other->GetEffectiveShape( aTargetLayer );
557 int otherNet = -1;
558
559 if( other->IsConnected() )
560 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
561
562 int actual;
563 VECTOR2I pos;
564 int clearance = 0;
565
566 if( aRefLayer == F_Mask || aRefLayer == B_Mask )
567 {
568 // Aperture-to-aperture must enforce web-min-width
570 }
571 else // ( aRefLayer == F_Cu || aRefLayer == B_Cu )
572 {
573 // Copper-to-aperture uses the solder-mask-to-copper-clearance
574 clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
575 }
576
577 if( pad )
578 clearance += pad->GetSolderMaskExpansion( aRefLayer );
579 else if( via && !via->IsTented( aRefLayer ) )
580 clearance += via->GetSolderMaskExpansion();
581 else if( shape )
583
584 if( otherPad )
585 clearance += otherPad->GetSolderMaskExpansion( aTargetLayer );
586 else if( otherVia && !otherVia->IsTented( aTargetLayer ) )
587 clearance += otherVia->GetSolderMaskExpansion();
588 else if( otherShape )
589 clearance += otherShape->GetSolderMaskExpansion();
590
591 if( itemShape->Collide( otherItemShape.get(), clearance, &actual, &pos ) )
592 {
593 if( !itemConstraint.has_value() )
594 itemConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, aItem, nullptr, aRefLayer );
595
596 otherConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, other, nullptr, aTargetLayer );
597
598 bool itemConstraintIgnored = itemConstraint->GetSeverity() == RPT_SEVERITY_IGNORE;
599 bool otherConstraintIgnored = otherConstraint.GetSeverity() == RPT_SEVERITY_IGNORE;
600
601 // Mask apertures are ignored on their own; in other cases both participants must be ignored
602 if( ( isMaskAperture( aItem ) && itemConstraintIgnored )
603 || ( isMaskAperture( other ) && otherConstraintIgnored )
604 || ( itemConstraintIgnored && otherConstraintIgnored ) )
605 {
606 return !m_drcEngine->IsCancelled();
607 }
608
609 wxString msg;
610 BOARD_ITEM* colliding = nullptr;
611
612 if( aTargetLayer == F_Mask )
613 msg = _( "Front solder mask aperture bridges items with different nets" );
614 else
615 msg = _( "Rear solder mask aperture bridges items with different nets" );
616
617 // Simple mask apertures aren't associated with copper items, so they only
618 // constitute a bridge when they expose other copper items having at least
619 // two distinct nets.
620 if( isMaskAperture( aItem ) )
621 {
622 if( checkMaskAperture( aItem, other, aRefLayer, otherNet, &colliding ) )
623 {
624 // Store collision info for deferred reporting after all threads complete.
625 // This avoids race conditions where some items haven't been added yet.
626 std::lock_guard<std::mutex> lock( m_collisionMutex );
627
628 m_pendingCollisions.push_back( { aItem, other, otherNet, pos, aTargetLayer } );
629 }
630 }
631 else if( isMaskAperture( other ) )
632 {
633 if( checkMaskAperture( other, aItem, aRefLayer, itemNet, &colliding ) )
634 {
635 // Store collision info for deferred reporting after all threads complete.
636 // This avoids race conditions where some items haven't been added yet.
637 std::lock_guard<std::mutex> lock( m_collisionMutex );
638
639 m_pendingCollisions.push_back( { other, aItem, itemNet, pos, aTargetLayer } );
640 }
641 }
642 else if( checkItemMask( other, itemNet ) )
643 {
644 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
645
646 drce->SetErrorMessage( msg );
647 drce->SetItems( aItem, other );
648 drce->SetViolatingRule( &m_bridgeRule );
649 reportViolation( drce, pos, aTargetLayer );
650 }
651 }
652
653 return !m_drcEngine->IsCancelled();
654 },
656}
657
658
660 PCB_LAYER_ID aMaskLayer, PCB_LAYER_ID aTargetLayer )
661{
662 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
663 PCB_VIA* via = aItem->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( aItem ) : nullptr;
664 PCB_SHAPE* shape = aItem->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( aItem ) : nullptr;
665
666 for( ZONE* zone : m_board->m_DRCCopperZones )
667 {
668 if( !zone->GetLayerSet().test( aTargetLayer ) )
669 continue;
670
671 int zoneNet = zone->GetNetCode();
672
673 if( aItem->IsConnected() )
674 {
675 BOARD_CONNECTED_ITEM* connectedItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
676
677 if( zoneNet == connectedItem->GetNetCode() && zoneNet > 0 )
678 continue;
679 }
680
681 BOX2I inflatedBBox( aItemBBox );
682 int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
683
684 if( pad )
685 clearance += pad->GetSolderMaskExpansion( aTargetLayer );
686 else if( via && !via->IsTented( aTargetLayer ) )
687 clearance += via->GetSolderMaskExpansion();
688 else if( shape )
690
691 inflatedBBox.Inflate( clearance );
692
693 if( !inflatedBBox.Intersects( zone->GetBoundingBox() ) )
694 continue;
695
696 DRC_RTREE* zoneTree = m_board->m_CopperZoneRTreeCache[ zone ].get();
697 int actual;
698 VECTOR2I pos;
699
700 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aMaskLayer );
701
702 if( zoneTree && zoneTree->QueryColliding( aItemBBox, itemShape.get(), aTargetLayer, clearance,
703 &actual, &pos ) )
704 {
705 wxString msg;
706 BOARD_ITEM* colliding = nullptr;
707
708 if( aMaskLayer == F_Mask )
709 msg = _( "Front solder mask aperture bridges items with different nets" );
710 else
711 msg = _( "Rear solder mask aperture bridges items with different nets" );
712
713 // Simple mask apertures aren't associated with copper items, so they only constitute
714 // a bridge when they expose other copper items having at least two distinct nets.
715 if( isMaskAperture( aItem ) && zoneNet >= 0 )
716 {
717 if( checkMaskAperture( aItem, zone, aTargetLayer, zoneNet, &colliding ) )
718 {
719 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
720
721 drce->SetErrorMessage( msg );
722 drce->SetItems( aItem, colliding, zone );
723 drce->SetViolatingRule( &m_bridgeRule );
724 reportViolation( drce, pos, aTargetLayer );
725 }
726 }
727 else
728 {
729 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
730
731 drce->SetErrorMessage( msg );
732 drce->SetItems( aItem, zone );
733 drce->SetViolatingRule( &m_bridgeRule );
734 reportViolation( drce, pos, aTargetLayer );
735 }
736 }
737
738 if( m_drcEngine->IsCancelled() )
739 return;
740 }
741}
742
743
745{
746 LSET copperAndMaskLayers( { F_Mask, B_Mask, F_Cu, B_Cu } );
747 std::atomic<int> count = 0;
748 std::vector<BOARD_ITEM*> test_items;
749
750 forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers,
751 [&]( BOARD_ITEM* item ) -> bool
752 {
753 test_items.push_back( item );
754 return true;
755 } );
756
758
759 auto returns = tp.submit_loop( 0, test_items.size(),
760 [&]( size_t i ) -> bool
761 {
762 BOARD_ITEM* item = test_items[ i ];
763
764 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
765 return false;
766
767 BOX2I itemBBox = item->GetBoundingBox();
768
769 if( item->IsOnLayer( F_Mask ) && !isNPTHPadWithNoCopper( item ) )
770 {
771 // Test for aperture-to-aperture collisions
772 testItemAgainstItems( item, itemBBox, F_Mask, F_Mask );
773
774 // Test for aperture-to-zone collisions
775 testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu );
776 }
777 else if( item->IsOnLayer( PADSTACK::ALL_LAYERS ) )
778 {
779 // Test for copper-item-to-aperture collisions
780 testItemAgainstItems( item, itemBBox, F_Cu, F_Mask );
781 }
782
783 if( item->IsOnLayer( B_Mask ) && !isNPTHPadWithNoCopper( item ) )
784 {
785 // Test for aperture-to-aperture collisions
786 testItemAgainstItems( item, itemBBox, B_Mask, B_Mask );
787
788 // Test for aperture-to-zone collisions
789 testMaskItemAgainstZones( item, itemBBox, B_Mask, B_Cu );
790 }
791 else if( item->IsOnLayer( B_Cu ) )
792 {
793 // Test for copper-item-to-aperture collisions
794 testItemAgainstItems( item, itemBBox, B_Cu, B_Mask );
795 }
796
797 ++count;
798
799 return true;
800 } );
801
802 for( auto& ret : returns )
803 {
804 if( !ret.valid() )
805 continue;
806
807 while( ret.wait_for( std::chrono::milliseconds( 100 ) ) == std::future_status::timeout )
808 reportProgress( count, test_items.size() );
809 }
810
811 // Process deferred mask aperture violations now that all threads have completed.
812 // This ensures we have the complete list of items for each aperture.
813 std::set<std::tuple<BOARD_ITEM*, BOARD_ITEM*, BOARD_ITEM*>> reportedTriplets;
814
815 for( const MASK_APERTURE_COLLISION& collision : m_pendingCollisions )
816 {
817 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
818 break;
819
820 PCB_LAYER_ID maskLayer = IsFrontLayer( collision.layer ) ? F_Mask : B_Mask;
821 PTR_LAYER_CACHE_KEY key = { collision.aperture, maskLayer };
822
823 std::vector<std::pair<BOARD_ITEM*, int>> itemsInAperture;
824
825 {
826 std::lock_guard<std::mutex> lock( m_netMapMutex );
827 auto it = m_maskApertureNetMapAll.find( key );
828
829 if( it != m_maskApertureNetMapAll.end() )
830 itemsInAperture = it->second;
831 }
832
833 wxString msg;
834
835 if( collision.layer == F_Mask )
836 msg = _( "Front solder mask aperture bridges items with different nets" );
837 else
838 msg = _( "Rear solder mask aperture bridges items with different nets" );
839
840 bool reportedAnyTrack = false;
841
842 for( auto& [firstNetItem, firstNet] : itemsInAperture )
843 {
844 // Only report items from a different net than the colliding item.
845 if( firstNet == collision.collidingNet )
846 continue;
847
848 // No-net items cannot bridge.
849 if( firstNet <= 0 )
850 continue;
851
852 // Deduplicate: ensure we don't report the same triplet twice.
853 auto tripletKey = std::make_tuple( collision.aperture, firstNetItem, collision.collidingItem );
854
855 if( reportedTriplets.count( tripletKey ) )
856 continue;
857
858 reportedTriplets.insert( tripletKey );
859
860 // Also insert the reverse to avoid reporting (A, B, C) and (A, C, B).
861 reportedTriplets.insert( std::make_tuple( collision.aperture, collision.collidingItem, firstNetItem ) );
862
863 bool firstIsTrack = firstNetItem->Type() == PCB_TRACE_T || firstNetItem->Type() == PCB_ARC_T;
864
865 if( firstIsTrack )
866 {
867 if( m_drcEngine->GetReportAllTrackErrors() || !reportedAnyTrack )
868 {
870
871 drce->SetErrorMessage( msg );
872 drce->SetItems( collision.aperture, firstNetItem, collision.collidingItem );
873 drce->SetViolatingRule( &m_bridgeRule );
874 reportViolation( drce, collision.pos, collision.layer );
875 reportedAnyTrack = true;
876 }
877 }
878 else
879 {
881
882 drce->SetErrorMessage( msg );
883 drce->SetItems( collision.aperture, firstNetItem, collision.collidingItem );
884 drce->SetViolatingRule( &m_bridgeRule );
885 reportViolation( drce, collision.pos, collision.layer );
886 }
887 }
888 }
889}
890
891
893{
894 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE )
895 && m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
896 {
897 REPORT_AUX( wxT( "Solder mask violations ignored. Tests not run." ) );
898 return true; // continue with other tests
899 }
900
901 m_board = m_drcEngine->GetBoard();
902 m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth;
903 m_maxError = m_board->GetDesignSettings().m_MaxError;
905
906 auto updateLargestClearance =
907 [&]( int aClearance )
908 {
909 m_largestClearance = std::max( m_largestClearance, aClearance );
910 };
911
912 for( FOOTPRINT* footprint : m_board->Footprints() )
913 {
914 for( PAD* pad : footprint->Pads() )
915 updateLargestClearance( pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ) );
916
917 for( BOARD_ITEM* item : footprint->GraphicalItems() )
918 {
919 if( item->Type() == PCB_SHAPE_T )
920 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
921 }
922 }
923
924 for( PCB_TRACK* track : m_board->Tracks() )
925 updateLargestClearance( track->GetSolderMaskExpansion() );
926
927 for( BOARD_ITEM* item : m_board->Drawings() )
928 {
929 if( item->Type() == PCB_SHAPE_T )
930 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
931 }
932
933 // Order is important here: m_webWidth must be added in before m_largestClearance is
934 // maxed with the various clearance constraints.
936
937 // Include SolderMaskToCopperClearance so R-tree queries find copper items that are within
938 // the required distance of mask apertures. Without this, tracks passing near pad apertures
939 // from different nets would not be found if SolderMaskToCopperClearance > m_largestClearance.
941 m_board->GetDesignSettings().m_SolderMaskToCopperClearance );
942
943 DRC_CONSTRAINT worstClearanceConstraint;
944
945 if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
946 m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() );
947
948 if( !reportPhase( _( "Building solder mask..." ) ) )
949 return false; // DRC cancelled
950
951 m_checkedPairs.clear();
952 m_maskApertureNetMap.clear();
954 m_pendingCollisions.clear();
955
956 buildRTrees();
957
958 if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) )
959 return false; // DRC cancelled
960
962
963 if( !reportPhase( _( "Checking solder mask web integrity..." ) ) )
964 return false; // DRC cancelled
965
967
968 return !m_drcEngine->IsCancelled();
969}
970
971
972namespace detail
973{
975}
@ ERROR_OUTSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:155
virtual void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:347
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.
virtual const BOARD * GetBoard() const
Return the BOARD in which this BOARD_ITEM resides, or NULL if none.
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
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
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
MINOPTMAX< int > m_Value
Definition drc_rule.h:240
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 const wxString GetName() const override
void testMaskItemAgainstZones(BOARD_ITEM *item, const BOX2I &itemBBox, PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer)
virtual ~DRC_TEST_PROVIDER_SOLDER_MASK()=default
bool checkMaskAperture(BOARD_ITEM *aMaskItem, BOARD_ITEM *aTestItem, PCB_LAYER_ID aTestLayer, int aTestNet, BOARD_ITEM **aCollidingItem)
std::unique_ptr< DRC_RTREE > m_fullSolderMaskRTree
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
std::unordered_map< PTR_PTR_CACHE_KEY, LSET > m_checkedPairs
bool checkItemMask(BOARD_ITEM *aItem, int aTestNet)
std::unordered_map< PTR_LAYER_CACHE_KEY, std::pair< BOARD_ITEM *, int > > m_maskApertureNetMap
void testItemAgainstItems(BOARD_ITEM *aItem, const BOX2I &aItemBBox, PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer)
std::vector< MASK_APERTURE_COLLISION > m_pendingCollisions
std::unordered_map< PTR_LAYER_CACHE_KEY, std::vector< std::pair< BOARD_ITEM *, int > > > m_maskApertureNetMapAll
static std::vector< KICAD_T > s_allBasicItemsButZones
virtual bool reportPhase(const wxString &aStageName)
int forEachGeometryItem(const std::vector< KICAD_T > &aTypes, const LSET &aLayers, const std::function< bool(BOARD_ITEM *)> &aFunc)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
static std::vector< KICAD_T > s_allBasicItems
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
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:108
bool AllowSolderMaskBridges() const
Definition footprint.h:513
std::map< wxString, int > MapPadNumbersToNetTieGroups() const
const std::set< int > & GetNetTieCache(const BOARD_ITEM *aItem) const
Get the set of net codes that are allowed to connect to a footprint item.
Definition footprint.h:736
bool IsNetTie() const
Definition footprint.h:520
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
T Min() const
Definition minoptmax.h:29
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition padstack.h:177
Definition pad.h:61
const wxString & GetNumber() const
Definition pad.h:143
bool SameLogicalPadAs(const PAD *aOther) const
Before we had custom pad shapes it was common to have multiple overlapping pads to represent a more c...
Definition pad.h:166
int GetSolderMaskExpansion(PCB_LAYER_ID aLayer) const
Definition pad.cpp:1951
bool IsFreePad() const
Definition pad.cpp:573
bool SharesNetTieGroup(const PAD *aOther) const
Definition pad.cpp:550
int GetSolderMaskExpansion() const
bool IsTented(PCB_LAYER_ID aLayer) const override
Checks if the given object is tented (its copper shape is covered by solder mask) on a given side of ...
int GetSolderMaskExpansion() const
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
void Deflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Handle a list of polygons defining a copper zone.
Definition zone.h:70
void CacheTriangulation(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, const SHAPE_POLY_SET::TASK_SUBMITTER &aSubmitter={})
Create a list of triangles that "fill" the solid areas used for instance to draw these solid areas on...
Definition zone.cpp:1555
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:811
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:697
const BOX2I GetBoundingBox() const override
Definition zone.cpp:737
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition zone.h:300
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:704
void SetIsFilled(bool isFilled)
Definition zone.h:307
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
The common library.
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ DRCE_SILK_MASK_CLEARANCE
Definition drc_item.h:94
@ DRCE_SOLDERMASK_BRIDGE
Definition drc_item.h:91
@ BRIDGED_MASK_CONSTRAINT
Definition drc_rule.h:88
@ SILK_CLEARANCE_CONSTRAINT
Definition drc_rule.h:58
#define REPORT_AUX(s)
bool isMaskAperture(BOARD_ITEM *aItem)
bool isNPTHPadWithNoCopper(BOARD_ITEM *aItem)
#define _(s)
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:778
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ F_SilkS
Definition layer_ids.h:96
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
@ RPT_SEVERITY_IGNORE
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
@ 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_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:101
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:85
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:83
@ 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