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