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