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 std::vector<const PAD*> overlappingSameNumPads;
173
174 for( const PAD* p : sameNumPads )
175 {
176 if( p->IsOnLayer( aLayer )
177 && pad->GetBoundingBox().Intersects( p->GetBoundingBox() ) )
178 {
179 overlappingSameNumPads.push_back( p );
180 }
181 }
182
183 // Same-number pads only add copper. Skip the slow path unless one
184 // fully covers this pad (combined outline is then bigger than this
185 // pad alone) or one's drill cuts into this pad (drill-to-drill copper
186 // becomes the real limit).
187 bool overlapHasConstrainingHole = false;
188 bool overlapCoversThisPad = false;
189
190 for( const PAD* p : overlappingSameNumPads )
191 {
192 if( p->GetBoundingBox().Contains( pad->GetBoundingBox() ) )
193 overlapCoversThisPad = true;
194
195 if( p->HasHole() && pad->GetBoundingBox().Intersects( p->GetEffectiveHoleShape()->BBox() ) )
196 {
197 overlapHasConstrainingHole = true;
198 }
199
200 if( overlapCoversThisPad && overlapHasConstrainingHole )
201 break;
202 }
203
204 if( handled && !overlappingSameNumPads.empty() && !overlapHasConstrainingHole && !overlapCoversThisPad
205 && constraint.Value().HasMin() && !constraint.Value().HasMax() )
206 {
207 // Circle: same annular width all around, so the fast value is exact
208 // whenever any direction is uncovered. Non-circle has a narrow side
209 // an SMD can rescue by itself, so trust the fast value here only
210 // when it already passes.
211 if( pad->GetShape( aLayer ) == PAD_SHAPE::CIRCLE )
212 {
213 return;
214 }
215 else
216 {
217 int width = ( *ptA - *ptB ).EuclideanNorm();
218
219 if( width >= constraint.Value().Min() )
220 return;
221 }
222 }
223
224 if( !handled || !overlappingSameNumPads.empty() )
225 {
226 // Slow (but general purpose) method.
227 SHAPE_POLY_SET padOutline;
228 std::shared_ptr<SHAPE_SEGMENT> slot = pad->GetEffectiveHoleShape();
229
230 pad->TransformShapeToPolygon( padOutline, aLayer, 0, pad->GetMaxError(), ERROR_INSIDE );
231
232 if( sameNumPads.empty() )
233 {
234 if( !padOutline.Collide( pad->GetPosition() ) )
235 {
236 // Hole outside pad
237 *ptA = pad->GetPosition();
238 *ptB = pad->GetPosition();
239 }
240 else
241 {
242 padOutline.NearestPoints( slot.get(), *ptA, *ptB );
243 }
244 }
245 else if( constraint.Value().HasMin() )
246 {
247 SHAPE_POLY_SET aggregatePadOutline = padOutline;
248 SHAPE_POLY_SET otherPadHoles;
249 SHAPE_POLY_SET slotPolygon;
250
251 slot->TransformToPolygon( slotPolygon, 0, ERROR_INSIDE );
252
253 for( const PAD* sameNumPad : sameNumPads )
254 {
255 // Construct the full pad with outline and hole.
256 sameNumPad->TransformShapeToPolygon( aggregatePadOutline, aLayer, 0, pad->GetMaxError(),
258
259 sameNumPad->TransformHoleToPolygon( otherPadHoles, 0, pad->GetMaxError(), ERROR_INSIDE );
260 }
261
262 aggregatePadOutline.BooleanSubtract( otherPadHoles );
263
264 if( !aggregatePadOutline.Collide( pad->GetPosition() ) )
265 {
266 // Hole outside pad
267 *ptA = pad->GetPosition();
268 *ptB = pad->GetPosition();
269 }
270 else
271 {
272 aggregatePadOutline.NearestPoints( slot.get(), *ptA, *ptB );
273 }
274 }
275 }
276 };
277
278 auto checkConstraint =
279 [&]( DRC_CONSTRAINT& constraint, BOARD_ITEM* item, const VECTOR2I& ptA, const VECTOR2I& ptB,
280 PCB_LAYER_ID aLayer )
281 {
282 if( constraint.GetSeverity() == RPT_SEVERITY_IGNORE )
283 return;
284
285 int v_min = 0;
286 int v_max = 0;
287 bool fail_min = false;
288 bool fail_max = false;
289 int width = ( ptA - ptB ).EuclideanNorm();
290
291 if( constraint.Value().HasMin() )
292 {
293 v_min = constraint.Value().Min();
294 fail_min = width < v_min;
295 }
296
297 if( constraint.Value().HasMax() )
298 {
299 v_max = constraint.Value().Max();
300 fail_max = width > v_max;
301 }
302
303 if( fail_min || fail_max )
304 {
305 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_ANNULAR_WIDTH );
306
307 if( fail_min )
308 {
309 drcItem->SetErrorDetail( formatMsg( _( "(%s min annular width %s; actual %s)" ),
310 constraint.GetName(),
311 v_min,
312 width ) );
313 }
314
315 if( fail_max )
316 {
317 drcItem->SetErrorDetail( formatMsg( _( "(%s max annular width %s; actual %s)" ),
318 constraint.GetName(),
319 v_max,
320 width ) );
321 }
322
323 drcItem->SetItems( item );
324 drcItem->SetViolatingRule( constraint.GetParentRule() );
325 reportTwoPointGeometry( drcItem, item->GetPosition(), ptA, ptB, aLayer );
326 }
327 };
328
329 auto checkAnnularWidth =
330 [&]( BOARD_ITEM* item ) -> bool
331 {
332 if( m_drcEngine->IsErrorLimitExceeded( DRCE_ANNULAR_WIDTH ) )
333 return false;
334
335 if( item->Type() == PCB_VIA_T )
336 {
337 PCB_VIA* via = static_cast<PCB_VIA*>( item );
338
339 via->Padstack().ForEachUniqueLayer(
340 [&]( PCB_LAYER_ID aLayer )
341 {
342 auto constraint = m_drcEngine->EvalRules( ANNULAR_WIDTH_CONSTRAINT, item,
343 nullptr, aLayer );
344
345 VECTOR2I ptA = via->GetPosition() - VECTOR2I( via->GetDrillValue() / 2, 0 );
346 VECTOR2I ptB = via->GetPosition() - VECTOR2I( via->GetWidth( aLayer ) / 2, 0 );
347 checkConstraint( constraint, via, ptA, ptB, aLayer );
348 } );
349 }
350 else if( item->Type() == PCB_PAD_T )
351 {
352 PAD* pad = static_cast<PAD*>( item );
353
354 if( !pad->HasHole() || pad->GetAttribute() != PAD_ATTRIB::PTH )
355 return true;
356
357 std::vector<const PAD*> sameNumPads;
358
359 if( const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( pad->GetParent() ) )
360 sameNumPads = fp->GetPads( pad->GetNumber(), pad );
361
362 pad->Padstack().ForEachUniqueLayer(
363 [&]( PCB_LAYER_ID aLayer )
364 {
365 auto constraint = m_drcEngine->EvalRules( ANNULAR_WIDTH_CONSTRAINT, item,
366 nullptr, aLayer );
367
368 VECTOR2I ptA;
369 VECTOR2I ptB;
370 getPadAnnulusPts( pad, aLayer, constraint, sameNumPads, &ptA, &ptB );
371 checkConstraint( constraint, pad, ptA, ptB, aLayer );
372 } );
373 }
374
375 return true;
376 };
377
378 BOARD* board = m_drcEngine->GetBoard();
379 size_t ii = 0;
380 size_t total = 0;
381
382 for( PCB_TRACK* item : board->Tracks() )
383 total += calcEffort( item );
384
385 for( FOOTPRINT* footprint : board->Footprints() )
386 {
387 for( PAD* pad : footprint->Pads() )
388 total += calcEffort( pad );
389 }
390
391 for( PCB_TRACK* item : board->Tracks() )
392 {
393 ii += calcEffort( item );
394
395 if( !reportProgress( ii, total, progressDelta ) )
396 return false; // DRC cancelled
397
398 if( !checkAnnularWidth( item ) )
399 break;
400 }
401
402 for( FOOTPRINT* footprint : board->Footprints() )
403 {
404 for( PAD* pad : footprint->Pads() )
405 {
406 ii += calcEffort( pad );
407
408 if( !reportProgress( ii, total, progressDelta ) )
409 return false; // DRC cancelled
410
411 if( !checkAnnularWidth( pad ) )
412 break;
413 }
414 }
415
416 return !m_drcEngine->IsCancelled();
417}
418
419
420namespace detail
421{
423}
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
const FOOTPRINTS & Footprints() const
Definition board.h:420
const TRACKS & Tracks() const
Definition board.h:418
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:417
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)
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.
The common library.
@ DRCE_ANNULAR_WIDTH
Definition drc_item.h:55
@ 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
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:98
@ CHAMFERED_RECT
Definition padstack.h:60
@ ROUNDRECT
Definition padstack.h:57
@ RECTANGLE
Definition padstack.h:54
@ 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:90
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683