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/*
41 Solder mask tests. Checks for silkscreen which is clipped by mask openings and for bridges
42 between mask apertures with different nets.
43 Errors generated:
44 - DRCE_SILK_MASK_CLEARANCE
45 - DRCE_SOLDERMASK_BRIDGE
46*/
47
49{
50public:
52 m_board( nullptr ),
53 m_webWidth( 0 ),
54 m_maxError( 0 ),
56 {
57 m_bridgeRule.m_Name = _( "board setup solder mask min width" );
58 }
59
60 virtual ~DRC_TEST_PROVIDER_SOLDER_MASK() = default;
61
62 virtual bool Run() override;
63
64 virtual const wxString GetName() const override { return wxT( "solder_mask_issues" ); };
65
66private:
67 void addItemToRTrees( BOARD_ITEM* aItem );
68 void buildRTrees();
69
71 void testMaskBridges();
72
73 void testItemAgainstItems( BOARD_ITEM* aItem, const BOX2I& aItemBBox,
74 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer );
75 void testMaskItemAgainstZones( BOARD_ITEM* item, const BOX2I& itemBBox,
76 PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer );
77
78 bool checkMaskAperture( BOARD_ITEM* aMaskItem, BOARD_ITEM* aTestItem, PCB_LAYER_ID aTestLayer,
79 int aTestNet, BOARD_ITEM** aCollidingItem );
80
81 bool checkItemMask( BOARD_ITEM* aItem, int aTestNet );
82
83private:
85
90
91 std::unique_ptr<DRC_RTREE> m_fullSolderMaskRTree;
92 std::unique_ptr<DRC_RTREE> m_itemTree;
93
95 std::unordered_map<PTR_PTR_CACHE_KEY, LSET> m_checkedPairs;
96
97 // Shapes used to define solder mask apertures don't have nets, so we assign them the
98 // first object+net that bridges their aperture (after which any other nets will generate
99 // violations).
100 std::mutex m_netMapMutex;
101 std::unordered_map<PTR_LAYER_CACHE_KEY, std::pair<BOARD_ITEM*, int>> m_maskApertureNetMap;
102};
103
104
106{
107 for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
108 {
109 if( !aItem->IsOnLayer( layer ) )
110 continue;
111
112 SHAPE_POLY_SET* solderMask = m_board->m_SolderMaskBridges->GetFill( layer );
113
114 if( aItem->Type() == PCB_ZONE_T )
115 {
116 ZONE* zone = static_cast<ZONE*>( aItem );
117
118 solderMask->BooleanAdd( *zone->GetFilledPolysList( layer ) );
119 }
120 else
121 {
122 int clearance = m_webWidth / 2;
123
124 if( aItem->Type() == PCB_PAD_T )
125 clearance += static_cast<PAD*>( aItem )->GetSolderMaskExpansion( layer );
126 else if( aItem->Type() == PCB_VIA_T )
127 clearance += static_cast<PCB_VIA*>( aItem )->GetSolderMaskExpansion();
128 else if( aItem->Type() == PCB_SHAPE_T )
129 clearance += static_cast<PCB_SHAPE*>( aItem )->GetSolderMaskExpansion();
130
131 if( aItem->Type() == PCB_FIELD_T || aItem->Type() == PCB_TEXT_T )
132 {
133 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
134
135 text->TransformTextToPolySet( *solderMask, clearance, m_maxError, ERROR_OUTSIDE );
136 }
137 else
138 {
139 aItem->TransformShapeToPolygon( *solderMask, layer, clearance, m_maxError, ERROR_OUTSIDE );
140 }
141
142 m_itemTree->Insert( aItem, layer, m_largestClearance );
143 }
144 }
145}
146
147
149{
150 ZONE* solderMask = m_board->m_SolderMaskBridges;
151 LSET layers( { F_Mask, B_Mask, F_Cu, B_Cu } );
152
153 const size_t progressDelta = 500;
154 int count = 0;
155 int ii = 0;
156
157 solderMask->GetFill( F_Mask )->RemoveAllContours();
158 solderMask->GetFill( B_Mask )->RemoveAllContours();
159
160 m_fullSolderMaskRTree = std::make_unique<DRC_RTREE>();
161 m_itemTree = std::make_unique<DRC_RTREE>();
162
164 [&]( BOARD_ITEM* item ) -> bool
165 {
166 ++count;
167 return true;
168 } );
169
171 [&]( BOARD_ITEM* item ) -> bool
172 {
173 if( !reportProgress( ii++, count, progressDelta ) )
174 return false;
175
176 addItemToRTrees( item );
177 return true;
178 } );
179
180 solderMask->GetFill( F_Mask )->Simplify();
181 solderMask->GetFill( B_Mask )->Simplify();
182
183 if( m_webWidth > 0 )
184 {
187 }
188
189 solderMask->SetFillFlag( F_Mask, true );
190 solderMask->SetFillFlag( B_Mask, true );
191 solderMask->SetIsFilled( true );
192
193 solderMask->CacheTriangulation();
194
195 m_fullSolderMaskRTree->Insert( solderMask, F_Mask );
196 m_fullSolderMaskRTree->Insert( solderMask, B_Mask );
197
198 m_checkedPairs.clear();
199}
200
201
203{
204 LSET silkLayers( { F_SilkS, B_SilkS } );
205
206 // If we have no minimum web width then we delegate to the silk checker which does object-to-object
207 // testing (instead of object-to-solder-mask-zone-fill checking that we do here).
208 if( m_webWidth <= 0 )
209 return;
210
211 const size_t progressDelta = 250;
212 int count = 0;
213 int ii = 0;
214
216 [&]( BOARD_ITEM* item ) -> bool
217 {
218 ++count;
219 return true;
220 } );
221
223 [&]( BOARD_ITEM* item ) -> bool
224 {
225 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE ) )
226 return false;
227
228 if( !reportProgress( ii++, count, progressDelta ) )
229 return false;
230
231 if( isInvisibleText( item ) )
232 return true;
233
234 for( PCB_LAYER_ID layer : silkLayers )
235 {
236 if( !item->IsOnLayer( layer ) )
237 continue;
238
239 PCB_LAYER_ID maskLayer = layer == F_SilkS ? F_Mask : B_Mask;
240 BOX2I itemBBox = item->GetBoundingBox();
242 item, nullptr, maskLayer );
243 int clearance = constraint.GetValue().Min();
244 int actual;
245 VECTOR2I pos;
246
247 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance < 0 )
248 return true;
249
250 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( layer );
251
252 if( m_fullSolderMaskRTree->QueryColliding( itemBBox, itemShape.get(), maskLayer,
253 clearance, &actual, &pos ) )
254 {
255 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SILK_MASK_CLEARANCE );
256
257 if( clearance > 0 )
258 {
259 wxString msg = formatMsg( _( "(%s clearance %s; actual %s)" ),
260 constraint.GetName(),
261 clearance,
262 actual );
263
264 drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
265 }
266
267 drce->SetItems( item );
268 drce->SetViolatingRule( constraint.GetParentRule() );
269
270 reportViolation( drce, pos, layer );
271 }
272 }
273
274 return true;
275 } );
276}
277
278
280{
281 if( aItem->Type() == PCB_PAD_T )
282 {
283 PAD* pad = static_cast<PAD*>( aItem );
284
285 // TODO(JE) padstacks
286 if( pad->GetAttribute() == PAD_ATTRIB::NPTH
287 && ( pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CIRCLE
288 || pad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::OVAL )
289 && pad->GetSize( PADSTACK::ALL_LAYERS ).x <= pad->GetDrillSize().x
290 && pad->GetSize( PADSTACK::ALL_LAYERS ).y <= pad->GetDrillSize().y )
291 {
292 return true;
293 }
294 }
295
296 return false;
297}
298
299
300// Simple mask apertures aren't associated with copper items, so they only constitute a bridge
301// when they expose other copper items having at least two distinct nets. We use a map to record
302// the first net exposed by each mask aperture (on each copper layer).
303//
304// Note that this algorithm is also used for free pads.
305
307{
308 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->IsFreePad() )
309 return true;
310
311 static const LSET saved( { F_Mask, B_Mask } );
312
313 LSET maskLayers = aItem->GetLayerSet() & saved;
314 LSET copperLayers = ( aItem->GetLayerSet() & ~saved ) & LSET::AllCuMask();
315
316 return maskLayers.count() > 0 && copperLayers.count() == 0;
317}
318
319
321 PCB_LAYER_ID aTestLayer, int aTestNet,
322 BOARD_ITEM** aCollidingItem )
323{
324 if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) )
325 return false;
326
327 if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) )
328 return false;
329
330 PCB_LAYER_ID maskLayer = IsFrontLayer( aTestLayer ) ? F_Mask : B_Mask;
331
332 FOOTPRINT* fp = aMaskItem->GetParentFootprint();
333
334 // Mask apertures in footprints which allow soldermask bridges are ignored entirely.
335 if( fp && fp->AllowSolderMaskBridges() )
336 return false;
337
338 PTR_LAYER_CACHE_KEY key = { aMaskItem, maskLayer };
339 BOARD_ITEM* alreadyEncounteredItem = nullptr;
340 int encounteredItemNet = -1;
341
342 {
343 std::lock_guard<std::mutex> lock( m_checkedPairsMutex );
344 auto ii = m_maskApertureNetMap.find( key );
345
346 if( ii == m_maskApertureNetMap.end() )
347 {
348 m_maskApertureNetMap[ key ] = { aTestItem, aTestNet };
349
350 // First net; no bridge yet....
351 return false;
352 }
353
354 alreadyEncounteredItem = ii->second.first;
355 encounteredItemNet = ii->second.second;
356 }
357
358 if( encounteredItemNet == aTestNet && aTestNet >= 0 )
359 {
360 // Same net; still no bridge...
361 return false;
362 }
363
364 if( fp && aTestItem->GetParentFootprint() == fp )
365 {
366 std::map<wxString, int> padToNetTieGroupMap = fp->MapPadNumbersToNetTieGroups();
367 PAD* padA = nullptr;
368 PAD* padB = nullptr;
369
370 if( alreadyEncounteredItem->Type() == PCB_PAD_T )
371 padA = static_cast<PAD*>( alreadyEncounteredItem );
372
373 if( aTestItem->Type() == PCB_PAD_T )
374 padB = static_cast<PAD*>( aTestItem );
375
376 if( padA && padB && ( padA->SameLogicalPadAs( padB ) || padA->SharesNetTieGroup( padB ) ) )
377 {
378 return false;
379 }
380 else if( padA && aTestItem->Type() == PCB_SHAPE_T )
381 {
382 if( padToNetTieGroupMap.contains( padA->GetNumber() ) )
383 return false;
384 }
385 else if( padB && alreadyEncounteredItem->Type() == PCB_SHAPE_T )
386 {
387 if( padToNetTieGroupMap.contains( padB->GetNumber() ) )
388 return false;
389 }
390 }
391
392 *aCollidingItem = alreadyEncounteredItem;
393 return true;
394}
395
396
398{
399 if( FOOTPRINT* fp = aItem->GetParentFootprint() )
400 {
401 // If we're allowing bridges then we're allowing bridges. Nothing to check.
402 if( fp->AllowSolderMaskBridges() )
403 return false;
404
405 // Items belonging to a net-tie may share the mask aperture of pads in the same group.
406 if( aItem->Type() == PCB_PAD_T && fp->IsNetTie() )
407 {
408 PAD* pad = static_cast<PAD*>( aItem );
409 std::map<wxString, int> padNumberToGroupIdxMap = fp->MapPadNumbersToNetTieGroups();
410 int groupIdx = padNumberToGroupIdxMap[ pad->GetNumber() ];
411
412 if( groupIdx >= 0 )
413 {
414 if( aTestNet < 0 )
415 return false;
416
417 if( pad->GetNetCode() == aTestNet )
418 return false;
419
420 for( PAD* other : fp->GetNetTiePads( pad ) )
421 {
422 if( other->GetNetCode() == aTestNet )
423 return false;
424 }
425 }
426 }
427 }
428
429 return true;
430}
431
432
434 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer )
435{
436 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
437 PCB_VIA* via = aItem->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( aItem ) : nullptr;
438 PCB_SHAPE* shape = aItem->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( aItem ) : nullptr;
439 int itemNet = -1;
440
441 std::optional<DRC_CONSTRAINT> itemConstraint;
442 DRC_CONSTRAINT otherConstraint;
443
444 if( aItem->IsConnected() )
445 itemNet = static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode();
446
447 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aRefLayer );
448
449 m_itemTree->QueryColliding( aItem, aRefLayer, aTargetLayer,
450 // Filter:
451 [&]( BOARD_ITEM* other ) -> bool
452 {
453 FOOTPRINT* itemFP = aItem->GetParentFootprint();
454 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
455 int otherNet = -1;
456
457 if( other->IsConnected() )
458 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
459
460 if( otherNet > 0 && otherNet == itemNet )
461 return false;
462
463 if( isNullAperture( other ) )
464 return false;
465
466 if( itemFP && itemFP == other->GetParentFootprint() )
467 {
468 // Board-wide exclusion
469 if( BOARD* board = itemFP->GetBoard() )
470 {
471 if( board->GetDesignSettings().m_AllowSoldermaskBridgesInFPs )
472 return false;
473 }
474
475 // Footprint-specific exclusion
476 if( itemFP->AllowSolderMaskBridges() )
477 return false;
478 }
479
480 if( pad && otherPad && ( pad->SameLogicalPadAs( otherPad )
481 || pad->SharesNetTieGroup( otherPad ) ) )
482 {
483 return false;
484 }
485
486 if( itemFP && itemFP->IsNetTie() )
487 {
488 const std::set<int>& nets = itemFP->GetNetTieCache( aItem );
489
490 if( otherNet < 0 || nets.count( otherNet ) )
491 return false;
492 }
493
494 if( FOOTPRINT* otherFP = other->GetParentFootprint(); otherFP && otherFP->IsNetTie() )
495 {
496 const std::set<int>& nets = otherFP->GetNetTieCache( other );
497
498 if( itemNet < 0 || nets.count( itemNet ) )
499 return false;
500 }
501
502 BOARD_ITEM* a = aItem;
503 BOARD_ITEM* b = other;
504
505 // store canonical order so we don't collide in both directions (a:b and b:a)
506 if( static_cast<void*>( a ) > static_cast<void*>( b ) )
507 std::swap( a, b );
508
509 {
510 std::lock_guard<std::mutex> lock( m_checkedPairsMutex );
511 auto it = m_checkedPairs.find( { a, b } );
512
513 if( it != m_checkedPairs.end() && it->second.test( aTargetLayer ) )
514 {
515 return false;
516 }
517 else
518 {
519 m_checkedPairs[{ a, b }].set( aTargetLayer );
520 return true;
521 }
522 }
523 },
524 // Visitor:
525 [&]( BOARD_ITEM* other ) -> bool
526 {
527 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
528 PCB_VIA* otherVia = other->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( other ) : nullptr;
529 PCB_SHAPE* otherShape = other->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( other ) : nullptr;
530 auto otherItemShape = other->GetEffectiveShape( aTargetLayer );
531 int otherNet = -1;
532
533 if( other->IsConnected() )
534 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
535
536 int actual;
537 VECTOR2I pos;
538 int clearance = 0;
539
540 if( aRefLayer == F_Mask || aRefLayer == B_Mask )
541 {
542 // Aperture-to-aperture must enforce web-min-width
544 }
545 else // ( aRefLayer == F_Cu || aRefLayer == B_Cu )
546 {
547 // Copper-to-aperture uses the solder-mask-to-copper-clearance
548 clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
549 }
550
551 if( pad )
552 clearance += pad->GetSolderMaskExpansion( aRefLayer );
553 else if( via && !via->IsTented( aRefLayer ) )
554 clearance += via->GetSolderMaskExpansion();
555 else if( shape )
557
558 if( otherPad )
559 clearance += otherPad->GetSolderMaskExpansion( aTargetLayer );
560 else if( otherVia && !otherVia->IsTented( aTargetLayer ) )
561 clearance += otherVia->GetSolderMaskExpansion();
562 else if( otherShape )
563 clearance += otherShape->GetSolderMaskExpansion();
564
565 if( itemShape->Collide( otherItemShape.get(), clearance, &actual, &pos ) )
566 {
567 if( !itemConstraint.has_value() )
568 itemConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, aItem, nullptr, aRefLayer );
569
570 otherConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, other, nullptr, aTargetLayer );
571
572 bool itemConstraintIgnored = itemConstraint->GetSeverity() == RPT_SEVERITY_IGNORE;
573 bool otherConstraintIgnored = otherConstraint.GetSeverity() == RPT_SEVERITY_IGNORE;
574
575 // Mask apertures are ignored on their own; in other cases both participants must be ignored
576 if( ( isMaskAperture( aItem ) && itemConstraintIgnored )
577 || ( isMaskAperture( other ) && otherConstraintIgnored )
578 || ( itemConstraintIgnored && otherConstraintIgnored ) )
579 {
580 return !m_drcEngine->IsCancelled();
581 }
582
583 wxString msg;
584 BOARD_ITEM* colliding = nullptr;
585
586 if( aTargetLayer == F_Mask )
587 msg = _( "Front solder mask aperture bridges items with different nets" );
588 else
589 msg = _( "Rear solder mask aperture bridges items with different nets" );
590
591 // Simple mask apertures aren't associated with copper items, so they only
592 // constitute a bridge when they expose other copper items having at least
593 // two distinct nets.
594 if( isMaskAperture( aItem ) )
595 {
596 if( checkMaskAperture( aItem, other, aRefLayer, otherNet, &colliding ) )
597 {
598 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
599
600 drce->SetErrorMessage( msg );
601 drce->SetItems( aItem, colliding, other );
602 drce->SetViolatingRule( &m_bridgeRule );
603 reportViolation( drce, pos, aTargetLayer );
604 }
605 }
606 else if( isMaskAperture( other ) )
607 {
608 if( checkMaskAperture( other, aItem, aRefLayer, itemNet, &colliding ) )
609 {
610 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
611
612 drce->SetErrorMessage( msg );
613 drce->SetItems( other, colliding, aItem );
614 drce->SetViolatingRule( &m_bridgeRule );
615 reportViolation( drce, pos, aTargetLayer );
616 }
617 }
618 else if( checkItemMask( other, itemNet ) )
619 {
620 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
621
622 drce->SetErrorMessage( msg );
623 drce->SetItems( aItem, other );
624 drce->SetViolatingRule( &m_bridgeRule );
625 reportViolation( drce, pos, aTargetLayer );
626 }
627 }
628
629 return !m_drcEngine->IsCancelled();
630 },
632}
633
634
636 PCB_LAYER_ID aMaskLayer, PCB_LAYER_ID aTargetLayer )
637{
638 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
639 PCB_VIA* via = aItem->Type() == PCB_VIA_T ? static_cast<PCB_VIA*>( aItem ) : nullptr;
640 PCB_SHAPE* shape = aItem->Type() == PCB_SHAPE_T ? static_cast<PCB_SHAPE*>( aItem ) : nullptr;
641
642 for( ZONE* zone : m_board->m_DRCCopperZones )
643 {
644 if( !zone->GetLayerSet().test( aTargetLayer ) )
645 continue;
646
647 int zoneNet = zone->GetNetCode();
648
649 if( aItem->IsConnected() )
650 {
651 BOARD_CONNECTED_ITEM* connectedItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
652
653 if( zoneNet == connectedItem->GetNetCode() && zoneNet > 0 )
654 continue;
655 }
656
657 BOX2I inflatedBBox( aItemBBox );
658 int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
659
660 if( pad )
661 clearance += pad->GetSolderMaskExpansion( aTargetLayer );
662 else if( via && !via->IsTented( aTargetLayer ) )
663 clearance += via->GetSolderMaskExpansion();
664 else if( shape )
666
667 inflatedBBox.Inflate( clearance );
668
669 if( !inflatedBBox.Intersects( zone->GetBoundingBox() ) )
670 continue;
671
672 DRC_RTREE* zoneTree = m_board->m_CopperZoneRTreeCache[ zone ].get();
673 int actual;
674 VECTOR2I pos;
675
676 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aMaskLayer );
677
678 if( zoneTree && zoneTree->QueryColliding( aItemBBox, itemShape.get(), aTargetLayer, clearance,
679 &actual, &pos ) )
680 {
681 wxString msg;
682 BOARD_ITEM* colliding = nullptr;
683
684 if( aMaskLayer == F_Mask )
685 msg = _( "Front solder mask aperture bridges items with different nets" );
686 else
687 msg = _( "Rear solder mask aperture bridges items with different nets" );
688
689 // Simple mask apertures aren't associated with copper items, so they only constitute
690 // a bridge when they expose other copper items having at least two distinct nets.
691 if( isMaskAperture( aItem ) && zoneNet >= 0 )
692 {
693 if( checkMaskAperture( aItem, zone, aTargetLayer, zoneNet, &colliding ) )
694 {
695 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
696
697 drce->SetErrorMessage( msg );
698 drce->SetItems( aItem, colliding, zone );
699 drce->SetViolatingRule( &m_bridgeRule );
700 reportViolation( drce, pos, aTargetLayer );
701 }
702 }
703 else
704 {
705 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
706
707 drce->SetErrorMessage( msg );
708 drce->SetItems( aItem, zone );
709 drce->SetViolatingRule( &m_bridgeRule );
710 reportViolation( drce, pos, aTargetLayer );
711 }
712 }
713
714 if( m_drcEngine->IsCancelled() )
715 return;
716 }
717}
718
719
721{
722 LSET copperAndMaskLayers( { F_Mask, B_Mask, F_Cu, B_Cu } );
723 std::atomic<int> count = 0;
724 std::vector<BOARD_ITEM*> test_items;
725
726 forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers,
727 [&]( BOARD_ITEM* item ) -> bool
728 {
729 test_items.push_back( item );
730 return true;
731 } );
732
734
735 auto returns = tp.submit_loop( 0, test_items.size(),
736 [&]( size_t i ) -> bool
737 {
738 BOARD_ITEM* item = test_items[ i ];
739
740 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
741 return false;
742
743 BOX2I itemBBox = item->GetBoundingBox();
744
745 if( item->IsOnLayer( F_Mask ) && !isNullAperture( item ) )
746 {
747 // Test for aperture-to-aperture collisions
748 testItemAgainstItems( item, itemBBox, F_Mask, F_Mask );
749
750 // Test for aperture-to-zone collisions
751 testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu );
752 }
753 else if( item->IsOnLayer( PADSTACK::ALL_LAYERS ) )
754 {
755 // Test for copper-item-to-aperture collisions
756 testItemAgainstItems( item, itemBBox, F_Cu, F_Mask );
757 }
758
759 if( item->IsOnLayer( B_Mask ) && !isNullAperture( item ) )
760 {
761 // Test for aperture-to-aperture collisions
762 testItemAgainstItems( item, itemBBox, B_Mask, B_Mask );
763
764 // Test for aperture-to-zone collisions
765 testMaskItemAgainstZones( item, itemBBox, B_Mask, B_Cu );
766 }
767 else if( item->IsOnLayer( B_Cu ) )
768 {
769 // Test for copper-item-to-aperture collisions
770 testItemAgainstItems( item, itemBBox, B_Cu, B_Mask );
771 }
772
773 ++count;
774
775 return true;
776 } );
777
778 for( auto& ret : returns )
779 {
780 if( !ret.valid() )
781 continue;
782
783 while( ret.wait_for( std::chrono::milliseconds( 100 ) ) == std::future_status::timeout )
784 reportProgress( count, test_items.size() );
785 }
786}
787
788
790{
791 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE )
792 && m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
793 {
794 REPORT_AUX( wxT( "Solder mask violations ignored. Tests not run." ) );
795 return true; // continue with other tests
796 }
797
798 m_board = m_drcEngine->GetBoard();
799 m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth;
800 m_maxError = m_board->GetDesignSettings().m_MaxError;
802
803 auto updateLargestClearance =
804 [&]( int aClearance )
805 {
806 m_largestClearance = std::max( m_largestClearance, aClearance );
807 };
808
809 for( FOOTPRINT* footprint : m_board->Footprints() )
810 {
811 for( PAD* pad : footprint->Pads() )
812 updateLargestClearance( pad->GetSolderMaskExpansion( PADSTACK::ALL_LAYERS ) );
813
814 for( BOARD_ITEM* item : footprint->GraphicalItems() )
815 {
816 if( item->Type() == PCB_SHAPE_T )
817 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
818 }
819 }
820
821 for( PCB_TRACK* track : m_board->Tracks() )
822 updateLargestClearance( track->GetSolderMaskExpansion() );
823
824 for( BOARD_ITEM* item : m_board->Drawings() )
825 {
826 if( item->Type() == PCB_SHAPE_T )
827 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
828 }
829
830 // Order is important here: m_webWidth must be added in before m_largestCourtyardClearance is
831 // maxed with the various SILK_CLEARANCE_CONSTRAINTS.
833
834 DRC_CONSTRAINT worstClearanceConstraint;
835
836 if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
837 m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() );
838
839 if( !reportPhase( _( "Building solder mask..." ) ) )
840 return false; // DRC cancelled
841
842 m_checkedPairs.clear();
843 m_maskApertureNetMap.clear();
844
845 buildRTrees();
846
847 if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) )
848 return false; // DRC cancelled
849
851
852 if( !reportPhase( _( "Checking solder mask web integrity..." ) ) )
853 return false; // DRC cancelled
854
856
857 return !m_drcEngine->IsCancelled();
858}
859
860
861namespace detail
862{
864}
@ 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:79
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:134
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:314
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:252
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:174
SEVERITY GetSeverity() const
Definition drc_rule.h:187
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:166
MINOPTMAX< int > m_Value
Definition drc_rule.h:208
DRC_RULE * GetParentRule() const
Definition drc_rule.h:170
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:381
Implement an R-tree for fast spatial and layer indexing of connectable items.
Definition drc_rtree.h:48
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:214
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)
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:333
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:556
bool IsNetTie() const
Definition footprint.h:340
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:591
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:145
Definition pad.h:54
const wxString & GetNumber() const
Definition pad.h:136
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:159
int GetSolderMaskExpansion(PCB_LAYER_ID aLayer) const
Definition pad.cpp:1177
bool IsFreePad() const
Definition pad.cpp:284
bool SharesNetTieGroup(const PAD *aOther) const
Definition pad.cpp:261
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
const std::shared_ptr< SHAPE_POLY_SET > & GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:600
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:1301
const BOX2I GetBoundingBox() const override
Definition zone.cpp:621
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition zone.h:290
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:606
void SetIsFilled(bool isFilled)
Definition zone.h:293
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:96
@ DRCE_SOLDERMASK_BRIDGE
Definition drc_item.h:93
@ 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:779
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:87
@ RPT_SEVERITY_IGNORE
int clearance
int actual
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::thread_pool< 0 > 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
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695