KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_courtyard_clearance.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
21#include <drc/drc_engine.h>
22#include <drc/drc_item.h>
23#include <pad.h>
26#include <footprint.h>
27
28/*
29 Couartyard clearance. Tests for malformed component courtyards and overlapping footprints.
30 Generated errors:
31 - DRCE_OVERLAPPING_FOOTPRINTS
32 - DRCE_MISSING_COURTYARD
33 - DRCE_MALFORMED_COURTYARD
34 - DRCE_PTH_IN_COURTYARD,
35 - DRCE_NPTH_IN_COURTYARD,
36*/
37
39{
40public:
47
49
50 virtual bool Run() override;
51
52 virtual const wxString GetName() const override { return wxT( "courtyard_clearance" ); }
53
54private:
56
58
59private:
61};
62
63
65{
66 // Detects missing (or malformed) footprint courtyards
67 if( !m_drcEngine->IsErrorLimitExceeded( DRCE_MALFORMED_COURTYARD)
68 || !m_drcEngine->IsErrorLimitExceeded( DRCE_MISSING_COURTYARD) )
69 {
70 if( !reportPhase( _( "Checking footprint courtyard definitions..." ) ) )
71 return false; // DRC cancelled
72 }
73 else if( !m_drcEngine->IsErrorLimitExceeded( DRCE_OVERLAPPING_FOOTPRINTS) )
74 {
75 if( !reportPhase( _( "Gathering footprint courtyards..." ) ) )
76 return false; // DRC cancelled
77 }
78 else
79 {
80 REPORT_AUX( wxT( "All courtyard violations ignored. Tests not run." ) );
81 return true; // continue with other tests
82 }
83
84 const int progressDelta = 500;
85 int ii = 0;
86
87 for( FOOTPRINT* footprint : m_board->Footprints() )
88 {
89 if( !reportProgress( ii++, m_board->Footprints().size(), progressDelta ) )
90 return false; // DRC cancelled
91
92 if( ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 )
93 {
94 if( m_drcEngine->IsErrorLimitExceeded( DRCE_MALFORMED_COURTYARD) )
95 continue;
96
97 OUTLINE_ERROR_HANDLER errorHandler =
98 [&]( const wxString& msg, BOARD_ITEM*, BOARD_ITEM*, const VECTOR2I& pt )
99 {
100 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MALFORMED_COURTYARD );
101 drcItem->SetErrorDetail( msg );
102 drcItem->SetItems( footprint );
103 reportViolation( drcItem, pt, UNDEFINED_LAYER );
104 };
105
106 // Re-run courtyard tests to generate DRC_ITEMs
107 footprint->BuildCourtyardCaches( &errorHandler );
108 }
109 else if( footprint->GetCourtyard( F_CrtYd ).OutlineCount() == 0
110 && footprint->GetCourtyard( B_CrtYd ).OutlineCount() == 0 )
111 {
112 if( m_drcEngine->IsErrorLimitExceeded( DRCE_MISSING_COURTYARD ) )
113 continue;
114
115 if( footprint->AllowMissingCourtyard() )
116 continue;
117
118 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_MISSING_COURTYARD );
119 drcItem->SetItems( footprint );
120 reportViolation( drcItem, footprint->GetPosition(), UNDEFINED_LAYER );
121 }
122 else
123 {
124 footprint->GetCourtyard( F_CrtYd ).BuildBBoxCaches();
125 footprint->GetCourtyard( B_CrtYd ).BuildBBoxCaches();
126 }
127 }
128
129 return !m_drcEngine->IsCancelled();
130}
131
132
134{
135 if( !reportPhase( _( "Checking footprints for overlapping courtyards..." ) ) )
136 return false; // DRC cancelled
137
138 const int progressDelta = 100;
139 int ii = 0;
140
141 // Stable sorting gives stable violation generation (and stable comparisons to previously-
142 // generated violations for exclusion checking).
143 std::vector<FOOTPRINT*> footprints;
144
145 footprints.insert( footprints.begin(), m_board->Footprints().begin(),
146 m_board->Footprints().end() );
147
148 std::sort( footprints.begin(), footprints.end(),
149 []( const FOOTPRINT* a, const FOOTPRINT* b )
150 {
151 return a->m_Uuid < b->m_Uuid;
152 } );
153
154 for( auto itA = footprints.begin(); itA != footprints.end(); itA++ )
155 {
156 if( !reportProgress( ii++, footprints.size(), progressDelta ) )
157 return false; // DRC cancelled
158
159 // Ensure tests realted to courtyard constraints are not fully disabled:
160 if( m_drcEngine->IsErrorLimitExceeded( DRCE_OVERLAPPING_FOOTPRINTS)
161 && m_drcEngine->IsErrorLimitExceeded( DRCE_PTH_IN_COURTYARD )
162 && m_drcEngine->IsErrorLimitExceeded( DRCE_NPTH_IN_COURTYARD ) )
163 {
164 return true; // continue with other tests
165 }
166
167 FOOTPRINT* fpA = *itA;
168 const SHAPE_POLY_SET& frontA = fpA->GetCourtyard( F_CrtYd );
169 const SHAPE_POLY_SET& backA = fpA->GetCourtyard( B_CrtYd );
170
171 if( frontA.OutlineCount() == 0 && backA.OutlineCount() == 0
172 && m_drcEngine->IsErrorLimitExceeded( DRCE_PTH_IN_COURTYARD )
173 && m_drcEngine->IsErrorLimitExceeded( DRCE_NPTH_IN_COURTYARD ) )
174 {
175 // No courtyards defined and no hole testing against other footprint's courtyards
176 continue;
177 }
178
179 BOX2I frontA_worstCaseBBox = frontA.BBoxFromCaches();
180 BOX2I backA_worstCaseBBox = backA.BBoxFromCaches();
181
182 frontA_worstCaseBBox.Inflate( m_largestCourtyardClearance );
183 backA_worstCaseBBox.Inflate( m_largestCourtyardClearance );
184
185 BOX2I fpA_bbox = fpA->GetBoundingBox();
186
187 for( auto itB = itA + 1; itB != footprints.end(); itB++ )
188 {
189 FOOTPRINT* fpB = *itB;
190 const SHAPE_POLY_SET& frontB = fpB->GetCourtyard( F_CrtYd );
191 const SHAPE_POLY_SET& backB = fpB->GetCourtyard( B_CrtYd );
192
193 if( frontB.OutlineCount() == 0 && backB.OutlineCount() == 0
194 && m_drcEngine->IsErrorLimitExceeded( DRCE_PTH_IN_COURTYARD )
195 && m_drcEngine->IsErrorLimitExceeded( DRCE_NPTH_IN_COURTYARD ) )
196 {
197 // No courtyards defined and no hole testing against other footprint's courtyards
198 continue;
199 }
200
201 BOX2I frontB_worstCaseBBox = frontB.BBoxFromCaches();
202 BOX2I backB_worstCaseBBox = backB.BBoxFromCaches();
203
204 frontB_worstCaseBBox.Inflate( m_largestCourtyardClearance );
205 backB_worstCaseBBox.Inflate( m_largestCourtyardClearance );
206
207 BOX2I fpB_bbox = fpB->GetBoundingBox();
208 DRC_CONSTRAINT constraint;
209 int clearance;
210 int actual;
211 VECTOR2I pos;
212
213 // Check courtyard-to-courtyard collisions on front of board,
214 // if DRCE_OVERLAPPING_FOOTPRINTS is not diasbled
215 if( frontA.OutlineCount() > 0 && frontB.OutlineCount() > 0
216 && frontA_worstCaseBBox.Intersects( frontB.BBoxFromCaches() )
217 && !m_drcEngine->IsErrorLimitExceeded( DRCE_OVERLAPPING_FOOTPRINTS ) )
218 {
219 constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpA, fpB, F_Cu );
220 clearance = constraint.GetValue().Min();
221
222 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
223 {
224 if( frontA.Collide( &frontB, clearance, &actual, &pos ) )
225 {
226 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_OVERLAPPING_FOOTPRINTS );
227
228 if( clearance > 0 )
229 {
230 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
231 constraint.GetName(),
232 clearance,
233 actual ) );
234 }
235
236 drcItem->SetViolatingRule( constraint.GetParentRule() );
237 drcItem->SetItems( fpA, fpB );
238 reportTwoShapeGeometry( drcItem, pos, &frontA, &frontB, F_CrtYd, actual );
239 }
240 }
241 }
242
243 // Check courtyard-to-courtyard collisions on back of board,
244 // if DRCE_OVERLAPPING_FOOTPRINTS is not disabled
245 if( backA.OutlineCount() > 0 && backB.OutlineCount() > 0
246 && backA_worstCaseBBox.Intersects( backB.BBoxFromCaches() )
247 && !m_drcEngine->IsErrorLimitExceeded( DRCE_OVERLAPPING_FOOTPRINTS ) )
248 {
249 constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpA, fpB, B_Cu );
250 clearance = constraint.GetValue().Min();
251
252 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
253 {
254 if( backA.Collide( &backB, clearance, &actual, &pos ) )
255 {
256 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_OVERLAPPING_FOOTPRINTS );
257
258 if( clearance > 0 )
259 {
260 drcItem->SetErrorDetail( formatMsg( _( "(%s clearance %s; actual %s)" ),
261 constraint.GetName(),
262 clearance,
263 actual ) );
264 }
265
266 drcItem->SetViolatingRule( constraint.GetParentRule() );
267 drcItem->SetItems( fpA, fpB );
268 reportTwoShapeGeometry( drcItem, pos, &backA, &backB, B_CrtYd, actual );
269 }
270 }
271 }
272
273 //
274 // Check pad-hole-to-courtyard collisions on front and back of board.
275 //
276 // NB: via holes are not checked. There is a presumption that a physical object goes
277 // through a pad hole, which is not the case for via holes.
278 //
279 bool checkFront = false;
280 bool checkBack = false;
281
282 // checkFront/checkBack are only consulted by the pad-hole tests below, which run
283 // only when a footprint bbox overlaps the other's inflated courtyard. Evaluating the
284 // courtyard-clearance rule for every pair up front is the dominant cost on dense
285 // boards, so gate it behind the same bbox test and skip it for far-apart pairs.
286 bool padCheckPossible =
287 ( frontA.OutlineCount() > 0 && frontA_worstCaseBBox.Intersects( fpB_bbox ) )
288 || ( backA.OutlineCount() > 0 && backA_worstCaseBBox.Intersects( fpB_bbox ) )
289 || ( frontB.OutlineCount() > 0 && frontB.BBoxFromCaches().Intersects( fpA_bbox ) )
290 || ( backB.OutlineCount() > 0 && backB.BBoxFromCaches().Intersects( fpA_bbox ) );
291
292 if( padCheckPossible )
293 {
294 constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpA, fpB, F_Cu );
295 clearance = constraint.GetValue().Min();
296
297 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
298 checkFront = true;
299
300 constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpB, fpA, F_Cu );
301 clearance = constraint.GetValue().Min();
302
303 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
304 checkFront = true;
305
306 constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpA, fpB, B_Cu );
307 clearance = constraint.GetValue().Min();
308
309 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
310 checkBack = true;
311
312 constraint = m_drcEngine->EvalRules( COURTYARD_CLEARANCE_CONSTRAINT, fpB, fpA, B_Cu );
313 clearance = constraint.GetValue().Min();
314
315 if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE && clearance >= 0 )
316 checkBack = true;
317 }
318
319 auto testPadAgainstCourtyards =
320 [&]( const PAD* pad, const FOOTPRINT* fp )
321 {
322 int errorCode = 0;
323
324 if( pad->GetProperty() == PAD_PROP::HEATSINK )
325 return;
326 else if( pad->GetAttribute() == PAD_ATTRIB::PTH )
327 errorCode = DRCE_PTH_IN_COURTYARD;
328 else if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
329 errorCode = DRCE_NPTH_IN_COURTYARD;
330 else
331 return;
332
333 if( m_drcEngine->IsErrorLimitExceeded( errorCode ) )
334 return;
335
336 if( pad->HasHole() )
337 {
338 std::shared_ptr<SHAPE_SEGMENT> hole = pad->GetEffectiveHoleShape();
339 const SHAPE_POLY_SET& front = fp->GetCourtyard( F_CrtYd );
340 const SHAPE_POLY_SET& back = fp->GetCourtyard( B_CrtYd );
341
342 if( checkFront && front.OutlineCount() > 0 && front.Collide( hole.get(), 0 ) )
343 {
344 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( errorCode );
345 drce->SetItems( pad, fp );
346 reportViolation( drce, pad->GetPosition(), F_CrtYd );
347 }
348 else if( checkBack && back.OutlineCount() > 0 && back.Collide( hole.get(), 0 ) )
349 {
350 std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( errorCode );
351 drce->SetItems( pad, fp );
352 reportViolation( drce, pad->GetPosition(), B_CrtYd );
353 }
354 }
355 };
356
357 if( ( frontA.OutlineCount() > 0 && frontA_worstCaseBBox.Intersects( fpB_bbox ) )
358 || ( backA.OutlineCount() > 0 && backA_worstCaseBBox.Intersects( fpB_bbox ) ) )
359 {
360 for( const PAD* padB : fpB->Pads() )
361 testPadAgainstCourtyards( padB, fpA );
362 }
363
364 if( ( frontB.OutlineCount() > 0 && frontB.BBoxFromCaches().Intersects( fpA_bbox ) )
365 || ( backB.OutlineCount() > 0 && backB.BBoxFromCaches().Intersects( fpA_bbox ) ) )
366 {
367 for( const PAD* padA : fpA->Pads() )
368 testPadAgainstCourtyards( padA, fpB );
369 }
370
371 if( m_drcEngine->IsCancelled() )
372 return false;
373 }
374 }
375
376 return !m_drcEngine->IsCancelled();
377}
378
379
381{
382 m_board = m_drcEngine->GetBoard();
383 DRC_CONSTRAINT constraint;
384
385 if( m_drcEngine->QueryWorstConstraint( COURTYARD_CLEARANCE_CONSTRAINT, constraint ) )
387
389 return false;
390
392 return false;
393
394 return true;
395}
396
397
398namespace detail
399{
401}
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:81
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition box2.h:307
wxString GetName() const
Definition drc_rule.h:204
SEVERITY GetSeverity() const
Definition drc_rule.h:217
const MINOPTMAX< int > & GetValue() const
Definition drc_rule.h:196
DRC_RULE * GetParentRule() const
Definition drc_rule.h:200
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_COURTYARD_CLEARANCE()=default
virtual bool reportPhase(const wxString &aStageName)
void reportTwoShapeGeometry(std::shared_ptr< DRC_ITEM > &aDrcItem, const VECTOR2I &aMarkerPos, const SHAPE *aShape1, const SHAPE *aShape2, PCB_LAYER_ID aLayer, int aDistance)
void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::function< void(PCB_MARKER *)> &aPathGenerator=[](PCB_MARKER *){})
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)
std::deque< PAD * > & Pads()
Definition footprint.h:375
const SHAPE_POLY_SET & GetCourtyard(PCB_LAYER_ID aLayer) const
Used in DRC to test the courtyard area (a complex polygon).
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
T Min() const
Definition minoptmax.h:29
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,...
int OutlineCount() const
Return the number of outlines in the set.
const BOX2I BBoxFromCaches() const
const std::function< void(const wxString &msg, BOARD_ITEM *itemA, BOARD_ITEM *itemB, const VECTOR2I &pt)> OUTLINE_ERROR_HANDLER
@ DRCE_OVERLAPPING_FOOTPRINTS
Definition drc_item.h:62
@ DRCE_MISSING_COURTYARD
Definition drc_item.h:63
@ DRCE_PTH_IN_COURTYARD
Definition drc_item.h:66
@ DRCE_MALFORMED_COURTYARD
Definition drc_item.h:64
@ DRCE_NPTH_IN_COURTYARD
Definition drc_item.h:67
@ COURTYARD_CLEARANCE_CONSTRAINT
Definition drc_rule.h:57
#define REPORT_AUX(s)
#define _(s)
#define MALFORMED_COURTYARDS
@ F_CrtYd
Definition layer_ids.h:112
@ B_Cu
Definition layer_ids.h:61
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ F_Cu
Definition layer_ids.h:60
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:103
@ PTH
Plated through hole pad.
Definition padstack.h:98
@ HEATSINK
a pad used as heat sink, usually in SMD footprints
Definition padstack.h:120
@ RPT_SEVERITY_IGNORE
int clearance
int actual
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683