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