KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_test_provider_connection_width.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 The KiCad Developers.
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 3
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/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 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
24#include <algorithm>
25#include <atomic>
26#include <deque>
27#include <optional>
28#include <utility>
29
30#include <wx/debug.h>
31
32#include <board.h>
35#include <drc/drc_item.h>
37#include <drc/drc_rtree.h>
39#include <footprint.h>
40#include <geometry/seg.h>
42#include <geometry/vertex_set.h>
43#include <math/box2.h>
44#include <math/vector2d.h>
45#include <pcb_shape.h>
46#include <progress_reporter.h>
47#include <thread_pool.h>
48#include <pcb_track.h>
49#include <pad.h>
50#include <zone.h>
51#include <advanced_config.h>
52
53/*
54 Checks for copper connections that are less than the specified minimum width
55
56 Errors generated:
57 - DRCE_CONNECTION_WIDTH
58*/
59
61{
64
65 bool operator==(const NETCODE_LAYER_CACHE_KEY& other) const
66 {
67 return Netcode == other.Netcode && Layer == other.Layer;
68 }
69};
70
71
72namespace std
73{
74 template <>
76 {
77 std::size_t operator()( const NETCODE_LAYER_CACHE_KEY& k ) const
78 {
79 constexpr std::size_t prime = 19937;
80
81 return hash<int>()( k.Netcode ) ^ ( hash<int>()( k.Layer ) * prime );
82 }
83 };
84}
85
86
88{
89public:
92
94
95 virtual bool Run() override;
96
97 virtual const wxString GetName() const override { return wxT( "copper width" ); };
98
99private:
100 wxString layerDesc( PCB_LAYER_ID aLayer );
101};
102
103
105{
106public:
107 POLYGON_TEST( int aLimit ) :
108 VERTEX_SET( 0 ),
109 m_limit( aLimit )
110 {};
111
112 bool FindPairs( const SHAPE_LINE_CHAIN& aPoly )
113 {
114 m_hits.clear();
115 m_vertices.clear();
116 m_bbox = aPoly.BBox();
117
118 createList( aPoly );
119
120 m_vertices.front().updateList();
121
122 VERTEX* p = m_vertices.front().next;
123 std::set<VERTEX*> all_hits;
124
125 while( p != &m_vertices.front() )
126 {
127 VERTEX* match = nullptr;
128
129 // Only run the expensive search if we don't already have a match for the point
130 if( ( all_hits.empty() || all_hits.count( p ) == 0 ) && ( match = getKink( p ) ) != nullptr )
131 {
132 if( !all_hits.count( match ) && m_hits.emplace( p->i, match->i ).second )
133 {
134 all_hits.emplace( p );
135 all_hits.emplace( match );
136 all_hits.emplace( p->next );
137 all_hits.emplace( p->prev );
138 all_hits.emplace( match->next );
139 all_hits.emplace( match->prev );
140 }
141 }
142
143 p = p->next;
144 }
145
146 return !m_hits.empty();
147 }
148
149 std::set<std::pair<int, int>>& GetVertices()
150 {
151 return m_hits;
152 }
153
162 bool isSubstantial( const VERTEX* aA, const VERTEX* aB ) const
163 {
164 bool x_change = false;
165 bool y_change = false;
166
167 // This is a failsafe in case of invalid lists. Never check
168 // more than the total number of points in m_vertices
169 size_t checked = 0;
170 size_t total_pts = m_vertices.size();
171
172 const VERTEX* p0 = aA;
173 const VERTEX* p = getNextOutlineVertex( p0 );
174
175 while( !same_point( p, aB ) // We've reached the other inflection point
176 && !same_point( p, aA ) // We've gone around in a circle
177 && checked < total_pts // Fail-safe for invalid lists
178 && !( x_change && y_change ) ) // We've found a substantial change in both directions
179 {
180 double diff_x = std::abs( p->x - p0->x );
181 double diff_y = std::abs( p->y - p0->y );
182
183 // Check for a substantial change in the x or y direction
184 // This is measured by the set value of the minimum connection width
185 if( diff_x > m_limit )
186 x_change = true;
187
188 if( diff_y > m_limit )
189 y_change = true;
190
191 p = getNextOutlineVertex( p );
192
193 ++checked;
194 }
195
196 wxCHECK_MSG( checked < total_pts, false, wxT( "Invalid polygon detected. Missing points to check" ) );
197
198 if( !same_point( p, aA ) && ( !x_change || !y_change ) )
199 return false;
200
201 p = getPrevOutlineVertex( p0 );
202
203 x_change = false;
204 y_change = false;
205 checked = 0;
206
207 while( !same_point( p, aB ) // We've reached the other inflection point
208 && !same_point( p, aA ) // We've gone around in a circle
209 && checked < total_pts // Fail-safe for invalid lists
210 && !( x_change && y_change ) ) // We've found a substantial change in both directions
211 {
212 double diff_x = std::abs( p->x - p0->x );
213 double diff_y = std::abs( p->y - p0->y );
214
215 // Floating point zeros can have a negative sign, so we need to
216 // ensure that only substantive diversions count for a direction
217 // change
218 if( diff_x > m_limit )
219 x_change = true;
220
221 if( diff_y > m_limit )
222 y_change = true;
223
224 p = getPrevOutlineVertex( p );
225
226 ++checked;
227 }
228
229 wxCHECK_MSG( checked < total_pts, false, wxT( "Invalid polygon detected. Missing points to check" ) );
230
231 return ( same_point( p, aA ) || ( x_change && y_change ) );
232 }
233
234 VERTEX* getKink( VERTEX* aPt ) const
235 {
236 // The point needs to be at a concave surface
237 if( locallyInside( aPt->prev, aPt->next ) )
238 return nullptr;
239
240 // z-order range for the current point ± limit bounding box
241 const uint32_t maxZ = zOrder( aPt->x + m_limit, aPt->y + m_limit );
242 const uint32_t minZ = zOrder( aPt->x - m_limit, aPt->y - m_limit );
243 const SEG::ecoord limit2 = SEG::Square( m_limit );
244
245 // first look for points in increasing z-order
246 VERTEX* p = aPt->nextZ;
247 SEG::ecoord min_dist = std::numeric_limits<SEG::ecoord>::max();
248 VERTEX* retval = nullptr;
249
250 while( p && p->z <= maxZ )
251 {
252 int delta_i = std::abs( p->i - aPt->i );
253 VECTOR2D diff( p->x - aPt->x, p->y - aPt->y );
254 SEG::ecoord dist2 = diff.SquaredEuclideanNorm();
255
256 if( delta_i > 1 && dist2 < limit2 && dist2 < min_dist && dist2 > 0
257 && locallyInside( p, aPt ) && isSubstantial( p, aPt ) && isSubstantial( aPt, p ) )
258 {
259 min_dist = dist2;
260 retval = p;
261 }
262
263 p = p->nextZ;
264 }
265
266 p = aPt->prevZ;
267
268 while( p && p->z >= minZ )
269 {
270 int delta_i = std::abs( p->i - aPt->i );
271 VECTOR2D diff( p->x - aPt->x, p->y - aPt->y );
272 SEG::ecoord dist2 = diff.SquaredEuclideanNorm();
273
274 if( delta_i > 1 && dist2 < limit2 && dist2 < min_dist && dist2 > 0
275 && locallyInside( p, aPt ) && isSubstantial( p, aPt ) && isSubstantial( aPt, p ) )
276 {
277 min_dist = dist2;
278 retval = p;
279 }
280
281 p = p->prevZ;
282 }
283 return retval;
284 }
285
286private:
288 std::set<std::pair<int, int>> m_hits;
289};
290
291
293{
294 return wxString::Format( wxT( "(%s)" ), m_drcEngine->GetBoard()->GetLayerName( aLayer ) );
295}
296
297
299{
300 if( m_drcEngine->IsErrorLimitExceeded( DRCE_CONNECTION_WIDTH ) )
301 {
302 REPORT_AUX( wxT( "Connection width violations ignored. Tests not run." ) );
303 return true; // Continue with other tests
304 }
305
306 if( !reportPhase( _( "Checking nets for minimum connection width..." ) ) )
307 return false; // DRC cancelled
308
309 BOARD* board = m_drcEngine->GetBoard();
310 int epsilon = board->GetDesignSettings().GetDRCEpsilon();
311
312 // Zone knockouts can be approximated, and always have extra clearance built in
313 epsilon += board->GetDesignSettings().m_MaxError + pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_ExtraClearance );
314
315 // A neck in a zone fill can be between two knockouts. In this case it will be epsilon smaller
316 // on -each- side.
317 epsilon *= 2;
318
319 /*
320 * Build a set of distinct minWidths specified by various DRC rules. We'll run a test for
321 * each distinct minWidth, and then decide if any copper which failed that minWidth actually
322 * was required to abide by it or not.
323 */
324 std::set<int> distinctMinWidths = m_drcEngine->QueryDistinctConstraints( CONNECTION_WIDTH_CONSTRAINT );
325
326 if( m_drcEngine->IsCancelled() )
327 return false; // DRC cancelled
328
329 struct ITEMS_POLY
330 {
331 std::set<BOARD_ITEM*> Items;
332 SHAPE_POLY_SET Poly;
333 };
334
335 std::unordered_map<NETCODE_LAYER_CACHE_KEY, ITEMS_POLY> dataset;
336 std::atomic<size_t> done( 1 );
337
338 auto calc_effort =
339 [&]( const std::set<BOARD_ITEM*>& items, PCB_LAYER_ID aLayer ) -> size_t
340 {
341 size_t effort = 0;
342
343 for( BOARD_ITEM* item : items )
344 {
345 if( item->Type() == PCB_ZONE_T )
346 {
347 ZONE* zone = static_cast<ZONE*>( item );
348 effort += zone->GetFilledPolysList( aLayer )->FullPointCount();
349 }
350 else
351 {
352 effort += 4;
353 }
354 }
355
356 return effort;
357 };
358
359 /*
360 * For each net, on each layer, build a polygonSet which contains all the copper associated
361 * with that net on that layer.
362 */
363 auto build_netlayer_polys =
364 [&]( int aNetcode, const PCB_LAYER_ID aLayer ) -> size_t
365 {
366 if( m_drcEngine->IsCancelled() )
367 return 0;
368
369 ITEMS_POLY& itemsPoly = dataset[ { aNetcode, aLayer } ];
370
371 for( BOARD_ITEM* item : itemsPoly.Items )
372 item->TransformShapeToPolygon( itemsPoly.Poly, aLayer, 0, ARC_HIGH_DEF, ERROR_OUTSIDE );
373
374 itemsPoly.Poly.Fracture();
375
376 done.fetch_add( calc_effort( itemsPoly.Items, aLayer ) );
377
378 return 1;
379 };
380
381 /*
382 * Examine all necks in a given polygonSet which fail a given minWidth.
383 */
384 auto min_checker =
385 [&]( const ITEMS_POLY& aItemsPoly, const PCB_LAYER_ID aLayer, int aMinWidth ) -> size_t
386 {
387 if( m_drcEngine->IsCancelled() )
388 return 0;
389
390 int testWidth = aMinWidth - epsilon;
391
392 POLYGON_TEST test( testWidth );
393
394 for( int ii = 0; ii < aItemsPoly.Poly.OutlineCount(); ++ii )
395 {
396 const SHAPE_LINE_CHAIN& chain = aItemsPoly.Poly.COutline( ii );
397
398 test.FindPairs( chain );
399 auto& ret = test.GetVertices();
400
401 for( const std::pair<int, int>& pt : ret )
402 {
403 /*
404 * We've found a neck that fails the given aMinWidth. We now need to know
405 * if the objects the produced the copper at this location are required to
406 * abide by said aMinWidth or not. (If so, we have a violation.)
407 *
408 * We find the contributingItems by hit-testing at the choke point (the
409 * centre point of the neck), and then run the rules engine on those
410 * contributingItems. If the reported constraint matches aMinWidth, then
411 * we've got a violation.
412 */
413 SEG span( chain.CPoint( pt.first ), chain.CPoint( pt.second ) );
414 VECTOR2I location = ( span.A + span.B ) / 2;
415 int dist = ( span.A - span.B ).EuclideanNorm();
416
417 std::vector<BOARD_ITEM*> contributingItems;
418
419 for( BOARD_ITEM* item : board->m_CopperItemRTreeCache->GetObjectsAt( location, aLayer,
420 aMinWidth ) )
421 {
422 if( item->HitTest( location, aMinWidth ) )
423 contributingItems.push_back( item );
424 }
425
426 for( auto& [ zone, rtree ] : board->m_CopperZoneRTreeCache )
427 {
428 if( !rtree.get() )
429 continue;
430
431 auto obj_list = rtree->GetObjectsAt( location, aLayer, aMinWidth );
432
433 if( !obj_list.empty() && zone->HitTestFilledArea( aLayer, location, aMinWidth ) )
434 contributingItems.push_back( zone );
435 }
436
437 if( !contributingItems.empty() )
438 {
439 BOARD_ITEM* item1 = contributingItems[0];
440 BOARD_ITEM* item2 = contributingItems.size() > 1 ? contributingItems[1]
441 : nullptr;
443 item1, item2, aLayer );
444
445 if( c.Value().Min() == aMinWidth )
446 {
448 wxString msg;
449
450 msg = formatMsg( _( "(%s minimum connection width %s; actual %s)" ),
451 c.GetName(),
452 c.Value().Min(),
453 dist );
454
455 msg += wxS( " " ) + layerDesc( aLayer );
456
457 drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg );
458 drce->SetViolatingRule( c.GetParentRule() );
459
460 for( BOARD_ITEM* item : contributingItems )
461 drce->AddItem( item );
462
463 reportViolation( drce, location, aLayer );
464 }
465 }
466 }
467 }
468
469 done.fetch_add( calc_effort( aItemsPoly.Items, aLayer ) );
470
471 return 1;
472 };
473
474 for( PCB_LAYER_ID layer : LSET::AllCuMask( board->GetCopperLayerCount() ) )
475 {
476 for( ZONE* zone : board->m_DRCCopperZones )
477 {
478 if( !zone->GetIsRuleArea() && zone->IsOnLayer( layer ) )
479 dataset[ { zone->GetNetCode(), layer } ].Items.emplace( zone );
480 }
481
482 for( PCB_TRACK* track : board->Tracks() )
483 {
484 if( PCB_VIA* via = dynamic_cast<PCB_VIA*>( track ) )
485 {
486 if( via->FlashLayer( static_cast<int>( layer ) ) )
487 dataset[ { via->GetNetCode(), layer } ].Items.emplace( via );
488 }
489 else if( track->IsOnLayer( layer ) )
490 {
491 dataset[ { track->GetNetCode(), layer } ].Items.emplace( track );
492 }
493 }
494
495 for( FOOTPRINT* fp : board->Footprints() )
496 {
497 for( PAD* pad : fp->Pads() )
498 {
499 if( pad->FlashLayer( static_cast<int>( layer ) ) )
500 dataset[ { pad->GetNetCode(), layer } ].Items.emplace( pad );
501 }
502
503 // Footprint zones are also in the m_DRCCopperZones cache
504 }
505 }
506
508 size_t total_effort = 0;
509
510 for( const auto& [ netLayer, itemsPoly ] : dataset )
511 total_effort += calc_effort( itemsPoly.Items, netLayer.Layer );
512
513 total_effort += std::max( (size_t) 1, total_effort ) * distinctMinWidths.size();
514
515 std::vector<std::future<size_t>> returns;
516 returns.reserve( dataset.size() );
517
518 for( const auto& [ netLayer, itemsPoly ] : dataset )
519 {
520 int netcode = netLayer.Netcode;
521 PCB_LAYER_ID layer = netLayer.Layer;
522 returns.emplace_back( tp.submit_task(
523 [&, netcode, layer]()
524 {
525 return build_netlayer_polys( netcode, layer );
526 } ) );
527 }
528
529 for( auto& ret : returns )
530 {
531 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
532
533 while( status != std::future_status::ready )
534 {
535 reportProgress( done, total_effort );
536 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
537 }
538 }
539
540 returns.clear();
541 returns.reserve( dataset.size() * distinctMinWidths.size() );
542
543 for( const auto& [ netLayer, itemsPoly ] : dataset )
544 {
545 for( int minWidth : distinctMinWidths )
546 {
547 if( minWidth - epsilon <= 0 )
548 continue;
549
550 returns.emplace_back( tp.submit_task(
551 [min_checker, &itemsPoly, &netLayer, minWidth]()
552 {
553 return min_checker( itemsPoly, netLayer.Layer, minWidth );
554 } ) );
555 }
556 }
557
558 for( auto& ret : returns )
559 {
560 std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
561
562 while( status != std::future_status::ready )
563 {
564 reportProgress( done, total_effort );
565 status = ret.wait_for( std::chrono::milliseconds( 250 ) );
566 }
567 }
568
569 return true;
570}
571
572
573namespace detail
574{
576}
@ ERROR_OUTSIDE
constexpr int ARC_HIGH_DEF
Definition base_units.h:129
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:112
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
int GetDRCEpsilon() const
Return an epsilon which accounts for rounding errors, etc.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:79
virtual void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the item shape to a closed polygon.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:317
std::vector< ZONE * > m_DRCCopperZones
Definition board.h:1381
int GetCopperLayerCount() const
Definition board.cpp:875
const FOOTPRINTS & Footprints() const
Definition board.h:358
const TRACKS & Tracks() const
Definition board.h:356
std::shared_ptr< DRC_RTREE > m_CopperItemRTreeCache
Definition board.h:1375
std::unordered_map< ZONE *, std::unique_ptr< DRC_RTREE > > m_CopperZoneRTreeCache
Definition board.h:1374
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1040
wxString GetName() const
Definition drc_rule.h:170
MINOPTMAX< int > & Value()
Definition drc_rule.h:163
DRC_RULE * GetParentRule() const
Definition drc_rule.h:166
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:381
std::unordered_set< BOARD_ITEM * > GetObjectsAt(const VECTOR2I &aPt, PCB_LAYER_ID aLayer, int aClearance=0)
Gets the BOARD_ITEMs that overlap the specified point/layer.
Definition drc_rtree.h:421
virtual ~DRC_TEST_PROVIDER_CONNECTION_WIDTH()=default
virtual const wxString GetName() const override
virtual bool Run() override
Run this provider against the given PCB with configured options (if any).
virtual bool reportPhase(const wxString &aStageName)
virtual void reportViolation(std::shared_ptr< DRC_ITEM > &item, const VECTOR2I &aMarkerPos, int aMarkerLayer, const std::vector< PCB_SHAPE > &aShapes={})
wxString formatMsg(const wxString &aFormatString, const wxString &aSource, double aConstraint, double aActual, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
virtual bool reportProgress(size_t aCount, size_t aSize, size_t aDelta=1)
static LSET AllCuMask(int aCuLayerCount)
Return a mask holding the requested number of Cu PCB_LAYER_IDs.
Definition lset.cpp:582
T Min() const
Definition minoptmax.h:33
Definition pad.h:54
bool isSubstantial(const VERTEX *aA, const VERTEX *aB) const
Checks to see if there is a "substantial" protrusion in each polygon produced by the cut from aA to a...
VERTEX * getKink(VERTEX *aPt) const
bool FindPairs(const SHAPE_LINE_CHAIN &aPoly)
std::set< std::pair< int, int > > & GetVertices()
std::set< std::pair< int, int > > m_hits
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I::extended_type ecoord
Definition seg.h:44
VECTOR2I B
Definition seg.h:50
static SEG::ecoord Square(int a)
Definition seg.h:123
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Represent a set of closed polygons.
int FullPointCount() const
Return the number of points in the shape poly set.
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:307
std::deque< VERTEX > m_vertices
Definition vertex_set.h:343
friend class VERTEX
Definition vertex_set.h:255
VERTEX * createList(const SHAPE_LINE_CHAIN &points, VERTEX *aTail=nullptr, void *aUserData=nullptr)
Create a list of vertices from a line chain.
bool locallyInside(const VERTEX *a, const VERTEX *b) const
Check whether the segment from vertex a -> vertex b is inside the polygon around the immediate area o...
BOX2I m_bbox
Definition vertex_set.h:342
VERTEX * getPrevOutlineVertex(const VERTEX *aPt) const
Get the previous vertex in the outline, avoiding steiner points and points that overlap with splits.
VERTEX_SET(int aSimplificationLevel)
Definition vertex_set.h:258
VERTEX * getNextOutlineVertex(const VERTEX *aPt) const
Get the next vertex in the outline, avoiding steiner points and points that overlap with splits.
uint32_t zOrder(const double aX, const double aY) const
Note that while the inputs are doubles, these are scaled by the size of the bounding box to fit into ...
bool same_point(const VERTEX *aA, const VERTEX *aB) const
Check if two vertices are at the same point.
const double x
Definition vertex_set.h:235
VERTEX * next
Definition vertex_set.h:241
VERTEX * prevZ
Definition vertex_set.h:247
VERTEX * nextZ
Definition vertex_set.h:248
VERTEX * prev
Definition vertex_set.h:240
const int i
Definition vertex_set.h:234
uint32_t z
Definition vertex_set.h:244
const double y
Definition vertex_set.h:236
Handle a list of polygons defining a copper zone.
Definition zone.h:74
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:704
const std::shared_ptr< SHAPE_POLY_SET > & GetFilledPolysList(PCB_LAYER_ID aLayer) const
Definition zone.h:600
virtual bool IsOnLayer(PCB_LAYER_ID) const override
Test to see if this object is on the given layer.
Definition zone.cpp:615
@ DRCE_CONNECTION_WIDTH
Definition drc_item.h:59
@ CONNECTION_WIDTH_CONSTRAINT
Definition drc_rule.h:80
#define REPORT_AUX(s)
#define _(s)
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
static DRC_REGISTER_TEST_PROVIDER< DRC_TEST_PROVIDER_ANNULAR_WIDTH > dummy
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
const double epsilon
bool operator==(const NETCODE_LAYER_CACHE_KEY &other) const
std::size_t operator()(const NETCODE_LAYER_CACHE_KEY &k) const
const SHAPE_LINE_CHAIN chain
VECTOR2I location
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::thread_pool< 0 > thread_pool
Definition thread_pool.h:31
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:107
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694