KiCad PCB EDA Suite
drc_rtree.h
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) 2020 KiCad Developers, see AUTHORS.txt for contributors.
5  * Copyright (C) 2020 CERN
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-3.0.html
20  * or you may search the http://www.gnu.org website for the version 3 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 #ifndef DRC_RTREE_H_
26 #define DRC_RTREE_H_
27 
28 #include <eda_rect.h>
29 #include <board_item.h>
30 #include <track.h>
31 #include <unordered_set>
32 #include <set>
33 #include <vector>
34 
35 #include <geometry/rtree.h>
36 #include <math/vector2d.h>
37 
43 class DRC_RTREE
44 {
45 
46 public:
47 
49  {
50  ITEM_WITH_SHAPE( BOARD_ITEM *aParent, SHAPE* aShape,
51  std::shared_ptr<SHAPE> aParentShape = nullptr ) :
52  parent ( aParent ),
53  shape ( aShape ),
54  parentShape( aParentShape )
55  {};
56 
59  std::shared_ptr<SHAPE> parentShape;
60  };
61 
62 private:
63 
64  using drc_rtree = RTree<ITEM_WITH_SHAPE*, int, 2, double>;
65 
66 public:
67 
69  {
70  for( int layer : LSET::AllLayersMask().Seq() )
71  m_tree[layer] = new drc_rtree();
72 
73  m_count = 0;
74  }
75 
77  {
78  for( auto tree : m_tree )
79  delete tree;
80  }
81 
86  void Insert( BOARD_ITEM* aItem, int aWorstClearance = 0, int aLayer = UNDEFINED_LAYER )
87  {
88  std::vector<SHAPE*> subshapes;
89 
90  auto addLayer =
91  [&]( PCB_LAYER_ID layer )
92  {
93  std::shared_ptr<SHAPE> shape = aItem->GetEffectiveShape( layer );
94  subshapes.clear();
95 
96  if( shape->HasIndexableSubshapes() )
97  shape->GetIndexableSubshapes( subshapes );
98  else
99  subshapes.push_back( shape.get() );
100 
101  for( SHAPE* subshape : subshapes )
102  {
103  BOX2I bbox = subshape->BBox();
104 
105  bbox.Inflate( aWorstClearance );
106 
107  const int mmin[2] = { bbox.GetX(), bbox.GetY() };
108  const int mmax[2] = { bbox.GetRight(), bbox.GetBottom() };
109 
110  m_tree[layer]->Insert( mmin, mmax, new ITEM_WITH_SHAPE( aItem, subshape,
111  shape ) );
112  m_count++;
113  }
114  };
115 
116  if( aItem->Type() == PCB_FP_TEXT_T && !static_cast<FP_TEXT*>( aItem )->IsVisible() )
117  return;
118 
119  if( aLayer != UNDEFINED_LAYER )
120  {
121  addLayer( (PCB_LAYER_ID) aLayer );
122  }
123  else
124  {
125  LSET layers = aItem->GetLayerSet();
126 
127  // Special-case pad holes which pierce all the copper layers
128  if( aItem->Type() == PCB_PAD_T )
129  {
130  PAD* pad = static_cast<PAD*>( aItem );
131 
132  if( pad->GetDrillSizeX() > 0 && pad->GetDrillSizeY() > 0 )
133  layers |= LSET::AllCuMask();
134  }
135 
136  for( int layer : layers.Seq() )
137  addLayer( (PCB_LAYER_ID) layer );
138  }
139  }
140 
145  void clear()
146  {
147  for( auto tree : m_tree )
148  tree->RemoveAll();
149 
150  m_count = 0;
151  }
152 
153  bool CheckColliding( SHAPE* aRefShape, PCB_LAYER_ID aTargetLayer, int aClearance = 0,
154  std::function<bool( BOARD_ITEM*)> aFilter = nullptr ) const
155  {
156  BOX2I box = aRefShape->BBox();
157  box.Inflate( aClearance );
158 
159  int min[2] = { box.GetX(), box.GetY() };
160  int max[2] = { box.GetRight(), box.GetBottom() };
161 
162  int count = 0;
163 
164  auto visit =
165  [&] ( ITEM_WITH_SHAPE* aItem ) -> bool
166  {
167  if( !aFilter || aFilter( aItem->parent ) )
168  {
169  int actual;
170 
171  if( aRefShape->Collide( aItem->shape, aClearance, &actual ) )
172  {
173  count++;
174  return false;
175  }
176  }
177 
178  return true;
179  };
180 
181  this->m_tree[aTargetLayer]->Search( min, max, visit );
182  return count > 0;
183  }
184 
190  int QueryColliding( BOARD_ITEM* aRefItem,
191  PCB_LAYER_ID aRefLayer,
192  PCB_LAYER_ID aTargetLayer,
193  std::function<bool( BOARD_ITEM* )> aFilter = nullptr,
194  std::function<bool( BOARD_ITEM* )> aVisitor = nullptr,
195  int aClearance = 0 ) const
196  {
197  // keep track of BOARD_ITEMs that have been already found to collide (some items
198  // might be build of COMPOUND/triangulated shapes and a single subshape collision
199  // means we have a hit)
200  std::unordered_set<BOARD_ITEM*> collidingCompounds;
201 
202  EDA_RECT box = aRefItem->GetBoundingBox();
203  box.Inflate( aClearance );
204 
205  int min[2] = { box.GetX(), box.GetY() };
206  int max[2] = { box.GetRight(), box.GetBottom() };
207 
208  std::shared_ptr<SHAPE> refShape = aRefItem->GetEffectiveShape( aRefLayer );
209 
210  int count = 0;
211 
212  auto visit =
213  [&]( ITEM_WITH_SHAPE* aItem ) -> bool
214  {
215  if( aItem->parent == aRefItem )
216  return true;
217 
218  if( collidingCompounds.find( aItem->parent ) != collidingCompounds.end() )
219  return true;
220 
221  if( !aFilter || aFilter( aItem->parent ) )
222  {
223  if( refShape->Collide( aItem->shape, aClearance ) )
224  {
225  collidingCompounds.insert( aItem->parent );
226  count++;
227 
228  if( aVisitor )
229  return aVisitor( aItem->parent );
230  }
231  }
232 
233  return true;
234  };
235 
236  this->m_tree[aTargetLayer]->Search( min, max, visit );
237  return count;
238  }
239 
246  bool QueryColliding( EDA_RECT aBox, SHAPE* aRefShape, PCB_LAYER_ID aLayer, int aClearance,
247  int* aActual, VECTOR2I* aPos ) const
248  {
249  aBox.Inflate( aClearance );
250 
251  int min[2] = { aBox.GetX(), aBox.GetY() };
252  int max[2] = { aBox.GetRight(), aBox.GetBottom() };
253 
254  bool collision = false;
255  int actual = INT_MAX;
256  VECTOR2I pos;
257 
258  auto visit =
259  [&]( ITEM_WITH_SHAPE* aItem ) -> bool
260  {
261  int curActual;
262  VECTOR2I curPos;
263 
264  if( aRefShape->Collide( aItem->shape, aClearance, &curActual, &curPos ) )
265  {
266  collision = true;
267 
268  if( curActual < actual )
269  {
270  actual = curActual;
271  pos = curPos;
272  }
273  }
274 
275  return true;
276  };
277 
278  this->m_tree[aLayer]->Search( min, max, visit );
279 
280  if( collision )
281  {
282  if( aActual )
283  *aActual = std::max( 0, actual );
284 
285  if( aPos )
286  *aPos = pos;
287 
288  return true;
289  }
290 
291  return false;
292  }
293 
294  typedef std::pair<PCB_LAYER_ID, PCB_LAYER_ID> LAYER_PAIR;
295 
296  struct PAIR_INFO
297  {
299  layerPair( aPair ),
300  refItem( aRef ),
301  testItem( aTest )
302  { };
303 
307  };
308 
310  std::vector<LAYER_PAIR> aLayerPairs,
311  std::function<bool( const LAYER_PAIR&,
313  bool* aCollision )> aVisitor,
314  int aMaxClearance,
315  std::function<bool(int, int )> aProgressReporter ) const
316  {
317  std::vector<PAIR_INFO> pairsToVisit;
318 
319  for( LAYER_PAIR& layerPair : aLayerPairs )
320  {
321  const PCB_LAYER_ID refLayer = layerPair.first;
322  const PCB_LAYER_ID targetLayer = layerPair.second;
323 
324  for( ITEM_WITH_SHAPE* refItem : aRefTree->OnLayer( refLayer ) )
325  {
326  BOX2I box = refItem->shape->BBox();
327  box.Inflate( aMaxClearance );
328 
329  int min[2] = { box.GetX(), box.GetY() };
330  int max[2] = { box.GetRight(), box.GetBottom() };
331 
332  auto visit =
333  [&]( ITEM_WITH_SHAPE* aItemToTest ) -> bool
334  {
335  // don't collide items against themselves
336  if( aItemToTest->parent == refItem->parent )
337  return true;
338 
339  pairsToVisit.emplace_back( layerPair, refItem, aItemToTest );
340  return true;
341  };
342 
343  this->m_tree[targetLayer]->Search( min, max, visit );
344  };
345  }
346 
347  // keep track of BOARD_ITEMs pairs that have been already found to collide (some items
348  // might be build of COMPOUND/triangulated shapes and a single subshape collision
349  // means we have a hit)
350  std::map< std::pair<BOARD_ITEM*, BOARD_ITEM*>, int> collidingCompounds;
351 
352  int progress = 0;
353  int count = pairsToVisit.size();
354 
355  for( const PAIR_INFO& pair : pairsToVisit )
356  {
357  if( !aProgressReporter( progress++, count ) )
358  break;
359 
360  BOARD_ITEM* a = pair.refItem->parent;
361  BOARD_ITEM* b = pair.testItem->parent;
362 
363  // store canonical order so we don't collide in both directions (a:b and b:a)
364  if( static_cast<void*>( a ) > static_cast<void*>( b ) )
365  std::swap( a, b );
366 
367  // don't report multiple collisions for compound or triangulated shapes
368  if( collidingCompounds.count( { a, b } ) )
369  continue;
370 
371  bool collisionDetected = false;
372 
373  if( !aVisitor( pair.layerPair, pair.refItem, pair.testItem, &collisionDetected ) )
374  break;
375 
376  if( collisionDetected )
377  collidingCompounds[ { a, b } ] = 1;
378  }
379 
380  return 0;
381  }
382 
387  size_t size() const
388  {
389  return m_count;
390  }
391 
392  bool empty() const
393  {
394  return m_count == 0;
395  }
396 
397  using iterator = typename drc_rtree::Iterator;
398 
407  struct DRC_LAYER
408  {
409  DRC_LAYER( drc_rtree* aTree ) : layer_tree( aTree )
410  {
411  m_rect = { { INT_MIN, INT_MIN }, { INT_MAX, INT_MAX } };
412  };
413 
414  DRC_LAYER( drc_rtree* aTree, const EDA_RECT aRect ) : layer_tree( aTree )
415  {
416  m_rect = { { aRect.GetX(), aRect.GetY() },
417  { aRect.GetRight(), aRect.GetBottom() } };
418  };
419 
420  drc_rtree::Rect m_rect;
422 
424  {
425  return layer_tree->begin( m_rect );
426  }
427 
429  {
430  return layer_tree->end( m_rect );
431  }
432  };
433 
435  {
436  return DRC_LAYER( m_tree[int( aLayer )] );
437  }
438 
439  DRC_LAYER Overlapping( PCB_LAYER_ID aLayer, const wxPoint& aPoint, int aAccuracy = 0 )
440  {
441  EDA_RECT rect( aPoint, wxSize( 0, 0 ) );
442  rect.Inflate( aAccuracy );
443  return DRC_LAYER( m_tree[int( aLayer )], rect );
444  }
445 
446  DRC_LAYER Overlapping( PCB_LAYER_ID aLayer, const EDA_RECT& aRect )
447  {
448  return DRC_LAYER( m_tree[int( aLayer )], aRect );
449  }
450 
451 
452 private:
454  size_t m_count;
455 };
456 
457 
458 #endif /* DRC_RTREE_H_ */
static LSET AllCuMask(int aCuLayerCount=MAX_CU_LAYERS)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition: lset.cpp:750
iterator begin()
Definition: drc_rtree.h:423
ITEM_WITH_SHAPE(BOARD_ITEM *aParent, SHAPE *aShape, std::shared_ptr< SHAPE > aParentShape=nullptr)
Definition: drc_rtree.h:50
const int GetDrillSizeX() const
Definition: pad.h:244
class FP_TEXT, text in a footprint
Definition: typeinfo.h:92
coord_type GetX() const
Definition: box2.h:190
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:82
int GetX() const
Definition: eda_rect.h:103
bool empty() const
Definition: drc_rtree.h:392
drc_rtree * layer_tree
Definition: drc_rtree.h:421
coord_type GetRight() const
Definition: box2.h:199
LAYER_PAIR layerPair
Definition: drc_rtree.h:302
DRC_LAYER(drc_rtree *aTree)
Definition: drc_rtree.h:409
class PAD, a pad in a footprint
Definition: typeinfo.h:89
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > LAYER_PAIR
Definition: drc_rtree.h:294
coord_type GetBottom() const
Definition: box2.h:200
drc_rtree::Rect m_rect
Definition: drc_rtree.h:418
DRC_LAYER OnLayer(PCB_LAYER_ID aLayer)
Definition: drc_rtree.h:434
LSEQ Seq(const PCB_LAYER_ID *aWishListSequence, unsigned aCount) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition: lset.cpp:411
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition: shape.h:165
void Insert(BOARD_ITEM *aItem, int aWorstClearance=0, int aLayer=UNDEFINED_LAYER)
Function Insert() Inserts an item into the tree.
Definition: drc_rtree.h:86
virtual const BOX2I BBox(int aClearance=0) const =0
Compute a bounding box of the shape, with a margin of aClearance a collision.
RTree< ITEM_WITH_SHAPE *, int, 2, double > drc_rtree
Definition: drc_rtree.h:64
int GetBottom() const
Definition: eda_rect.h:119
DRC_LAYER(drc_rtree *aTree, const EDA_RECT aRect)
Definition: drc_rtree.h:414
The DRC_LAYER struct provides a layer-specific auto-range iterator to the RTree.
Definition: drc_rtree.h:407
PCB_LAYER_ID
A quick note on layer IDs:
LSET is a set of PCB_LAYER_IDs.
bool CheckColliding(SHAPE *aRefShape, PCB_LAYER_ID aTargetLayer, int aClearance=0, std::function< bool(BOARD_ITEM *)> aFilter=nullptr) const
Definition: drc_rtree.h:153
size_t size() const
Returns the number of items in the tree.
Definition: drc_rtree.h:387
drc_rtree * m_tree[PCB_LAYER_ID_COUNT]
Definition: drc_rtree.h:453
std::shared_ptr< SHAPE > parentShape
Definition: drc_rtree.h:59
ITEM_WITH_SHAPE * testItem
Definition: drc_rtree.h:306
ITEM_WITH_SHAPE * refItem
Definition: drc_rtree.h:305
int GetRight() const
Definition: eda_rect.h:116
size_t m_count
Definition: drc_rtree.h:454
void clear()
Function RemoveAll() Removes all items from the RTree.
Definition: drc_rtree.h:145
static LSET AllLayersMask()
Definition: lset.cpp:787
An abstract shape on 2D plane.
Definition: shape.h:116
coord_type GetY() const
Definition: box2.h:191
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Function Inflate inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:302
int QueryCollidingPairs(DRC_RTREE *aRefTree, std::vector< LAYER_PAIR > aLayerPairs, std::function< bool(const LAYER_PAIR &, ITEM_WITH_SHAPE *, ITEM_WITH_SHAPE *, bool *aCollision)> aVisitor, int aMaxClearance, std::function< bool(int, int)> aProgressReporter) const
Definition: drc_rtree.h:309
DRC_RTREE()
Definition: drc_rtree.h:68
PAIR_INFO(LAYER_PAIR aPair, ITEM_WITH_SHAPE *aRef, ITEM_WITH_SHAPE *aTest)
Definition: drc_rtree.h:298
const int GetDrillSizeY() const
Definition: pad.h:246
DRC_LAYER Overlapping(PCB_LAYER_ID aLayer, const wxPoint &aPoint, int aAccuracy=0)
Definition: drc_rtree.h:439
Handle the component boundary box.
Definition: eda_rect.h:42
int GetY() const
Definition: eda_rect.h:104
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
Definition: board_item.cpp:153
DRC_LAYER Overlapping(PCB_LAYER_ID aLayer, const EDA_RECT &aRect)
Definition: drc_rtree.h:446
virtual const EDA_RECT GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition: eda_item.cpp:89
Definition: pad.h:60
DRC_RTREE - Implements an R-tree for fast spatial and layer indexing of connectable items.
Definition: drc_rtree.h:43
typename drc_rtree::Iterator iterator
Definition: drc_rtree.h:397
bool QueryColliding(EDA_RECT aBox, SHAPE *aRefShape, PCB_LAYER_ID aLayer, int aClearance, int *aActual, VECTOR2I *aPos) const
This one is for tessellated items.
Definition: drc_rtree.h:246
virtual LSET GetLayerSet() const
Return a std::bitset of all layers on which the item physically resides.
Definition: board_item.h:178
EDA_RECT & Inflate(wxCoord dx, wxCoord dy)
Inflate the rectangle horizontally by dx and vertically by dy.
Definition: eda_rect.cpp:363
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:162
int QueryColliding(BOARD_ITEM *aRefItem, PCB_LAYER_ID aRefLayer, PCB_LAYER_ID aTargetLayer, std::function< bool(BOARD_ITEM *)> aFilter=nullptr, std::function< bool(BOARD_ITEM *)> aVisitor=nullptr, int aClearance=0) const
This is a fast test which essentially does bounding-box overlap given a worst-case clearance.
Definition: drc_rtree.h:190
~DRC_RTREE()
Definition: drc_rtree.h:76