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