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
220 m_checkedPairs.clear();
221}
222
223
225{
226 LSET silkLayers( { F_SilkS, B_SilkS } );
227
228 // If we have no minimum web width then we delegate to the silk checker which does object-to-object
229 // testing (instead of object-to-solder-mask-zone-fill checking that we do here).
230 if( m_webWidth <= 0 )
231 return;
232
233 const size_t progressDelta = 250;
234 int count = 0;
235 int ii = 0;
236
238 [&]( BOARD_ITEM* item ) -> bool
239 {
240 ++count;
241 return true;
242 } );
243
245 [&]( BOARD_ITEM* item ) -> bool
246 {
247 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE ) )
248 return false;
249
250 if( !reportProgress( ii++, count, progressDelta ) )
251 return false;
252
253 if( isInvisibleText( item ) )
254 return true;
255
256 for( PCB_LAYER_ID layer : silkLayers )
257 {
258 if( !item->IsOnLayer( layer ) )
259 continue;
260
261 PCB_LAYER_ID maskLayer = layer == F_SilkS ? F_Mask : B_Mask;
262 BOX2I itemBBox = item->GetBoundingBox();
264 item, nullptr, maskLayer );
265 int clearance = constraint.GetValue().Min();
266 int actual;
267 VECTOR2I pos;
268
269 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance < 0 )
270 return true;
271
272 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( layer );
273
274 if( m_fullSolderMaskRTree->QueryColliding( itemBBox, itemShape.get(), maskLayer,
275 clearance, &actual, &pos ) )
276 {
277 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SILK_MASK_CLEARANCE );
278
279 if( clearance > 0 )
280 {
281 drce->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
282 constraint.GetName(),
283 clearance,
284 actual ) );
285 }
286
287 drce->SetItems( item );
288 drce->SetViolatingRule( constraint.GetParentRule() );
289
290 reportViolation( drce, pos, layer );
291 }
292 }
293
294 return true;
295 } );
296}
297
298
300{
301 if( aItem->Type() == PCB_PAD_T )
302 {
303 PAD* pad = static_cast<PAD*>( aItem );
304
305 // TODO(JE) padstacks
306 if( pad->GetAttribute() == PAD_ATTRIB::NPTH
307 && ( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE
308 || pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::OVAL )
309 && pad->GetSize( PADSTACK::ALL_LAYERS ).x <= pad->GetDrillSize().x
310 && pad->GetSize( PADSTACK::ALL_LAYERS ).y <= pad->GetDrillSize().y )
311 {
312 return true;
313 }
314 }
315
316 return false;
317}
318
319
320// Simple mask apertures aren't associated with copper items, so they only constitute a bridge
321// when they expose other copper items having at least two distinct nets. We use a map to record
322// the first net exposed by each mask aperture (on each copper layer).
323//
324// Note that this algorithm is also used for free pads.
325
327{
328 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->IsFreePad() )
329 return true;
330
331 static const LSET saved( { F_Mask, B_Mask } );
332
333 LSET maskLayers = aItem->GetLayerSet() & saved;
334 LSET copperLayers = ( aItem->GetLayerSet() & ~saved ) & LSET::AllCuMask();
335
336 return maskLayers.count() > 0 && copperLayers.count() == 0;
337}
338
339
341 PCB_LAYER_ID aTestLayer, int aTestNet,
342 BOARD_ITEM** aCollidingItem )
343{
344 if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) )
345 return false;
346
347 if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) )
348 return false;
349
350 PCB_LAYER_ID maskLayer = IsFrontLayer( aTestLayer ) ? F_Mask : B_Mask;
351
352 FOOTPRINT* fp = aMaskItem->GetParentFootprint();
353
354 // Mask apertures in footprints which allow soldermask bridges are ignored entirely.
355 if( fp && fp->AllowSolderMaskBridges() )
356 return false;
357
358 PTR_LAYER_CACHE_KEY key = { aMaskItem, maskLayer };
359 BOARD_ITEM* alreadyEncounteredItem = nullptr;
360 int encounteredItemNet = -1;
361
362 {
363 std::lock_guard<std::mutex> lock( m_netMapMutex );
364 auto ii = m_maskApertureNetMap.find( key );
365
366 if( ii == m_maskApertureNetMap.end() )
367 {
368 m_maskApertureNetMap[ key ] = { aTestItem, aTestNet };
369 m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
370
371 // First net; no bridge yet....
372 return false;
373 }
374
375 alreadyEncounteredItem = ii->second.first;
376 encounteredItemNet = ii->second.second;
377
378 // Always store the item in the full list for complete violation reporting.
379 // This ensures all items are available when we generate violations in post-processing,
380 // avoiding race conditions from parallel thread execution.
381 m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
382
383 if( encounteredItemNet == aTestNet && aTestNet >= 0 )
384 {
385 // Same net; no bridge.
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( isNullAperture( 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 ) && !isNullAperture( 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 ) && !isNullAperture( 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 // Deduplicate: ensure we don't report the same triplet twice.
849 auto tripletKey = std::make_tuple( collision.aperture, firstNetItem, collision.collidingItem );
850
851 if( reportedTriplets.count( tripletKey ) )
852 continue;
853
854 reportedTriplets.insert( tripletKey );
855
856 // Also insert the reverse to avoid reporting (A, B, C) and (A, C, B).
857 reportedTriplets.insert( std::make_tuple( collision.aperture, collision.collidingItem, firstNetItem ) );
858
859 bool firstIsTrack = firstNetItem->Type() == PCB_TRACE_T || firstNetItem->Type() == PCB_ARC_T;
860
861 if( firstIsTrack )
862 {
863 if( m_drcEngine->GetReportAllTrackErrors() || !reportedAnyTrack )
864 {
866
867 drce->SetErrorMessage( msg );
868 drce->SetItems( collision.aperture, firstNetItem, collision.collidingItem );
869 drce->SetViolatingRule( &m_bridgeRule );
870 reportViolation( drce, collision.pos, collision.layer );
871 reportedAnyTrack = true;
872 }
873 }
874 else
875 {
877
878 drce->SetErrorMessage( msg );
879 drce->SetItems( collision.aperture, firstNetItem, collision.collidingItem );
880 drce->SetViolatingRule( &m_bridgeRule );
881 reportViolation( drce, collision.pos, collision.layer );
882 }
883 }
884 }
885}
886
887
889{
890 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE )
891 && m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
892 {
893 REPORT_AUX( wxT( "Solder mask violations ignored. Tests not run." ) );
894 return true; // continue with other tests
895 }
896
897 m_board = m_drcEngine->GetBoard();
898 m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth;
899 m_maxError = m_board->GetDesignSettings().m_MaxError;
901
902 auto updateLargestClearance =
903 [&]( int aClearance )
904 {
905 m_largestClearance = std::max( m_largestClearance, aClearance );
906 };
907
908 for( FOOTPRINT* footprint : m_board->Footprints() )
909 {
910 for( PAD* pad : footprint->Pads() )
911 updateLargestClearance( pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ) );
912
913 for( BOARD_ITEM* item : footprint->GraphicalItems() )
914 {
915 if( item->Type() == PCB_SHAPE_T )
916 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
917 }
918 }
919
920 for( PCB_TRACK* track : m_board->Tracks() )
921 updateLargestClearance( track->GetSolderMaskExpansion() );
922
923 for( BOARD_ITEM* item : m_board->Drawings() )
924 {
925 if( item->Type() == PCB_SHAPE_T )
926 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
927 }
928
929 // Order is important here: m_webWidth must be added in before m_largestClearance is
930 // maxed with the various clearance constraints.
932
933 // Include SolderMaskToCopperClearance so R-tree queries find copper items that are within
934 // the required distance of mask apertures. Without this, tracks passing near pad apertures
935 // from different nets would not be found if SolderMaskToCopperClearance > m_largestClearance.
937 m_board->GetDesignSettings().m_SolderMaskToCopperClearance );
938
939 DRC_CONSTRAINT worstClearanceConstraint;
940
941 if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
942 m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() );
943
944 if( !reportPhase( _( "Building solder mask..." ) ) )
945 return false; // DRC cancelled
946
947 m_checkedPairs.clear();
948 m_maskApertureNetMap.clear();
950 m_pendingCollisions.clear();
951
952 buildRTrees();
953
954 if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) )
955 return false; // DRC cancelled
956
958
959 if( !reportPhase( _( "Checking solder mask web integrity..." ) ) )
960 return false; // DRC cancelled
961
963
964 return !m_drcEngine->IsCancelled();
965}
966
967
968namespace detail
969{
971}
@ 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:83
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:138
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:318
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:256
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
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:194
SEVERITY GetSeverity() const
Definition drc_rule.h:207
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:186
MINOPTMAX< int > m_Value
Definition drc_rule.h:228
DRC_RULE * GetParentRule() const
Definition drc_rule.h:190
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:400
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:217
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:110
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
bool AllowSolderMaskBridges() const
Definition footprint.h:423
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:646
bool IsNetTie() const
Definition footprint.h:430
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static 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:1603
bool IsFreePad() const
Definition pad.cpp:330
bool SharesNetTieGroup(const PAD *aOther) const
Definition pad.cpp:307
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)
Create a list of triangles that "fill" the solid areas used for instance to draw these solid areas on...
Definition zone.cpp:1348
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:607
const BOX2I GetBoundingBox() const override
Definition zone.cpp:646
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition zone.h:292
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:614
void SetIsFilled(bool isFilled)
Definition zone.h:299
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:83
@ SILK_CLEARANCE_CONSTRAINT
Definition drc_rule.h:56
#define REPORT_AUX(s)
bool isMaskAperture(BOARD_ITEM *aItem)
bool isNullAperture(BOARD_ITEM *aItem)
#define _(s)
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:780
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
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:103
@ 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:88
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:108
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:90
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695