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 return static_cast<PAD*>( aItem )->IsNPTHWithNoCopper();
303
304 return false;
305}
306
307
308// Simple mask apertures aren't associated with copper items, so they only constitute a bridge
309// when they expose other copper items having at least two distinct nets. We use a map to record
310// the first net exposed by each mask aperture (on each copper layer).
311//
312// Note that this algorithm is also used for free pads.
313
315{
316 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->IsFreePad() )
317 return true;
318
319 static const LSET saved( { F_Mask, B_Mask } );
320
321 LSET maskLayers = aItem->GetLayerSet() & saved;
322 LSET copperLayers = ( aItem->GetLayerSet() & ~saved ) & LSET::AllCuMask();
323
324 return maskLayers.count() > 0 && copperLayers.count() == 0;
325}
326
327
329 PCB_LAYER_ID aTestLayer, int aTestNet,
330 BOARD_ITEM** aCollidingItem )
331{
332 if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) )
333 return false;
334
335 if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) )
336 return false;
337
338 PCB_LAYER_ID maskLayer = IsFrontLayer( aTestLayer ) ? F_Mask : B_Mask;
339
340 FOOTPRINT* fp = aMaskItem->GetParentFootprint();
341
342 // Mask apertures in footprints which allow soldermask bridges are ignored entirely.
343 if( fp && fp->AllowSolderMaskBridges() )
344 return false;
345
346 PTR_LAYER_CACHE_KEY key = { aMaskItem, maskLayer };
347 BOARD_ITEM* alreadyEncounteredItem = nullptr;
348 int encounteredItemNet = -1;
349
350 {
351 std::lock_guard<std::mutex> lock( m_netMapMutex );
352 auto ii = m_maskApertureNetMap.find( key );
353
354 if( ii == m_maskApertureNetMap.end() )
355 {
356 m_maskApertureNetMap[ key ] = { aTestItem, aTestNet };
357 m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
358
359 // First net; no bridge yet....
360 return false;
361 }
362
363 alreadyEncounteredItem = ii->second.first;
364 encounteredItemNet = ii->second.second;
365
366 // Always store the item in the full list for complete violation reporting.
367 // This ensures all items are available when we generate violations in post-processing,
368 // avoiding race conditions from parallel thread execution.
369 m_maskApertureNetMapAll[ key ].push_back( { aTestItem, aTestNet } );
370
371 if( encounteredItemNet == aTestNet && aTestNet >= 0 )
372 {
373 // Same net; no bridge.
374 return false;
375 }
376 }
377
378 if( fp && aTestItem->GetParentFootprint() == fp )
379 {
380 std::map<wxString, int> padToNetTieGroupMap = fp->MapPadNumbersToNetTieGroups();
381 PAD* padA = nullptr;
382 PAD* padB = nullptr;
383
384 if( alreadyEncounteredItem->Type() == PCB_PAD_T )
385 padA = static_cast<PAD*>( alreadyEncounteredItem );
386
387 if( aTestItem->Type() == PCB_PAD_T )
388 padB = static_cast<PAD*>( aTestItem );
389
390 if( padA && padB && ( padA->SameLogicalPadAs( padB ) || padA->SharesNetTieGroup( padB ) ) )
391 {
392 return false;
393 }
394 else if( padA && aTestItem->Type() == PCB_SHAPE_T )
395 {
396 if( padToNetTieGroupMap.contains( padA->GetNumber() ) )
397 return false;
398 }
399 else if( padB && alreadyEncounteredItem->Type() == PCB_SHAPE_T )
400 {
401 if( padToNetTieGroupMap.contains( padB->GetNumber() ) )
402 return false;
403 }
404 }
405
406 *aCollidingItem = alreadyEncounteredItem;
407 return true;
408}
409
410
412{
413 if( FOOTPRINT* fp = aItem->GetParentFootprint() )
414 {
415 // If we're allowing bridges then we're allowing bridges. Nothing to check.
416 if( fp->AllowSolderMaskBridges() )
417 return false;
418
419 // Items belonging to a net-tie may share the mask aperture of pads in the same group.
420 if( aItem->Type() == PCB_PAD_T && fp->IsNetTie() )
421 {
422 PAD* pad = static_cast<PAD*>( aItem );
423 std::map<wxString, int> padNumberToGroupIdxMap = fp->MapPadNumbersToNetTieGroups();
424 int groupIdx = padNumberToGroupIdxMap[ pad->GetNumber() ];
425
426 if( groupIdx >= 0 )
427 {
428 if( aTestNet < 0 )
429 return false;
430
431 if( pad->GetNetCode() == aTestNet )
432 return false;
433
434 for( PAD* other : fp->GetNetTiePads( pad ) )
435 {
436 if( other->GetNetCode() == aTestNet )
437 return false;
438 }
439 }
440 }
441 }
442
443 return true;
444}
445
446
448 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer )
449{
450 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
451 PCB_VIA* via = aItem->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( aItem ) : nullptr;
452 PCB_SHAPE* shape = aItem->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( aItem ) : nullptr;
453 int itemNet = -1;
454
455 std::optional<DRC_CONSTRAINT> itemConstraint;
456 DRC_CONSTRAINT otherConstraint;
457
458 if( aItem->IsConnected() )
459 itemNet = static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode();
460
461 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aRefLayer );
462
463 m_itemTree->QueryColliding( aItem, aRefLayer, aTargetLayer,
464 // Filter:
465 [&]( BOARD_ITEM* other ) -> bool
466 {
467 FOOTPRINT* itemFP = aItem->GetParentFootprint();
468 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
469 int otherNet = -1;
470
471 if( other->IsConnected() )
472 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
473
474 if( otherNet > 0 && otherNet == itemNet )
475 return false;
476
477 if( isNPTHPadWithNoCopper( other ) )
478 return false;
479
480 if( itemFP && itemFP == other->GetParentFootprint() )
481 {
482 // Board-wide exclusion
483 if( BOARD* board = itemFP->GetBoard() )
484 {
485 if( board->GetDesignSettings().m_AllowSoldermaskBridgesInFPs )
486 return false;
487 }
488
489 // Footprint-specific exclusion
490 if( itemFP->AllowSolderMaskBridges() )
491 return false;
492 }
493
494 if( pad && otherPad && ( pad->SameLogicalPadAs( otherPad )
495 || pad->SharesNetTieGroup( otherPad ) ) )
496 {
497 return false;
498 }
499
500 if( itemFP && itemFP->IsNetTie() )
501 {
502 const std::set<int>& nets = itemFP->GetNetTieCache( aItem );
503
504 if( otherNet < 0 || nets.count( otherNet ) )
505 return false;
506 }
507
508 if( FOOTPRINT* otherFP = other->GetParentFootprint(); otherFP && otherFP->IsNetTie() )
509 {
510 const std::set<int>& nets = otherFP->GetNetTieCache( other );
511
512 if( itemNet < 0 || nets.count( itemNet ) )
513 return false;
514 }
515
516 BOARD_ITEM* a = aItem;
517 BOARD_ITEM* b = other;
518
519 // store canonical order so we don't collide in both directions (a:b and b:a)
520 if( static_cast<void*>( a ) > static_cast<void*>( b ) )
521 std::swap( a, b );
522
523 {
524 std::lock_guard<std::mutex> lock( m_checkedPairsMutex );
525 auto it = m_checkedPairs.find( { a, b } );
526
527 if( it != m_checkedPairs.end() && it->second.test( aTargetLayer ) )
528 {
529 return false;
530 }
531 else
532 {
533 m_checkedPairs[{ a, b }].set( aTargetLayer );
534 return true;
535 }
536 }
537 },
538 // Visitor:
539 [&]( BOARD_ITEM* other ) -> bool
540 {
541 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
542 PCB_VIA* otherVia = other->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( other ) : nullptr;
543 PCB_SHAPE* otherShape = other->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( other ) : nullptr;
544 auto otherItemShape = other->GetEffectiveShape( aTargetLayer );
545 int otherNet = -1;
546
547 if( other->IsConnected() )
548 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
549
550 int actual;
551 VECTOR2I pos;
552 int clearance = 0;
553
554 if( aRefLayer == F_Mask || aRefLayer == B_Mask )
555 {
556 // Aperture-to-aperture must enforce web-min-width
558 }
559 else // ( aRefLayer == F_Cu || aRefLayer == B_Cu )
560 {
561 // Copper-to-aperture uses the solder-mask-to-copper-clearance
562 clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
563 }
564
565 if( pad )
566 clearance += pad->GetSolderMaskExpansion( aRefLayer );
567 else if( via && !via->IsTented( aRefLayer ) )
568 clearance += via->GetSolderMaskExpansion();
569 else if( shape )
571
572 if( otherPad )
573 clearance += otherPad->GetSolderMaskExpansion( aTargetLayer );
574 else if( otherVia && !otherVia->IsTented( aTargetLayer ) )
575 clearance += otherVia->GetSolderMaskExpansion();
576 else if( otherShape )
577 clearance += otherShape->GetSolderMaskExpansion();
578
579 if( itemShape->Collide( otherItemShape.get(), clearance, &actual, &pos ) )
580 {
581 if( !itemConstraint.has_value() )
582 itemConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, aItem, nullptr, aRefLayer );
583
584 otherConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, other, nullptr, aTargetLayer );
585
586 bool itemConstraintIgnored = itemConstraint->GetSeverity() == RPT_SEVERITY_IGNORE;
587 bool otherConstraintIgnored = otherConstraint.GetSeverity() == RPT_SEVERITY_IGNORE;
588
589 // Mask apertures are ignored on their own; in other cases both participants must be ignored
590 if( ( isMaskAperture( aItem ) && itemConstraintIgnored )
591 || ( isMaskAperture( other ) && otherConstraintIgnored )
592 || ( itemConstraintIgnored && otherConstraintIgnored ) )
593 {
594 return !m_drcEngine->IsCancelled();
595 }
596
597 wxString msg;
598 BOARD_ITEM* colliding = nullptr;
599
600 if( aTargetLayer == F_Mask )
601 msg = _( "Front solder mask aperture bridges items with different nets" );
602 else
603 msg = _( "Rear solder mask aperture bridges items with different nets" );
604
605 // Simple mask apertures aren't associated with copper items, so they only
606 // constitute a bridge when they expose other copper items having at least
607 // two distinct nets.
608 if( isMaskAperture( aItem ) )
609 {
610 if( checkMaskAperture( aItem, other, aRefLayer, otherNet, &colliding ) )
611 {
612 // Store collision info for deferred reporting after all threads complete.
613 // This avoids race conditions where some items haven't been added yet.
614 std::lock_guard<std::mutex> lock( m_collisionMutex );
615
616 m_pendingCollisions.push_back( { aItem, other, otherNet, pos, aTargetLayer } );
617 }
618 }
619 else if( isMaskAperture( other ) )
620 {
621 if( checkMaskAperture( other, aItem, aRefLayer, itemNet, &colliding ) )
622 {
623 // Store collision info for deferred reporting after all threads complete.
624 // This avoids race conditions where some items haven't been added yet.
625 std::lock_guard<std::mutex> lock( m_collisionMutex );
626
627 m_pendingCollisions.push_back( { other, aItem, itemNet, pos, aTargetLayer } );
628 }
629 }
630 else if( checkItemMask( other, itemNet ) )
631 {
632 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
633
634 drce->SetErrorMessage( msg );
635 drce->SetItems( aItem, other );
636 drce->SetViolatingRule( &m_bridgeRule );
637 reportViolation( drce, pos, aTargetLayer );
638 }
639 }
640
641 return !m_drcEngine->IsCancelled();
642 },
644}
645
646
648 PCB_LAYER_ID aMaskLayer, PCB_LAYER_ID aTargetLayer )
649{
650 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
651 PCB_VIA* via = aItem->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( aItem ) : nullptr;
652 PCB_SHAPE* shape = aItem->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( aItem ) : nullptr;
653
654 for( ZONE* zone : m_board->m_DRCCopperZones )
655 {
656 if( !zone->GetLayerSet().test( aTargetLayer ) )
657 continue;
658
659 int zoneNet = zone->GetNetCode();
660
661 if( aItem->IsConnected() )
662 {
663 BOARD_CONNECTED_ITEM* connectedItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
664
665 if( zoneNet == connectedItem->GetNetCode() && zoneNet > 0 )
666 continue;
667 }
668
669 BOX2I inflatedBBox( aItemBBox );
670 int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
671
672 if( pad )
673 clearance += pad->GetSolderMaskExpansion( aTargetLayer );
674 else if( via && !via->IsTented( aTargetLayer ) )
675 clearance += via->GetSolderMaskExpansion();
676 else if( shape )
678
679 inflatedBBox.Inflate( clearance );
680
681 if( !inflatedBBox.Intersects( zone->GetBoundingBox() ) )
682 continue;
683
684 DRC_RTREE* zoneTree = m_board->m_CopperZoneRTreeCache[ zone ].get();
685 int actual;
686 VECTOR2I pos;
687
688 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aMaskLayer );
689
690 if( zoneTree && zoneTree->QueryColliding( aItemBBox, itemShape.get(), aTargetLayer, clearance,
691 &actual, &pos ) )
692 {
693 wxString msg;
694 BOARD_ITEM* colliding = nullptr;
695
696 if( aMaskLayer == F_Mask )
697 msg = _( "Front solder mask aperture bridges items with different nets" );
698 else
699 msg = _( "Rear solder mask aperture bridges items with different nets" );
700
701 // Simple mask apertures aren't associated with copper items, so they only constitute
702 // a bridge when they expose other copper items having at least two distinct nets.
703 if( isMaskAperture( aItem ) && zoneNet >= 0 )
704 {
705 if( checkMaskAperture( aItem, zone, aTargetLayer, zoneNet, &colliding ) )
706 {
707 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
708
709 drce->SetErrorMessage( msg );
710 drce->SetItems( aItem, colliding, zone );
711 drce->SetViolatingRule( &m_bridgeRule );
712 reportViolation( drce, pos, aTargetLayer );
713 }
714 }
715 else
716 {
717 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
718
719 drce->SetErrorMessage( msg );
720 drce->SetItems( aItem, zone );
721 drce->SetViolatingRule( &m_bridgeRule );
722 reportViolation( drce, pos, aTargetLayer );
723 }
724 }
725
726 if( m_drcEngine->IsCancelled() )
727 return;
728 }
729}
730
731
733{
734 LSET copperAndMaskLayers( { F_Mask, B_Mask, F_Cu, B_Cu } );
735 std::atomic<int> count = 0;
736 std::vector<BOARD_ITEM*> test_items;
737
738 forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers,
739 [&]( BOARD_ITEM* item ) -> bool
740 {
741 test_items.push_back( item );
742 return true;
743 } );
744
746
747 auto returns = tp.submit_loop( 0, test_items.size(),
748 [&]( size_t i ) -> bool
749 {
750 BOARD_ITEM* item = test_items[ i ];
751
752 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
753 return false;
754
755 BOX2I itemBBox = item->GetBoundingBox();
756
757 if( item->IsOnLayer( F_Mask ) && !isNPTHPadWithNoCopper( item ) )
758 {
759 // Test for aperture-to-aperture collisions
760 testItemAgainstItems( item, itemBBox, F_Mask, F_Mask );
761
762 // Test for aperture-to-zone collisions
763 testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu );
764 }
765 else if( item->IsOnLayer( PADSTACK::ALL_LAYERS ) )
766 {
767 // Test for copper-item-to-aperture collisions
768 testItemAgainstItems( item, itemBBox, F_Cu, F_Mask );
769 }
770
771 if( item->IsOnLayer( B_Mask ) && !isNPTHPadWithNoCopper( item ) )
772 {
773 // Test for aperture-to-aperture collisions
774 testItemAgainstItems( item, itemBBox, B_Mask, B_Mask );
775
776 // Test for aperture-to-zone collisions
777 testMaskItemAgainstZones( item, itemBBox, B_Mask, B_Cu );
778 }
779 else if( item->IsOnLayer( B_Cu ) )
780 {
781 // Test for copper-item-to-aperture collisions
782 testItemAgainstItems( item, itemBBox, B_Cu, B_Mask );
783 }
784
785 ++count;
786
787 return true;
788 } );
789
790 for( auto& ret : returns )
791 {
792 if( !ret.valid() )
793 continue;
794
795 while( ret.wait_for( std::chrono::milliseconds( 100 ) ) == std::future_status::timeout )
796 reportProgress( count, test_items.size() );
797 }
798
799 // Process deferred mask aperture violations now that all threads have completed.
800 // This ensures we have the complete list of items for each aperture.
801 std::set<std::tuple<BOARD_ITEM*, BOARD_ITEM*, BOARD_ITEM*>> reportedTriplets;
802
803 for( const MASK_APERTURE_COLLISION& collision : m_pendingCollisions )
804 {
805 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
806 break;
807
808 PCB_LAYER_ID maskLayer = IsFrontLayer( collision.layer ) ? F_Mask : B_Mask;
809 PTR_LAYER_CACHE_KEY key = { collision.aperture, maskLayer };
810
811 std::vector<std::pair<BOARD_ITEM*, int>> itemsInAperture;
812
813 {
814 std::lock_guard<std::mutex> lock( m_netMapMutex );
815 auto it = m_maskApertureNetMapAll.find( key );
816
817 if( it != m_maskApertureNetMapAll.end() )
818 itemsInAperture = it->second;
819 }
820
821 wxString msg;
822
823 if( collision.layer == F_Mask )
824 msg = _( "Front solder mask aperture bridges items with different nets" );
825 else
826 msg = _( "Rear solder mask aperture bridges items with different nets" );
827
828 bool reportedAnyTrack = false;
829
830 for( auto& [firstNetItem, firstNet] : itemsInAperture )
831 {
832 // Only report items from a different net than the colliding item.
833 if( firstNet == collision.collidingNet )
834 continue;
835
836 // Deduplicate: ensure we don't report the same triplet twice.
837 auto tripletKey = std::make_tuple( collision.aperture, firstNetItem, collision.collidingItem );
838
839 if( reportedTriplets.count( tripletKey ) )
840 continue;
841
842 reportedTriplets.insert( tripletKey );
843
844 // Also insert the reverse to avoid reporting (A, B, C) and (A, C, B).
845 reportedTriplets.insert( std::make_tuple( collision.aperture, collision.collidingItem, firstNetItem ) );
846
847 bool firstIsTrack = firstNetItem->Type() == PCB_TRACE_T || firstNetItem->Type() == PCB_ARC_T;
848
849 if( firstIsTrack )
850 {
851 if( m_drcEngine->GetReportAllTrackErrors() || !reportedAnyTrack )
852 {
854
855 drce->SetErrorMessage( msg );
856 drce->SetItems( collision.aperture, firstNetItem, collision.collidingItem );
857 drce->SetViolatingRule( &m_bridgeRule );
858 reportViolation( drce, collision.pos, collision.layer );
859 reportedAnyTrack = true;
860 }
861 }
862 else
863 {
865
866 drce->SetErrorMessage( msg );
867 drce->SetItems( collision.aperture, firstNetItem, collision.collidingItem );
868 drce->SetViolatingRule( &m_bridgeRule );
869 reportViolation( drce, collision.pos, collision.layer );
870 }
871 }
872 }
873}
874
875
877{
878 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE )
879 && m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
880 {
881 REPORT_AUX( wxT( "Solder mask violations ignored. Tests not run." ) );
882 return true; // continue with other tests
883 }
884
885 m_board = m_drcEngine->GetBoard();
886 m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth;
887 m_maxError = m_board->GetDesignSettings().m_MaxError;
889
890 auto updateLargestClearance =
891 [&]( int aClearance )
892 {
893 m_largestClearance = std::max( m_largestClearance, aClearance );
894 };
895
896 for( FOOTPRINT* footprint : m_board->Footprints() )
897 {
898 for( PAD* pad : footprint->Pads() )
899 updateLargestClearance( pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ) );
900
901 for( BOARD_ITEM* item : footprint->GraphicalItems() )
902 {
903 if( item->Type() == PCB_SHAPE_T )
904 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
905 }
906 }
907
908 for( PCB_TRACK* track : m_board->Tracks() )
909 updateLargestClearance( track->GetSolderMaskExpansion() );
910
911 for( BOARD_ITEM* item : m_board->Drawings() )
912 {
913 if( item->Type() == PCB_SHAPE_T )
914 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
915 }
916
917 // Order is important here: m_webWidth must be added in before m_largestClearance is
918 // maxed with the various clearance constraints.
920
921 // Include SolderMaskToCopperClearance so R-tree queries find copper items that are within
922 // the required distance of mask apertures. Without this, tracks passing near pad apertures
923 // from different nets would not be found if SolderMaskToCopperClearance > m_largestClearance.
925 m_board->GetDesignSettings().m_SolderMaskToCopperClearance );
926
927 DRC_CONSTRAINT worstClearanceConstraint;
928
929 if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
930 m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() );
931
932 if( !reportPhase( _( "Building solder mask..." ) ) )
933 return false; // DRC cancelled
934
935 m_checkedPairs.clear();
936 m_maskApertureNetMap.clear();
938 m_pendingCollisions.clear();
939
940 buildRTrees();
941
942 if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) )
943 return false; // DRC cancelled
944
946
947 if( !reportPhase( _( "Checking solder mask web integrity..." ) ) )
948 return false; // DRC cancelled
949
951
952 return !m_drcEngine->IsCancelled();
953}
954
955
956namespace detail
957{
959}
@ 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:139
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:319
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:257
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:195
SEVERITY GetSeverity() const
Definition drc_rule.h:208
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:187
MINOPTMAX< int > m_Value
Definition drc_rule.h:229
DRC_RULE * GetParentRule() const
Definition drc_rule.h:191
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:120
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:111
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:1639
bool IsFreePad() const
Definition pad.cpp:363
bool SharesNetTieGroup(const PAD *aOther) const
Definition pad.cpp:340
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:73
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:1353
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:606
const BOX2I GetBoundingBox() const override
Definition zone.cpp:651
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition zone.h:291
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:613
void SetIsFilled(bool isFilled)
Definition zone.h:298
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:136
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 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: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
@ 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