KiCad PCB EDA Suite
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 (C) 2018 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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
25
28#include <board.h>
30#include <footprint.h>
31#include <pcb_marker.h>
32#include <drc/drc_item.h>
33#include <drc/drc_engine.h>
34#include <widgets/ui_common.h>
35
37#include "drc_test_utils.h"
38
43{
47
48 // On front or back layer (the exact layer is context-dependent)
49 bool m_front;
50};
51
52
53/*
54 * A simple mock footprint with a set of courtyard rectangles and some other information
55 */
57{
58 std::string m_refdes;
59 std::vector<RECT_DEFINITION> m_rects;
61};
62
63
64/*
65 * Struct holding information about a courtyard collision
66 */
68{
69 // The two colliding parts
70 std::string m_refdes_a;
71 std::string m_refdes_b;
72};
73
74
75std::ostream& operator<<( std::ostream& os, const COURTYARD_COLLISION& aColl )
76{
77 os << "COURTYARD_COLLISION[ " << aColl.m_refdes_a << " -> " << aColl.m_refdes_b << "]";
78 return os;
79}
80
81
87{
88 std::string m_case_name;
89 std::vector<COURTYARD_TEST_FP> m_fpDefs; // The footprint in the test case
90 std::vector<COURTYARD_COLLISION> m_collisions; // The expected number of collisions
91};
92
93
97void AddRectCourtyard( FOOTPRINT& aFootprint, const RECT_DEFINITION& aRect )
98{
99 const PCB_LAYER_ID layer = aRect.m_front ? F_CrtYd : B_CrtYd;
100
101 const int width = pcbIUScale.mmToIU( 0.1 );
102
103 KI_TEST::DrawRect( aFootprint, aRect.m_centre, aRect.m_size, aRect.m_corner_rad, width, layer );
104}
105
106
110std::unique_ptr<FOOTPRINT> MakeCourtyardTestFP( BOARD& aBoard, const COURTYARD_TEST_FP& aFPDef )
111{
112 std::unique_ptr<FOOTPRINT> footprint = std::make_unique<FOOTPRINT>( &aBoard );
113
114 for( const RECT_DEFINITION& rect : aFPDef.m_rects )
115 AddRectCourtyard( *footprint, rect );
116
117 footprint->SetReference( aFPDef.m_refdes );
118
119 // This has to go after adding the courtyards, or all the poly sets are empty when DRC'd
120 footprint->SetPosition( aFPDef.m_pos );
121
122 return footprint;
123}
124
130std::unique_ptr<BOARD> MakeBoard( const std::vector<COURTYARD_TEST_FP>& aFPDefs )
131{
132 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
133
134 for( const COURTYARD_TEST_FP& fpDef : aFPDefs )
135 {
136 std::unique_ptr<FOOTPRINT> footprint = MakeCourtyardTestFP( *board, fpDef );
137
138 board->Add( footprint.release() );
139 }
140
141 return board;
142}
143
144
146{
148};
149
150
151BOOST_FIXTURE_TEST_SUITE( DrcCourtyardOverlap, COURTYARD_TEST_FIXTURE )
152
153// clang-format off
155 {
156 "empty board",
157 {}, // no footprint
158 {}, // no collisions
159 },
160 {
161 "single empty footprint",
162 {
163 {
164 "U1",
165 {}, // no courtyard
166 { 0, 0 }, // at origin
167 },
168 },
169 {}, // no collisions
170 },
171 {
172 // A single footprint can't overlap itself
173 "single footprint, single courtyard",
174 {
175 {
176 "U1",
177 {
178 {
179 { 0, 0 },
180 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
181 0,
182 true,
183 },
184 },
185 { 0, 0 },
186 },
187 },
188 {}, // no collisions
189 },
190 {
191 "two footprint, no overlap",
192 {
193 {
194 "U1",
195 {
196 {
197 { 0, 0 },
198 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
199 0,
200 true,
201 },
202 },
203 { 0, 0 },
204 },
205 {
206 "U2",
207 {
208 {
209 { 0, 0 },
210 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
211 0,
212 true,
213 },
214 },
215 { pcbIUScale.mmToIU( 3 ), pcbIUScale.mmToIU( 1 ) }, // One footprint is far from the other
216 },
217 },
218 {}, // no collisions
219 },
220 {
221 "two footprints, touching, no overlap",
222 {
223 {
224 "U1",
225 {
226 {
227 { 0, 0 },
228 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
229 0,
230 true,
231 },
232 },
233 { 0, 0 },
234 },
235 {
236 "U2",
237 {
238 {
239 { 0, 0 },
240 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
241 0,
242 true,
243 },
244 },
245 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 0 ) }, // Just touching
246 },
247 },
248 {}, // Touching means not colliding
249 },
250 {
251 "two footprints, overlap",
252 {
253 {
254 "U1",
255 {
256 {
257 { 0, 0 },
258 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
259 0,
260 true,
261 },
262 },
263 { 0, 0 },
264 },
265 {
266 "U2",
267 {
268 {
269 { 0, 0 },
270 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
271 0,
272 true,
273 },
274 },
275 { pcbIUScale.mmToIU( 0.5 ), pcbIUScale.mmToIU( 0 ) }, // Partial overlap
276 },
277 },
278 {
279 { "U1", "U2" }, // These two collide
280 },
281 },
282 {
283 "two footprints, overlap, different sides",
284 {
285 {
286 "U1",
287 {
288 {
289 { 0, 0 },
290 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
291 0,
292 true,
293 },
294 },
295 { 0, 0 },
296 },
297 {
298 "U2",
299 {
300 {
301 { 0, 0 },
302 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
303 0,
304 false,
305 },
306 },
307 { 0, 0 }, // complete overlap
308 },
309 },
310 {}, // but on different sides
311 },
312 {
313 "two footprints, multiple courtyards, overlap",
314 {
315 {
316 "U1",
317 {
318 {
319 { 0, 0 },
320 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
321 0,
322 true,
323 },
324 {
325 { pcbIUScale.mmToIU( 2 ), pcbIUScale.mmToIU( 0 ) },
326 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
327 0,
328 true,
329 },
330 },
331 { 0, 0 },
332 },
333 {
334 "U2",
335 {
336 {
337 { 0, 0 },
338 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
339 0,
340 true,
341 },
342 },
343 { 0, 0 }, // complete overlap with one of the others
344 },
345 },
346 {
347 { "U1", "U2" },
348 },
349 },
350 {
351 // The courtyards do not overlap, but their bounding boxes do
352 "two footprints, no overlap, bbox overlap",
353 {
354 {
355 "U1",
356 {
357 {
358 { 0, 0 },
359 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
360 pcbIUScale.mmToIU( 0.5 ),
361 true,
362 },
363 },
364 { 0, 0 },
365 },
366 {
367 "U2",
368 {
369 {
370 { pcbIUScale.mmToIU( 0.9 ), pcbIUScale.mmToIU( 0.9 ) },
371 { pcbIUScale.mmToIU( 1 ), pcbIUScale.mmToIU( 1 ) },
372 pcbIUScale.mmToIU( 0.5 ),
373 true,
374 },
375 },
376 { 0, 0 },
377 },
378 },
379 {},
380 },
381};
382// clang-format on
383
384
388static bool CollisionMatchesExpected( BOARD& aBoard, const PCB_MARKER& aMarker,
389 const COURTYARD_COLLISION& aCollision )
390{
391 auto reporter = std::static_pointer_cast<DRC_ITEM>( aMarker.GetRCItem() );
392
393 const FOOTPRINT* item_a = dynamic_cast<FOOTPRINT*>( aBoard.GetItem( reporter->GetMainItemID() ) );
394 const FOOTPRINT* item_b = dynamic_cast<FOOTPRINT*>( aBoard.GetItem( reporter->GetAuxItemID() ) );
395
396 // can't find the items!
397 if( !item_a || !item_b )
398 return false;
399
400 const bool ref_match_aa_bb = ( item_a->GetReference() == aCollision.m_refdes_a )
401 && ( item_b->GetReference() == aCollision.m_refdes_b );
402
403 const bool ref_match_ab_ba = ( item_a->GetReference() == aCollision.m_refdes_b )
404 && ( item_b->GetReference() == aCollision.m_refdes_a );
405
406 // Doesn't matter which way around it is, but both have to match somehow
407 return ref_match_aa_bb || ref_match_ab_ba;
408}
409
410
419 const std::vector<std::unique_ptr<PCB_MARKER>>& aMarkers,
420 const std::vector<COURTYARD_COLLISION>& aExpCollisions )
421{
422 for( const auto& marker : aMarkers )
423 {
424 BOOST_CHECK_PREDICATE(
426 }
427
428 KI_TEST::CheckUnorderedMatches( aExpCollisions, aMarkers,
429 [&]( const COURTYARD_COLLISION& aColl, const std::unique_ptr<PCB_MARKER>& aMarker )
430 {
431 return CollisionMatchesExpected( aBoard, *aMarker, aColl );
432 } );
433}
434
435
441 const KI_TEST::BOARD_DUMPER& aDumper )
442{
443 auto board = MakeBoard( aCase.m_fpDefs );
444
445 // Dump if env var set
446 aDumper.DumpBoardToFile( *board, aCase.m_case_name );
447
448 BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
449
451
452 // we might not always have courtyards - that's a separate test
454
455 // list of markers to collect
456 std::vector<std::unique_ptr<PCB_MARKER>> markers;
457
458 DRC_ENGINE drcEngine( board.get(), &board->GetDesignSettings() );
459
460 drcEngine.InitEngine( wxFileName() );
461
462 drcEngine.SetViolationHandler(
463 [&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aLayer )
464 {
465 if( aItem->GetErrorCode() == DRCE_OVERLAPPING_FOOTPRINTS
466 || aItem->GetErrorCode() == DRCE_MALFORMED_COURTYARD
467 || aItem->GetErrorCode() == DRCE_MISSING_COURTYARD )
468 {
469 markers.push_back( std::make_unique<PCB_MARKER>( aItem, aPos ) );
470 }
471 } );
472
473 drcEngine.RunTests( EDA_UNITS::MILLIMETRES, true, false );
474
475 CheckCollisionsMatchExpected( *board, markers, aCase.m_collisions );
476}
477
478
479BOOST_AUTO_TEST_CASE( OverlapCases )
480{
481 for( const auto& c : courtyard_cases )
482 {
483 BOOST_TEST_CONTEXT( c.m_case_name )
484 {
485 DoCourtyardOverlapTest( c, m_dumper );
486 }
487 }
488}
489
490BOOST_AUTO_TEST_SUITE_END()
constexpr EDA_IU_SCALE pcbIUScale
Definition: base_units.h:109
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:265
BOARD_ITEM * GetItem(const KIID &aID) const
Definition: board.cpp:958
Design Rule Checker object that performs all the DRC tests.
Definition: drc_engine.h:83
void SetViolationHandler(DRC_VIOLATION_HANDLER aHandler)
Set an optional DRC violation handler (receives DRC_ITEMs and positions).
Definition: drc_engine.h:110
void RunTests(EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints)
Run the DRC tests.
Definition: drc_engine.cpp:565
void InitEngine(const wxFileName &aRulePath)
Initialize the DRC engine.
Definition: drc_engine.cpp:512
const wxString & GetReference() const
Definition: footprint.h:519
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
Definition: marker_base.h:105
@ DRCE_OVERLAPPING_FOOTPRINTS
Definition: drc_item.h:61
@ DRCE_MISSING_COURTYARD
Definition: drc_item.h:62
@ DRCE_MALFORMED_COURTYARD
Definition: drc_item.h:63
General utilities for DRC-related PCB tests.
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:59
@ F_CrtYd
Definition: layer_ids.h:117
@ B_CrtYd
Definition: layer_ids.h:116
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.
Definition: bitmap.cpp:64
@ 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
constexpr int mmToIU(double mm) const
Definition: base_units.h:89
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.
Functions to provide common constants and other functions to assist in making a consistent UI.
#define BOOST_TEST_CONTEXT(A)