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 <fp_text.h>
31 #include <memory>
32 #include <unordered_set>
33 #include <set>
34 #include <vector>
35 
36 #include <geometry/rtree.h>
37 #include <geometry/shape.h>
38 #include <math/vector2d.h>
39 
45 class DRC_RTREE
46 {
47 
48 public:
49 
51  {
52  ITEM_WITH_SHAPE( BOARD_ITEM *aParent, SHAPE* aShape,
53  std::shared_ptr<SHAPE> aParentShape = nullptr ) :
54  parent ( aParent ),
55  shape ( aShape ),
56  parentShape( aParentShape )
57  {};
58 
61  std::shared_ptr<SHAPE> parentShape;
62  };
63 
64 private:
65 
66  using drc_rtree = RTree<ITEM_WITH_SHAPE*, int, 2, double>;
67 
68 public:
69 
71  {
72  for( int layer : LSET::AllLayersMask().Seq() )
73  m_tree[layer] = new drc_rtree();
74 
75  m_count = 0;
76  }
77 
79  {
80  for( auto tree : m_tree )
81  delete tree;
82  }
83 
88  void Insert( BOARD_ITEM* aItem, PCB_LAYER_ID aLayer, int aWorstClearance = 0 )
89  {
90  wxASSERT( aLayer != UNDEFINED_LAYER );
91 
92  if( aItem->Type() == PCB_FP_TEXT_T && !static_cast<FP_TEXT*>( aItem )->IsVisible() )
93  return;
94 
95  std::vector<SHAPE*> subshapes;
96  std::shared_ptr<SHAPE> shape = aItem->GetEffectiveShape( ToLAYER_ID( aLayer ) );
97  subshapes.clear();
98 
99  if( shape->HasIndexableSubshapes() )
100  shape->GetIndexableSubshapes( subshapes );
101  else
102  subshapes.push_back( shape.get() );
103 
104  for( SHAPE* subshape : subshapes )
105  {
106  BOX2I bbox = subshape->BBox();
107 
108  bbox.Inflate( aWorstClearance );
109 
110  const int mmin[2] = { bbox.GetX(), bbox.GetY() };
111  const int mmax[2] = { bbox.GetRight(), bbox.GetBottom() };
112  ITEM_WITH_SHAPE* itemShape = new ITEM_WITH_SHAPE( aItem, subshape, shape );
113 
114  m_tree[aLayer]->Insert( mmin, mmax, itemShape );
115  m_count++;
116  }
117  }
118 
123  void clear()
124  {
125  for( auto tree : m_tree )
126  tree->RemoveAll();
127 
128  m_count = 0;
129  }
130 
131  bool CheckColliding( SHAPE* aRefShape, PCB_LAYER_ID aTargetLayer, int aClearance = 0,
132  std::function<bool( BOARD_ITEM*)> aFilter = nullptr ) const
133  {
134  BOX2I box = aRefShape->BBox();
135  box.Inflate( aClearance );
136 
137  int min[2] = { box.GetX(), box.GetY() };
138  int max[2] = { box.GetRight(), box.GetBottom() };
139 
140  int count = 0;
141 
142  auto visit =
143  [&] ( ITEM_WITH_SHAPE* aItem ) -> bool
144  {
145  if( !aFilter || aFilter( aItem->parent ) )
146  {
147  int actual;
148 
149  if( aRefShape->Collide( aItem->shape, aClearance, &actual ) )
150  {
151  count++;
152  return false;
153  }
154  }
155 
156  return true;
157  };
158 
159  this->m_tree[aTargetLayer]->Search( min, max, visit );
160  return count > 0;
161  }
162 
168  int QueryColliding( BOARD_ITEM* aRefItem,
169  PCB_LAYER_ID aRefLayer,
170  PCB_LAYER_ID aTargetLayer,
171  std::function<bool( BOARD_ITEM* )> aFilter = nullptr,
172  std::function<bool( BOARD_ITEM* )> aVisitor = nullptr,
173  int aClearance = 0 ) const
174  {
175  // keep track of BOARD_ITEMs that have been already found to collide (some items
176  // might be build of COMPOUND/triangulated shapes and a single subshape collision
177  // means we have a hit)
178  std::unordered_set<BOARD_ITEM*> collidingCompounds;
179 
180  // keep track of results of client filter so we don't ask more than once for compound
181  // shapes
182  std::map<BOARD_ITEM*, bool> filterResults;
183 
184  EDA_RECT box = aRefItem->GetBoundingBox();
185  box.Inflate( aClearance );
186 
187  int min[2] = { box.GetX(), box.GetY() };
188  int max[2] = { box.GetRight(), box.GetBottom() };
189 
190  std::shared_ptr<SHAPE> refShape = aRefItem->GetEffectiveShape( aRefLayer );
191 
192  int count = 0;
193 
194  auto visit =
195  [&]( ITEM_WITH_SHAPE* aItem ) -> bool
196  {
197  if( aItem->parent == aRefItem )
198  return true;
199 
200  if( collidingCompounds.find( aItem->parent ) != collidingCompounds.end() )
201  return true;
202 
203  bool filtered;
204  auto it = filterResults.find( aItem->parent );
205 
206  if( it == filterResults.end() )
207  {
208  filtered = aFilter && !aFilter( aItem->parent );
209  filterResults[ aItem->parent ] = filtered;
210  }
211  else
212  {
213  filtered = it->second;
214  }
215 
216  if( filtered )
217  return true;
218 
219  if( refShape->Collide( aItem->shape, aClearance ) )
220  {
221  collidingCompounds.insert( aItem->parent );
222  count++;
223 
224  if( aVisitor )
225  return aVisitor( aItem->parent );
226  }
227 
228  return true;
229  };
230 
231  this->m_tree[aTargetLayer]->Search( min, max, visit );
232  return count;
233  }
234 
241  bool QueryColliding( EDA_RECT aBox, SHAPE* aRefShape, PCB_LAYER_ID aLayer, int aClearance,
242  int* aActual, VECTOR2I* aPos ) const
243  {
244  aBox.Inflate( aClearance );
245 
246  int min[2] = { aBox.GetX(), aBox.GetY() };
247  int max[2] = { aBox.GetRight(), aBox.GetBottom() };
248 
249  bool collision = false;
250  int actual = INT_MAX;
251  VECTOR2I pos;
252 
253  auto visit =
254  [&]( ITEM_WITH_SHAPE* aItem ) -> bool
255  {
256  int curActual;
257  VECTOR2I curPos;
258 
259  if( aRefShape->Collide( aItem->shape, aClearance, &curActual, &curPos ) )
260  {
261  collision = true;
262 
263  if( curActual < actual )
264  {
265  actual = curActual;
266  pos = curPos;
267  }
268 
269  // Stop looking after we have a true collision
270  if( actual <= 0 )
271  return false;
272  }
273 
274  return true;
275  };
276 
277  this->m_tree[aLayer]->Search( min, max, visit );
278 
279  if( collision )
280  {
281  if( aActual )
282  *aActual = std::max( 0, actual );
283 
284  if( aPos )
285  *aPos = pos;
286 
287  return true;
288  }
289 
290  return false;
291  }
292 
296  bool QueryColliding( EDA_RECT aBox, SHAPE* aRefShape, PCB_LAYER_ID aLayer ) const
297  {
298  int min[2] = { aBox.GetX(), aBox.GetY() };
299  int max[2] = { aBox.GetRight(), aBox.GetBottom() };
300  bool collision = false;
301 
302  auto visit =
303  [&]( ITEM_WITH_SHAPE* aItem ) -> bool
304  {
305  if( aRefShape->Collide( aItem->shape, 0 ) )
306  {
307  collision = true;
308  return false;
309  }
310 
311  return true;
312  };
313 
314  this->m_tree[aLayer]->Search( min, max, visit );
315 
316  return collision;
317  }
318 
319  typedef std::pair<PCB_LAYER_ID, PCB_LAYER_ID> LAYER_PAIR;
320 
321  struct PAIR_INFO
322  {
324  layerPair( aPair ),
325  refItem( aRef ),
326  testItem( aTest )
327  { };
328 
332  };
333 
335  std::vector<LAYER_PAIR> aLayerPairs,
336  std::function<bool( const LAYER_PAIR&,
338  bool* aCollision )> aVisitor,
339  int aMaxClearance,
340  std::function<bool(int, int )> aProgressReporter ) const
341  {
342  std::vector<PAIR_INFO> pairsToVisit;
343 
344  for( LAYER_PAIR& layerPair : aLayerPairs )
345  {
346  const PCB_LAYER_ID refLayer = layerPair.first;
347  const PCB_LAYER_ID targetLayer = layerPair.second;
348 
349  for( ITEM_WITH_SHAPE* refItem : aRefTree->OnLayer( refLayer ) )
350  {
351  BOX2I box = refItem->shape->BBox();
352  box.Inflate( aMaxClearance );
353 
354  int min[2] = { box.GetX(), box.GetY() };
355  int max[2] = { box.GetRight(), box.GetBottom() };
356 
357  auto visit =
358  [&]( ITEM_WITH_SHAPE* aItemToTest ) -> bool
359  {
360  // don't collide items against themselves
361  if( aItemToTest->parent == refItem->parent )
362  return true;
363 
364  pairsToVisit.emplace_back( layerPair, refItem, aItemToTest );
365  return true;
366  };
367 
368  this->m_tree[targetLayer]->Search( min, max, visit );
369  };
370  }
371 
372  // keep track of BOARD_ITEMs pairs that have been already found to collide (some items
373  // might be build of COMPOUND/triangulated shapes and a single subshape collision
374  // means we have a hit)
375  std::map< std::pair<BOARD_ITEM*, BOARD_ITEM*>, int> collidingCompounds;
376 
377  int progress = 0;
378  int count = pairsToVisit.size();
379 
380  for( const PAIR_INFO& pair : pairsToVisit )
381  {
382  if( !aProgressReporter( progress++, count ) )
383  break;
384 
385  BOARD_ITEM* a = pair.refItem->parent;
386  BOARD_ITEM* b = pair.testItem->parent;
387 
388  // store canonical order so we don't collide in both directions (a:b and b:a)
389  if( static_cast<void*>( a ) > static_cast<void*>( b ) )
390  std::swap( a, b );
391 
392  // don't report multiple collisions for compound or triangulated shapes
393  if( collidingCompounds.count( { a, b } ) )
394  continue;
395 
396  bool collisionDetected = false;
397 
398  if( !aVisitor( pair.layerPair, pair.refItem, pair.testItem, &collisionDetected ) )
399  break;
400 
401  if( collisionDetected )
402  collidingCompounds[ { a, b } ] = 1;
403  }
404 
405  return 0;
406  }
407 
412  size_t size() const
413  {
414  return m_count;
415  }
416 
417  bool empty() const
418  {
419  return m_count == 0;
420  }
421 
422  using iterator = typename drc_rtree::Iterator;
423 
432  struct DRC_LAYER
433  {
434  DRC_LAYER( drc_rtree* aTree ) : layer_tree( aTree )
435  {
436  m_rect = { { INT_MIN, INT_MIN }, { INT_MAX, INT_MAX } };
437  };
438 
439  DRC_LAYER( drc_rtree* aTree, const EDA_RECT aRect ) : layer_tree( aTree )
440  {
441  m_rect = { { aRect.GetX(), aRect.GetY() },
442  { aRect.GetRight(), aRect.GetBottom() } };
443  };
444 
445  drc_rtree::Rect m_rect;
447 
449  {
450  return layer_tree->begin( m_rect );
451  }
452 
454  {
455  return layer_tree->end( m_rect );
456  }
457  };
458 
460  {
461  return DRC_LAYER( m_tree[int( aLayer )] );
462  }
463 
464  DRC_LAYER Overlapping( PCB_LAYER_ID aLayer, const wxPoint& aPoint, int aAccuracy = 0 ) const
465  {
466  EDA_RECT rect( aPoint, wxSize( 0, 0 ) );
467  rect.Inflate( aAccuracy );
468  return DRC_LAYER( m_tree[int( aLayer )], rect );
469  }
470 
471  DRC_LAYER Overlapping( PCB_LAYER_ID aLayer, const EDA_RECT& aRect ) const
472  {
473  return DRC_LAYER( m_tree[int( aLayer )], aRect );
474  }
475 
476 
477 private:
479  size_t m_count;
480 };
481 
482 
483 #endif /* DRC_RTREE_H_ */
iterator begin()
Definition: drc_rtree.h:448
ITEM_WITH_SHAPE(BOARD_ITEM *aParent, SHAPE *aShape, std::shared_ptr< SHAPE > aParentShape=nullptr)
Definition: drc_rtree.h:52
class FP_TEXT, text in a footprint
Definition: typeinfo.h:92
coord_type GetX() const
Definition: box2.h:180
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:80
int GetX() const
Definition: eda_rect.h:98
bool empty() const
Definition: drc_rtree.h:417
drc_rtree * layer_tree
Definition: drc_rtree.h:446
coord_type GetRight() const
Definition: box2.h:189
LAYER_PAIR layerPair
Definition: drc_rtree.h:327
DRC_LAYER(drc_rtree *aTree)
Definition: drc_rtree.h:434
std::pair< PCB_LAYER_ID, PCB_LAYER_ID > LAYER_PAIR
Definition: drc_rtree.h:319
coord_type GetBottom() const
Definition: box2.h:190
drc_rtree::Rect m_rect
Definition: drc_rtree.h:443
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
virtual const BOX2I BBox(int aClearance=0) const =0
Compute a bounding box of the shape, with a margin of aClearance a collision.
DRC_LAYER Overlapping(PCB_LAYER_ID aLayer, const EDA_RECT &aRect) const
Definition: drc_rtree.h:471
RTree< ITEM_WITH_SHAPE *, int, 2, double > drc_rtree
Definition: drc_rtree.h:66
int GetBottom() const
Definition: eda_rect.h:114
DRC_LAYER(drc_rtree *aTree, const EDA_RECT aRect)
Definition: drc_rtree.h:439
The DRC_LAYER struct provides a layer-specific auto-range iterator to the RTree.
Definition: drc_rtree.h:432
DRC_LAYER Overlapping(PCB_LAYER_ID aLayer, const wxPoint &aPoint, int aAccuracy=0) const
Definition: drc_rtree.h:464
PCB_LAYER_ID
A quick note on 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:131
size_t size() const
Returns the number of items in the tree.
Definition: drc_rtree.h:412
drc_rtree * m_tree[PCB_LAYER_ID_COUNT]
Definition: drc_rtree.h:478
std::shared_ptr< SHAPE > parentShape
Definition: drc_rtree.h:61
ITEM_WITH_SHAPE * testItem
Definition: drc_rtree.h:331
ITEM_WITH_SHAPE * refItem
Definition: drc_rtree.h:330
int GetRight() const
Definition: eda_rect.h:111
size_t m_count
Definition: drc_rtree.h:479
void clear()
Function RemoveAll() Removes all items from the RTree.
Definition: drc_rtree.h:123
static LSET AllLayersMask()
Definition: lset.cpp:787
An abstract shape on 2D plane.
Definition: shape.h:116
coord_type GetY() const
Definition: box2.h:181
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Function Inflate inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:292
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:334
DRC_RTREE()
Definition: drc_rtree.h:70
PAIR_INFO(LAYER_PAIR aPair, ITEM_WITH_SHAPE *aRef, ITEM_WITH_SHAPE *aTest)
Definition: drc_rtree.h:323
Handle the component boundary box.
Definition: eda_rect.h:42
int GetY() const
Definition: eda_rect.h:99
void Insert(BOARD_ITEM *aItem, PCB_LAYER_ID aLayer, int aWorstClearance=0)
Function Insert() Inserts an item into the tree on a particular layer with an optional worst clearanc...
Definition: drc_rtree.h:88
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:157
virtual const EDA_RECT GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition: eda_item.cpp:75
DRC_RTREE - Implements an R-tree for fast spatial and layer indexing of connectable items.
Definition: drc_rtree.h:45
typename drc_rtree::Iterator iterator
Definition: drc_rtree.h:422
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:241
bool QueryColliding(EDA_RECT aBox, SHAPE *aRefShape, PCB_LAYER_ID aLayer) const
Quicker version of above that just reports a raw yes/no.
Definition: drc_rtree.h:296
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition: lset.cpp:905
EDA_RECT & Inflate(wxCoord dx, wxCoord dy)
Inflate the rectangle horizontally by dx and vertically by dy.
Definition: eda_rect.cpp:364
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:113
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:168
DRC_LAYER OnLayer(PCB_LAYER_ID aLayer) const
Definition: drc_rtree.h:459
~DRC_RTREE()
Definition: drc_rtree.h:78