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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <common.h>
23#include <footprint.h>
24#include <pad.h>
25#include <pcb_shape.h>
26#include <pcb_track.h>
27#include <pcb_text.h>
28#include <thread_pool.h>
29#include <zone.h>
30#include <geometry/seg.h>
31#include <drc/drc_engine.h>
32#include <drc/drc_item.h>
33#include <drc/drc_rule.h>
35#include <drc/drc_rtree.h>
36
37#include <algorithm>
38#include <array>
39#include <set>
40#include <unordered_set>
41#include <vector>
42
43/*
44 Solder mask tests. Checks for silkscreen which is clipped by mask openings and for bridges
45 between mask apertures with different nets.
46 Errors generated:
47 - DRCE_SILK_MASK_CLEARANCE
48 - DRCE_SOLDERMASK_BRIDGE
49*/
50
51
52static void addItemPolysWithEndings( BOARD_ITEM* aItem, SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
53 int aError, ERROR_LOC aErrorLoc )
54{
55 if( aItem->Type() == PCB_SHAPE_T )
56 {
57 PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( aItem );
58 shape->TransformWithLineEndingsToPolygon( aBuffer, aClearance, aError, aErrorLoc );
59 }
60 else
61 {
62 aItem->TransformShapeToPolygon( aBuffer, aLayer, aClearance, aError, aErrorLoc );
63 }
64}
65
66
68{
69public:
71 m_board( nullptr ),
72 m_webWidth( 0 ),
73 m_maxError( 0 ),
75 m_bridgeLimit( 0 )
76 {
77 m_bridgeRule.m_Name = _( "board setup solder mask min width" );
78 }
79
80 virtual ~DRC_TEST_PROVIDER_SOLDER_MASK() = default;
81
82 virtual bool Run() override;
83
84 virtual const wxString GetName() const override { return wxT( "solder_mask_issues" ); };
85
86private:
87 void addItemToRTrees( BOARD_ITEM* aItem );
88 void buildRTrees();
89
91 void testMaskBridges();
92
93 void testItemAgainstItems( BOARD_ITEM* aItem, const BOX2I& aItemBBox,
94 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer );
95 void testMaskItemAgainstZones( BOARD_ITEM* item, const BOX2I& itemBBox,
96 PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer );
97
98 void recordMaskAperture( BOARD_ITEM* aMaskItem, BOARD_ITEM* aTestItem, PCB_LAYER_ID aTestLayer,
99 int aTestNet, const VECTOR2I& aPos );
100
101 bool maskApertureBridgeExcluded( FOOTPRINT* aApertureFootprint,
102 const std::map<wxString, int>& aNetTieGroups,
103 BOARD_ITEM* aRefItem, BOARD_ITEM* aTestItem );
104
106
107 void collectBridge( BOARD_ITEM* aItemA, BOARD_ITEM* aItemB, BOARD_ITEM* aItemC,
108 const VECTOR2I& aPos, PCB_LAYER_ID aLayer );
109
111
112 bool checkItemMask( BOARD_ITEM* aItem, int aTestNet );
113
114private:
116
121
122 std::unique_ptr<DRC_RTREE> m_fullSolderMaskRTree;
123 std::unique_ptr<DRC_RTREE> m_itemTree;
124
126 std::unordered_map<PTR_PTR_CACHE_KEY, LSET> m_checkedPairs;
127
128 // Shapes used to define solder mask apertures don't have nets, so a bridge exists only when
129 // an aperture exposes copper on two different nets. Every (item, net) that collides with an
130 // aperture is recorded during the parallel pass, then bridges are decided and reported
131 // single-threaded afterwards so the reported set never depends on worker arrival order.
138
139 std::mutex m_apertureMutex;
140 std::unordered_map<PTR_LAYER_CACHE_KEY, std::vector<MASK_APERTURE_ITEM>> m_maskApertureItems;
141
142 // Bridges are collected during the tests and reported in a deterministic order afterwards.
143 // Without a stable order the error-limit cap keeps a different subset of an over-limit board's
144 // bridges on every run, so the report entries wobble even though the count is stable. Order by
145 // layer, then the sorted UUIDs of the participating items, then the position, none of which
146 // depend on heap addresses or worker scheduling.
148 {
154 std::array<KIID, 3> ids;
155
156 bool operator<( const PENDING_BRIDGE& aRhs ) const
157 {
158 if( layer != aRhs.layer )
159 return layer < aRhs.layer;
160
161 for( size_t ii = 0; ii < ids.size(); ++ii )
162 {
163 if( ids[ ii ] != aRhs.ids[ ii ] )
164 return ids[ ii ] < aRhs.ids[ ii ];
165 }
166
167 if( pos.x != aRhs.pos.x )
168 return pos.x < aRhs.pos.x;
169
170 return pos.y < aRhs.pos.y;
171 }
172 };
173
174 // Only the first m_bridgeLimit bridges in that order can ever be reported, so the collection is
175 // a bounded max-heap rather than the full list. A board whose bridge count runs into the
176 // millions would otherwise cost memory and a sort proportional to a count the report discards.
177 std::mutex m_bridgeMutex;
178 std::vector<PENDING_BRIDGE> m_bridgeViolations;
180};
181
182
184{
185 // Rule areas are purely logical: no copper, no mask, no silk. Skip them entirely
186 // so they cannot contribute to solder-mask bridge or silk-to-mask collisions.
187 if( aItem->Type() == PCB_ZONE_T && static_cast<ZONE*>( aItem )->GetIsRuleArea() )
188 return;
189
190 for( PCB_LAYER_ID layer : { F_Mask, B_Mask } )
191 {
192 if( !aItem->IsOnLayer( layer ) )
193 continue;
194
195 SHAPE_POLY_SET* solderMask = m_board->m_SolderMaskBridges->GetFill( layer );
196
197 if( aItem->Type() == PCB_ZONE_T )
198 {
199 ZONE* zone = static_cast<ZONE*>( aItem );
200
201 solderMask->BooleanAdd( *zone->GetFilledPolysList( layer ) );
202 }
203 else
204 {
205 int clearance = m_webWidth / 2;
206
207 if( aItem->Type() == PCB_PAD_T )
208 clearance += static_cast<PAD*>( aItem )->GetSolderMaskExpansion( layer );
209 else if( aItem->Type() == PCB_VIA_T )
210 clearance += static_cast<PCB_VIA*>( aItem )->GetSolderMaskExpansion();
211 else if( aItem->Type() == PCB_TRACE_T )
212 clearance += static_cast<PCB_TRACK*>( aItem )->GetSolderMaskExpansion();
213 else if( aItem->Type() == PCB_SHAPE_T )
214 clearance += static_cast<PCB_SHAPE*>( aItem )->GetSolderMaskExpansion();
215
216 if( aItem->Type() == PCB_FIELD_T || aItem->Type() == PCB_TEXT_T )
217 {
218 PCB_TEXT* text = static_cast<PCB_TEXT*>( aItem );
219
220 text->TransformTextToPolySet( *solderMask, clearance, m_maxError, ERROR_OUTSIDE );
221 }
222 else
223 {
224 addItemPolysWithEndings( aItem, *solderMask, layer, clearance, m_maxError, ERROR_OUTSIDE );
225 }
226
227 m_itemTree->Insert( aItem, layer, NULL_CONSTRAINT, m_largestClearance );
228 }
229 }
230}
231
232
234{
235 ZONE* solderMask = m_board->m_SolderMaskBridges;
236 LSET layers( { F_Mask, B_Mask, F_Cu, B_Cu } );
237
238 const size_t progressDelta = 500;
239 int count = 0;
240 int ii = 0;
241
242 solderMask->GetFill( F_Mask )->RemoveAllContours();
243 solderMask->GetFill( B_Mask )->RemoveAllContours();
244
245 m_fullSolderMaskRTree = std::make_unique<DRC_RTREE>();
246 m_itemTree = std::make_unique<DRC_RTREE>();
247
249 [&]( BOARD_ITEM* item ) -> bool
250 {
251 ++count;
252 return true;
253 } );
254
256 [&]( BOARD_ITEM* item ) -> bool
257 {
258 if( !reportProgress( ii++, count, progressDelta ) )
259 return false;
260
261 addItemToRTrees( item );
262 return true;
263 } );
264
265 solderMask->GetFill( F_Mask )->Simplify();
266 solderMask->GetFill( B_Mask )->Simplify();
267
268 if( m_webWidth > 0 )
269 {
272 }
273
274 solderMask->SetFillFlag( F_Mask, true );
275 solderMask->SetFillFlag( B_Mask, true );
276 solderMask->SetIsFilled( true );
277
278 solderMask->CacheTriangulation();
279
280 m_fullSolderMaskRTree->Insert( solderMask, F_Mask, NULL_CONSTRAINT );
281 m_fullSolderMaskRTree->Insert( solderMask, B_Mask, NULL_CONSTRAINT );
282 m_fullSolderMaskRTree->Build();
283
284 m_itemTree->Build();
285
286 m_checkedPairs.clear();
287}
288
289
291{
292 LSET silkLayers( { F_SilkS, B_SilkS } );
293
294 // If we have no minimum web width then we delegate to the silk checker which does object-to-object
295 // testing (instead of object-to-solder-mask-zone-fill checking that we do here).
296 if( m_webWidth <= 0 )
297 return;
298
299 const size_t progressDelta = 250;
300 int count = 0;
301 int ii = 0;
302
304 [&]( BOARD_ITEM* item ) -> bool
305 {
306 ++count;
307 return true;
308 } );
309
311 [&]( BOARD_ITEM* item ) -> bool
312 {
313 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE ) )
314 return false;
315
316 if( !reportProgress( ii++, count, progressDelta ) )
317 return false;
318
319 if( isInvisibleText( item ) )
320 return true;
321
322 for( PCB_LAYER_ID layer : silkLayers )
323 {
324 if( !item->IsOnLayer( layer ) )
325 continue;
326
327 PCB_LAYER_ID maskLayer = layer == F_SilkS ? F_Mask : B_Mask;
328 BOX2I itemBBox = item->GetBoundingBox();
330 item, nullptr, maskLayer );
331 int clearance = constraint.GetValue().Min();
332 int actual;
333 VECTOR2I pos;
334
335 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE || clearance < 0 )
336 return true;
337
338 std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( layer );
339
340 if( m_fullSolderMaskRTree->QueryColliding( itemBBox, itemShape.get(), maskLayer,
341 clearance, &actual, &pos ) )
342 {
343 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SILK_MASK_CLEARANCE );
344
345 if( clearance > 0 )
346 {
347 drce->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
348 constraint.GetName(),
349 clearance,
350 actual ) );
351 }
352
353 drce->SetItems( item );
354 drce->SetViolatingRule( constraint.GetParentRule() );
355
356 reportViolation( drce, pos, layer );
357 }
358 }
359
360 return true;
361 } );
362}
363
364
366{
367 if( aItem->Type() == PCB_PAD_T )
368 return static_cast<PAD*>( aItem )->IsNPTHWithNoCopper();
369
370 return false;
371}
372
373
374// Simple mask apertures aren't associated with copper items, so they only constitute a bridge
375// when they expose other copper items having at least two distinct nets.
376//
377// Note that this algorithm is also used for free pads.
378
380{
381 if( aItem->Type() == PCB_PAD_T && static_cast<PAD*>( aItem )->IsFreePad() )
382 return true;
383
384 static const LSET saved( { F_Mask, B_Mask } );
385
386 LSET maskLayers = aItem->GetLayerSet() & saved;
387 LSET copperLayers = ( aItem->GetLayerSet() & ~saved ) & LSET::AllCuMask();
388
389 return maskLayers.count() > 0 && copperLayers.count() == 0;
390}
391
392
394 PCB_LAYER_ID aTestLayer, int aTestNet,
395 const VECTOR2I& aPos )
396{
397 // Only positive nets can bridge, and the pairing below is quadratic in what is recorded here.
398 if( aTestNet <= 0 )
399 return;
400
401 if( aTestLayer == F_Mask && !aTestItem->IsOnLayer( F_Cu ) )
402 return;
403
404 if( aTestLayer == B_Mask && !aTestItem->IsOnLayer( B_Cu ) )
405 return;
406
407 // Mask apertures in footprints which allow soldermask bridges are ignored entirely.
408 if( FOOTPRINT* fp = aMaskItem->GetParentFootprint(); fp && fp->AllowSolderMaskBridges() )
409 return;
410
411 PCB_LAYER_ID maskLayer = IsFrontLayer( aTestLayer ) ? F_Mask : B_Mask;
412 PTR_LAYER_CACHE_KEY key = { aMaskItem, maskLayer };
413
414 std::lock_guard<std::mutex> lock( m_apertureMutex );
415 m_maskApertureItems[ key ].push_back( { aTestItem, aTestNet, aPos } );
416}
417
418
419// Items belonging to the same net-tie group (or the same logical pad) may legitimately share a
420// mask aperture, so a pairing between them is not a bridge.
421
423 FOOTPRINT* aApertureFootprint, const std::map<wxString, int>& aNetTieGroups,
424 BOARD_ITEM* aRefItem, BOARD_ITEM* aTestItem )
425{
426 if( !aApertureFootprint || aTestItem->GetParentFootprint() != aApertureFootprint )
427 return false;
428
429 PAD* padA = aRefItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aRefItem ) : nullptr;
430 PAD* padB = aTestItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aTestItem ) : nullptr;
431
432 if( padA && padB )
433 return padA->SameLogicalPadAs( padB ) || padA->SharesNetTieGroup( padB );
434
435 if( padA && aTestItem->Type() == PCB_SHAPE_T )
436 return aNetTieGroups.contains( padA->GetNumber() );
437 else if( padB && aRefItem->Type() == PCB_SHAPE_T )
438 return aNetTieGroups.contains( padB->GetNumber() );
439
440 return false;
441}
442
443
445{
446 if( FOOTPRINT* fp = aItem->GetParentFootprint() )
447 {
448 // If we're allowing bridges then we're allowing bridges. Nothing to check.
449 if( fp->AllowSolderMaskBridges() )
450 return false;
451
452 // Items belonging to a net-tie may share the mask aperture of pads in the same group.
453 if( aItem->Type() == PCB_PAD_T && fp->IsNetTie() )
454 {
455 PAD* pad = static_cast<PAD*>( aItem );
456 std::map<wxString, int> padNumberToGroupIdxMap = fp->MapPadNumbersToNetTieGroups();
457 int groupIdx = padNumberToGroupIdxMap[ pad->GetNumber() ];
458
459 if( groupIdx >= 0 )
460 {
461 if( aTestNet < 0 )
462 return false;
463
464 if( pad->GetNetCode() == aTestNet )
465 return false;
466
467 for( PAD* other : fp->GetNetTiePads( pad ) )
468 {
469 if( other->GetNetCode() == aTestNet )
470 return false;
471 }
472 }
473 }
474 }
475
476 return true;
477}
478
479
481 PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer )
482{
483 PAD* pad = aItem->Type() == PCB_PAD_T ? static_cast<PAD*>( aItem ) : nullptr;
484 int itemNet = -1;
485
486 std::optional<DRC_CONSTRAINT> itemConstraint;
487 DRC_CONSTRAINT otherConstraint;
488
489 if( aItem->IsConnected() )
490 itemNet = static_cast<BOARD_CONNECTED_ITEM*>( aItem )->GetNetCode();
491
492 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aRefLayer );
493
494 m_itemTree->QueryColliding(
495 aItem, aRefLayer, aTargetLayer,
496 // Filter:
497 [&]( BOARD_ITEM* other ) -> bool
498 {
499 FOOTPRINT* itemFP = aItem->GetParentFootprint();
500 PAD* otherPad = other->Type() == PCB_PAD_T ? static_cast<PAD*>( other ) : nullptr;
501 int otherNet = -1;
502
503 if( other->IsConnected() )
504 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
505
506 if( otherNet > 0 && otherNet == itemNet )
507 return false;
508
509 if( isNPTHPadWithNoCopper( other ) )
510 return false;
511
512 if( itemFP && itemFP == other->GetParentFootprint() )
513 {
514 // Board-wide exclusion
515 if( BOARD* board = itemFP->GetBoard() )
516 {
517 if( board->GetDesignSettings().m_AllowSoldermaskBridgesInFPs )
518 return false;
519 }
520
521 // Footprint-specific exclusion
522 if( itemFP->AllowSolderMaskBridges() )
523 return false;
524 }
525
526 if( pad && otherPad && ( pad->SameLogicalPadAs( otherPad )
527 || pad->SharesNetTieGroup( otherPad ) ) )
528 {
529 return false;
530 }
531
532 if( itemFP && itemFP->IsNetTie() )
533 {
534 const std::set<int>& nets = itemFP->GetNetTieCache( aItem );
535
536 if( otherNet < 0 || nets.count( otherNet ) )
537 return false;
538 }
539
540 if( FOOTPRINT* otherFP = other->GetParentFootprint(); otherFP && otherFP->IsNetTie() )
541 {
542 const std::set<int>& nets = otherFP->GetNetTieCache( other );
543
544 if( itemNet < 0 || nets.count( itemNet ) )
545 return false;
546 }
547
548 BOARD_ITEM* a = aItem;
549 BOARD_ITEM* b = other;
550
551 // store canonical order so we don't collide in both directions (a:b and b:a)
552 if( static_cast<void*>( a ) > static_cast<void*>( b ) )
553 std::swap( a, b );
554
555 {
556 std::lock_guard<std::mutex> lock( m_checkedPairsMutex );
557 auto it = m_checkedPairs.find( { a, b } );
558
559 if( it != m_checkedPairs.end() && it->second.test( aTargetLayer ) )
560 {
561 return false;
562 }
563 else
564 {
565 m_checkedPairs[{ a, b }].set( aTargetLayer );
566 return true;
567 }
568 }
569 },
570 // Visitor:
571 [&]( BOARD_ITEM* other ) -> bool
572 {
573 auto otherItemShape = other->GetEffectiveShape( aTargetLayer );
574 int otherNet = -1;
575
576 if( other->IsConnected() )
577 otherNet = static_cast<BOARD_CONNECTED_ITEM*>( other )->GetNetCode();
578
579 int actual;
580 VECTOR2I pos;
581 int clearance = 0;
582
583 if( aRefLayer == F_Mask || aRefLayer == B_Mask )
584 {
585 // Aperture-to-aperture must enforce web-min-width
587 }
588 else // ( aRefLayer == F_Cu || aRefLayer == B_Cu )
589 {
590 // Copper-to-aperture uses the solder-mask-to-copper-clearance
591 clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
592 }
593
594 if( itemShape->Collide( otherItemShape.get(), clearance, &actual, &pos ) )
595 {
596 if( !itemConstraint.has_value() )
597 itemConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, aItem, nullptr, aRefLayer );
598
599 otherConstraint = m_drcEngine->EvalRules( BRIDGED_MASK_CONSTRAINT, other, nullptr, aTargetLayer );
600
601 bool itemConstraintIgnored = itemConstraint->GetSeverity() == RPT_SEVERITY_IGNORE;
602 bool otherConstraintIgnored = otherConstraint.GetSeverity() == RPT_SEVERITY_IGNORE;
603
604 // Mask apertures are ignored on their own; in other cases both participants must be ignored
605 if( ( isMaskAperture( aItem ) && itemConstraintIgnored )
606 || ( isMaskAperture( other ) && otherConstraintIgnored )
607 || ( itemConstraintIgnored && otherConstraintIgnored ) )
608 {
609 return !m_drcEngine->IsCancelled();
610 }
611
612 // Simple mask apertures aren't associated with copper items, so they only
613 // constitute a bridge when they expose other copper items having at least
614 // two distinct nets. Record the colliding item now and decide/report bridges
615 // deterministically once all threads have finished.
616 if( isMaskAperture( aItem ) )
617 {
618 recordMaskAperture( aItem, other, aRefLayer, otherNet, pos );
619 }
620 else if( isMaskAperture( other ) )
621 {
622 recordMaskAperture( other, aItem, aRefLayer, itemNet, pos );
623 }
624 else if( checkItemMask( other, itemNet ) )
625 {
626 collectBridge( aItem, other, nullptr, pos, aTargetLayer );
627 }
628 }
629
630 return !m_drcEngine->IsCancelled();
631 },
633}
634
635
637 PCB_LAYER_ID aMaskLayer, PCB_LAYER_ID aTargetLayer )
638{
639 std::shared_ptr<SHAPE> itemShape = aItem->GetEffectiveShape( aMaskLayer );
640 BOX2I apertureBBox( aItemBBox );
641
642 apertureBBox.Merge( itemShape->BBox() );
643
644 for( ZONE* zone : m_board->m_DRCCopperZones )
645 {
646 if( !zone->GetLayerSet().test( aTargetLayer ) )
647 continue;
648
649 int zoneNet = zone->GetNetCode();
650
651 if( aItem->IsConnected() )
652 {
653 BOARD_CONNECTED_ITEM* connectedItem = static_cast<BOARD_CONNECTED_ITEM*>( aItem );
654
655 if( zoneNet == connectedItem->GetNetCode() && zoneNet > 0 )
656 continue;
657 }
658
659 BOX2I inflatedBBox( apertureBBox );
660 int clearance = m_board->GetDesignSettings().m_SolderMaskToCopperClearance;
661
662 inflatedBBox.Inflate( clearance );
663
664 if( !inflatedBBox.Intersects( zone->GetBoundingBox() ) )
665 continue;
666
667 DRC_RTREE* zoneTree = m_board->GetCopperZoneRTree( zone );
668 int actual;
669 VECTOR2I pos;
670
671 if( zoneTree
672 && zoneTree->QueryColliding( apertureBBox, itemShape.get(), aTargetLayer, clearance, &actual, &pos ) )
673 {
674 // Simple mask apertures aren't associated with copper items, so they only constitute
675 // a bridge when they expose other copper items having at least two distinct nets.
676 if( isMaskAperture( aItem ) && zoneNet >= 0 )
677 {
678 recordMaskAperture( aItem, zone, aMaskLayer, zoneNet, pos );
679 }
680 else
681 {
682 collectBridge( aItem, zone, nullptr, pos, aTargetLayer );
683 }
684 }
685
686 if( m_drcEngine->IsCancelled() )
687 return;
688 }
689}
690
691
693{
694 LSET copperAndMaskLayers( { F_Mask, B_Mask, F_Cu, B_Cu } );
695 std::atomic<int> count = 0;
696 std::vector<BOARD_ITEM*> test_items;
697
698 forEachGeometryItem( s_allBasicItemsButZones, copperAndMaskLayers,
699 [&]( BOARD_ITEM* item ) -> bool
700 {
701 test_items.push_back( item );
702 return true;
703 } );
704
706
707 auto returns = tp.submit_loop( 0, test_items.size(),
708 [&]( size_t i ) -> bool
709 {
710 BOARD_ITEM* item = test_items[ i ];
711
712 if( m_drcEngine->IsCancelled() )
713 return false;
714
715 BOX2I itemBBox = item->GetBoundingBox();
716
717 if( item->IsOnLayer( F_Mask ) && !isNPTHPadWithNoCopper( item ) )
718 {
719 // Test for aperture-to-aperture collisions
720 testItemAgainstItems( item, itemBBox, F_Mask, F_Mask );
721
722 // Test for aperture-to-zone collisions
723 testMaskItemAgainstZones( item, itemBBox, F_Mask, F_Cu );
724 }
725 else if( item->IsOnLayer( F_Cu ) )
726 {
727 // Test for copper-item-to-aperture collisions
728 testItemAgainstItems( item, itemBBox, F_Cu, F_Mask );
729 }
730
731 if( item->IsOnLayer( B_Mask ) && !isNPTHPadWithNoCopper( item ) )
732 {
733 // Test for aperture-to-aperture collisions
734 testItemAgainstItems( item, itemBBox, B_Mask, B_Mask );
735
736 // Test for aperture-to-zone collisions
737 testMaskItemAgainstZones( item, itemBBox, B_Mask, B_Cu );
738 }
739 else if( item->IsOnLayer( B_Cu ) )
740 {
741 // Test for copper-item-to-aperture collisions
742 testItemAgainstItems( item, itemBBox, B_Cu, B_Mask );
743 }
744
745 ++count;
746
747 return true;
748 } );
749
750 for( auto& ret : returns )
751 {
752 if( !ret.valid() )
753 continue;
754
755 while( ret.wait_for( std::chrono::milliseconds( 100 ) ) == std::future_status::timeout )
756 reportProgress( count, test_items.size() );
757 }
758
759 // Decide mask aperture bridges now that all threads have completed and the full set of items
760 // exposed by each aperture is known, then emit every collected bridge in a deterministic order.
761 reportMaskApertureBridges();
762 flushBridgeViolations();
763}
764
765
767 BOARD_ITEM* aItemC, const VECTOR2I& aPos,
768 PCB_LAYER_ID aLayer )
769{
770 // Canonicalize the item order so the reported violation is identical no matter which worker
771 // observed the collision. The aperture (when present) stays first; the copper items are
772 // ordered by UUID.
773 if( !aItemC )
774 {
775 if( aItemB->m_Uuid < aItemA->m_Uuid )
776 std::swap( aItemA, aItemB );
777 }
778 else if( aItemC->m_Uuid < aItemB->m_Uuid )
779 {
780 std::swap( aItemB, aItemC );
781 }
782
783 // Only the third item is ever absent, and the swaps above already ordered the copper items, so
784 // the key needs at most the aperture inserted. The unused slot stays trailing.
785 std::array<KIID, 3> ids = { aItemA->m_Uuid, aItemB->m_Uuid,
786 aItemC ? aItemC->m_Uuid : niluuid };
787
788 // Inserted by hand because std::sort over a two- or three-element runtime range inlines an
789 // introsort GCC cannot bound
790 if( ids[1] < ids[0] )
791 std::swap( ids[0], ids[1] );
792
793 if( aItemC && ids[2] < ids[1] )
794 {
795 std::swap( ids[1], ids[2] );
796
797 if( ids[1] < ids[0] )
798 std::swap( ids[0], ids[1] );
799 }
800
801 PENDING_BRIDGE bridge = { aItemA, aItemB, aItemC, aPos, aLayer, ids };
802
803 std::lock_guard<std::mutex> lock( m_bridgeMutex );
804
805 if( static_cast<int>( m_bridgeViolations.size() ) == m_bridgeLimit )
806 {
807 // Anything sorting after the worst kept bridge can never reach the report.
808 if( !( bridge < m_bridgeViolations.front() ) )
809 return;
810
811 std::pop_heap( m_bridgeViolations.begin(), m_bridgeViolations.end() );
812 m_bridgeViolations.pop_back();
813 }
814
815 m_bridgeViolations.push_back( bridge );
816 std::push_heap( m_bridgeViolations.begin(), m_bridgeViolations.end() );
817}
818
819
821{
822 // The collection is a max-heap holding at most the reportable count, so this puts it in
823 // reporting order without ever having sorted the bridges the cap discards.
824 std::sort_heap( m_bridgeViolations.begin(), m_bridgeViolations.end() );
825
826 const wxString frontMsg = _( "Front solder mask aperture bridges items with different nets" );
827 const wxString backMsg = _( "Rear solder mask aperture bridges items with different nets" );
828
829 for( const PENDING_BRIDGE& bridge : m_bridgeViolations )
830 {
831 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) || m_drcEngine->IsCancelled() )
832 break;
833
834 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_SOLDERMASK_BRIDGE );
835
836 drce->SetErrorMessage( IsFrontLayer( bridge.layer ) ? frontMsg : backMsg );
837
838 // SetItems skips null participants, so the two-item case needs no separate call.
839 drce->SetItems( bridge.a, bridge.b, bridge.c );
840 drce->SetViolatingRule( &m_bridgeRule );
841
842 // Recompute the marker position from the two (UUID-ordered) copper items so it does not
843 // depend on which worker observed the collision. Query the copper layer, where every
844 // participant (including zones) has a real shape.
845 BOARD_ITEM* itemX = bridge.c ? bridge.b : bridge.a;
846 BOARD_ITEM* itemY = bridge.c ? bridge.c : bridge.b;
847 PCB_LAYER_ID copperLayer = IsFrontLayer( bridge.layer ) ? F_Cu : B_Cu;
848 VECTOR2I markerPos = itemX->GetPosition();
849
850 std::shared_ptr<SHAPE> shapeX = itemX->GetEffectiveShape( copperLayer );
851 std::shared_ptr<SHAPE> shapeY = itemY->GetEffectiveShape( copperLayer );
852 VECTOR2I ptX, ptY;
853
854 if( shapeX->NearestPoints( shapeY.get(), ptX, ptY ) )
855 markerPos = SEG( ptX, ptY ).Center();
856
857 reportViolation( drce, markerPos, bridge.layer );
858 }
859}
860
861
863{
864 // Visit apertures in a stable order (heap addresses and worker order are not reproducible).
865 std::vector<PTR_LAYER_CACHE_KEY> apertureKeys;
866 apertureKeys.reserve( m_maskApertureItems.size() );
867
868 for( const auto& [key, items] : m_maskApertureItems )
869 apertureKeys.push_back( key );
870
871 std::sort( apertureKeys.begin(), apertureKeys.end(),
872 []( const PTR_LAYER_CACHE_KEY& a, const PTR_LAYER_CACHE_KEY& b ) -> bool
873 {
874 if( a.A->m_Uuid != b.A->m_Uuid )
875 return a.A->m_Uuid < b.A->m_Uuid;
876
877 return a.Layer < b.Layer;
878 } );
879
880 for( const PTR_LAYER_CACHE_KEY& key : apertureKeys )
881 {
882 // Pairing is quadratic in the items an aperture exposes, so stay interruptible.
883 if( m_drcEngine->IsCancelled() )
884 return;
885
886 BOARD_ITEM* aperture = key.A;
887 PCB_LAYER_ID maskLayer = key.Layer;
888
889 // Built once per aperture rather than per candidate pair; the map covers every pad in the
890 // footprint and the pairing below is quadratic.
891 FOOTPRINT* apertureFootprint = aperture->GetParentFootprint();
892 std::map<wxString, int> netTieGroups;
893
894 if( apertureFootprint )
895 netTieGroups = apertureFootprint->MapPadNumbersToNetTieGroups();
896
897 std::vector<MASK_APERTURE_ITEM>& items = m_maskApertureItems.at( key );
898
899 std::sort( items.begin(), items.end(),
900 []( const MASK_APERTURE_ITEM& a, const MASK_APERTURE_ITEM& b ) -> bool
901 {
902 if( a.item->m_Uuid != b.item->m_Uuid )
903 return a.item->m_Uuid < b.item->m_Uuid;
904
905 return a.net < b.net;
906 } );
907
908 // The first positive net exposed by the aperture is the reference net; any item on a
909 // different positive net bridges it.
910 BOARD_ITEM* refItem = nullptr;
911 int refNet = -1;
912
913 for( const MASK_APERTURE_ITEM& entry : items )
914 {
915 if( entry.net > 0 )
916 {
917 refItem = entry.item;
918 refNet = entry.net;
919 break;
920 }
921 }
922
923 if( !refItem )
924 continue;
925
926 const bool reportAllTracks = m_drcEngine->GetReportAllTrackErrors();
927
928 std::unordered_set<PTR_PTR_CACHE_KEY> reportedPairs;
929
930 for( const MASK_APERTURE_ITEM& collision : items )
931 {
932 if( m_drcEngine->IsCancelled() )
933 return;
934
935 if( collision.net == refNet )
936 continue;
937
938 // Footprint-local exclusions need the colliding item inside the aperture's footprint,
939 // which is invariant across the inner loop.
940 bool collisionInFootprint = apertureFootprint
941 && collision.item->GetParentFootprint() == apertureFootprint;
942
943 if( collisionInFootprint
944 && maskApertureBridgeExcluded( apertureFootprint, netTieGroups, refItem,
945 collision.item ) )
946 {
947 continue;
948 }
949
950 bool reportedAnyTrack = false;
951
952 for( const MASK_APERTURE_ITEM& entry : items )
953 {
954 if( entry.net == collision.net )
955 continue;
956
957 if( collisionInFootprint
958 && maskApertureBridgeExcluded( apertureFootprint, netTieGroups, entry.item,
959 collision.item ) )
960 {
961 continue;
962 }
963
964 bool entryIsTrack = entry.item->Type() == PCB_TRACE_T || entry.item->Type() == PCB_ARC_T;
965
966 // Track throttling is per-collision, so it must gate emission before the pair is
967 // marked reported. Marking a suppressed pair here would drop it for good under a
968 // later collision ordering, making the total count depend on the reference net.
969 if( entryIsTrack && reportedAnyTrack && !reportAllTracks )
970 continue;
971
972 // Deduplicate unordered pairs so (B, C) and (C, B) are reported once. std::less
973 // gives a total order over unrelated pointers, which bare < does not.
974 BOARD_ITEM* lo = entry.item;
975 BOARD_ITEM* hi = collision.item;
976
977 if( std::less<BOARD_ITEM*>{}( hi, lo ) )
978 std::swap( lo, hi );
979
980 if( !reportedPairs.insert( { lo, hi } ).second )
981 continue;
982
983 collectBridge( aperture, entry.item, collision.item, collision.pos, maskLayer );
984
985 if( entryIsTrack )
986 reportedAnyTrack = true;
987 }
988 }
989 }
990}
991
992
994{
995 if( m_drcEngine->IsErrorLimitExceeded( DRCE_SILK_MASK_CLEARANCE )
996 && m_drcEngine->IsErrorLimitExceeded( DRCE_SOLDERMASK_BRIDGE ) )
997 {
998 REPORT_AUX( wxT( "Solder mask violations ignored. Tests not run." ) );
999 return true; // continue with other tests
1000 }
1001
1002 m_board = m_drcEngine->GetBoard();
1003 m_webWidth = m_board->GetDesignSettings().m_SolderMaskMinWidth;
1004 m_maxError = m_board->GetDesignSettings().m_MaxError;
1006
1007 auto updateLargestClearance =
1008 [&]( int aClearance )
1009 {
1010 m_largestClearance = std::max( m_largestClearance, aClearance );
1011 };
1012
1013 for( FOOTPRINT* footprint : m_board->Footprints() )
1014 {
1015 for( PAD* pad : footprint->Pads() )
1016 {
1017 pad->Padstack().ForEachUniqueLayer(
1018 [&]( PCB_LAYER_ID aLayer )
1019 {
1020 updateLargestClearance( pad->GetSolderMaskExpansion( aLayer ) );
1021 } );
1022 }
1023
1024 for( BOARD_ITEM* item : footprint->GraphicalItems() )
1025 {
1026 if( item->Type() == PCB_SHAPE_T )
1027 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
1028 }
1029 }
1030
1031 for( PCB_TRACK* track : m_board->Tracks() )
1032 updateLargestClearance( track->GetSolderMaskExpansion() );
1033
1034 for( BOARD_ITEM* item : m_board->Drawings() )
1035 {
1036 if( item->Type() == PCB_SHAPE_T )
1037 updateLargestClearance( static_cast<PCB_SHAPE*>( item )->GetSolderMaskExpansion() );
1038 }
1039
1040 // Order is important here: m_webWidth must be added in before m_largestClearance is
1041 // maxed with the various clearance constraints.
1043
1044 // Include SolderMaskToCopperClearance so R-tree queries find copper items that are within
1045 // the required distance of mask apertures. Without this, tracks passing near pad apertures
1046 // from different nets would not be found if SolderMaskToCopperClearance > m_largestClearance.
1048 m_board->GetDesignSettings().m_SolderMaskToCopperClearance );
1049
1050 DRC_CONSTRAINT worstClearanceConstraint;
1051
1052 if( m_drcEngine->QueryWorstConstraint( SILK_CLEARANCE_CONSTRAINT, worstClearanceConstraint ) )
1053 m_largestClearance = std::max( m_largestClearance, worstClearanceConstraint.m_Value.Min() );
1054
1055 if( !reportPhase( _( "Building solder mask..." ) ) )
1056 return false; // DRC cancelled
1057
1058 m_checkedPairs.clear();
1059 m_maskApertureItems.clear();
1060 m_bridgeViolations.clear();
1061
1062 // Snapshotting the cap keeps the bridge collection bounded without contending on the engine's
1063 // error-limit mutex from the worker threads. Nothing else reports DRCE_SOLDERMASK_BRIDGE, so
1064 // the budget cannot shrink under us.
1067
1068 buildRTrees();
1069
1070 if( !reportPhase( _( "Checking solder mask to silk clearance..." ) ) )
1071 return false; // DRC cancelled
1072
1074
1075 if( m_bridgeLimit > 0 )
1076 {
1077 if( !reportPhase( _( "Checking solder mask web integrity..." ) ) )
1078 return false; // DRC cancelled
1079
1081 }
1082
1083 return !m_drcEngine->IsCancelled();
1084}
1085
1086
1087namespace detail
1088{
1090}
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
@ ERROR_OUTSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
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:85
virtual bool IsConnected() const
Returns information if the object is derived from BOARD_CONNECTED_ITEM.
Definition board_item.h:173
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:418
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:355
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
wxString GetName() const
Definition drc_rule.h:162
SEVERITY GetSeverity() const
Definition drc_rule.h:181
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:129
MINOPTMAX< int > m_Value
Definition drc_rule.h:204
DRC_RULE * GetParentRule() const
Definition drc_rule.h:147
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:492
Implement an R-tree for fast spatial and layer indexing of connectable items.
Definition drc_rtree.h:46
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:231
virtual const wxString GetName() const override
void testMaskItemAgainstZones(BOARD_ITEM *item, const BOX2I &itemBBox, PCB_LAYER_ID refLayer, PCB_LAYER_ID targetLayer)
void collectBridge(BOARD_ITEM *aItemA, BOARD_ITEM *aItemB, BOARD_ITEM *aItemC, const VECTOR2I &aPos, PCB_LAYER_ID aLayer)
virtual ~DRC_TEST_PROVIDER_SOLDER_MASK()=default
std::unique_ptr< DRC_RTREE > m_fullSolderMaskRTree
bool maskApertureBridgeExcluded(FOOTPRINT *aApertureFootprint, const std::map< wxString, int > &aNetTieGroups, BOARD_ITEM *aRefItem, BOARD_ITEM *aTestItem)
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
std::vector< PENDING_BRIDGE > m_bridgeViolations
bool checkItemMask(BOARD_ITEM *aItem, int aTestNet)
std::unordered_map< PTR_LAYER_CACHE_KEY, std::vector< MASK_APERTURE_ITEM > > m_maskApertureItems
void recordMaskAperture(BOARD_ITEM *aMaskItem, BOARD_ITEM *aTestItem, PCB_LAYER_ID aTestLayer, int aTestNet, const VECTOR2I &aPos)
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 VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
const KIID m_Uuid
Definition eda_item.h:599
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
void TransformWithLineEndingsToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the shape body shortened for line endings plus line-ending geometry to polygons.
bool AllowSolderMaskBridges() const
Definition footprint.h:518
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:682
bool IsNetTie() const
Definition footprint.h:525
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
T Min() const
Definition minoptmax.h:29
Definition pad.h:61
const wxString & GetNumber() const
Definition pad.h:143
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:166
bool IsFreePad() const
Definition pad.cpp:609
bool SharesNetTieGroup(const PAD *aOther) const
Definition pad.cpp:586
Definition seg.h:38
VECTOR2I Center() const
Returns the center point of the line.
Definition seg.h:376
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:70
void CacheTriangulation(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, const SHAPE_POLY_SET::TASK_SUBMITTER &aSubmitter={})
Definition zone.cpp:1713
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:832
std::shared_ptr< SHAPE_POLY_SET > GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:699
const BOX2I GetBoundingBox() const override
Definition zone.cpp:822
void SetFillFlag(PCB_LAYER_ID aLayer, bool aFlag)
Definition zone.h:300
SHAPE_POLY_SET * GetFill(PCB_LAYER_ID aLayer)
Definition zone.h:706
void SetIsFilled(bool isFilled)
Definition zone.h:307
virtual LSET GetLayerSet() const override
Return a std::bitset of all layers on which the item physically resides.
Definition zone.h:133
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ BRIDGED_MASK_CONSTRAINT
@ SILK_CLEARANCE_CONSTRAINT
@ NULL_CONSTRAINT
@ DRCE_SILK_MASK_CLEARANCE
Definition drc_item.h:111
@ DRCE_SOLDERMASK_BRIDGE
Definition drc_item.h:108
#define REPORT_AUX(s)
bool isMaskAperture(BOARD_ITEM *aItem)
static void addItemPolysWithEndings(BOARD_ITEM *aItem, SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc)
bool isNPTHPadWithNoCopper(BOARD_ITEM *aItem)
#define _(s)
KIID niluuid(0)
bool IsFrontLayer(PCB_LAYER_ID aLayerId)
Layer classification: check if it's a front layer.
Definition layer_ids.h:844
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Mask
Definition layer_ids.h:94
@ B_Cu
Definition layer_ids.h:61
@ F_Mask
Definition layer_ids.h:93
@ F_SilkS
Definition layer_ids.h:96
@ B_SilkS
Definition layer_ids.h:97
@ F_Cu
Definition layer_ids.h:60
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:27
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708