KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drc_courtyard_overlap.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, see AUTHORS.TXT for contributors.
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
24#include <board.h>
26#include <footprint.h>
27#include <pcb_marker.h>
28#include <drc/drc_item.h>
29#include <drc/drc_engine.h>
30#include <widgets/ui_common.h>
31
33#include "drc_test_utils.h"
34
39{
43
44 // On front or back layer (the exact layer is context-dependent)
45 bool m_front;
46};
47
48
49/*
50 * A simple mock footprint with a set of courtyard rectangles and some other information
51 */
53{
54 std::string m_refdes;
55 std::vector<RECT_DEFINITION> m_rects;
57};
58
59
60/*
61 * Struct holding information about a courtyard collision
62 */
64{
65 // The two colliding parts
66 std::string m_refdes_a;
67 std::string m_refdes_b;
68};
69
70
71std::ostream& operator<<( std::ostream& os, const COURTYARD_COLLISION& aColl )
72{
73 os << "COURTYARD_COLLISION[ " << aColl.m_refdes_a << " -> " << aColl.m_refdes_b << "]";
74 return os;
75}
76
77
83{
84 std::string m_case_name;
85 std::vector<COURTYARD_TEST_FP> m_fpDefs; // The footprint in the test case
86 std::vector<COURTYARD_COLLISION> m_collisions; // The expected number of collisions
87};
88
89
93void AddRectCourtyard( FOOTPRINT& aFootprint, const RECT_DEFINITION& aRect )
94{
95 const PCB_LAYER_ID layer = aRect.m_front ? F_CrtYd : B_CrtYd;
96
97 const int width = pcbIUScale.mmToIU( 0.1 );
98
99 KI_TEST::DrawRect( aFootprint, aRect.m_centre, aRect.m_size, aRect.m_corner_rad, width, layer );
100}
101
102
106std::unique_ptr<FOOTPRINT> MakeCourtyardTestFP( BOARD& aBoard, const COURTYARD_TEST_FP& aFPDef )
107{
108 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( &aBoard );
109
110 for( const RECT_DEFINITION& rect : aFPDef.m_rects )
111 AddRectCourtyard( *footprint, rect );
112
113 footprint->SetReference( aFPDef.m_refdes );
114
115 // This has to go after adding the courtyards, or all the poly sets are empty when DRC'd
116 footprint->SetPosition( aFPDef.m_pos );
117
118 return footprint;
119}
120
126std::unique_ptr<BOARD> MakeBoard( const std::vector<COURTYARD_TEST_FP>& aFPDefs )
127{
128 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
129
130 for( const COURTYARD_TEST_FP& fpDef : aFPDefs )
131 {
132 std::unique_ptr<FOOTPRINT> footprint = MakeCourtyardTestFP( *board, fpDef );
133
134 board->Add( footprint.release() );
135 }
136
137 return board;
138}
139
140
142{
143 const KI_TEST::BOARD_DUMPER m_dumper;
144};
145
146
147BOOST_FIXTURE_TEST_SUITE( DrcCourtyardOverlap, COURTYARD_TEST_FIXTURE )
148
149// clang-format off
151 {
152 "empty board",
153 {}, // no footprint
154 {}, // no collisions
155 },
156 {
157 "single empty footprint",
158 {
159 {
160 "U1",
161 {}, // no courtyard
162 { 0, 0 }, // at origin
163 },
164 },
165 {}, // no collisions
166 },
167 {
168 // A single footprint can't overlap itself
169 "single footprint, single courtyard",
170 {
171 {
172 "U1",
173 {
174 {
175 { 0, 0 },
176 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
177 0,
178 true,
179 },
180 },
181 { 0, 0 },
182 },
183 },
184 {}, // no collisions
185 },
186 {
187 "two footprint, no overlap",
188 {
189 {
190 "U1",
191 {
192 {
193 { 0, 0 },
194 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
195 0,
196 true,
197 },
198 },
199 { 0, 0 },
200 },
201 {
202 "U2",
203 {
204 {
205 { 0, 0 },
206 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
207 0,
208 true,
209 },
210 },
211 { pcbIUScale.mmToIU( 3 ), pcbIUScale.mmToIU( 1 ) }, // One footprint is far from the other
212 },
213 },
214 {}, // no collisions
215 },
216 {
217 "two footprints, touching, no overlap",
218 {
219 {
220 "U1",
221 {
222 {
223 { 0, 0 },
224 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
225 0,
226 true,
227 },
228 },
229 { 0, 0 },
230 },
231 {
232 "U2",
233 {
234 {
235 { 0, 0 },
236 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
237 0,
238 true,
239 },
240 },
241 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 0 ) }, // Just touching
242 },
243 },
244 {}, // Touching means not colliding
245 },
246 {
247 "two footprints, overlap",
248 {
249 {
250 "U1",
251 {
252 {
253 { 0, 0 },
254 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
255 0,
256 true,
257 },
258 },
259 { 0, 0 },
260 },
261 {
262 "U2",
263 {
264 {
265 { 0, 0 },
266 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
267 0,
268 true,
269 },
270 },
271 { pcbIUScale.mmToIU( 0.5 ), pcbIUScale.mmToIU( 0 ) }, // Partial overlap
272 },
273 },
274 {
275 { "U1", "U2" }, // These two collide
276 },
277 },
278 {
279 "two footprints, overlap, different sides",
280 {
281 {
282 "U1",
283 {
284 {
285 { 0, 0 },
286 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
287 0,
288 true,
289 },
290 },
291 { 0, 0 },
292 },
293 {
294 "U2",
295 {
296 {
297 { 0, 0 },
298 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
299 0,
300 false,
301 },
302 },
303 { 0, 0 }, // complete overlap
304 },
305 },
306 {}, // but on different sides
307 },
308 {
309 "two footprints, multiple courtyards, overlap",
310 {
311 {
312 "U1",
313 {
314 {
315 { 0, 0 },
316 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
317 0,
318 true,
319 },
320 {
321 { pcbIUScale.mmToIU( 2 ), pcbIUScale.mmToIU( 0 ) },
322 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
323 0,
324 true,
325 },
326 },
327 { 0, 0 },
328 },
329 {
330 "U2",
331 {
332 {
333 { 0, 0 },
334 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
335 0,
336 true,
337 },
338 },
339 { 0, 0 }, // complete overlap with one of the others
340 },
341 },
342 {
343 { "U1", "U2" },
344 },
345 },
346 {
347 // The courtyards do not overlap, but their bounding boxes do
348 "two footprints, no overlap, bbox overlap",
349 {
350 {
351 "U1",
352 {
353 {
354 { 0, 0 },
355 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
356 pcbIUScale.mmToIU( 0.5 ),
357 true,
358 },
359 },
360 { 0, 0 },
361 },
362 {
363 "U2",
364 {
365 {
366 { pcbIUScale.mmToIU( 0.9 ), pcbIUScale.mmToIU( 0.9 ) },
367 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
368 pcbIUScale.mmToIU( 0.5 ),
369 true,
370 },
371 },
372 { 0, 0 },
373 },
374 },
375 {},
376 },
377};
378// clang-format on
379
380
384static bool CollisionMatchesExpected( BOARD& aBoard, const PCB_MARKER& aMarker,
385 const COURTYARD_COLLISION& aCollision )
386{
387 auto reporter = std::static_pointer_cast<DRC_ITEM>( aMarker.GetRCItem() );
388
389 const FOOTPRINT* item_a = dynamic_cast<FOOTPRINT*>( aBoard.ResolveItem( reporter->GetMainItemID(), true ) );
390 const FOOTPRINT* item_b = dynamic_cast<FOOTPRINT*>( aBoard.ResolveItem( reporter->GetAuxItemID(), true ) );
391
392 // can't find the items!
393 if( !item_a || !item_b )
394 return false;
395
396 const bool ref_match_aa_bb = ( item_a->GetReference() == aCollision.m_refdes_a )
397 && ( item_b->GetReference() == aCollision.m_refdes_b );
398
399 const bool ref_match_ab_ba = ( item_a->GetReference() == aCollision.m_refdes_b )
400 && ( item_b->GetReference() == aCollision.m_refdes_a );
401
402 // Doesn't matter which way around it is, but both have to match somehow
403 return ref_match_aa_bb || ref_match_ab_ba;
404}
405
406
415 const std::vector<std::unique_ptr<PCB_MARKER>>& aMarkers,
416 const std::vector<COURTYARD_COLLISION>& aExpCollisions )
417{
418 for( const auto& marker : aMarkers )
419 {
422 }
423
424 KI_TEST::CheckUnorderedMatches( aExpCollisions, aMarkers,
425 [&]( const COURTYARD_COLLISION& aColl, const std::unique_ptr<PCB_MARKER>& aMarker )
426 {
427 return CollisionMatchesExpected( aBoard, *aMarker, aColl );
428 } );
429}
430
431
437 const KI_TEST::BOARD_DUMPER& aDumper )
438{
439 auto board = MakeBoard( aCase.m_fpDefs );
440
441 // Dump if env var set
442 aDumper.DumpBoardToFile( *board, aCase.m_case_name );
443
444 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
445
447
448 // we might not always have courtyards - that's a separate test
450
451 // list of markers to collect
452 std::vector<std::unique_ptr<PCB_MARKER>> markers;
453
454 DRC_ENGINE drcEngine( board.get(), &board->GetDesignSettings() );
455
456 drcEngine.InitEngine( wxFileName() );
457
458 drcEngine.SetViolationHandler(
459 [&]( const std::shared_ptr<DRC_ITEM>& aItem, const VECTOR2I& aPos, int aLayer,
460 const std::function<void( PCB_MARKER* )>& aPathGenerator )
461 {
462 if( aItem->GetErrorCode() == DRCE_OVERLAPPING_FOOTPRINTS
463 || aItem->GetErrorCode() == DRCE_MALFORMED_COURTYARD
464 || aItem->GetErrorCode() == DRCE_MISSING_COURTYARD )
465 {
466 markers.push_back( std::make_unique<PCB_MARKER>( aItem, aPos ) );
467 }
468 } );
469
470 drcEngine.RunTests( EDA_UNITS::MM, true, false );
471
472 CheckCollisionsMatchExpected( *board, markers, aCase.m_collisions );
473}
474
475
476BOOST_AUTO_TEST_CASE( OverlapCases )
477{
478 for( const auto& c : courtyard_cases )
479 {
480 BOOST_TEST_CONTEXT( c.m_case_name )
481 {
482 DoCourtyardOverlapTest( c, m_dumper );
483 }
484 }
485}
486
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
Construction utilities for PCB tests.
General utilities for PCB file IO for QA programs.
Container for design settings for a BOARD object.
std::map< int, SEVERITY > m_DRCSeverities
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:372
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:1846
Design Rule Checker object that performs all the DRC tests.
Definition drc_engine.h:129
void RunTests(EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, BOARD_COMMIT *aCommit=nullptr)
Run the DRC tests.
void SetViolationHandler(DRC_VIOLATION_HANDLER aHandler)
Set an optional DRC violation handler (receives DRC_ITEMs and positions).
Definition drc_engine.h:164
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
const wxString & GetReference() const
Definition footprint.h:841
A helper that contains logic to assist in dumping boards to disk depending on some environment variab...
void DumpBoardToFile(BOARD &aBoard, const std::string &aName) const
std::shared_ptr< RC_ITEM > GetRCItem() const
@ DRCE_OVERLAPPING_FOOTPRINTS
Definition drc_item.h:62
@ DRCE_MISSING_COURTYARD
Definition drc_item.h:63
@ DRCE_MALFORMED_COURTYARD
Definition drc_item.h:64
General utilities for DRC-related PCB tests.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ B_CrtYd
Definition layer_ids.h:111
void CheckUnorderedMatches(const EXP_CONT &aExpected, const FOUND_CONT &aFound, MATCH_PRED aMatchPredicate)
Check that a container of "found" objects matches a container of "expected" objects.
void DrawRect(FOOTPRINT &aFootprint, const VECTOR2I &aPos, const VECTOR2I &aSize, int aRadius, int aWidth, PCB_LAYER_ID aLayer)
Draw a rectangle on a footprint.
bool IsDrcMarkerOfType(const PCB_MARKER &aMarker, int aErrorCode)
Predicate for testing the type of a DRC marker.
STL namespace.
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
A complete courtyard overlap test case: a name, the board footprint list and the expected collisions.
std::vector< COURTYARD_COLLISION > m_collisions
std::vector< COURTYARD_TEST_FP > m_fpDefs
const KI_TEST::BOARD_DUMPER m_dumper
std::vector< RECT_DEFINITION > m_rects
Simple definition of a rectangle, can be rounded.
std::unique_ptr< FOOTPRINT > MakeCourtyardTestFP(BOARD &aBoard, const COURTYARD_TEST_FP &aFPDef)
Construct a FOOTPRINT to use in a courtyard test from a COURTYARD_TEST_FP definition.
static bool CollisionMatchesExpected(BOARD &aBoard, const PCB_MARKER &aMarker, const COURTYARD_COLLISION &aCollision)
Check if a PCB_MARKER is described by a particular COURTYARD_COLLISION object.
static void CheckCollisionsMatchExpected(BOARD &aBoard, const std::vector< std::unique_ptr< PCB_MARKER > > &aMarkers, const std::vector< COURTYARD_COLLISION > &aExpCollisions)
Check that the produced markers match the expected.
BOOST_AUTO_TEST_CASE(OverlapCases)
void AddRectCourtyard(FOOTPRINT &aFootprint, const RECT_DEFINITION &aRect)
Add a rectangular courtyard outline to a footprint.
static std::vector< COURTYARD_OVERLAP_TEST_CASE > courtyard_cases
static void DoCourtyardOverlapTest(const COURTYARD_OVERLAP_TEST_CASE &aCase, const KI_TEST::BOARD_DUMPER &aDumper)
Run a single courtyard overlap testcase.
std::ostream & operator<<(std::ostream &os, const COURTYARD_COLLISION &aColl)
std::unique_ptr< BOARD > MakeBoard(const std::vector< COURTYARD_TEST_FP > &aFPDefs)
Make a board for courtyard testing.
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
BOOST_CHECK_PREDICATE(ArePolylineEndPointsNearCircle,(chain)(c.m_geom.m_center_point)(radius)(accuracy+epsilon))
BOOST_TEST_CONTEXT("Test Clearance")
Functions to provide common constants and other functions to assist in making a consistent UI.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683