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>
29 #include <board_design_settings.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 
36 #include "../board_test_utils.h"
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 
75 std::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 
97 void 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 = Millimeter2iu( 0.1 );
102 
103  KI_TEST::DrawRect( aFootprint, aRect.m_centre, aRect.m_size, aRect.m_corner_rad, width, layer );
104 }
105 
106 
110 std::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( (wxPoint) aFPDef.m_pos );
121 
122  return footprint;
123 }
124 
130 std::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 
151 BOOST_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  { Millimeter2iu( 1 ), Millimeter2iu( 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  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
199  0,
200  true,
201  },
202  },
203  { 0, 0 },
204  },
205  {
206  "U2",
207  {
208  {
209  { 0, 0 },
210  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
211  0,
212  true,
213  },
214  },
215  { Millimeter2iu( 3 ), Millimeter2iu( 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  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
229  0,
230  true,
231  },
232  },
233  { 0, 0 },
234  },
235  {
236  "U2",
237  {
238  {
239  { 0, 0 },
240  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
241  0,
242  true,
243  },
244  },
245  { Millimeter2iu( 1 ), Millimeter2iu( 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  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
259  0,
260  true,
261  },
262  },
263  { 0, 0 },
264  },
265  {
266  "U2",
267  {
268  {
269  { 0, 0 },
270  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
271  0,
272  true,
273  },
274  },
275  { Millimeter2iu( 0.5 ), Millimeter2iu( 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  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
291  0,
292  true,
293  },
294  },
295  { 0, 0 },
296  },
297  {
298  "U2",
299  {
300  {
301  { 0, 0 },
302  { Millimeter2iu( 1 ), Millimeter2iu( 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  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
321  0,
322  true,
323  },
324  {
325  { Millimeter2iu( 2 ), Millimeter2iu( 0 ) },
326  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
327  0,
328  true,
329  },
330  },
331  { 0, 0 },
332  },
333  {
334  "U2",
335  {
336  {
337  { 0, 0 },
338  { Millimeter2iu( 1 ), Millimeter2iu( 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  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
360  Millimeter2iu( 0.5 ),
361  true,
362  },
363  },
364  { 0, 0 },
365  },
366  {
367  "U2",
368  {
369  {
370  { Millimeter2iu( 0.9 ), Millimeter2iu( 0.9 ) },
371  { Millimeter2iu( 1 ), Millimeter2iu( 1 ) },
372  Millimeter2iu( 0.5 ),
373  true,
374  },
375  },
376  { 0, 0 },
377  },
378  },
379  {},
380  },
381 };
382 // clang-format on
383 
384 
388 static 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 
418 static void CheckCollisionsMatchExpected( BOARD& aBoard,
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, wxPoint aPos )
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 
479 BOOST_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 
490 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:845
BOOST_AUTO_TEST_CASE(OverlapCases)
Design Rule Checker object that performs all the DRC tests.
Definition: drc_engine.h:81
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:64
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
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:463
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:190
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:65
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)
Initialize the DRC engine.
Definition: drc_engine.cpp:626
Container for design settings for a BOARD object.