KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_annular_width.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>
21#include <pcb_track.h>
22#include <pad.h>
23#include <footprint.h>
24#include <drc/drc_engine.h>
25#include <drc/drc_item.h>
27#include <macros.h>
30
31/*
32 Via/pad annular ring width test. Checks if there's sufficient copper ring around
33 PTH/NPTH holes (vias/pads)
34 Errors generated:
35 - DRCE_ANNULAR_WIDTH
36
37 Todo:
38 - check pad holes too.
39*/
40
41
43{
44public:
47
49
50 virtual bool Run() override;
51
52 virtual const wxString GetName() const override { return wxT( "annular_width" ); };
53};
54
55
57{
58 if( m_drcEngine->IsErrorLimitExceeded( DRCE_ANNULAR_WIDTH ) )
59 {
60 REPORT_AUX( wxT( "Annular width violations ignored. Skipping check." ) );
61 return true; // continue with other tests
62 }
63
64 const int progressDelta = 500;
65
66 if( !m_drcEngine->HasRulesForConstraintType( ANNULAR_WIDTH_CONSTRAINT ) )
67 {
68 REPORT_AUX( wxT( "No annular width constraints found. Tests not run." ) );
69 return true; // continue with other tests
70 }
71
72 if( !reportPhase( _( "Checking pad & via annular rings..." ) ) )
73 return false; // DRC cancelled
74
75 auto calcEffort =
76 []( BOARD_ITEM* item ) -> size_t
77 {
78 switch( item->Type() )
79 {
80 case PCB_VIA_T:
81 return 1;
82
83 case PCB_PAD_T:
84 {
85 PAD* pad = static_cast<PAD*>( item );
86
87 if( !pad->HasHole() || pad->GetAttribute() != PAD_ATTRIB::PTH )
88 return 0;
89
90 size_t effort = 0;
91
92 pad->Padstack().ForEachUniqueLayer(
93 [&pad, &effort]( PCB_LAYER_ID aLayer )
94 {
95 if( pad->GetOffset( aLayer ) == VECTOR2I( 0, 0 ) )
96 {
97 switch( pad->GetShape( aLayer ) )
98 {
100 if( pad->GetChamferRectRatio( aLayer ) > 0.30 )
101 break;
102
104
106 case PAD_SHAPE::OVAL:
109 effort += 1;
110 break;
111
112 default:
113 break;
114 }
115 }
116
117 effort += 5;
118 } );
119
120 return effort;
121 }
122
123 default:
124 return 0;
125 }
126 };
127
128 auto getPadAnnulusPts =
129 []( PAD* pad, PCB_LAYER_ID aLayer, DRC_CONSTRAINT& constraint,
130 const std::vector<const PAD*>& sameNumPads, VECTOR2I* ptA, VECTOR2I* ptB )
131 {
132 bool handled = false;
133
134 if( pad->GetOffset( aLayer ) == VECTOR2I( 0, 0 ) )
135 {
136 int xDist = KiROUND( ( pad->GetSizeX() - pad->GetDrillSizeX() ) / 2.0 );
137 int yDist = KiROUND( ( pad->GetSizeY() - pad->GetDrillSizeY() ) / 2.0 );
138
139 if( yDist < xDist )
140 {
141 *ptA = pad->GetPosition() - VECTOR2I( 0, pad->GetDrillSizeY() / 2 );
142 *ptB = pad->GetPosition() - VECTOR2I( 0, pad->GetSizeY() / 2 );
143 }
144 else
145 {
146 *ptA = pad->GetPosition() - VECTOR2I( pad->GetDrillSizeX() / 2, 0 );
147 *ptB = pad->GetPosition() - VECTOR2I( pad->GetSizeX() / 2, 0 );
148 }
149
150 RotatePoint( *ptA, pad->GetPosition(), pad->GetOrientation() );
151 RotatePoint( *ptB, pad->GetPosition(), pad->GetOrientation() );
152
153 switch( pad->GetShape( aLayer ) )
154 {
156 handled = pad->GetChamferRectRatio( aLayer ) <= 0.30;
157 break;
158
160 case PAD_SHAPE::OVAL:
163 handled = true;
164
165 break;
166
167 default:
168 break;
169 }
170 }
171
172 BOX2I padBBox = pad->GetBoundingBox( aLayer );
173 std::vector<const PAD*> overlappingSameNumPads;
174
175 for( const PAD* p : sameNumPads )
176 {
177 if( p->IsOnLayer( aLayer ) && padBBox.Intersects( p->GetBoundingBox( aLayer) ) )
178 overlappingSameNumPads.push_back( p );
179 }
180
181 // Same-number pads only add copper. Skip the slow path unless one fully covers this pad
182 // (combined outline is then bigger than this pad alone) or one's drill cuts into this pad
183 // (drill-to-drill copper becomes the real limit).
184 bool overlapHasConstrainingHole = false;
185 bool overlapCoversThisPad = false;
186
187 for( const PAD* p : overlappingSameNumPads )
188 {
189 if( p->GetBoundingBox( aLayer ).Contains( padBBox ) )
190 overlapCoversThisPad = true;
191
192 if( p->HasHole() )
193 {
194 BOX2I holeBBox = p->GetEffectiveHoleShape( aLayer, ANNULAR_WIDTH_CONSTRAINT )->BBox();
195
196 if( padBBox.Intersects( holeBBox ) )
197 overlapHasConstrainingHole = true;
198 }
199
200 if( overlapCoversThisPad && overlapHasConstrainingHole )
201 break;
202 }
203
204 if( handled
205 && !overlappingSameNumPads.empty()
206 && !overlapHasConstrainingHole
207 && !overlapCoversThisPad
208 && constraint.Value().HasMin()
209 && !constraint.Value().HasMax() )
210 {
211 // Circle: same annular width all around, so the fast value is exact whenever any direction
212 // is uncovered. Non-circle has a narrow side an SMD can rescue by itself, so trust the fast
213 // value here only when it already passes.
214 if( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE )
215 {
216 return;
217 }
218 else
219 {
220 int width = ( *ptA - *ptB ).EuclideanNorm();
221
222 if( width >= constraint.Value().Min() )
223 return;
224 }
225 }
226
227 if( !handled || !overlappingSameNumPads.empty() )
228 {
229 // Slow (but general purpose) method.
230 SHAPE_POLY_SET padOutline;
231 std::shared_ptr<SHAPE_SEGMENT> slot = pad->GetEffectiveHoleShape( aLayer,
233
234 pad->TransformShapeToPolygon( padOutline, aLayer, 0, pad->GetMaxError(), ERROR_INSIDE );
235
236 if( sameNumPads.empty() )
237 {
238 if( !padOutline.Collide( pad->GetPosition() ) )
239 {
240 // Hole outside pad
241 *ptA = pad->GetPosition();
242 *ptB = pad->GetPosition();
243 }
244 else
245 {
246 padOutline.NearestPoints( slot.get(), *ptA, *ptB );
247 }
248 }
249 else if( constraint.Value().HasMin() )
250 {
251 SHAPE_POLY_SET aggregatePadOutline = padOutline;
252 SHAPE_POLY_SET otherPadHoles;
253 SHAPE_POLY_SET slotPolygon;
254
255 slot->TransformToPolygon( slotPolygon, 0, ERROR_INSIDE );
256
257 for( const PAD* sameNumPad : sameNumPads )
258 {
259 // Construct the full pad with outline and hole.
260 sameNumPad->TransformShapeToPolygon( aggregatePadOutline, aLayer, 0, pad->GetMaxError(),
262
263 sameNumPad->TransformHoleToPolygon( otherPadHoles, 0, pad->GetMaxError(), ERROR_INSIDE );
264 }
265
266 aggregatePadOutline.BooleanSubtract( otherPadHoles );
267
268 if( !aggregatePadOutline.Collide( pad->GetPosition() ) )
269 {
270 // Hole outside pad
271 *ptA = pad->GetPosition();
272 *ptB = pad->GetPosition();
273 }
274 else
275 {
276 aggregatePadOutline.NearestPoints( slot.get(), *ptA, *ptB );
277 }
278 }
279 }
280 };
281
282 auto checkConstraint =
283 [&]( DRC_CONSTRAINT& constraint, BOARD_ITEM* item, const VECTOR2I& ptA, const VECTOR2I& ptB,
284 PCB_LAYER_ID aLayer )
285 {
286 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE )
287 return;
288
289 int v_min = 0;
290 int v_max = 0;
291 bool fail_min = false;
292 bool fail_max = false;
293 int width = ( ptA - ptB ).EuclideanNorm();
294
295 auto padstackMode =
296 [&]()
297 {
298 if( PCB_VIA* via = dynamic_cast<PCB_VIA*>( item ) )
299 return via->Padstack().Mode();
300 else if( PAD* pad = dynamic_cast<PAD*>( item ) )
301 return pad->Padstack().Mode();
302 else
304 };
305
306 auto layerDesc =
307 [&]() -> wxString
308 {
309 if( aLayer == F_Cu )
310 return m_drcEngine->GetBoard()->GetLayerName( F_Cu );
311 else if( aLayer == B_Cu )
312 return m_drcEngine->GetBoard()->GetLayerName( B_Cu );
313 else if( padstackMode() == PADSTACK::MODE::FRONT_INNER_BACK )
314 return _( "Inner Layers" );
315 else
316 return m_drcEngine->GetBoard()->GetLayerName( aLayer );
317 };
318
319 if( constraint.Value().HasMin() )
320 {
321 v_min = constraint.Value().Min();
322 fail_min = width < v_min;
323 }
324
325 if( constraint.Value().HasMax() )
326 {
327 v_max = constraint.Value().Max();
328 fail_max = width > v_max;
329 }
330
331 if( fail_min || fail_max )
332 {
333 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ANNULAR_WIDTH );
334
335 if( fail_min )
336 {
337 if( padstackMode() == PADSTACK::MODE::NORMAL )
338 {
339 drcItem->SetErrorDetail( formatMsg( _( "(%s min annular width %s; actual %s)" ),
340 constraint.GetName(),
341 v_min,
342 width ) );
343 }
344 else
345 {
346 drcItem->SetErrorDetail( formatMsg( _( "(%s min annular width %s; actual %s on %s)" ),
347 constraint.GetName(),
348 v_min,
349 width,
350 layerDesc() ) );
351 }
352 }
353
354 if( fail_max )
355 {
356 drcItem->SetErrorDetail( formatMsg( _( "(%s max annular width %s; actual %s)" ),
357 constraint.GetName(),
358 v_max,
359 width ) );
360 }
361
362 drcItem->SetItems( item );
363 drcItem->SetViolatingRule( constraint.GetParentRule() );
364 reportTwoPointGeometry( drcItem, item->GetPosition(), ptA, ptB, aLayer );
365 }
366 };
367
368 auto checkAnnularWidth =
369 [&]( BOARD_ITEM* item ) -> bool
370 {
371 if( m_drcEngine->IsErrorLimitExceeded( DRCE_ANNULAR_WIDTH ) )
372 return false;
373
374 if( item->Type() == PCB_VIA_T )
375 {
376 PCB_VIA* via = static_cast<PCB_VIA*>( item );
377
378 via->Padstack().ForEachUniqueLayer(
379 [&]( PCB_LAYER_ID aLayer )
380 {
381 if( via->IsGhostLayer( aLayer ) )
382 return;
383
384 auto constraint = m_drcEngine->EvalRules( ANNULAR_WIDTH_CONSTRAINT, item,
385 nullptr, aLayer );
386
387 VECTOR2I ptA = via->GetPosition() - VECTOR2I( via->GetDrillValue() / 2, 0 );
388 VECTOR2I ptB = via->GetPosition() - VECTOR2I( via->GetWidth( aLayer ) / 2, 0 );
389 checkConstraint( constraint, via, ptA, ptB, aLayer );
390 } );
391 }
392 else if( item->Type() == PCB_PAD_T )
393 {
394 PAD* pad = static_cast<PAD*>( item );
395
396 if( !pad->HasHole() || pad->GetAttribute() != PAD_ATTRIB::PTH )
397 return true;
398
399 std::vector<const PAD*> sameNumPads;
400
401 if( const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( pad->GetParent() ) )
402 sameNumPads = fp->GetPads( pad->GetNumber(), pad );
403
404 pad->Padstack().ForEachUniqueLayer(
405 [&]( PCB_LAYER_ID aLayer )
406 {
407 auto constraint = m_drcEngine->EvalRules( ANNULAR_WIDTH_CONSTRAINT, item,
408 nullptr, aLayer );
409
410 VECTOR2I ptA;
411 VECTOR2I ptB;
412 getPadAnnulusPts( pad, aLayer, constraint, sameNumPads, &ptA, &ptB );
413 checkConstraint( constraint, pad, ptA, ptB, aLayer );
414 } );
415 }
416
417 return true;
418 };
419
420 BOARD* board = m_drcEngine->GetBoard();
421 size_t ii = 0;
422 size_t total = 0;
423
424 for( PCB_TRACK* item : board->Tracks() )
425 total += calcEffort( item );
426
427 for( FOOTPRINT* footprint : board->Footprints() )
428 {
429 for( PAD* pad : footprint->Pads() )
430 total += calcEffort( pad );
431 }
432
433 for( PCB_TRACK* item : board->Tracks() )
434 {
435 ii += calcEffort( item );
436
437 if( !reportProgress( ii, total, progressDelta ) )
438 return false; // DRC cancelled
439
440 if( !checkAnnularWidth( item ) )
441 break;
442 }
443
444 for( FOOTPRINT* footprint : board->Footprints() )
445 {
446 for( PAD* pad : footprint->Pads() )
447 {
448 ii += calcEffort( pad );
449
450 if( !reportProgress( ii, total, progressDelta ) )
451 return false; // DRC cancelled
452
453 if( !checkAnnularWidth( pad ) )
454 break;
455 }
456 }
457
458 return !m_drcEngine->IsCancelled();
459}
460
461
462namespace detail
463{
465}
@ ERROR_OUTSIDE
@ ERROR_INSIDE
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const FOOTPRINTS & Footprints() const
Definition board.h:463
const TRACKS & Tracks() const
Definition board.h:461
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:308
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:444
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
virtual ~DRC_TEST_PROVIDER_ANNULAR_WIDTH()=default
virtual const wxString GetName() const override
virtual bool reportPhase(const wxString &aStageName)
void reportTwoPointGeometry(std::shared_ptr< DRC_ITEM > &aDrcItem, const VECTOR2I &aMarkerPos, const VECTOR2I &ptA, const VECTOR2I &ptB, PCB_LAYER_ID aLayer)
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)
@ NORMAL
Shape is the same on all layers.
Definition padstack.h:170
@ FRONT_INNER_BACK
Up to three shapes can be defined (F_Cu, inner copper layers, B_Cu)
Definition padstack.h:171
Definition pad.h:61
Represent a set of closed polygons.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
bool NearestPoints(const SHAPE *aOther, VECTOR2I &aPtThis, VECTOR2I &aPtOther) const
Return the two points that mark the closest distance between this shape and aOther.
@ DRCE_ANNULAR_WIDTH
Definition drc_item.h:56
@ ANNULAR_WIDTH_CONSTRAINT
Definition drc_rule.h:63
#define REPORT_AUX(s)
#define _(s)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:79
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
@ PTH
Plated through hole pad.
Definition padstack.h:97
@ CHAMFERED_RECT
Definition padstack.h:59
@ ROUNDRECT
Definition padstack.h:56
@ RECTANGLE
Definition padstack.h:53
@ RPT_SEVERITY_IGNORE
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:225
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683