KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_creepage_utils.cpp
Go to the documentation of this file.
1/*
2 * Copyright The KiCad Developers.
3 * Copyright (C) 2024 Fabien Corona f.corona<at>laposte.net
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
20
23#include <pcb_track.h>
24#include <thread_pool.h>
25
26
27void BuildCreepageBoardEdges( BOARD& aBoard, std::vector<BOARD_ITEM*>& aVector,
28 std::vector<std::unique_ptr<PCB_SHAPE>>& aOwned,
29 const std::set<const BOARD_ITEM*>* aExclude )
30{
31 const int errorMax = aBoard.GetDesignSettings().m_MaxError;
32
33 auto excluded =
34 [&]( const BOARD_ITEM* aItem ) -> bool
35 {
36 if( !aExclude || !aItem )
37 return false;
38
39 if( aExclude->count( aItem ) )
40 return true;
41
42 const BOARD_ITEM* parent = dynamic_cast<const BOARD_ITEM*>( aItem->GetParent() );
43
44 return parent && aExclude->count( parent );
45 };
46
47 // The creepage graph only handles SEGMENT/ARC/CIRCLE/RECTANGLE/POLY, so Bezier curves must be
48 // flattened to segments or they are silently ignored and creepage paths pass through them
49 auto addEdgeDrawing =
50 [&]( BOARD_ITEM* aDrawing )
51 {
52 if( !aDrawing || !aDrawing->IsOnLayer( Edge_Cuts ) )
53 return;
54
55 if( excluded( aDrawing ) )
56 return;
57
58 // Downstream code static_casts every item in m_boardEdge to PCB_SHAPE, so non-shape items
59 // (text, dimensions, ...) on Edge.Cuts must not enter the graph
60 PCB_SHAPE* shape = dynamic_cast<PCB_SHAPE*>( aDrawing );
61
62 if( !shape )
63 return;
64
65 if( shape->GetShape() != SHAPE_T::BEZIER )
66 {
67 aVector.push_back( shape );
68 return;
69 }
70
71 shape->RebuildBezierToSegmentsPointsList( errorMax );
72 const std::vector<VECTOR2I>& pts = shape->GetBezierPoints();
73
74 for( size_t i = 1; i < pts.size(); ++i )
75 {
76 if( pts[i - 1] == pts[i] )
77 continue;
78
79 auto seg = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
80 seg->SetStart( pts[i - 1] );
81 seg->SetEnd( pts[i] );
82 aVector.push_back( seg.get() );
83 aOwned.push_back( std::move( seg ) );
84 }
85 };
86
87 for( BOARD_ITEM* drawing : aBoard.Drawings() )
88 addEdgeDrawing( drawing );
89
90 for( FOOTPRINT* fp : aBoard.Footprints() )
91 {
92 if( !fp )
93 continue;
94
95 for( BOARD_ITEM* drawing : fp->GraphicalItems() )
96 addEdgeDrawing( drawing );
97 }
98
99 for( const PAD* p : aBoard.GetPads() )
100 {
101 if( !p || p->GetAttribute() != PAD_ATTRIB::NPTH )
102 continue;
103
104 if( excluded( p ) )
105 continue;
106
107 // TODO: handle backdrilling and post-machining
108
109 std::shared_ptr<SHAPE_SEGMENT> hole = p->GetEffectiveHoleShape();
110
111 if( !hole )
112 continue;
113
114 VECTOR2I ptA = hole->GetSeg().A;
115 VECTOR2I ptB = hole->GetSeg().B;
116 int radius = hole->GetWidth() / 2;
117
118 if( ptA == ptB )
119 {
120 auto s = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::CIRCLE );
121 s->SetRadius( radius );
122 s->SetPosition( ptA );
123 aVector.push_back( s.get() );
124 aOwned.push_back( std::move( s ) );
125 }
126 else
127 {
128 // Oblong slot outline as two straight sides and two semicircular end caps
129 VECTOR2I axis = ptB - ptA;
130 VECTOR2I perp = axis.Perpendicular().Resize( radius );
131
132 auto seg1 = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
133 seg1->SetStart( ptA + perp );
134 seg1->SetEnd( ptB + perp );
135 aVector.push_back( seg1.get() );
136 aOwned.push_back( std::move( seg1 ) );
137
138 auto seg2 = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::SEGMENT );
139 seg2->SetStart( ptA - perp );
140 seg2->SetEnd( ptB - perp );
141 aVector.push_back( seg2.get() );
142 aOwned.push_back( std::move( seg2 ) );
143
144 VECTOR2I midA = ptA - axis.Resize( radius );
145 auto arcA = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::ARC );
146 arcA->SetArcGeometry( ptA + perp, midA, ptA - perp );
147 aVector.push_back( arcA.get() );
148 aOwned.push_back( std::move( arcA ) );
149
150 VECTOR2I midB = ptB + axis.Resize( radius );
151 auto arcB = std::make_unique<PCB_SHAPE>( nullptr, SHAPE_T::ARC );
152 arcB->SetArcGeometry( ptB - perp, midB, ptB + perp );
153 aVector.push_back( arcB.get() );
154 aOwned.push_back( std::move( arcB ) );
155 }
156 }
157}
158
159
160bool segmentIntersectsArc( const VECTOR2I& p1, const VECTOR2I& p2, const VECTOR2I& center,
161 double radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle,
162 std::vector<VECTOR2I>* aIntersectionPoints = nullptr )
163{
164 SEG segment( p1, p2 );
165 VECTOR2I startPoint( radius * cos( startAngle.AsRadians() ), radius * sin( startAngle.AsRadians() ) );
166 SHAPE_ARC arc( center, startPoint + center, endAngle - startAngle );
167
168 INTERSECTABLE_GEOM geom1 = segment;
169 INTERSECTABLE_GEOM geom2 = arc;
170
171 std::vector<VECTOR2I> rawPoints;
172 INTERSECTION_VISITOR visitor( geom2, rawPoints );
173 std::visit( visitor, geom1 );
174
175 // A path is allowed to end on the arc, so an intersection at either endpoint is a touch,
176 // not a crossing. Only interior crossings count. Tolerance absorbs solver rounding.
177 std::vector<VECTOR2I> filtered;
178
179 const VECTOR2I::extended_type tolerance = 50;
180 const VECTOR2I::extended_type toleranceSq = tolerance * tolerance;
181
182 auto coincident =
183 [&]( const VECTOR2I& a, const VECTOR2I& b )
184 {
185 return ( a - b ).SquaredEuclideanNorm() <= toleranceSq;
186 };
187
188 for( const VECTOR2I& ip : rawPoints )
189 {
190 if( !coincident( ip, p1 ) && !coincident( ip, p2 ) )
191 filtered.push_back( ip );
192 }
193
194 if( aIntersectionPoints )
195 {
196 for( const VECTOR2I& ip : filtered )
197 aIntersectionPoints->push_back( ip );
198 }
199
200 return !filtered.empty();
201}
202
203
204//Check if line segments 'p1q1' and 'p2q2' intersect, excluding endpoint overlap
205
206bool segments_intersect( const VECTOR2I& p1, const VECTOR2I& q1, const VECTOR2I& p2, const VECTOR2I& q2,
207 std::vector<VECTOR2I>& aIntersectionPoints )
208{
209 if( p1 == p2 || p1 == q2 || q1 == p2 || q1 == q2 )
210 return false;
211
212 SEG segment1( p1, q1 );
213 SEG segment2( p2, q2 );
214
215 INTERSECTABLE_GEOM geom1 = segment1;
216 INTERSECTABLE_GEOM geom2 = segment2;
217
218 size_t startCount = aIntersectionPoints.size();
219
220 INTERSECTION_VISITOR visitor( geom2, aIntersectionPoints );
221 std::visit( visitor, geom1 );
222
223 return aIntersectionPoints.size() > startCount;
224}
225
226
227bool compareShapes( const CREEP_SHAPE* a, const CREEP_SHAPE* b )
228{
229 if( !a )
230 return true;
231
232 if( !b )
233 return false;
234
235 if( a->GetType() != b->GetType() )
236 return a->GetType() < b->GetType();
237
238 if( a->GetType() == CREEP_SHAPE::TYPE::UNDEFINED )
239 return true;
240
241 if( a->GetPos() != b->GetPos() )
242 return a->GetPos() < b->GetPos();
243
244 if( a->GetType() == CREEP_SHAPE::TYPE::CIRCLE )
245 return a->GetRadius() < b->GetRadius();
246
247 return false;
248}
249
250
251bool areEquivalent( const CREEP_SHAPE* a, const CREEP_SHAPE* b )
252{
253 if( !a && !b )
254 return true;
255
256 if( !a || !b )
257 return false;
258
259 if( a->GetType() != b->GetType() )
260 return false;
261
262 if( a->GetType() == CREEP_SHAPE::TYPE::POINT_TYPE )
263 return a->GetPos() == b->GetPos();
264
265 if( a->GetType() == CREEP_SHAPE::TYPE::CIRCLE )
266 return a->GetPos() == b->GetPos() && ( a->GetRadius() == b->GetRadius() );
267
268 return false;
269}
270
271
272std::vector<PATH_CONNECTION> BE_SHAPE_POINT::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
273 double aMaxSquaredWeight ) const
274{
275 std::vector<PATH_CONNECTION> result;
276
277 double weight = ( this->GetPos() - aS2.GetPos() ).SquaredEuclideanNorm();
278
279 if( weight > aMaxSquaredWeight )
280 return result;
281
283 pc.a1 = this->GetPos();
284 pc.a2 = aS2.GetPos();
285 pc.weight = sqrt( weight );
286
287 result.push_back( pc );
288 return result;
289}
290
291
292std::vector<PATH_CONNECTION> BE_SHAPE_POINT::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
293 double aMaxSquaredWeight ) const
294{
295 std::vector<PATH_CONNECTION> result;
296 int radius = aS2.GetRadius();
297 VECTOR2I pointPos = this->GetPos();
298 VECTOR2I circleCenter = aS2.GetPos();
299
300 if( radius <= 0 )
301 return result;
302
303 double pointToCenterDistanceSquared = ( pointPos - circleCenter ).SquaredEuclideanNorm();
304 double weightSquared = pointToCenterDistanceSquared - (float) radius * (float) radius;
305
306 if( weightSquared > aMaxSquaredWeight )
307 return result;
308
309 VECTOR2D direction1 = VECTOR2D( pointPos.x - circleCenter.x, pointPos.y - circleCenter.y );
310 direction1 = direction1.Resize( 1 );
311
312 VECTOR2D direction2 = direction1.Perpendicular();
313
314 double radiusSquared = double( radius ) * double( radius );
315
316 double distance = sqrt( pointToCenterDistanceSquared );
317 double value1 = radiusSquared / distance;
318 double value2 = sqrt( radiusSquared - value1 * value1 );
319
320 VECTOR2D resultPoint;
321
323 pc.a1 = pointPos;
324 pc.weight = sqrt( weightSquared );
325
326 resultPoint = direction1 * value1 + direction2 * value2 + circleCenter;
327 pc.a2.x = int( resultPoint.x );
328 pc.a2.y = int( resultPoint.y );
329 result.push_back( pc );
330
331 resultPoint = direction1 * value1 - direction2 * value2 + circleCenter;
332 pc.a2.x = int( resultPoint.x );
333 pc.a2.y = int( resultPoint.y );
334 result.push_back( pc );
335
336 return result;
337}
338
339
340std::pair<bool, bool> BE_SHAPE_ARC::IsThereATangentPassingThroughPoint( const BE_SHAPE_POINT aPoint ) const
341{
342 std::pair<bool, bool> result;
343 double R = m_radius;
344
345 VECTOR2I newPoint = aPoint.GetPos() - m_pos;
346
347 if( newPoint.SquaredEuclideanNorm() <= R * R )
348 {
349 // If the point is inside the arc
350 result.first = false;
351 result.second = false;
352 return result;
353 }
354
355 EDA_ANGLE testAngle = AngleBetweenStartAndEnd( aPoint.GetPos() );
356
357 double startAngle = m_startAngle.AsRadians();
358 double endAngle = m_endAngle.AsRadians();
359 double pointAngle = testAngle.AsRadians();
360
361 bool greaterThan180 = ( m_endAngle - m_startAngle ) > EDA_ANGLE( 180 );
362 bool connectToEndPoint;
363
364 connectToEndPoint = ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y >= R );
365
366 if( greaterThan180 )
367 connectToEndPoint &= ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y <= R );
368
369 connectToEndPoint |= ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y <= R )
370 && ( pointAngle >= endAngle || pointAngle <= startAngle );
371
372 result.first = !connectToEndPoint;
373
374 connectToEndPoint = ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y >= R );
375
376 if( greaterThan180 )
377 connectToEndPoint &= ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y <= R );
378
379 connectToEndPoint |= ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y <= R )
380 && ( pointAngle >= endAngle || pointAngle <= startAngle );
381
382 result.second = !connectToEndPoint;
383 return result;
384}
385
386
387std::vector<PATH_CONNECTION> BE_SHAPE_POINT::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
388 double aMaxSquaredWeight ) const
389{
390 std::vector<PATH_CONNECTION> result;
391 VECTOR2I center = aS2.GetPos();
392 double radius = aS2.GetRadius();
393
394 // First path tries to connect to start point
395 // Second path tries to connect to end point
396 std::pair<bool, bool> behavesLikeCircle;
397 behavesLikeCircle = aS2.IsThereATangentPassingThroughPoint( *this );
398
399 if( behavesLikeCircle.first && behavesLikeCircle.second )
400 {
402 return this->Paths( csc, aMaxWeight, aMaxSquaredWeight );
403 }
404
405 if( behavesLikeCircle.first )
406 {
408 std::vector<PATH_CONNECTION> paths = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );
409
410 if( paths.size() > 1 ) // Point to circle creates either 0 or 2 connections
411 result.push_back( paths[1] );
412 }
413 else
414 {
415 BE_SHAPE_POINT csp1( aS2.GetStartPoint() );
416
417 for( const PATH_CONNECTION& pc : this->Paths( csp1, aMaxWeight, aMaxSquaredWeight ) )
418 result.push_back( pc );
419 }
420
421 if( behavesLikeCircle.second )
422 {
424 std::vector<PATH_CONNECTION> paths = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );
425
426 if( paths.size() > 1 ) // Point to circle creates either 0 or 2 connections
427 result.push_back( paths[0] );
428 }
429 else
430 {
431 BE_SHAPE_POINT csp1( aS2.GetEndPoint() );
432
433 for( const PATH_CONNECTION& pc : this->Paths( csp1, aMaxWeight, aMaxSquaredWeight ) )
434 result.push_back( pc );
435 }
436
437 return result;
438}
439
440std::vector<PATH_CONNECTION> BE_SHAPE_CIRCLE::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
441 double aMaxSquaredWeight ) const
442{
443 std::vector<PATH_CONNECTION> result;
444 VECTOR2I circleCenter = this->GetPos();
445 double circleRadius = this->GetRadius();
446 VECTOR2I arcCenter = aS2.GetPos();
447 double arcRadius = aS2.GetRadius();
448 EDA_ANGLE arcStartAngle = aS2.GetStartAngle();
449 EDA_ANGLE arcEndAngle = aS2.GetEndAngle();
450
451 double centerDistance = ( circleCenter - arcCenter ).EuclideanNorm();
452
453 if( centerDistance + arcRadius < circleRadius )
454 {
455 // The arc is inside the circle
456 return result;
457 }
458
459 BE_SHAPE_POINT csp1( aS2.GetStartPoint() );
460 BE_SHAPE_POINT csp2( aS2.GetEndPoint() );
461 BE_SHAPE_CIRCLE csc( arcCenter, arcRadius );
462
463 for( const PATH_CONNECTION& pc : this->Paths( csc, aMaxWeight, aMaxSquaredWeight ) )
464 {
465 EDA_ANGLE pointAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );
466
467 if( pointAngle <= aS2.GetEndAngle() )
468 result.push_back( pc );
469 }
470
471 if( result.size() == 4 )
472 {
473 // It behaved as a circle
474 return result;
475 }
476
477 for( const BE_SHAPE_POINT& csp : { csp1, csp2 } )
478 {
479 for( const PATH_CONNECTION& pc : this->Paths( csp, aMaxWeight, aMaxSquaredWeight ) )
480 {
481 if( !segmentIntersectsArc( pc.a1, pc.a2, arcCenter, arcRadius, arcStartAngle, arcEndAngle ) )
482 result.push_back( pc );
483 }
484 }
485
486 return result;
487}
488
489
490std::vector<PATH_CONNECTION> BE_SHAPE_ARC::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
491 double aMaxSquaredWeight ) const
492{
493 std::vector<PATH_CONNECTION> result;
494 VECTOR2I circleCenter = this->GetPos();
495 double circleRadius = this->GetRadius();
496 VECTOR2I arcCenter = aS2.GetPos();
497 double arcRadius = aS2.GetRadius();
498
499 double centerDistance = ( circleCenter - arcCenter ).EuclideanNorm();
500
501 if( centerDistance + arcRadius < circleRadius )
502 {
503 // The arc is inside the circle
504 return result;
505 }
506
507 BE_SHAPE_POINT csp1( aS2.GetStartPoint() );
508 BE_SHAPE_POINT csp2( aS2.GetEndPoint() );
509 BE_SHAPE_CIRCLE csc( arcCenter, arcRadius );
510
511
512 for( const PATH_CONNECTION& pc : this->Paths( BE_SHAPE_CIRCLE( aS2.GetPos(), aS2.GetRadius() ),
513 aMaxWeight, aMaxSquaredWeight ) )
514 {
515 EDA_ANGLE pointAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );
516
517 if( pointAngle <= aS2.GetEndAngle() )
518 result.push_back( pc );
519 }
520
521 for( const PATH_CONNECTION& pc : BE_SHAPE_CIRCLE( this->GetPos(), this->GetRadius() )
522 .Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
523 {
524 EDA_ANGLE pointAngle = this->AngleBetweenStartAndEnd( pc.a1 );
525
526 if( pointAngle <= this->GetEndAngle() )
527 result.push_back( pc );
528 }
529
530 return result;
531}
532
533
534std::vector<PATH_CONNECTION> BE_SHAPE_CIRCLE::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
535 double aMaxSquaredWeight ) const
536{
537 std::vector<PATH_CONNECTION> result;
538
539 VECTOR2I p1 = this->GetPos();
540 VECTOR2I p2 = aS2.GetPos();
541
542 VECTOR2D distSquared( double( ( p2 - p1 ).x ), double( ( p2 - p1 ).y ) );
543 double weightSquared = distSquared.SquaredEuclideanNorm();
544
545 double R1 = this->GetRadius();
546 double R2 = aS2.GetRadius();
547
548 double Rdiff = abs( R1 - R2 );
549 double Rsum = R1 + R2;
550
551 // "Straight" paths
552 double weightSquared1 = weightSquared - Rdiff * Rdiff;
553 // "Crossed" paths
554 double weightSquared2 = weightSquared - Rsum * Rsum;
555
556 if( weightSquared1 <= aMaxSquaredWeight )
557 {
558 VECTOR2D direction1 = VECTOR2D( p2.x - p1.x, p2.y - p1.y );
559 direction1 = direction1.Resize( 1 );
560 VECTOR2D direction2 = direction1.Perpendicular();
561
562 double D = sqrt( weightSquared );
563 double ratio1 = ( R1 - R2 ) / D;
564 double ratio2 = sqrt( 1 - ratio1 * ratio1 );
565
566
568 pc.weight = sqrt( weightSquared1 );
569
570 pc.a1 = p1 + direction1 * R1 * ratio1 + direction2 * R1 * ratio2;
571 pc.a2 = p2 + direction1 * R2 * ratio1 + direction2 * R2 * ratio2;
572
573 result.push_back( pc );
574
575 pc.a1 = p1 + direction1 * R1 * ratio1 - direction2 * R1 * ratio2;
576 pc.a2 = p2 + direction1 * R2 * ratio1 - direction2 * R2 * ratio2;
577
578 result.push_back( pc );
579 }
580 if( weightSquared2 <= aMaxSquaredWeight )
581 {
582 VECTOR2D direction1 = VECTOR2D( p2.x - p1.x, p2.y - p1.y );
583 direction1 = direction1.Resize( 1 );
584 VECTOR2D direction2 = direction1.Perpendicular();
585
586 double D = sqrt( weightSquared );
587 double ratio1 = ( R1 + R2 ) / D;
588 double ratio2 = sqrt( 1 - ratio1 * ratio1 );
589
590
592 pc.weight = sqrt( weightSquared2 );
593
594 pc.a1 = p1 + direction1 * R1 * ratio1 + direction2 * R1 * ratio2;
595 pc.a2 = p2 - direction1 * R2 * ratio1 - direction2 * R2 * ratio2;
596
597 result.push_back( pc );
598
599 pc.a1 = p1 + direction1 * R1 * ratio1 - direction2 * R1 * ratio2;
600 pc.a2 = p2 - direction1 * R2 * ratio1 + direction2 * R2 * ratio2;
601
602 result.push_back( pc );
603 }
604
605 return result;
606}
607
608
609void CREEPAGE_GRAPH::TransformCreepShapesToNodes( const std::vector<std::unique_ptr<CREEP_SHAPE>>& aShapes )
610{
611 for( const std::unique_ptr<CREEP_SHAPE>& shape : aShapes )
612 {
613 CREEP_SHAPE* p1 = shape.get();
614
615 if( !p1 )
616 continue;
617
618 switch( p1->GetType() )
619 {
620 case CREEP_SHAPE::TYPE::POINT_TYPE: AddNode( GRAPH_NODE::TYPE::POINT, p1, p1->GetPos() ); break;
621 case CREEP_SHAPE::TYPE::CIRCLE: AddNode( GRAPH_NODE::TYPE::CIRCLE, p1, p1->GetPos() ); break;
622 case CREEP_SHAPE::TYPE::ARC: AddNode( GRAPH_NODE::TYPE::ARC, p1, p1->GetPos() ); break;
623 default: break;
624 }
625 }
626}
627
629{
630 std::sort( m_shapeCollection.begin(), m_shapeCollection.end(),
631 []( const auto& a, const auto& b ) { return compareShapes( a.get(), b.get() ); } );
632 size_t kept = 0;
633
634 for( size_t i = 0; i < m_shapeCollection.size(); ++i )
635 {
636 if( !m_shapeCollection[i] )
637 continue;
638
639 // Keep the last equivalent shape: its parent metadata may differ from earlier shapes.
640 if( i + 1 < m_shapeCollection.size()
641 && areEquivalent( m_shapeCollection[i].get(), m_shapeCollection[i + 1].get() ) )
642 continue;
643
644 if( kept != i )
645 m_shapeCollection[kept] = std::move( m_shapeCollection[i] );
646
647 ++kept;
648 }
649
650 m_shapeCollection.resize( kept );
651}
652
654{
655 // Flag overlapping cutouts so the arc void check below only runs when needed.
656 std::vector<BOX2I> cutouts;
657
658 for( BOARD_ITEM* be : m_boardEdge )
659 {
660 PCB_SHAPE* s = static_cast<PCB_SHAPE*>( be );
661
662 if( s
664 || s->GetShape() == SHAPE_T::POLY ) )
665 {
666 cutouts.push_back( s->GetBoundingBox() );
667 }
668 }
669
670 for( size_t i = 0; i < cutouts.size() && !m_hasOverlappingCutouts; ++i )
671 {
672 for( size_t j = i + 1; j < cutouts.size(); ++j )
673 {
674 if( cutouts[i].Intersects( cutouts[j] ) && !cutouts[i].Contains( cutouts[j] )
675 && !cutouts[j].Contains( cutouts[i] ) )
676 {
678 break;
679 }
680 }
681 }
682
683 for( BOARD_ITEM* drawing : m_boardEdge )
684 {
685 PCB_SHAPE* d = dynamic_cast<PCB_SHAPE*>( drawing );
686
687 if( !d )
688 continue;
689
690 switch( d->GetShape() )
691 {
692 case SHAPE_T::SEGMENT:
693 {
694 auto a = std::make_unique<BE_SHAPE_POINT>( d->GetStart() );
695 a->SetParent( d );
696 m_shapeCollection.push_back( std::move( a ) );
697 a = std::make_unique<BE_SHAPE_POINT>( d->GetEnd() );
698 a->SetParent( d );
699 m_shapeCollection.push_back( std::move( a ) );
700 break;
701 }
702
704 {
705 int r = d->GetCornerRadius();
706
707 if( r > 0 )
708 {
709 // Rounded rectangle: decompose into arcs.
710 // Normalize coordinates so x1 < x2 and y1 < y2.
711 int x1 = std::min( d->GetStart().x, d->GetEnd().x );
712 int y1 = std::min( d->GetStart().y, d->GetEnd().y );
713 int x2 = std::max( d->GetStart().x, d->GetEnd().x );
714 int y2 = std::max( d->GetStart().y, d->GetEnd().y );
715
716 int w = x2 - x1;
717 int h = y2 - y1;
718
719 auto addArc = [&]( const VECTOR2I& center, const VECTOR2I& startPt,
720 const VECTOR2I& endPt )
721 {
722 EDA_ANGLE startAngle( VECTOR2D( startPt - center ) );
723 EDA_ANGLE endAngle( VECTOR2D( endPt - center ) );
724
725 while( endAngle < startAngle )
726 endAngle += ANGLE_360;
727
728 auto arc = std::make_unique<BE_SHAPE_ARC>( center, r, startAngle, endAngle,
729 startPt, endPt );
730 arc->SetParent( d );
731 m_shapeCollection.push_back( std::move( arc ) );
732 };
733
734 if( h == 2 * r )
735 {
736 // Horizontal stadium: left and right semicircles. The endpoint order
737 // makes addArc sweep the outer half of each circle so the caps bulge
738 // away from the slot.
739 addArc( { x1 + r, y1 + r }, { x1 + r, y2 }, { x1 + r, y1 } );
740 addArc( { x2 - r, y1 + r }, { x2 - r, y1 }, { x2 - r, y2 } );
741 }
742 else if( w == 2 * r )
743 {
744 // Vertical stadium: top and bottom semicircles
745 addArc( { x1 + r, y1 + r }, { x1, y1 + r }, { x2, y1 + r } );
746 addArc( { x1 + r, y2 - r }, { x2, y2 - r }, { x1, y2 - r } );
747 }
748 else
749 {
750 // General rounded rectangle: four quarter-circle arcs
751 addArc( { x1 + r, y1 + r }, { x1, y1 + r }, { x1 + r, y1 } );
752 addArc( { x2 - r, y1 + r }, { x2 - r, y1 }, { x2, y1 + r } );
753 addArc( { x2 - r, y2 - r }, { x2, y2 - r }, { x2 - r, y2 } );
754 addArc( { x1 + r, y2 - r }, { x1 + r, y2 }, { x1, y2 - r } );
755 }
756 }
757 else
758 {
759 auto a = std::make_unique<BE_SHAPE_POINT>( d->GetStart() );
760 a->SetParent( d );
761 m_shapeCollection.push_back( std::move( a ) );
762 a = std::make_unique<BE_SHAPE_POINT>( d->GetEnd() );
763 a->SetParent( d );
764 m_shapeCollection.push_back( std::move( a ) );
765 a = std::make_unique<BE_SHAPE_POINT>( VECTOR2I( d->GetEnd().x, d->GetStart().y ) );
766 a->SetParent( d );
767 m_shapeCollection.push_back( std::move( a ) );
768 a = std::make_unique<BE_SHAPE_POINT>( VECTOR2I( d->GetStart().x, d->GetEnd().y ) );
769 a->SetParent( d );
770 m_shapeCollection.push_back( std::move( a ) );
771 }
772
773 break;
774 }
775
776 case SHAPE_T::POLY:
777 for( const VECTOR2I& p : d->GetPolyPoints() )
778 {
779 auto a = std::make_unique<BE_SHAPE_POINT>( p );
780 a->SetParent( d );
781 m_shapeCollection.push_back( std::move( a ) );
782 }
783
784 break;
785
786 case SHAPE_T::CIRCLE:
787 {
788 auto a = std::make_unique<BE_SHAPE_CIRCLE>( d->GetCenter(), d->GetRadius() );
789 a->SetParent( d );
790 m_shapeCollection.push_back( std::move( a ) );
791 break;
792 }
793
794 case SHAPE_T::ARC:
795 {
796 // If the arc is not locally convex, only use the endpoints
797 double tolerance = 10;
798 VECTOR2D center( double( d->GetCenter().x ), double( d->GetCenter().y ) );
799 VECTOR2D mid( double( d->GetArcMid().x ), double( d->GetArcMid().y ) );
800 VECTOR2D dir( mid - center );
801 dir = dir / d->GetRadius() * ( d->GetRadius() - tolerance );
802
803 EDA_ANGLE alpha, beta;
804 d->CalcArcAngles( alpha, beta );
805 auto a = std::make_unique<BE_SHAPE_ARC>( d->GetCenter(), d->GetRadius(), alpha, beta,
806 d->GetStart(), d->GetEnd() );
807 a->SetParent( d );
808
809 m_shapeCollection.push_back( std::move( a ) );
810 break;
811 }
812
813 default:
814 break;
815 }
816 }
817}
818
819
820void GRAPH_CONNECTION::GetShapes( std::vector<PCB_SHAPE>& aShapes )
821{
822 if( !m_path.m_show )
823 return;
824
825 if( !n1 || !n2 )
826 return;
827
828 if( n1->m_type == GRAPH_NODE::TYPE::VIRTUAL || n2->m_type == GRAPH_NODE::TYPE::VIRTUAL )
829 return;
830
831 if( !m_forceStraightLine && n1->m_parent
832 && n1->m_parent == n2->m_parent
833 && n1->m_parent->GetType() == CREEP_SHAPE::TYPE::CIRCLE )
834 {
835 VECTOR2I center = n1->m_parent->GetPos();
836 VECTOR2I R1 = n1->m_pos - center;
837 VECTOR2I R2 = n2->m_pos - center;
838 PCB_SHAPE s( nullptr, SHAPE_T::ARC );
839
840 if( R1.Cross( R2 ) > 0 )
841 {
842 s.SetStart( n1->m_pos );
843 s.SetEnd( n2->m_pos );
844 }
845 else
846 {
847 s.SetStart( n2->m_pos );
848 s.SetEnd( n1->m_pos );
849 }
850
851 s.SetCenter( center );
852 aShapes.push_back( s );
853 return;
854 }
855
856 if( !m_forceStraightLine && n1->m_parent
857 && n1->m_parent == n2->m_parent
858 && n1->m_parent->GetType() == CREEP_SHAPE::TYPE::ARC )
859 {
860 if( BE_SHAPE_ARC* arc = dynamic_cast<BE_SHAPE_ARC*>( n1->m_parent ) )
861 {
862 VECTOR2I center = arc->GetPos();
863 VECTOR2I R1 = n1->m_pos - center;
864 VECTOR2I R2 = n2->m_pos - center;
865 PCB_SHAPE s( nullptr, SHAPE_T::ARC );
866
867 if( R1.Cross( R2 ) > 0 )
868 {
869 s.SetStart( n1->m_pos );
870 s.SetEnd( n2->m_pos );
871 }
872 else
873 {
874 s.SetStart( n2->m_pos );
875 s.SetEnd( n1->m_pos );
876 }
877
878 s.SetCenter( center );
879
880 //Check that we are on the correct side of the arc.
881 VECTOR2I mid = s.GetArcMid();
882 EDA_ANGLE midAngle = arc->AngleBetweenStartAndEnd( mid );
883
884 if( midAngle > arc->GetEndAngle() )
885 {
886 VECTOR2I tmp;
887 tmp = s.GetStart();
888 s.SetStart( s.GetEnd() );
889 s.SetEnd( tmp );
890 s.SetCenter( center );
891 }
892
893 aShapes.push_back( s );
894 return;
895 }
896 }
897
898 PCB_SHAPE s( nullptr, SHAPE_T::SEGMENT );
899 s.SetStart( m_path.a1 );
900 s.SetEnd( m_path.a2 );
901 aShapes.push_back( s );
902}
903
904
905void CREEP_SHAPE::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>&,
906 CREEPAGE_GRAPH& aG ) const
907{
908}
909
910
911void BE_SHAPE_POINT::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>&,
912 CREEPAGE_GRAPH& aG ) const
913{
914}
915
916
917void BE_SHAPE_CIRCLE::ShortenChildDueToGV( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>& a2,
918 CREEPAGE_GRAPH& aG, double aNormalWeight ) const
919{
920 EDA_ANGLE angle1 = EDA_ANGLE( a1->m_pos - m_pos );
921 EDA_ANGLE angle2 = EDA_ANGLE( a2->m_pos - m_pos );
922
923 while( angle1 < ANGLE_0 )
924 angle1 += ANGLE_360;
925 while( angle2 < ANGLE_0 )
926 angle2 += ANGLE_360;
927 while( angle1 > ANGLE_360 )
928 angle1 -= ANGLE_360;
929 while( angle2 > ANGLE_360 )
930 angle2 -= ANGLE_360;
931
932 EDA_ANGLE maxAngle = angle1 > angle2 ? angle1 : angle2;
933 EDA_ANGLE skipAngle =
934 EDA_ANGLE( asin( float( aG.m_minGrooveWidth ) / ( 2 * m_radius ) ), RADIANS_T );
935 skipAngle += skipAngle; // Cannot multiply EDA_ANGLE by scalar, but this really is angle *2
936 EDA_ANGLE pointAngle = maxAngle - skipAngle;
937
938 VECTOR2I skipPoint = m_pos;
939 skipPoint.x += m_radius * cos( pointAngle.AsRadians() );
940 skipPoint.y += m_radius * sin( pointAngle.AsRadians() );
941
942 std::shared_ptr<GRAPH_NODE> gnt = aG.AddNode( GRAPH_NODE::POINT, a1->m_parent, skipPoint );
943
945
946 pc.a1 = maxAngle == angle2 ? a1->m_pos : a2->m_pos;
947 pc.a2 = skipPoint;
948 pc.weight = aNormalWeight - aG.m_minGrooveWidth;
949 aG.AddConnection( maxAngle == angle2 ? a1 : a2, gnt, pc );
950
951 pc.a1 = skipPoint;
952 pc.a2 = maxAngle == angle2 ? a2->m_pos : a1->m_pos;
953 pc.weight = aG.m_minGrooveWidth;
954
955 std::shared_ptr<GRAPH_CONNECTION> gc = aG.AddConnection( gnt, maxAngle == angle2 ? a2 : a1, pc );
956
957 if( gc )
958 gc->m_forceStraightLine = true;
959}
960
961
962void BE_SHAPE_CIRCLE::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>& a2,
963 CREEPAGE_GRAPH& aG ) const
964{
965 if( !a1 || !a2 )
966 return;
967
968 if( m_radius == 0 )
969 return;
970
971 // When cutouts overlap, part of this wall runs inside the merged void and is not
972 // a real edge to hug. Check the shorter arc, the one the solver measures and draws.
974 {
975 int tol = aG.m_board.GetDesignSettings().m_MaxError + 1000;
976 double a1r = EDA_ANGLE( a1->m_pos - m_pos ).AsRadians();
977 double a2r = EDA_ANGLE( a2->m_pos - m_pos ).AsRadians();
978 double delta = a2r - a1r;
979
980 while( delta > M_PI )
981 delta -= 2 * M_PI;
982 while( delta < -M_PI )
983 delta += 2 * M_PI;
984
985 for( int i = 0; i <= 8; ++i )
986 {
987 double a = a1r + delta * i / 8.0;
988 VECTOR2I p( m_pos.x + m_radius * cos( a ), m_pos.y + m_radius * sin( a ) );
989
990 if( !aG.m_boardOutline->Contains( p, -1, tol ) && !aG.m_boardOutline->PointOnEdge( p, tol ) )
991 return;
992 }
993 }
994
995 VECTOR2D distI( a1->m_pos - a2->m_pos );
996 VECTOR2D distD( double( distI.x ), double( distI.y ) );
997
998 double weight = m_radius * 2 * asin( distD.EuclideanNorm() / ( 2.0 * m_radius ) );
999
1000 if( weight > aG.GetTarget() )
1001 return;
1002
1003 if( aG.m_minGrooveWidth <= 0 )
1004 {
1005 PATH_CONNECTION pc;
1006 pc.a1 = a1->m_pos;
1007 pc.a2 = a2->m_pos;
1008 pc.weight = std::max( weight, 0.0 );
1009
1010 aG.AddConnection( a1, a2, pc );
1011 return;
1012 }
1013
1014 if( weight > aG.m_minGrooveWidth )
1015 ShortenChildDueToGV( a1, a2, aG, weight );
1016 // Else well.. this paths will be "shorted" by another one
1017}
1018
1019
1020void BE_SHAPE_ARC::ConnectChildren( std::shared_ptr<GRAPH_NODE>& a1, std::shared_ptr<GRAPH_NODE>& a2,
1021 CREEPAGE_GRAPH& aG ) const
1022{
1023 if( !a1 || !a2 )
1024 return;
1025
1026 EDA_ANGLE angle1 = AngleBetweenStartAndEnd( a1->m_pos );
1027 EDA_ANGLE angle2 = AngleBetweenStartAndEnd( a2->m_pos );
1028
1029 // Skip an arc that dips into an overlapping cutout, it is not a real edge to hug.
1030 // Sample the whole sub-arc, the tolerance clears the outline arc-to-segment error.
1032 {
1033 int tol = aG.m_board.GetDesignSettings().m_MaxError + 1000;
1034 double a1r = angle1.AsRadians();
1035 double a2r = angle2.AsRadians();
1036
1037 for( int i = 0; i <= 8; ++i )
1038 {
1039 double a = a1r + ( a2r - a1r ) * i / 8.0;
1040 VECTOR2I p( m_pos.x + m_radius * cos( a ), m_pos.y + m_radius * sin( a ) );
1041
1042 if( !aG.m_boardOutline->Contains( p, -1, tol ) && !aG.m_boardOutline->PointOnEdge( p, tol ) )
1043 return;
1044 }
1045 }
1046
1047 double weight = abs( m_radius * ( angle2 - angle1 ).AsRadians() );
1048
1049 if( aG.m_minGrooveWidth <= 0 )
1050 {
1051 if( ( weight > aG.GetTarget() ) )
1052 return;
1053
1054 PATH_CONNECTION pc;
1055 pc.a1 = a1->m_pos;
1056 pc.a2 = a2->m_pos;
1057 pc.weight = weight;
1058
1059 aG.AddConnection( a1, a2, pc );
1060 return;
1061 }
1062
1063 if( weight > aG.m_minGrooveWidth )
1064 ShortenChildDueToGV( a1, a2, aG, weight );
1065}
1066
1067
1068void CREEPAGE_GRAPH::SetTarget( double aTarget )
1069{
1070 m_creepageTarget = aTarget;
1071 m_creepageTargetSquared = aTarget * aTarget;
1072}
1073
1074
1075std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
1076 double aMaxSquaredWeight ) const
1077{
1078 std::vector<PATH_CONNECTION> result;
1079 VECTOR2I start = this->GetStart();
1080 VECTOR2I end = this->GetEnd();
1081 double halfWidth = this->GetWidth() / 2;
1082 EDA_ANGLE trackAngle( end - start );
1083 VECTOR2I pointPos = aS2.GetPos();
1084
1085 double length = ( start - end ).EuclideanNorm();
1086 double projectedPos = cos( trackAngle.AsRadians() ) * ( pointPos.x - start.x )
1087 + sin( trackAngle.AsRadians() ) * ( pointPos.y - start.y );
1088
1089 VECTOR2I newPoint;
1090
1091 if( projectedPos <= 0 )
1092 {
1093 newPoint = start + ( pointPos - start ).Resize( halfWidth );
1094 }
1095 else if( projectedPos >= length )
1096 {
1097 newPoint = end + ( pointPos - end ).Resize( halfWidth );
1098 }
1099 else
1100 {
1101 double posOnSegment = ( start - pointPos ).SquaredEuclideanNorm()
1102 - ( end - pointPos ).SquaredEuclideanNorm();
1103 posOnSegment = posOnSegment / ( 2 * length ) + length / 2;
1104
1105 newPoint = start + ( end - start ).Resize( posOnSegment );
1106 newPoint += ( pointPos - newPoint ).Resize( halfWidth );
1107 }
1108
1109 double weightSquared = ( pointPos - newPoint ).SquaredEuclideanNorm();
1110
1111 if( weightSquared > aMaxSquaredWeight )
1112 return result;
1113
1114 PATH_CONNECTION pc;
1115 pc.a1 = newPoint;
1116 pc.a2 = pointPos;
1117 pc.weight = sqrt( weightSquared );
1118
1119 result.push_back( pc );
1120 return result;
1121}
1122
1123
1124std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
1125 double aMaxSquaredWeight ) const
1126{
1127 std::vector<PATH_CONNECTION> result;
1128 VECTOR2I start = this->GetStart();
1129 VECTOR2I end = this->GetEnd();
1130 double halfWidth = this->GetWidth() / 2;
1131
1132 double circleRadius = aS2.GetRadius();
1133 VECTOR2I circleCenter = aS2.GetPos();
1134 double length = ( start - end ).EuclideanNorm();
1135 EDA_ANGLE trackAngle( end - start );
1136
1137 double weightSquared = std::numeric_limits<double>::infinity();
1138 VECTOR2I PointOnTrack, PointOnCircle;
1139
1140 // There are two possible paths
1141 // First the one on the side of the start of the track.
1142 double projectedPos1 = cos( trackAngle.AsRadians() ) * ( circleCenter.x - start.x )
1143 + sin( trackAngle.AsRadians() ) * ( circleCenter.y - start.y );
1144 double projectedPos2 = projectedPos1 + circleRadius;
1145 projectedPos1 = projectedPos1 - circleRadius;
1146
1147 double trackSide = ( end - start ).Cross( circleCenter - start ) > 0 ? 1 : -1;
1148
1149 if( ( projectedPos1 < 0 && projectedPos2 < 0 ) )
1150 {
1151 CU_SHAPE_CIRCLE csc( start, halfWidth );
1152 for( PATH_CONNECTION pc : csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
1153 {
1154 result.push_back( pc );
1155 }
1156 }
1157 else if( ( projectedPos1 > length && projectedPos2 > length ) )
1158 {
1159 CU_SHAPE_CIRCLE csc( end, halfWidth );
1160
1161 for( const PATH_CONNECTION& pc : csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
1162 result.push_back( pc );
1163 }
1164
1165 else if( ( projectedPos1 >= 0 ) && ( projectedPos1 <= length ) && ( projectedPos2 >= 0 )
1166 && ( projectedPos2 <= length ) )
1167 {
1168 // Both point connects to the segment part of the track
1169 PointOnTrack = start;
1170 PointOnTrack += ( end - start ).Resize( projectedPos1 );
1171 PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
1172 PointOnCircle = circleCenter - ( end - start ).Resize( circleRadius );
1173 weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm();
1174
1175 if( weightSquared < aMaxSquaredWeight )
1176 {
1177 PATH_CONNECTION pc;
1178 pc.a1 = PointOnTrack;
1179 pc.a2 = PointOnCircle;
1180 pc.weight = sqrt( weightSquared );
1181
1182 result.push_back( pc );
1183
1184 PointOnTrack = start;
1185 PointOnTrack += ( end - start ).Resize( projectedPos2 );
1186 PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
1187 PointOnCircle = circleCenter + ( end - start ).Resize( circleRadius );
1188
1189
1190 pc.a1 = PointOnTrack;
1191 pc.a2 = PointOnCircle;
1192
1193 result.push_back( pc );
1194 }
1195 }
1196 else if( ( ( projectedPos1 >= 0 ) && ( projectedPos1 <= length ) )
1197 && ( ( projectedPos2 > length ) || projectedPos2 < 0 ) )
1198 {
1199 CU_SHAPE_CIRCLE csc( end, halfWidth );
1200 std::vector<PATH_CONNECTION> pcs = csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1201
1202 if( pcs.size() < 2 )
1203 return result;
1204
1205 result.push_back( pcs.at( trackSide == 1 ? 1 : 0 ) );
1206
1207
1208 PointOnTrack = start;
1209 PointOnTrack += ( end - start ).Resize( projectedPos1 );
1210 PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
1211 PointOnCircle = circleCenter - ( end - start ).Resize( circleRadius );
1212 weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm();
1213
1214 if( weightSquared < aMaxSquaredWeight )
1215 {
1216 PATH_CONNECTION pc;
1217 pc.a1 = PointOnTrack;
1218 pc.a2 = PointOnCircle;
1219 pc.weight = sqrt( weightSquared );
1220
1221 result.push_back( pc );
1222 }
1223 }
1224 else if( ( ( projectedPos2 >= 0 ) && ( projectedPos2 <= length ) )
1225 && ( ( projectedPos1 > length ) || projectedPos1 < 0 ) )
1226 {
1227 CU_SHAPE_CIRCLE csc( start, halfWidth );
1228 std::vector<PATH_CONNECTION> pcs = csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1229
1230 if( pcs.size() < 2 )
1231 return result;
1232
1233 result.push_back( pcs.at( trackSide == 1 ? 0 : 1 ) );
1234
1235 PointOnTrack = start;
1236 PointOnTrack += ( end - start ).Resize( projectedPos2 );
1237 PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide;
1238 PointOnCircle = circleCenter + ( end - start ).Resize( circleRadius );
1239 weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm();
1240
1241 if( weightSquared < aMaxSquaredWeight )
1242 {
1243 PATH_CONNECTION pc;
1244 pc.a1 = PointOnTrack;
1245 pc.a2 = PointOnCircle;
1246 pc.weight = sqrt( weightSquared );
1247
1248 result.push_back( pc );
1249 }
1250 }
1251 else if( projectedPos1 < 0 && projectedPos2 > length )
1252 {
1253 // The circle projects past both ends of the track, so neither tangent lands on the
1254 // track flank and each end cap carries one side of the path
1255 CU_SHAPE_CIRCLE cscStart( start, halfWidth );
1256 std::vector<PATH_CONNECTION> startPcs = cscStart.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1257
1258 if( startPcs.size() >= 2 )
1259 result.push_back( startPcs.at( trackSide == 1 ? 0 : 1 ) );
1260
1261 CU_SHAPE_CIRCLE cscEnd( end, halfWidth );
1262 std::vector<PATH_CONNECTION> endPcs = cscEnd.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1263
1264 if( endPcs.size() >= 2 )
1265 result.push_back( endPcs.at( trackSide == 1 ? 1 : 0 ) );
1266 }
1267
1268 return result;
1269}
1270
1271
1272std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
1273 double aMaxSquaredWeight ) const
1274{
1275 std::vector<PATH_CONNECTION> result;
1276
1277 BE_SHAPE_CIRCLE bsc( aS2.GetPos(), aS2.GetRadius() );
1278
1279 for( const PATH_CONNECTION& pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) )
1280 {
1281 EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );
1282
1283 if( testAngle < aS2.GetEndAngle() )
1284 result.push_back( pc );
1285 }
1286
1287 if( result.size() < 2 )
1288 {
1289 BE_SHAPE_POINT bsp1( aS2.GetStartPoint() );
1290 BE_SHAPE_POINT bsp2( aS2.GetEndPoint() );
1291
1292 VECTOR2I beArcPos = aS2.GetPos();
1293 int beArcRadius = aS2.GetRadius();
1294 EDA_ANGLE beArcStartAngle = aS2.GetStartAngle();
1295 EDA_ANGLE beArcEndAngle = aS2.GetEndAngle();
1296
1297 for( const PATH_CONNECTION& pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) )
1298 {
1299 if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle, beArcEndAngle ) )
1300 result.push_back( pc );
1301 }
1302
1303 for( const PATH_CONNECTION& pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) )
1304 {
1305 if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle, beArcEndAngle ) )
1306 result.push_back( pc );
1307 }
1308 }
1309
1310 return result;
1311}
1312
1313
1314std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
1315 double aMaxSquaredWeight ) const
1316{
1317 std::vector<PATH_CONNECTION> result;
1318 VECTOR2I beArcPos = aS2.GetPos();
1319 int beArcRadius = aS2.GetRadius();
1320 EDA_ANGLE beArcStartAngle = aS2.GetStartAngle();
1321 EDA_ANGLE beArcEndAngle = aS2.GetEndAngle();
1322
1323 BE_SHAPE_CIRCLE bsc( beArcPos, beArcRadius );
1324
1325 for( const PATH_CONNECTION& pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) )
1326 {
1327 EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );
1328
1329 if( testAngle < aS2.GetEndAngle() )
1330 result.push_back( pc );
1331 }
1332
1333 if( result.size() < 2 )
1334 {
1335 BE_SHAPE_POINT bsp1( aS2.GetStartPoint() );
1336 BE_SHAPE_POINT bsp2( aS2.GetEndPoint() );
1337
1338 for( const PATH_CONNECTION& pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) )
1339 {
1340 if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle, beArcEndAngle ) )
1341 result.push_back( pc );
1342 }
1343
1344 for( const PATH_CONNECTION& pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) )
1345 {
1346 if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle, beArcEndAngle ) )
1347 result.push_back( pc );
1348 }
1349
1350 }
1351 return result;
1352}
1353
1354
1355std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
1356 double aMaxSquaredWeight ) const
1357{
1358 std::vector<PATH_CONNECTION> result;
1359
1360 CU_SHAPE_CIRCLE csc( this->GetPos(), this->GetRadius() + this->GetWidth() / 2 );
1361
1362 for( const PATH_CONNECTION& pc : this->Paths( csc, aMaxWeight, aMaxSquaredWeight ) )
1363 {
1364 EDA_ANGLE testAngle = this->AngleBetweenStartAndEnd( pc.a2 );
1365
1366 if( testAngle < this->GetEndAngle() )
1367 result.push_back( pc );
1368 }
1369
1370 if( result.size() < 2 )
1371 {
1372 CU_SHAPE_CIRCLE csc1( this->GetStartPoint(), this->GetWidth() / 2 );
1373 CU_SHAPE_CIRCLE csc2( this->GetEndPoint(), this->GetWidth() / 2 );
1374
1375 for( const PATH_CONNECTION& pc : this->Paths( csc1, aMaxWeight, aMaxSquaredWeight ) )
1376 result.push_back( pc );
1377
1378 for( const PATH_CONNECTION& pc : this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ) )
1379 result.push_back( pc );
1380 }
1381
1382 return result;
1383}
1384
1385
1386std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight,
1387 double aMaxSquaredWeight ) const
1388{
1389 std::vector<PATH_CONNECTION> result;
1390 VECTOR2I beArcPos = aS2.GetPos();
1391 int beArcRadius = aS2.GetRadius();
1392 EDA_ANGLE beArcStartAngle = aS2.GetStartAngle();
1393 EDA_ANGLE beArcEndAngle = aS2.GetEndAngle();
1394
1395 BE_SHAPE_CIRCLE bsc( aS2.GetPos(), aS2.GetRadius() );
1396
1397 for( const PATH_CONNECTION& pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) )
1398 {
1399 EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 );
1400
1401 if( testAngle < aS2.GetEndAngle() )
1402 result.push_back( pc );
1403 }
1404
1405 if( result.size() < 2 )
1406 {
1407 BE_SHAPE_POINT bsp1( aS2.GetStartPoint() );
1408 BE_SHAPE_POINT bsp2( aS2.GetEndPoint() );
1409
1410 for( const PATH_CONNECTION& pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) )
1411 {
1412 if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle, beArcEndAngle ) )
1413 result.push_back( pc );
1414 }
1415
1416 for( const PATH_CONNECTION& pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) )
1417 {
1418 if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, beArcStartAngle, beArcEndAngle ) )
1419 result.push_back( pc );
1420 }
1421 }
1422
1423 return result;
1424}
1425
1426
1427std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
1428 double aMaxSquaredWeight ) const
1429{
1430 std::vector<PATH_CONNECTION> result;
1431
1432 double R = this->GetRadius();
1433 VECTOR2I center = this->GetPos();
1434 VECTOR2I point = aS2.GetPos();
1435 double weight = ( center - point ).EuclideanNorm() - R;
1436
1437 if( weight > aMaxWeight )
1438 return result;
1439
1440 PATH_CONNECTION pc;
1441 pc.weight = std::max( weight, 0.0 );
1442 pc.a2 = point;
1443 pc.a1 = center + ( point - center ).Resize( R );
1444
1445 result.push_back( pc );
1446 return result;
1447}
1448
1449
1450std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight,
1451 double aMaxSquaredWeight ) const
1452{
1453 std::vector<PATH_CONNECTION> result;
1454
1455 double R1 = this->GetRadius();
1456 double R2 = aS2.GetRadius();
1457 VECTOR2I C1 = this->GetPos();
1458 VECTOR2I C2 = aS2.GetPos();
1459
1460 if( ( C1 - C2 ).SquaredEuclideanNorm() < ( R1 - R2 ) * ( R1 - R2 ) )
1461 {
1462 // One of the circles is inside the other
1463 return result;
1464 }
1465
1466 double weight = ( C1 - C2 ).EuclideanNorm() - R1 - R2;
1467
1468 if( weight > aMaxWeight || weight < 0 )
1469 return result;
1470
1471 PATH_CONNECTION pc;
1472 pc.weight = std::max( weight, 0.0 );
1473 pc.a1 = ( C2 - C1 ).Resize( R1 ) + C1;
1474 pc.a2 = ( C1 - C2 ).Resize( R2 ) + C2;
1475 result.push_back( pc );
1476 return result;
1477}
1478
1479
1480std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight,
1481 double aMaxSquaredWeight ) const
1482{
1483 std::vector<PATH_CONNECTION> result;
1484
1485 VECTOR2I s_start = this->GetStart();
1486 VECTOR2I s_end = this->GetEnd();
1487 double halfWidth = this->GetWidth() / 2;
1488
1489 EDA_ANGLE trackAngle( s_end - s_start );
1490 VECTOR2I pointPos = aS2.GetPos();
1491
1492 double length = ( s_start - s_end ).EuclideanNorm();
1493 double projectedPos = cos( trackAngle.AsRadians() ) * ( pointPos.x - s_start.x )
1494 + sin( trackAngle.AsRadians() ) * ( pointPos.y - s_start.y );
1495
1496 if( ( projectedPos <= 0 ) || ( s_start == s_end ) )
1497 {
1498 CU_SHAPE_CIRCLE csc( s_start, halfWidth );
1499 return csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1500 }
1501
1502 if( projectedPos >= length )
1503 {
1504 CU_SHAPE_CIRCLE csc( s_end, halfWidth );
1505 return csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1506 }
1507
1508 double radius = aS2.GetRadius();
1509 double trackSide = ( s_end - s_start ).Cross( pointPos - s_start ) > 0 ? 1 : -1;
1510
1511 PATH_CONNECTION pc;
1512 pc.a1 = s_start + ( s_end - s_start ).Resize( projectedPos )
1513 + ( s_end - s_start ).Perpendicular().Resize( halfWidth ) * trackSide;
1514 pc.a2 = ( pc.a1 - pointPos ).Resize( radius ) + pointPos;
1515 pc.weight = ( pc.a2 - pc.a1 ).SquaredEuclideanNorm();
1516
1517 if( pc.weight <= aMaxSquaredWeight )
1518 {
1519 pc.weight = sqrt( pc.weight );
1520 result.push_back( pc );
1521 }
1522
1523 return result;
1524}
1525
1526
1527std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight,
1528 double aMaxSquaredWeight ) const
1529{
1530 std::vector<PATH_CONNECTION> result;
1531
1532 VECTOR2I circlePos = this->GetPos();
1533 VECTOR2I arcPos = aS2.GetPos();
1534
1535 double circleRadius = this->GetRadius();
1536 double arcRadius = aS2.GetRadius();
1537
1538 VECTOR2I startPoint = aS2.GetStartPoint();
1539 VECTOR2I endPoint = aS2.GetEndPoint();
1540
1541 CU_SHAPE_CIRCLE csc( arcPos, arcRadius + aS2.GetWidth() / 2 );
1542
1543 if( ( circlePos - arcPos ).EuclideanNorm() > arcRadius + circleRadius )
1544 {
1545 const std::vector<PATH_CONNECTION>& pcs = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );
1546
1547 if( pcs.size() == 1 )
1548 {
1549 EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pcs[0].a2 );
1550
1551 if( testAngle < aS2.GetEndAngle() )
1552 {
1553 result.push_back( pcs[0] );
1554 return result;
1555 }
1556 }
1557 }
1558
1559 CU_SHAPE_CIRCLE csc1( startPoint, aS2.GetWidth() / 2 );
1560 CU_SHAPE_CIRCLE csc2( endPoint, aS2.GetWidth() / 2 );
1561
1562 PATH_CONNECTION* bestPath = nullptr;
1563
1564
1565 std::vector<PATH_CONNECTION> pcs1 = this->Paths( csc1, aMaxWeight, aMaxSquaredWeight );
1566 std::vector<PATH_CONNECTION> pcs2 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight );
1567
1568 for( PATH_CONNECTION& pc : pcs1 )
1569 {
1570 if( !bestPath || ( ( bestPath->weight > pc.weight ) && ( pc.weight > 0 ) ) )
1571 bestPath = &pc;
1572 }
1573
1574 for( PATH_CONNECTION& pc : pcs2 )
1575 {
1576 if( !bestPath || ( ( bestPath->weight > pc.weight ) && ( pc.weight > 0 ) ) )
1577 bestPath = &pc;
1578 }
1579
1580 // If the circle center is insde the arc ring
1581
1582 PATH_CONNECTION pc3;
1583
1584 if( ( circlePos - arcPos ).SquaredEuclideanNorm() < arcRadius * arcRadius )
1585 {
1586 if( circlePos != arcPos ) // The best path is already found otherwise
1587 {
1588 EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( circlePos );
1589
1590 if( testAngle < aS2.GetEndAngle() )
1591 {
1592 pc3.weight = std::max( arcRadius - ( circlePos - arcPos ).EuclideanNorm() - circleRadius, 0.0 );
1593 pc3.a1 = circlePos + ( circlePos - arcPos ).Resize( circleRadius );
1594 pc3.a2 = arcPos + ( circlePos - arcPos ).Resize( arcRadius - aS2.GetWidth() / 2 );
1595
1596 if( !bestPath || ( ( bestPath->weight > pc3.weight ) && ( pc3.weight > 0 ) ) )
1597 bestPath = &pc3;
1598 }
1599 }
1600 }
1601
1602 if( bestPath && bestPath->weight > 0 )
1603 {
1604 result.push_back( *bestPath );
1605 }
1606
1607 return result;
1608}
1609
1610
1611std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight,
1612 double aMaxSquaredWeight ) const
1613{
1614 std::vector<PATH_CONNECTION> result;
1615
1616 VECTOR2I s_start = this->GetStart();
1617 VECTOR2I s_end = this->GetEnd();
1618 double halfWidth1 = this->GetWidth() / 2;
1619
1620 VECTOR2I arcPos = aS2.GetPos();
1621 double arcRadius = aS2.GetRadius();
1622 double halfWidth2 = aS2.GetWidth() / 2;
1623
1624
1625 CU_SHAPE_CIRCLE csc( arcPos, arcRadius + halfWidth2 );
1626
1627 std::vector<PATH_CONNECTION> pcs;
1628 pcs = this->Paths( csc, aMaxWeight, aMaxSquaredWeight );
1629
1630 if( pcs.size() < 1 )
1631 return result;
1632
1633 VECTOR2I circlePoint;
1634 EDA_ANGLE testAngle;
1635
1636 if( pcs.size() > 0 )
1637 {
1638 circlePoint = pcs[0].a1;
1639 testAngle = ( aS2.AngleBetweenStartAndEnd( pcs[0].a1 ) );
1640 }
1641
1642 if( testAngle < aS2.GetEndAngle() && pcs.size() > 0 )
1643 {
1644 result.push_back( pcs[0] );
1645 return result;
1646 }
1647
1648 CU_SHAPE_CIRCLE csc1( aS2.GetStartPoint(), halfWidth2 );
1649 CU_SHAPE_CIRCLE csc2( aS2.GetEndPoint(), halfWidth2 );
1650 PATH_CONNECTION* bestPath = nullptr;
1651
1652 for( PATH_CONNECTION& pc : this->Paths( csc1, aMaxWeight, aMaxSquaredWeight ) )
1653 {
1654 if( !bestPath || ( bestPath->weight > pc.weight ) )
1655 bestPath = &pc;
1656 }
1657
1658 for( PATH_CONNECTION& pc : this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ) )
1659 {
1660 if( !bestPath || ( bestPath->weight > pc.weight ) )
1661 bestPath = &pc;
1662 }
1663
1664 CU_SHAPE_CIRCLE csc3( s_start, halfWidth1 );
1665 CU_SHAPE_CIRCLE csc4( s_end, halfWidth1 );
1666
1667 for( PATH_CONNECTION& pc : csc3.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
1668 {
1669 if( !bestPath || ( bestPath->weight > pc.weight ) )
1670 bestPath = &pc;
1671 }
1672
1673
1674 for( PATH_CONNECTION& pc : csc4.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) )
1675 {
1676 if( !bestPath || ( bestPath->weight > pc.weight ) )
1677 bestPath = &pc;
1678 }
1679
1680 if( bestPath )
1681 result.push_back( *bestPath );
1682
1683 return result;
1684}
1685
1686// Function to compute the projection of point P onto the line segment AB
1688{
1689 if( A == B )
1690 return A;
1691 if( A == P )
1692 return A;
1693
1694 VECTOR2I AB = B - A;
1695 VECTOR2I AP = P - A;
1696
1697 double t = float( AB.Dot( AP ) ) / float( AB.SquaredEuclideanNorm() );
1698
1699 // Clamp t to the range [0, 1] to restrict the projection to the segment
1700 t = std::max( 0.0, std::min( 1.0, t ) );
1701
1702 return A + ( AB * t );
1703}
1704
1705
1706std::vector<PATH_CONNECTION> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_SEGMENT& aS2,
1707 double aMaxWeight,
1708 double aMaxSquaredWeight ) const
1709{
1710 std::vector<PATH_CONNECTION> result;
1711
1712 VECTOR2I A( this->GetStart() );
1713 VECTOR2I B( this->GetEnd() );
1714 double halfWidth1 = this->GetWidth() / 2;
1715
1716
1717 VECTOR2I C( aS2.GetStart() );
1718 VECTOR2I D( aS2.GetEnd() );
1719 double halfWidth2 = aS2.GetWidth() / 2;
1720
1725
1726 // Calculate all possible squared distances between the segments
1727 double dist1 = ( P1 - C ).SquaredEuclideanNorm();
1728 double dist2 = ( P2 - D ).SquaredEuclideanNorm();
1729 double dist3 = ( P3 - A ).SquaredEuclideanNorm();
1730 double dist4 = ( P4 - B ).SquaredEuclideanNorm();
1731
1732 // Find the minimum squared distance and update closest points
1733 double min_dist = dist1;
1734 VECTOR2I closest1 = P1;
1735 VECTOR2I closest2 = C;
1736
1737 if( dist2 < min_dist )
1738 {
1739 min_dist = dist2;
1740 closest1 = P2;
1741 closest2 = D;
1742 }
1743
1744 if( dist3 < min_dist )
1745 {
1746 min_dist = dist3;
1747 closest1 = A;
1748 closest2 = P3;
1749 }
1750
1751 if( dist4 < min_dist )
1752 {
1753 min_dist = dist4;
1754 closest1 = B;
1755 closest2 = P4;
1756 }
1757
1758
1759 PATH_CONNECTION pc;
1760 pc.a1 = closest1 + ( closest2 - closest1 ).Resize( halfWidth1 );
1761 pc.a2 = closest2 + ( closest1 - closest2 ).Resize( halfWidth2 );
1762 pc.weight = std::max( sqrt( min_dist ) - halfWidth1 - halfWidth2, 0.0 );
1763
1764 if( pc.weight <= aMaxWeight )
1765 result.push_back( pc );
1766
1767 return result;
1768}
1769
1770
1771std::vector<PATH_CONNECTION> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight,
1772 double aMaxSquaredWeight ) const
1773{
1774 std::vector<PATH_CONNECTION> result;
1775
1776 double R1 = this->GetRadius();
1777 double R2 = aS2.GetRadius();
1778 VECTOR2I center1 = this->GetPos();
1779 VECTOR2I center2 = aS2.GetPos();
1780 double dist = ( center1 - center2 ).EuclideanNorm();
1781
1782 // Prune on the tangent sqrt(dist^2 - R2^2) - R1, which is much shorter than the centre
1783 // distance beside a large hole
1784 double reach = aMaxWeight + R1;
1785
1786 if( dist == 0 || dist * dist > reach * reach + R2 * R2 )
1787 return result;
1788
1789 double circleAngle = EDA_ANGLE( center2 - center1 ).AsRadians();
1790
1791 if( dist <= R2 )
1792 {
1793 // Copper circle center is inside the board-edge circle so external tangent lines
1794 // don't exist. The nearest gap is the radial distance between circle boundaries.
1795 double weight = std::max( R2 - dist - R1, 0.0 );
1796
1797 if( weight > aMaxWeight )
1798 return result;
1799
1800 double radialAngle = circleAngle + M_PI;
1801 double cx = cos( radialAngle );
1802 double cy = sin( radialAngle );
1803 VECTOR2I pEnd = center2 + VECTOR2I( R2 * cx, R2 * cy );
1804 VECTOR2I pStart = center1 + VECTOR2I( R1 * cx, R1 * cy );
1805
1806 PATH_CONNECTION pc;
1807 pc.a1 = pStart;
1808 pc.a2 = pEnd;
1809 pc.weight = weight;
1810
1811 // Callers expect two entries (one per tangent side) and select by index.
1812 result.push_back( pc );
1813 result.push_back( pc );
1814
1815 return result;
1816 }
1817
1818 double weight = sqrt( dist * dist - R2 * R2 ) - R1;
1819 double theta = asin( R2 / dist );
1820 double psi = acos( R2 / dist );
1821
1822 if( weight > aMaxWeight )
1823 return result;
1824
1825 PATH_CONNECTION pc;
1826 pc.weight = std::max( weight, 0.0 );
1827
1828 VECTOR2I pStart;
1829 VECTOR2I pEnd;
1830
1831 pStart = VECTOR2I( R1 * cos( theta + circleAngle ), R1 * sin( theta + circleAngle ) );
1832 pStart += center1;
1833 pEnd = VECTOR2I( -R2 * cos( psi - circleAngle ), R2 * sin( psi - circleAngle ) );
1834 pEnd += center2;
1835
1836 pc.a1 = pStart;
1837 pc.a2 = pEnd;
1838 result.push_back( pc );
1839
1840 pStart = VECTOR2I( R1 * cos( -theta + circleAngle ), R1 * sin( -theta + circleAngle ) );
1841 pStart += center1;
1842 pEnd = VECTOR2I( -R2 * cos( -psi - circleAngle ), R2 * sin( -psi - circleAngle ) );
1843 pEnd += center2;
1844
1845 pc.a1 = pStart;
1846 pc.a2 = pEnd;
1847
1848 result.push_back( pc );
1849 return result;
1850}
1851
1852
1853std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight,
1854 double aMaxSquaredWeight ) const
1855{
1856 std::vector<PATH_CONNECTION> result;
1857 VECTOR2I point = aS2.GetPos();
1858 VECTOR2I arcCenter = this->GetPos();
1859
1860 double radius = this->GetRadius();
1861 double width = this->GetWidth();
1862
1863 EDA_ANGLE angle( point - arcCenter );
1864
1865 while( angle < this->GetStartAngle() )
1866 angle += ANGLE_360;
1867 while( angle > this->GetEndAngle() + ANGLE_360 )
1868 angle -= ANGLE_360;
1869
1870 if( angle < this->GetEndAngle() )
1871 {
1872 if( ( point - arcCenter ).SquaredEuclideanNorm() > radius * radius )
1873 {
1874 CU_SHAPE_CIRCLE circle( arcCenter, radius + width / 2 );
1875 return circle.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1876 }
1877 else
1878 {
1879 PATH_CONNECTION pc;
1880 pc.weight = std::max( ( radius - width / 2 ) - ( point - arcCenter ).EuclideanNorm(), 0.0 );
1881 pc.a1 = ( point - arcCenter ).Resize( radius - width / 2 ) + arcCenter;
1882 pc.a2 = point;
1883
1884 if( pc.weight > 0 && pc.weight < aMaxWeight )
1885 result.push_back( pc );
1886
1887 return result;
1888 }
1889 }
1890 else
1891 {
1892 VECTOR2I nearestPoint;
1893
1894 if( ( point - this->GetStartPoint() ).SquaredEuclideanNorm()
1895 > ( point - this->GetEndPoint() ).SquaredEuclideanNorm() )
1896 {
1897 nearestPoint = this->GetEndPoint();
1898 }
1899 else
1900 {
1901 nearestPoint = this->GetStartPoint();
1902 }
1903
1904 CU_SHAPE_CIRCLE circle( nearestPoint, width / 2 );
1905 return circle.Paths( aS2, aMaxWeight, aMaxSquaredWeight );
1906 }
1907}
1908
1909
1910std::vector<PATH_CONNECTION> CU_SHAPE_ARC::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight,
1911 double aMaxSquaredWeight ) const
1912{
1913 std::vector<PATH_CONNECTION> result;
1914
1915 double R1 = this->GetRadius();
1916 double R2 = aS2.GetRadius();
1917
1918 VECTOR2I C1 = this->GetPos();
1919 VECTOR2I C2 = aS2.GetPos();
1920
1921 PATH_CONNECTION bestPath;
1922 bestPath.weight = std::numeric_limits<double>::infinity();
1923 CU_SHAPE_CIRCLE csc1( C1, R1 + this->GetWidth() / 2 );
1924 CU_SHAPE_CIRCLE csc2( C2, R2 + aS2.GetWidth() / 2 );
1925
1926 CU_SHAPE_CIRCLE csc3( this->GetStartPoint(), this->GetWidth() / 2 );
1927 CU_SHAPE_CIRCLE csc4( this->GetEndPoint(), this->GetWidth() / 2 );
1928 CU_SHAPE_CIRCLE csc5( aS2.GetStartPoint(), aS2.GetWidth() / 2 );
1929 CU_SHAPE_CIRCLE csc6( aS2.GetEndPoint(), aS2.GetWidth() / 2 );
1930
1931 for( const std::vector<PATH_CONNECTION>& pcs : { csc1.Paths( csc2, aMaxWeight, aMaxSquaredWeight ),
1932 this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ),
1933 csc1.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) } )
1934 {
1935 for( const PATH_CONNECTION& pc : pcs )
1936 {
1937 EDA_ANGLE testAngle1 = this->AngleBetweenStartAndEnd( pc.a1 );
1938 EDA_ANGLE testAngle2 = aS2.AngleBetweenStartAndEnd( pc.a2 );
1939
1940 if( testAngle1 < this->GetEndAngle() && testAngle2 < aS2.GetEndAngle() && bestPath.weight > pc.weight )
1941 bestPath = pc;
1942 }
1943 }
1944
1945 for( const std::vector<PATH_CONNECTION>& pcs : { this->Paths( csc5, aMaxWeight, aMaxSquaredWeight ),
1946 this->Paths( csc6, aMaxWeight, aMaxSquaredWeight ),
1947 csc3.Paths( aS2, aMaxWeight, aMaxSquaredWeight ),
1948 csc4.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) } )
1949 {
1950 for( const PATH_CONNECTION& pc : pcs )
1951 {
1952 if( bestPath.weight > pc.weight )
1953 bestPath = pc;
1954 }
1955 }
1956
1957 if( bestPath.weight != std::numeric_limits<double>::infinity() )
1958 result.push_back( bestPath );
1959
1960 return result;
1961}
1962
1963
1964bool segmentIntersectsCircle( const VECTOR2I& p1, const VECTOR2I& p2, const VECTOR2I& center, double radius,
1965 std::vector<VECTOR2I>* aIntersectPoints )
1966{
1967 SEG segment( p1, p2 );
1969
1970 std::vector<VECTOR2I> intersectionPoints;
1971 INTERSECTABLE_GEOM geom1 = segment;
1972 INTERSECTABLE_GEOM geom2 = circle;
1973
1974 INTERSECTION_VISITOR visitor( geom2, intersectionPoints );
1975 std::visit( visitor, geom1 );
1976
1977 // A path is allowed to end on the circle, so an intersection at either endpoint is a
1978 // touch, not a crossing. Only interior crossings count.
1979 const VECTOR2I::extended_type toleranceSq = 50 * 50;
1980
1981 auto coincident = [&]( const VECTOR2I& a, const VECTOR2I& b )
1982 {
1983 return ( a - b ).SquaredEuclideanNorm() <= toleranceSq;
1984 };
1985
1986 std::vector<VECTOR2I> filtered;
1987
1988 for( const VECTOR2I& ip : intersectionPoints )
1989 {
1990 if( !coincident( ip, p1 ) && !coincident( ip, p2 ) )
1991 filtered.push_back( ip );
1992 }
1993
1994 if( aIntersectPoints )
1995 {
1996 for( VECTOR2I& point : filtered )
1997 aIntersectPoints->push_back( point );
1998 }
1999
2000 return filtered.size() > 0;
2001}
2002
2003bool SegmentIntersectsBoard( const VECTOR2I& aP1, const VECTOR2I& aP2,
2004 const std::vector<BOARD_ITEM*>& aBe,
2005 const std::vector<const BOARD_ITEM*>& aDontTestAgainst,
2006 int aMinGrooveWidth )
2007{
2008 std::vector<VECTOR2I> intersectionPoints;
2009 bool TestGrooveWidth = aMinGrooveWidth > 0;
2010
2011 for( BOARD_ITEM* be : aBe )
2012 {
2013 if( count( aDontTestAgainst.begin(), aDontTestAgainst.end(), be ) > 0 )
2014 continue;
2015
2016 PCB_SHAPE* d = static_cast<PCB_SHAPE*>( be );
2017 if( !d )
2018 continue;
2019
2020 switch( d->GetShape() )
2021 {
2022 case SHAPE_T::SEGMENT:
2023 {
2024 bool intersects = segments_intersect( aP1, aP2, d->GetStart(), d->GetEnd(), intersectionPoints );
2025
2026 if( intersects && !TestGrooveWidth )
2027 return false;
2028
2029 break;
2030 }
2031
2032 case SHAPE_T::RECTANGLE:
2033 {
2034 int r = d->GetCornerRadius();
2035
2036 if( r > 0 )
2037 {
2038 // Rounded rectangle: four shortened straight sides + four quarter-circle arcs.
2039 int x1 = std::min( d->GetStart().x, d->GetEnd().x );
2040 int y1 = std::min( d->GetStart().y, d->GetEnd().y );
2041 int x2 = std::max( d->GetStart().x, d->GetEnd().x );
2042 int y2 = std::max( d->GetStart().y, d->GetEnd().y );
2043
2044 // Straight sides (between arc endpoints). Skip zero-length
2045 // sides that occur when one dimension equals 2*r (stadium).
2046 int w = x2 - x1;
2047 int h = y2 - y1;
2048 bool intersects = false;
2049
2050 if( w > 2 * r )
2051 {
2052 intersects |= segments_intersect( aP1, aP2, { x1 + r, y1 }, { x2 - r, y1 },
2053 intersectionPoints );
2054 intersects |= segments_intersect( aP1, aP2, { x2 - r, y2 }, { x1 + r, y2 },
2055 intersectionPoints );
2056 }
2057
2058 if( h > 2 * r )
2059 {
2060 intersects |= segments_intersect( aP1, aP2, { x2, y1 + r }, { x2, y2 - r },
2061 intersectionPoints );
2062 intersects |= segments_intersect( aP1, aP2, { x1, y2 - r }, { x1, y1 + r },
2063 intersectionPoints );
2064 }
2065
2066 if( intersects && !TestGrooveWidth )
2067 return false;
2068
2069 // Corner arcs, matching the decomposition in TransformEdgeToCreepShapes.
2070 // Stadium shapes get semicircles instead of four quarter-arcs to
2071 // avoid duplicate centers that cause division by zero in Paths().
2072 struct CornerArcRange
2073 {
2075 EDA_ANGLE startAngle;
2076 EDA_ANGLE endAngle;
2077 };
2078
2079 std::vector<CornerArcRange> arcs;
2080
2081 if( h == 2 * r )
2082 {
2083 // Horizontal stadium: left and right semicircles. Each cap spans
2084 // the outer half of its circle so the modeled boundary matches the
2085 // decomposition in TransformEdgeToCreepShapes.
2086 arcs.push_back( { { x1 + r, y1 + r },
2087 EDA_ANGLE( 90.0, DEGREES_T ),
2088 EDA_ANGLE( 270.0, DEGREES_T ) } );
2089 arcs.push_back( { { x2 - r, y1 + r },
2090 EDA_ANGLE( -90.0, DEGREES_T ),
2091 EDA_ANGLE( 90.0, DEGREES_T ) } );
2092 }
2093 else if( w == 2 * r )
2094 {
2095 // Vertical stadium: top and bottom semicircles
2096 arcs.push_back( { { x1 + r, y1 + r },
2097 EDA_ANGLE( -180.0, DEGREES_T ),
2098 EDA_ANGLE( 0.0, DEGREES_T ) } );
2099 arcs.push_back( { { x1 + r, y2 - r },
2100 EDA_ANGLE( 0.0, DEGREES_T ),
2101 EDA_ANGLE( 180.0, DEGREES_T ) } );
2102 }
2103 else
2104 {
2105 arcs = {
2106 { { x1 + r, y1 + r }, EDA_ANGLE( -180.0, DEGREES_T ),
2107 EDA_ANGLE( -90.0, DEGREES_T ) },
2108 { { x2 - r, y1 + r }, EDA_ANGLE( -90.0, DEGREES_T ),
2109 EDA_ANGLE( 0.0, DEGREES_T ) },
2110 { { x2 - r, y2 - r }, EDA_ANGLE( 0.0, DEGREES_T ),
2111 EDA_ANGLE( 90.0, DEGREES_T ) },
2112 { { x1 + r, y2 - r }, EDA_ANGLE( 90.0, DEGREES_T ),
2113 EDA_ANGLE( 180.0, DEGREES_T ) },
2114 };
2115 }
2116
2117 for( const CornerArcRange& ca : arcs )
2118 {
2119 bool arcIntersects = segmentIntersectsArc( aP1, aP2, ca.center, r,
2120 ca.startAngle, ca.endAngle,
2121 &intersectionPoints );
2122
2123 if( arcIntersects && !TestGrooveWidth )
2124 return false;
2125 }
2126 }
2127 else
2128 {
2129 VECTOR2I c1 = d->GetStart();
2130 VECTOR2I c2( d->GetStart().x, d->GetEnd().y );
2131 VECTOR2I c3 = d->GetEnd();
2132 VECTOR2I c4( d->GetEnd().x, d->GetStart().y );
2133
2134 bool intersects = false;
2135 intersects |= segments_intersect( aP1, aP2, c1, c2, intersectionPoints );
2136 intersects |= segments_intersect( aP1, aP2, c2, c3, intersectionPoints );
2137 intersects |= segments_intersect( aP1, aP2, c3, c4, intersectionPoints );
2138 intersects |= segments_intersect( aP1, aP2, c4, c1, intersectionPoints );
2139
2140 if( intersects && !TestGrooveWidth )
2141 return false;
2142 }
2143
2144 break;
2145 }
2146
2147 case SHAPE_T::POLY:
2148 {
2149 std::vector<VECTOR2I> points = d->GetPolyPoints();
2150
2151 if( points.size() < 2 )
2152 break;
2153
2154 VECTOR2I prevPoint = points.back();
2155
2156 bool intersects = false;
2157
2158 for( const VECTOR2I& p : points )
2159 {
2160 intersects |= segments_intersect( aP1, aP2, prevPoint, p, intersectionPoints );
2161 prevPoint = p;
2162 }
2163
2164 if( intersects && !TestGrooveWidth )
2165 return false;
2166
2167 break;
2168 }
2169
2170 case SHAPE_T::CIRCLE:
2171 {
2172 VECTOR2I center = d->GetCenter();
2173 double radius = d->GetRadius();
2174
2175 bool intersects = segmentIntersectsCircle( aP1, aP2, center, radius, &intersectionPoints );
2176
2177 if( intersects && !TestGrooveWidth )
2178 return false;
2179
2180 break;
2181 }
2182
2183 case SHAPE_T::ARC:
2184 {
2185 VECTOR2I center = d->GetCenter();
2186 double radius = d->GetRadius();
2187
2188 EDA_ANGLE A, B;
2189 d->CalcArcAngles( A, B );
2190
2191 bool intersects = segmentIntersectsArc( aP1, aP2, center, radius, A, B, &intersectionPoints );
2192
2193 if( intersects && !TestGrooveWidth )
2194 return false;
2195
2196 break;
2197 }
2198
2199 default:
2200 break;
2201 }
2202 }
2203
2204 if( intersectionPoints.size() <= 0 )
2205 return true;
2206
2207 if( intersectionPoints.size() % 2 != 0 )
2208 return false; // Should not happen if the start and end are both on the board
2209
2210 int minx = intersectionPoints[0].x;
2211 int maxx = intersectionPoints[0].x;
2212 int miny = intersectionPoints[0].y;
2213 int maxy = intersectionPoints[0].y;
2214
2215 for( const VECTOR2I& v : intersectionPoints )
2216 {
2217 minx = v.x < minx ? v.x : minx;
2218 maxx = v.x > maxx ? v.x : maxx;
2219 miny = v.x < miny ? v.x : miny;
2220 maxy = v.x > maxy ? v.x : maxy;
2221 }
2222
2223 if( abs( maxx - minx ) > abs( maxy - miny ) )
2224 {
2225 std::sort( intersectionPoints.begin(), intersectionPoints.end(),
2226 []( const VECTOR2I& a, const VECTOR2I& b )
2227 {
2228 return a.x > b.x;
2229 } );
2230 }
2231 else
2232 {
2233 std::sort( intersectionPoints.begin(), intersectionPoints.end(),
2234 []( const VECTOR2I& a, const VECTOR2I& b )
2235 {
2236 return a.y > b.y;
2237 } );
2238 }
2239
2240 int GVSquared = aMinGrooveWidth * aMinGrooveWidth;
2241
2242 for( size_t i = 0; i < intersectionPoints.size(); i += 2 )
2243 {
2244 if( intersectionPoints[i].SquaredDistance( intersectionPoints[i + 1] ) > GVSquared )
2245 return false;
2246 }
2247
2248 return true;
2249}
2250
2251
2252std::vector<PATH_CONNECTION> GetPaths( CREEP_SHAPE* aS1, CREEP_SHAPE* aS2, double aMaxWeight )
2253{
2254 double maxWeight = aMaxWeight;
2255 double maxWeightSquared = maxWeight * maxWeight;
2256 std::vector<PATH_CONNECTION> result;
2257
2258 CU_SHAPE_SEGMENT* cusegment1 = dynamic_cast<CU_SHAPE_SEGMENT*>( aS1 );
2259 CU_SHAPE_SEGMENT* cusegment2 = dynamic_cast<CU_SHAPE_SEGMENT*>( aS2 );
2260 CU_SHAPE_CIRCLE* cucircle1 = dynamic_cast<CU_SHAPE_CIRCLE*>( aS1 );
2261 CU_SHAPE_CIRCLE* cucircle2 = dynamic_cast<CU_SHAPE_CIRCLE*>( aS2 );
2262 CU_SHAPE_ARC* cuarc1 = dynamic_cast<CU_SHAPE_ARC*>( aS1 );
2263 CU_SHAPE_ARC* cuarc2 = dynamic_cast<CU_SHAPE_ARC*>( aS2 );
2264
2265
2266 BE_SHAPE_POINT* bepoint1 = dynamic_cast<BE_SHAPE_POINT*>( aS1 );
2267 BE_SHAPE_POINT* bepoint2 = dynamic_cast<BE_SHAPE_POINT*>( aS2 );
2268 BE_SHAPE_CIRCLE* becircle1 = dynamic_cast<BE_SHAPE_CIRCLE*>( aS1 );
2269 BE_SHAPE_CIRCLE* becircle2 = dynamic_cast<BE_SHAPE_CIRCLE*>( aS2 );
2270 BE_SHAPE_ARC* bearc1 = dynamic_cast<BE_SHAPE_ARC*>( aS1 );
2271 BE_SHAPE_ARC* bearc2 = dynamic_cast<BE_SHAPE_ARC*>( aS2 );
2272
2273 // Cu to Cu
2274
2275 if( cuarc1 && cuarc2 )
2276 return cuarc1->Paths( *cuarc2, maxWeight, maxWeightSquared );
2277 if( cuarc1 && cucircle2 )
2278 return cuarc1->Paths( *cucircle2, maxWeight, maxWeightSquared );
2279 if( cuarc1 && cusegment2 )
2280 return cuarc1->Paths( *cusegment2, maxWeight, maxWeightSquared );
2281 if( cucircle1 && cuarc2 )
2282 return cucircle1->Paths( *cuarc2, maxWeight, maxWeightSquared );
2283 if( cucircle1 && cucircle2 )
2284 return cucircle1->Paths( *cucircle2, maxWeight, maxWeightSquared );
2285 if( cucircle1 && cusegment2 )
2286 return cucircle1->Paths( *cusegment2, maxWeight, maxWeightSquared );
2287 if( cusegment1 && cuarc2 )
2288 return cusegment1->Paths( *cuarc2, maxWeight, maxWeightSquared );
2289 if( cusegment1 && cucircle2 )
2290 return cusegment1->Paths( *cucircle2, maxWeight, maxWeightSquared );
2291 if( cusegment1 && cusegment2 )
2292 return cusegment1->Paths( *cusegment2, maxWeight, maxWeightSquared );
2293
2294
2295 // Cu to Be
2296
2297 if( cuarc1 && bearc2 )
2298 return cuarc1->Paths( *bearc2, maxWeight, maxWeightSquared );
2299 if( cuarc1 && becircle2 )
2300 return cuarc1->Paths( *becircle2, maxWeight, maxWeightSquared );
2301 if( cuarc1 && bepoint2 )
2302 return cuarc1->Paths( *bepoint2, maxWeight, maxWeightSquared );
2303 if( cucircle1 && bearc2 )
2304 return cucircle1->Paths( *bearc2, maxWeight, maxWeightSquared );
2305 if( cucircle1 && becircle2 )
2306 return cucircle1->Paths( *becircle2, maxWeight, maxWeightSquared );
2307 if( cucircle1 && bepoint2 )
2308 return cucircle1->Paths( *bepoint2, maxWeight, maxWeightSquared );
2309 if( cusegment1 && bearc2 )
2310 return cusegment1->Paths( *bearc2, maxWeight, maxWeightSquared );
2311 if( cusegment1 && becircle2 )
2312 return cusegment1->Paths( *becircle2, maxWeight, maxWeightSquared );
2313 if( cusegment1 && bepoint2 )
2314 return cusegment1->Paths( *bepoint2, maxWeight, maxWeightSquared );
2315
2316 // Reversed
2317
2318 if( cuarc2 && bearc1 )
2319 return bearc1->Paths( *cuarc2, maxWeight, maxWeightSquared );
2320 if( cuarc2 && becircle1 )
2321 return becircle1->Paths( *cuarc2, maxWeight, maxWeightSquared );
2322 if( cuarc2 && bepoint1 )
2323 return bepoint1->Paths( *cuarc2, maxWeight, maxWeightSquared );
2324 if( cucircle2 && bearc1 )
2325 return bearc1->Paths( *cucircle2, maxWeight, maxWeightSquared );
2326 if( cucircle2 && becircle1 )
2327 return becircle1->Paths( *cucircle2, maxWeight, maxWeightSquared );
2328 if( cucircle2 && bepoint1 )
2329 return bepoint1->Paths( *cucircle2, maxWeight, maxWeightSquared );
2330 if( cusegment2 && bearc1 )
2331 return bearc1->Paths( *cusegment2, maxWeight, maxWeightSquared );
2332 if( cusegment2 && becircle1 )
2333 return becircle1->Paths( *cusegment2, maxWeight, maxWeightSquared );
2334 if( cusegment2 && bepoint1 )
2335 return bepoint1->Paths( *cusegment2, maxWeight, maxWeightSquared );
2336
2337
2338 // Be to Be
2339
2340 if( bearc1 && bearc2 )
2341 return bearc1->Paths( *bearc2, maxWeight, maxWeightSquared );
2342 if( bearc1 && becircle2 )
2343 return bearc1->Paths( *becircle2, maxWeight, maxWeightSquared );
2344 if( bearc1 && bepoint2 )
2345 return bearc1->Paths( *bepoint2, maxWeight, maxWeightSquared );
2346 if( becircle1 && bearc2 )
2347 return becircle1->Paths( *bearc2, maxWeight, maxWeightSquared );
2348 if( becircle1 && becircle2 )
2349 return becircle1->Paths( *becircle2, maxWeight, maxWeightSquared );
2350 if( becircle1 && bepoint2 )
2351 return becircle1->Paths( *bepoint2, maxWeight, maxWeightSquared );
2352 if( bepoint1 && bearc2 )
2353 return bepoint1->Paths( *bearc2, maxWeight, maxWeightSquared );
2354 if( bepoint1 && becircle2 )
2355 return bepoint1->Paths( *becircle2, maxWeight, maxWeightSquared );
2356 if( bepoint1 && bepoint2 )
2357 return bepoint1->Paths( *bepoint2, maxWeight, maxWeightSquared );
2358
2359 return result;
2360}
2361
2362double CREEPAGE_GRAPH::Solve( std::shared_ptr<GRAPH_NODE>& aFrom, std::shared_ptr<GRAPH_NODE>& aTo,
2363 std::vector<std::shared_ptr<GRAPH_CONNECTION>>& aResult ) // Change to vector of pointers
2364{
2365 if( !aFrom || !aTo )
2366 return 0;
2367
2368 if( aFrom == aTo )
2369 return 0;
2370
2371 // Dijkstra's algorithm for shortest path
2372 std::unordered_map<GRAPH_NODE*, double> distances;
2373 std::unordered_map<GRAPH_NODE*, GRAPH_NODE*> previous;
2374
2375 // Each heap entry carries the tentative distance captured at push time. A comparator that read
2376 // the live distances map instead would let a decrease-key reinsertion silently corrupt the heap
2377 // ordering, so aTo could be popped on a non-shortest path and the early break would return it.
2378 using QUEUE_ITEM = std::pair<double, GRAPH_NODE*>;
2379
2380 auto cmp = []( const QUEUE_ITEM& aLeft, const QUEUE_ITEM& aRight )
2381 {
2382 if( aLeft.first == aRight.first )
2383 return aLeft.second > aRight.second; // Compare addresses to avoid ties.
2384 return aLeft.first > aRight.first;
2385 };
2386 std::priority_queue<QUEUE_ITEM, std::vector<QUEUE_ITEM>, decltype( cmp )> pq( cmp );
2387
2388 // Initialize distances to infinity for all nodes except the starting node
2389 for( const std::shared_ptr<GRAPH_NODE>& node : m_nodes )
2390 {
2391 if( node != nullptr )
2392 distances[node.get()] = std::numeric_limits<double>::infinity(); // Set to infinity
2393 }
2394
2395 distances[aFrom.get()] = 0.0;
2396 distances[aTo.get()] = std::numeric_limits<double>::infinity();
2397 pq.push( { 0.0, aFrom.get() } );
2398
2399 // Dijkstra's main loop
2400 while( !pq.empty() )
2401 {
2402 auto [dist, current] = pq.top();
2403 pq.pop();
2404
2405 // A stale entry left behind by a decrease-key reinsertion; its shorter copy was already
2406 // processed
2407 if( dist > distances[current] )
2408 continue;
2409
2410 if( current == aTo.get() )
2411 {
2412 break; // Shortest path found
2413 }
2414
2415 // Traverse neighbors
2416 for( const std::shared_ptr<GRAPH_CONNECTION>& connection : current->m_node_conns )
2417 {
2418 GRAPH_NODE* neighbor = ( connection->n1 ).get() == current ? ( connection->n2 ).get()
2419 : ( connection->n1 ).get();
2420
2421 if( !neighbor )
2422 continue;
2423
2424 // Ignore connections with negative weights as Dijkstra doesn't support them.
2425 if( connection->m_path.weight < 0.0 )
2426 {
2427 wxLogTrace( "CREEPAGE", "Negative weight connection found. Ignoring connection." );
2428 continue;
2429 }
2430
2431 double alt = distances[current] + connection->m_path.weight; // Calculate alternative path cost
2432
2433 if( alt < distances[neighbor] )
2434 {
2435 distances[neighbor] = alt;
2436 previous[neighbor] = current;
2437 pq.push( { alt, neighbor } );
2438 }
2439 }
2440 }
2441
2442 double pathWeight = distances[aTo.get()];
2443
2444 // If aTo is unreachable, return infinity
2445 if( pathWeight == std::numeric_limits<double>::infinity() )
2446 return std::numeric_limits<double>::infinity();
2447
2448 // Trace back the path from aTo to aFrom
2449 GRAPH_NODE* step = aTo.get();
2450
2451 while( step != aFrom.get() )
2452 {
2453 GRAPH_NODE* prevNode = previous[step];
2454
2455 for( const std::shared_ptr<GRAPH_CONNECTION>& node_conn : step->m_node_conns )
2456 {
2457 if( ( ( node_conn->n1 ).get() == prevNode && ( node_conn->n2 ).get() == step )
2458 || ( ( node_conn->n1 ).get() == step && ( node_conn->n2 ).get() == prevNode ) )
2459 {
2460 aResult.push_back( node_conn );
2461 break;
2462 }
2463 }
2464 step = prevNode;
2465 }
2466
2467 return pathWeight;
2468}
2469
2470void CREEPAGE_GRAPH::Addshape( const SHAPE& aShape, std::shared_ptr<GRAPH_NODE>& aConnectTo,
2471 BOARD_ITEM* aParent )
2472{
2473 std::unique_ptr<CREEP_SHAPE> newshape;
2474
2475 if( !aConnectTo )
2476 return;
2477
2478 switch( aShape.Type() )
2479 {
2480 case SH_SEGMENT:
2481 {
2482 const SHAPE_SEGMENT& segment = dynamic_cast<const SHAPE_SEGMENT&>( aShape );
2483 newshape = std::make_unique<CU_SHAPE_SEGMENT>( segment.GetSeg().A, segment.GetSeg().B,
2484 segment.GetWidth() );
2485 break;
2486 }
2487
2488 case SH_CIRCLE:
2489 {
2490 const SHAPE_CIRCLE& circle = dynamic_cast<const SHAPE_CIRCLE&>( aShape );
2491 newshape = std::make_unique<CU_SHAPE_CIRCLE>( circle.GetCenter(), circle.GetRadius() );
2492 break;
2493 }
2494
2495 case SH_ARC:
2496 {
2497 const SHAPE_ARC& arc = dynamic_cast<const SHAPE_ARC&>( aShape );
2498 EDA_ANGLE alpha, beta;
2499 VECTOR2I start, end;
2500
2502
2503 if( arc.IsClockwise() )
2504 {
2505 edaArc.SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() );
2506 start = arc.GetP0();
2507 end = arc.GetP1();
2508 }
2509 else
2510 {
2511 edaArc.SetArcGeometry( arc.GetP1(), arc.GetArcMid(), arc.GetP0() );
2512 start = arc.GetP1();
2513 end = arc.GetP0();
2514 }
2515
2516 edaArc.CalcArcAngles( alpha, beta );
2517
2518 auto cuarc = std::make_unique<CU_SHAPE_ARC>( edaArc.getCenter(), edaArc.GetRadius(), alpha, beta,
2519 arc.GetP0(), arc.GetP1() );
2520 cuarc->SetWidth( arc.GetWidth() );
2521 newshape = std::move( cuarc );
2522 break;
2523 }
2524
2525 case SH_COMPOUND:
2526 {
2527 int nbShapes = static_cast<const SHAPE_COMPOUND*>( &aShape )->Shapes().size();
2528 for( const SHAPE* subshape : ( static_cast<const SHAPE_COMPOUND*>( &aShape )->Shapes() ) )
2529 {
2530 if( subshape )
2531 {
2532 // We don't want to add shape for the inner rectangle of rounded rectangles
2533 if( !( ( subshape->Type() == SH_RECT ) && ( nbShapes == 5 ) ) )
2534 Addshape( *subshape, aConnectTo, aParent );
2535 }
2536 }
2537 break;
2538 }
2539
2540 case SH_POLY_SET:
2541 {
2542 const SHAPE_POLY_SET& polySet = dynamic_cast<const SHAPE_POLY_SET&>( aShape );
2543
2544 for( auto it = polySet.CIterateSegmentsWithHoles(); it; it++ )
2545 {
2546 const SEG object = *it;
2547 SHAPE_SEGMENT segment( object.A, object.B );
2548 Addshape( segment, aConnectTo, aParent );
2549 }
2550 break;
2551 }
2552
2553 case SH_LINE_CHAIN:
2554 {
2555 const SHAPE_LINE_CHAIN& lineChain = dynamic_cast<const SHAPE_LINE_CHAIN&>( aShape );
2556
2557 VECTOR2I prevPoint = lineChain.CLastPoint();
2558
2559 for( const VECTOR2I& point : lineChain.CPoints() )
2560 {
2561 SHAPE_SEGMENT segment( point, prevPoint );
2562 prevPoint = point;
2563 Addshape( segment, aConnectTo, aParent );
2564 }
2565
2566 break;
2567 }
2568
2569 case SH_SIMPLE:
2570 {
2571 // SHAPE_SIMPLE is the arbitrary-polygon form used for rectangular, trapezoidal and
2572 // chamfered pads when they are not axis-aligned (orthogonal rotations collapse to
2573 // SH_RECT instead). Decompose its closed outline into segments so the copper edge
2574 // is added to the graph, otherwise the pad contributes no creepage anchor and the
2575 // path snaps to the pad hole instead of the copper (issue #24543).
2576 const SHAPE_SIMPLE& simple = dynamic_cast<const SHAPE_SIMPLE&>( aShape );
2577 const SHAPE_LINE_CHAIN& vertices = simple.Vertices();
2578
2579 if( vertices.PointCount() < 3 )
2580 break;
2581
2582 VECTOR2I prevPoint = vertices.CLastPoint();
2583
2584 for( const VECTOR2I& point : vertices.CPoints() )
2585 {
2586 if( point != prevPoint )
2587 Addshape( SHAPE_SEGMENT( prevPoint, point ), aConnectTo, aParent );
2588
2589 prevPoint = point;
2590 }
2591
2592 break;
2593 }
2594
2595 case SH_RECT:
2596 {
2597 const SHAPE_RECT& rect = dynamic_cast<const SHAPE_RECT&>( aShape );
2598
2599 VECTOR2I point0 = rect.GetPosition();
2600 VECTOR2I point1 = rect.GetPosition() + VECTOR2I( rect.GetSize().x, 0 );
2601 VECTOR2I point2 = rect.GetPosition() + rect.GetSize();
2602 VECTOR2I point3 = rect.GetPosition() + VECTOR2I( 0, rect.GetSize().y );
2603
2604 Addshape( SHAPE_SEGMENT( point0, point1 ), aConnectTo, aParent );
2605 Addshape( SHAPE_SEGMENT( point1, point2 ), aConnectTo, aParent );
2606 Addshape( SHAPE_SEGMENT( point2, point3 ), aConnectTo, aParent );
2607 Addshape( SHAPE_SEGMENT( point3, point0 ), aConnectTo, aParent );
2608 break;
2609 }
2610
2611 default:
2612 break;
2613 }
2614
2615 if( !newshape )
2616 return;
2617
2618 std::shared_ptr<GRAPH_NODE> gnShape = nullptr;
2619
2620 newshape->SetParent( aParent );
2621
2622 switch( aShape.Type() )
2623 {
2624 case SH_SEGMENT: gnShape = AddNode( GRAPH_NODE::SEGMENT, newshape.get(), newshape->GetPos() ); break;
2625 case SH_CIRCLE: gnShape = AddNode( GRAPH_NODE::CIRCLE, newshape.get(), newshape->GetPos() ); break;
2626 case SH_ARC: gnShape = AddNode( GRAPH_NODE::ARC, newshape.get(), newshape->GetPos() ); break;
2627 default: break;
2628 }
2629
2630 if( gnShape )
2631 {
2632 m_shapeCollection.push_back( std::move( newshape ) );
2633 gnShape->m_net = aConnectTo->m_net;
2634 std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gnShape, aConnectTo );
2635
2636 if( gc )
2637 gc->m_path.m_show = false;
2638 }
2639}
2640
2641void CREEPAGE_GRAPH::GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer,
2642 const std::set<int>* aRelevantNets )
2643{
2644 auto irrelevantPair = [&]( const std::shared_ptr<GRAPH_NODE>& gn1,
2645 const std::shared_ptr<GRAPH_NODE>& gn2 ) -> bool
2646 {
2647 return aRelevantNets && gn1->m_parent && gn2->m_parent && gn1->m_parent->IsConductive()
2648 && gn2->m_parent->IsConductive() && !aRelevantNets->count( gn1->m_net )
2649 && !aRelevantNets->count( gn2->m_net );
2650 };
2651
2652 std::vector<std::shared_ptr<GRAPH_NODE>> nodes;
2653 std::mutex nodes_lock;
2655
2656 std::vector<CREEPAGE_TRACK_ENTRY*> trackEntries;
2657 TRACK_RTREE::Builder trackBuilder;
2658
2659 if( aLayer != Edge_Cuts )
2660 {
2661 for( PCB_TRACK* track : m_board.Tracks() )
2662 {
2663 if( track && track->Type() == KICAD_T::PCB_TRACE_T && track->IsOnLayer( aLayer ) )
2664 {
2665 std::shared_ptr<SHAPE> sh = track->GetEffectiveShape();
2666
2667 if( sh && sh->Type() == SHAPE_TYPE::SH_SEGMENT )
2668 {
2670 entry->segment = SEG( track->GetStart(), track->GetEnd() );
2671 entry->layer = aLayer;
2672 entry->halfWidth = track->GetWidth() / 2;
2673 entry->track = track;
2674
2675 BOX2I bbox = track->GetBoundingBox();
2676 int minCoords[2] = { bbox.GetX(), bbox.GetY() };
2677 int maxCoords[2] = { bbox.GetRight(), bbox.GetBottom() };
2678 trackBuilder.Add( minCoords, maxCoords, entry );
2679 trackEntries.push_back( entry );
2680 }
2681 }
2682 }
2683 }
2684
2685 TRACK_RTREE trackIndex = trackBuilder.Build();
2686
2687 std::copy_if( m_nodes.begin(), m_nodes.end(), std::back_inserter( nodes ),
2688 [&]( const std::shared_ptr<GRAPH_NODE>& gn )
2689 {
2690 return gn && gn->m_parent && gn->m_connectDirectly && ( gn->m_type != GRAPH_NODE::TYPE::VIRTUAL );
2691 } );
2692
2693 std::sort( nodes.begin(), nodes.end(),
2694 []( const std::shared_ptr<GRAPH_NODE>& gn1, const std::shared_ptr<GRAPH_NODE>& gn2 )
2695 {
2696 return gn1->m_parent < gn2->m_parent
2697 || ( gn1->m_parent == gn2->m_parent && gn1->m_net < gn2->m_net );
2698 } );
2699
2700 // Build parent -> net -> nodes mapping for efficient filtering
2701 // Also cache bounding boxes for early spatial filtering
2702 std::unordered_map<const BOARD_ITEM*, std::unordered_map<int, std::vector<std::shared_ptr<GRAPH_NODE>>>> parent_net_groups;
2703 std::unordered_map<const BOARD_ITEM*, BOX2I> parent_bboxes;
2704 std::vector<const BOARD_ITEM*> parent_keys;
2705
2706 for( const auto& gn : nodes )
2707 {
2708 const BOARD_ITEM* parent = gn->m_parent->GetParent();
2709
2710 if( parent_net_groups[parent].empty() )
2711 {
2712 parent_keys.push_back( parent );
2713 if( parent )
2714 parent_bboxes[parent] = parent->GetBoundingBox();
2715 }
2716
2717 parent_net_groups[parent][gn->m_net].push_back( gn );
2718 }
2719
2720 // Generate work items using parent-level spatial indexing
2721 std::vector<std::pair<std::shared_ptr<GRAPH_NODE>, std::shared_ptr<GRAPH_NODE>>> work_items;
2722
2723 // Use RTree for spatial indexing of parent bounding boxes
2724 // Expand each bbox by maxWeight to find potentially overlapping parents
2725
2726 int64_t maxDist = static_cast<int64_t>( aMaxWeight );
2727
2728 struct ParentEntry
2729 {
2730 const BOARD_ITEM* parent;
2731 BOX2I bbox;
2732 };
2733
2734 std::vector<ParentEntry> parentEntries;
2735
2736 for( const auto* parent : parent_keys )
2737 {
2738 if( parent )
2739 {
2740 ParentEntry entry;
2741 entry.parent = parent;
2742 entry.bbox = parent_bboxes[parent];
2743 parentEntries.push_back( entry );
2744 }
2745 }
2746
2748
2749 for( ParentEntry& entry : parentEntries )
2750 {
2751 int minCoords[2] = { entry.bbox.GetLeft(), entry.bbox.GetTop() };
2752 int maxCoords[2] = { entry.bbox.GetRight(), entry.bbox.GetBottom() };
2753 parentBuilder.Add( minCoords, maxCoords, &entry );
2754 }
2755
2756 auto parentIndex = parentBuilder.Build();
2757
2758 // Parallelize parent pair search using thread pool
2759 std::mutex work_items_lock;
2760
2761 auto searchParent = [&]( size_t i ) -> bool
2762 {
2763 const ParentEntry& entry1 = parentEntries[i];
2764 const BOARD_ITEM* parent1 = entry1.parent;
2765 BOX2I bbox1 = entry1.bbox;
2766
2767 std::vector<std::pair<std::shared_ptr<GRAPH_NODE>, std::shared_ptr<GRAPH_NODE>>> localWorkItems;
2768
2769 // Search for parents within maxDist of bbox1
2770 int searchMin[2] = { bbox1.GetLeft() - (int) maxDist, bbox1.GetTop() - (int) maxDist };
2771 int searchMax[2] = { bbox1.GetRight() + (int) maxDist, bbox1.GetBottom() + (int) maxDist };
2772
2773 auto parentVisitor = [&]( ParentEntry* entry2 ) -> bool
2774 {
2775 const BOARD_ITEM* parent2 = entry2->parent;
2776
2777 // Only process if parent1 < parent2 to avoid duplicates
2778 if( parent1 >= parent2 )
2779 return true;
2780
2781 // Precise bbox distance check
2782 BOX2I bbox2 = entry2->bbox;
2783
2784 int64_t bboxDistX = 0;
2785
2786 if( bbox2.GetLeft() > bbox1.GetRight() )
2787 bboxDistX = bbox2.GetLeft() - bbox1.GetRight();
2788 else if( bbox1.GetLeft() > bbox2.GetRight() )
2789 bboxDistX = bbox1.GetLeft() - bbox2.GetRight();
2790
2791 int64_t bboxDistY = 0;
2792
2793 if( bbox2.GetTop() > bbox1.GetBottom() )
2794 bboxDistY = bbox2.GetTop() - bbox1.GetBottom();
2795 else if( bbox1.GetTop() > bbox2.GetBottom() )
2796 bboxDistY = bbox1.GetTop() - bbox2.GetBottom();
2797
2798 int64_t bboxDistSq = bboxDistX * bboxDistX + bboxDistY * bboxDistY;
2799
2800 if( bboxDistSq > maxDist * maxDist )
2801 return true;
2802
2803 // Get nodes for both parents (thread-safe reads from const map)
2804 auto it1 = parent_net_groups.find( parent1 );
2805 auto it2 = parent_net_groups.find( parent2 );
2806
2807 if( it1 == parent_net_groups.end() || it2 == parent_net_groups.end() )
2808 return true;
2809
2810 for( const auto& [net1, nodes1] : it1->second )
2811 {
2812 for( const auto& [net2, nodes2] : it2->second )
2813 {
2814 // Skip same net if both are conductive
2815 if( net1 == net2 && !nodes1.empty() && !nodes2.empty() )
2816 {
2817 if( nodes1[0]->m_parent->IsConductive()
2818 && nodes2[0]->m_parent->IsConductive() )
2819 continue;
2820 }
2821
2822 for( const auto& gn1 : nodes1 )
2823 {
2824 for( const auto& gn2 : nodes2 )
2825 {
2826 VECTOR2I pos1 = gn1->m_parent->GetPos();
2827 VECTOR2I pos2 = gn2->m_parent->GetPos();
2828 int r1 = gn1->m_parent->GetRadius();
2829 int r2 = gn2->m_parent->GetRadius();
2830
2831 int64_t centerDistSq = ( pos1 - pos2 ).SquaredEuclideanNorm();
2832 double threshold = aMaxWeight + r1 + r2;
2833 double thresholdSq = threshold * threshold;
2834
2835 if( (double) centerDistSq > thresholdSq )
2836 continue;
2837
2838 if( irrelevantPair( gn1, gn2 ) )
2839 continue;
2840
2841 localWorkItems.push_back( { gn1, gn2 } );
2842 }
2843 }
2844 }
2845 }
2846
2847 return true;
2848 };
2849
2850 parentIndex.Search( searchMin, searchMax, parentVisitor );
2851
2852 // Merge local results into global
2853 if( !localWorkItems.empty() )
2854 {
2855 std::lock_guard<std::mutex> lock( work_items_lock );
2856 work_items.insert( work_items.end(), localWorkItems.begin(), localWorkItems.end() );
2857 }
2858
2859 return true;
2860 };
2861
2862 // Use thread pool if there are enough parents
2863 if( parentEntries.size() > 100 && tp.get_tasks_total() < tp.get_thread_count() - 4 )
2864 {
2865 auto ret = tp.submit_loop( 0, parentEntries.size(), searchParent );
2866
2867 for( auto& r : ret )
2868 {
2869 if( r.valid() )
2870 r.wait();
2871 }
2872 }
2873 else
2874 {
2875 for( size_t i = 0; i < parentEntries.size(); ++i )
2876 searchParent( i );
2877 }
2878
2879 // Generate work items for same-parent node pairs. The cross-parent search above
2880 // skips pairs where parent1 == parent2, but creepage paths between different edge
2881 // segments of the same slot (which share a footprint grandparent) are needed for
2882 // the path to navigate around the slot geometry. Also handles null-parent nodes
2883 // (e.g. NPTH pad shapes) which were excluded from the RTree search entirely.
2884 for( const auto& [parent, net_groups] : parent_net_groups )
2885 {
2886 std::vector<std::shared_ptr<GRAPH_NODE>> sameParentNodes;
2887
2888 for( const auto& [net, nodeList] : net_groups )
2889 sameParentNodes.insert( sameParentNodes.end(), nodeList.begin(), nodeList.end() );
2890
2891 for( size_t i = 0; i < sameParentNodes.size(); i++ )
2892 {
2893 for( size_t j = i + 1; j < sameParentNodes.size(); j++ )
2894 {
2895 auto& gn1 = sameParentNodes[i];
2896 auto& gn2 = sameParentNodes[j];
2897
2898 // ConnectChildren already handles nodes on the same CREEP_SHAPE
2899 if( gn1->m_parent == gn2->m_parent )
2900 continue;
2901
2902 // Skip same-net conductive pairs
2903 if( gn1->m_parent->IsConductive() && gn2->m_parent->IsConductive()
2904 && gn1->m_net == gn2->m_net )
2905 {
2906 continue;
2907 }
2908
2909 VECTOR2I pos1 = gn1->m_parent->GetPos();
2910 VECTOR2I pos2 = gn2->m_parent->GetPos();
2911 int r1 = gn1->m_parent->GetRadius();
2912 int r2 = gn2->m_parent->GetRadius();
2913
2914 int64_t centerDistSq = ( pos1 - pos2 ).SquaredEuclideanNorm();
2915 double threshold = aMaxWeight + r1 + r2;
2916 double thresholdSq = threshold * threshold;
2917
2918 if( (double) centerDistSq > thresholdSq )
2919 continue;
2920
2921 if( irrelevantPair( gn1, gn2 ) )
2922 continue;
2923
2924 work_items.push_back( { gn1, gn2 } );
2925 }
2926 }
2927 }
2928
2929 auto processWorkItems =
2930 [&]( size_t idx ) -> bool
2931 {
2932 auto& [gn1, gn2] = work_items[idx];
2933
2934 // Distance filtering already done during work item creation
2935 CREEP_SHAPE* shape1 = gn1->m_parent;
2936 CREEP_SHAPE* shape2 = gn2->m_parent;
2937
2938 for( const PATH_CONNECTION& pc : GetPaths( shape1, shape2, aMaxWeight ) )
2939 {
2940 std::vector<const BOARD_ITEM*> IgnoreForTest;
2941
2942 // Don't ignore the whole parent board item for arc/circle ends. The
2943 // tangent touch is already handled by the endpoint exclusion in
2944 // segmentIntersectsArc/Circle (issue #24286). A rounded slot is a single
2945 // PCB_SHAPE, so ignoring the parent would exempt every other edge of the
2946 // same slot and let a path cut across it.
2947
2948 // Ignore each CU shape's own parent for the endpoint-inside-track
2949 // test so we don't reject paths that touch the track's own edge.
2950 if( shape1->IsConductive() )
2951 IgnoreForTest.push_back( shape1->GetParent() );
2952
2953 if( shape2->IsConductive() )
2954 IgnoreForTest.push_back( shape2->GetParent() );
2955
2956 bool valid = pc.isValid( m_board, aLayer, m_boardEdge, IgnoreForTest, m_boardOutline,
2957 { false, true }, m_minGrooveWidth, &trackIndex );
2958
2959 if( !valid )
2960 {
2961 continue;
2962 }
2963
2964 std::shared_ptr<GRAPH_NODE> connect1 = gn1, connect2 = gn2;
2965 std::lock_guard<std::mutex> lock( nodes_lock );
2966
2967 // Handle non-point node1
2968 if( gn1->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT_TYPE )
2969 {
2970 auto gnt1 = AddNode( GRAPH_NODE::POINT, gn1->m_parent, pc.a1 );
2971 gnt1->m_connectDirectly = false;
2972 connect1 = gnt1;
2973
2974 if( gn1->m_parent->IsConductive() )
2975 {
2976 if( std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gn1, gnt1 ) )
2977 gc->m_path.m_show = false;
2978 }
2979 }
2980
2981 // Handle non-point node2
2982 if( gn2->m_parent->GetType() != CREEP_SHAPE::TYPE::POINT_TYPE )
2983 {
2984 auto gnt2 = AddNode( GRAPH_NODE::POINT, gn2->m_parent, pc.a2 );
2985 gnt2->m_connectDirectly = false;
2986 connect2 = gnt2;
2987
2988 if( gn2->m_parent->IsConductive() )
2989 {
2990 if( std::shared_ptr<GRAPH_CONNECTION> gc = AddConnection( gn2, gnt2 ) )
2991 gc->m_path.m_show = false;
2992 }
2993 }
2994
2995 AddConnection( connect1, connect2, pc );
2996 }
2997
2998 return true;
2999 };
3000
3001 // If the number of tasks is high enough, this indicates that the calling process
3002 // has already parallelized the work, so we can process all items in one go.
3003 if( tp.get_tasks_total() >= tp.get_thread_count() - 4 )
3004 {
3005 for( size_t ii = 0; ii < work_items.size(); ii++ )
3006 processWorkItems( ii );
3007 }
3008 else
3009 {
3010 auto ret = tp.submit_loop( 0, work_items.size(), processWorkItems );
3011
3012 for( size_t ii = 0; ii < ret.size(); ii++ )
3013 {
3014 auto& r = ret[ii];
3015
3016 if( !r.valid() )
3017 continue;
3018
3019 while( r.wait_for( std::chrono::milliseconds( 100 ) ) != std::future_status::ready ){}
3020 }
3021 }
3022
3023 // Clean up track entries
3024 for( CREEPAGE_TRACK_ENTRY* entry : trackEntries )
3025 delete entry;
3026}
3027
3028
3029void CREEPAGE_GRAPH::detachConnection( const std::shared_ptr<GRAPH_CONNECTION>& aGc )
3030{
3031 if( !aGc )
3032 return;
3033
3034 for( const std::shared_ptr<GRAPH_NODE>& gn : { aGc->n1, aGc->n2 } )
3035 {
3036 if( gn )
3037 gn->m_node_conns.erase( aGc );
3038 }
3039}
3040
3041
3042void CREEPAGE_GRAPH::TruncateToPrefix( size_t aNodeCount, size_t aConnectionCount )
3043{
3044 size_t vectorSize = m_connections.size();
3045
3046 // Detach each connection from its endpoints' lists; the bulk resize drops them in one shot
3047 for( size_t i = aConnectionCount; i < vectorSize; i++ )
3049
3050 m_connections.resize( aConnectionCount, nullptr );
3051 m_nodes.resize( aNodeCount, nullptr );
3052
3053 // Without this, stale per-solve nodes corrupt subsequent FindNode/AddNode lookups
3054 m_nodeset.clear();
3055
3056 for( size_t i = 0; i < aNodeCount; ++i )
3057 {
3058 if( m_nodes[i] )
3059 m_nodeset.insert( m_nodes[i] );
3060 }
3061}
3062
3063
3064std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::AddNode( GRAPH_NODE::TYPE aType, CREEP_SHAPE* parent,
3065 const VECTOR2I& pos )
3066{
3067 std::shared_ptr<GRAPH_NODE> gn = FindNode( aType, parent, pos );
3068
3069 if( gn )
3070 return gn;
3071
3072 gn = std::make_shared<GRAPH_NODE>( aType, parent, pos );
3073 m_nodes.push_back( gn );
3074 m_nodeset.insert( gn );
3075 return gn;
3076}
3077
3078
3079std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::AddNodeVirtual()
3080{
3081 //Virtual nodes are always unique, do not try to find them
3082 std::shared_ptr<GRAPH_NODE> gn = std::make_shared<GRAPH_NODE>( GRAPH_NODE::TYPE::VIRTUAL, nullptr );
3083 m_nodes.push_back( gn );
3084 m_nodeset.insert( gn );
3085 return gn;
3086}
3087
3088
3089std::shared_ptr<GRAPH_CONNECTION> CREEPAGE_GRAPH::AddConnection( std::shared_ptr<GRAPH_NODE>& aN1,
3090 std::shared_ptr<GRAPH_NODE>& aN2,
3091 const PATH_CONNECTION& aPc )
3092{
3093 if( !aN1 || !aN2 )
3094 return nullptr;
3095
3096 wxASSERT_MSG( ( aN1 != aN2 ), "Creepage: a connection connects a node to itself" );
3097
3098 std::shared_ptr<GRAPH_CONNECTION> gc = std::make_shared<GRAPH_CONNECTION>( aN1, aN2, aPc );
3099 m_connections.push_back( gc );
3100 aN1->m_node_conns.insert( gc );
3101 aN2->m_node_conns.insert( gc );
3102
3103 return gc;
3104}
3105
3106
3107std::shared_ptr<GRAPH_CONNECTION> CREEPAGE_GRAPH::AddConnection( std::shared_ptr<GRAPH_NODE>& aN1,
3108 std::shared_ptr<GRAPH_NODE>& aN2 )
3109{
3110 if( !aN1 || !aN2 )
3111 return nullptr;
3112
3113 PATH_CONNECTION pc;
3114 pc.a1 = aN1->m_pos;
3115 pc.a2 = aN2->m_pos;
3116 pc.weight = 0;
3117
3118 return AddConnection( aN1, aN2, pc );
3119}
3120
3121
3122std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::FindNode( GRAPH_NODE::TYPE aType, CREEP_SHAPE* aParent,
3123 const VECTOR2I& aPos )
3124{
3125 auto it = m_nodeset.find( std::make_shared<GRAPH_NODE>( aType, aParent, aPos ) );
3126
3127 if( it != m_nodeset.end() )
3128 return *it;
3129
3130 return nullptr;
3131}
3132
3133
3134std::shared_ptr<GRAPH_NODE> CREEPAGE_GRAPH::AddNetElements( int aNetCode, PCB_LAYER_ID aLayer,
3135 int aMaxCreepage )
3136{
3137 std::shared_ptr<GRAPH_NODE> virtualNode = AddNodeVirtual();
3138 virtualNode->m_net = aNetCode;
3139
3140 for( FOOTPRINT* footprint : m_board.Footprints() )
3141 {
3142 for( PAD* pad : footprint->Pads() )
3143 {
3144 if( pad->GetNetCode() != aNetCode || !pad->IsOnLayer( aLayer ) )
3145 continue;
3146
3147 if( std::shared_ptr<SHAPE> padShape = pad->GetEffectiveShape( aLayer ) )
3148 Addshape( *padShape, virtualNode, pad );
3149 }
3150 }
3151
3152 for( PCB_TRACK* track : m_board.Tracks() )
3153 {
3154 if( track->GetNetCode() != aNetCode || !track->IsOnLayer( aLayer ) )
3155 continue;
3156
3157 if( std::shared_ptr<SHAPE> shape = track->GetEffectiveShape() )
3158 Addshape( *shape, virtualNode, track );
3159 }
3160
3161
3162 for( ZONE* zone : m_board.Zones() )
3163 {
3164 if( zone->GetIsRuleArea() )
3165 continue;
3166
3167 if( zone->GetNetCode() != aNetCode || !zone->IsOnLayer( aLayer ) )
3168 continue;
3169
3170 if( std::shared_ptr<SHAPE> shape = zone->GetEffectiveShape( aLayer ) )
3171 Addshape( *shape, virtualNode, zone );
3172 }
3173
3174 const DRAWINGS drawings = m_board.Drawings();
3175
3176 for( BOARD_ITEM* drawing : drawings )
3177 {
3178 if( drawing->IsConnected() )
3179 {
3180 BOARD_CONNECTED_ITEM* bci = static_cast<BOARD_CONNECTED_ITEM*>( drawing );
3181
3182 if( bci->GetNetCode() != aNetCode || !bci->IsOnLayer( aLayer ) )
3183 continue;
3184
3185 if( std::shared_ptr<SHAPE> shape = bci->GetEffectiveShape() )
3186 Addshape( *shape, virtualNode, bci );
3187 }
3188 }
3189
3190
3191 return virtualNode;
3192}
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
Creepage: a board edge arc.
std::pair< bool, bool > IsThereATangentPassingThroughPoint(const BE_SHAPE_POINT aPoint) const
EDA_ANGLE GetStartAngle() const override
int GetRadius() const override
BE_SHAPE_ARC(VECTOR2I aPos, int aRadius, EDA_ANGLE aStartAngle, EDA_ANGLE aEndAngle, VECTOR2D aStartPoint, VECTOR2D aEndPoint)
VECTOR2I GetStartPoint() const override
std::vector< PATH_CONNECTION > Paths(const BE_SHAPE_POINT &aS2, double aMaxWeight, double aMaxSquaredWeight) const override
void ConnectChildren(std::shared_ptr< GRAPH_NODE > &a1, std::shared_ptr< GRAPH_NODE > &a2, CREEPAGE_GRAPH &aG) const override
EDA_ANGLE GetEndAngle() const override
VECTOR2I GetEndPoint() const override
EDA_ANGLE AngleBetweenStartAndEnd(const VECTOR2I aPoint) const
Creepage: a board edge circle.
int GetRadius() const override
BE_SHAPE_CIRCLE(VECTOR2I aPos=VECTOR2I(0, 0), int aRadius=0)
void ShortenChildDueToGV(std::shared_ptr< GRAPH_NODE > &a1, std::shared_ptr< GRAPH_NODE > &a2, CREEPAGE_GRAPH &aG, double aNormalWeight) const
std::vector< PATH_CONNECTION > Paths(const BE_SHAPE_POINT &aS2, double aMaxWeight, double aMaxSquaredWeight) const override
void ConnectChildren(std::shared_ptr< GRAPH_NODE > &a1, std::shared_ptr< GRAPH_NODE > &a2, CREEPAGE_GRAPH &aG) const override
Creepage: a board edge point.
BE_SHAPE_POINT(VECTOR2I aPos)
void ConnectChildren(std::shared_ptr< GRAPH_NODE > &a1, std::shared_ptr< GRAPH_NODE > &a2, CREEPAGE_GRAPH &aG) const override
std::vector< PATH_CONNECTION > Paths(const BE_SHAPE_POINT &aS2, double aMaxWeight, double aMaxSquaredWeight) const override
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Test to see if this object is on the given layer.
Definition board_item.h:409
virtual std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const
Some pad shapes can be complex (rounded/chamfered rectangle), even without considering custom shapes.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
const std::vector< PAD * > GetPads() const
Return a reference to a list of all the pads.
Definition board.cpp:3908
const FOOTPRINTS & Footprints() const
Definition board.h:464
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
const DRAWINGS & Drawings() const
Definition board.h:466
constexpr coord_type GetY() const
Definition box2.h:205
constexpr coord_type GetX() const
Definition box2.h:204
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
A graph with nodes and connections for creepage calculation.
std::shared_ptr< GRAPH_NODE > AddNode(GRAPH_NODE::TYPE aType, CREEP_SHAPE *aParent=nullptr, const VECTOR2I &aPos=VECTOR2I())
std::shared_ptr< GRAPH_CONNECTION > AddConnection(std::shared_ptr< GRAPH_NODE > &aN1, std::shared_ptr< GRAPH_NODE > &aN2, const PATH_CONNECTION &aPc)
void SetTarget(double aTarget)
double Solve(std::shared_ptr< GRAPH_NODE > &aFrom, std::shared_ptr< GRAPH_NODE > &aTo, std::vector< std::shared_ptr< GRAPH_CONNECTION > > &aResult)
void Addshape(const SHAPE &aShape, std::shared_ptr< GRAPH_NODE > &aConnectTo, BOARD_ITEM *aParent=nullptr)
void GeneratePaths(double aMaxWeight, PCB_LAYER_ID aLayer, const std::set< int > *aRelevantNets=nullptr)
Generate creepage paths between graph nodes.
std::shared_ptr< GRAPH_NODE > AddNodeVirtual()
SHAPE_POLY_SET * m_boardOutline
void TransformCreepShapesToNodes(const std::vector< std::unique_ptr< CREEP_SHAPE > > &aShapes)
void TruncateToPrefix(size_t aNodeCount, size_t aConnectionCount)
Remove every node and connection added after the given prefix sizes, then rebuild the node lookup set...
std::vector< BOARD_ITEM * > m_boardEdge
std::vector< std::unique_ptr< CREEP_SHAPE > > m_shapeCollection
std::unordered_set< std::shared_ptr< GRAPH_NODE >, GraphNodeHash, GraphNodeEqual > m_nodeset
std::vector< std::shared_ptr< GRAPH_NODE > > m_nodes
std::vector< std::shared_ptr< GRAPH_CONNECTION > > m_connections
std::shared_ptr< GRAPH_NODE > AddNetElements(int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage)
std::shared_ptr< GRAPH_NODE > FindNode(GRAPH_NODE::TYPE aType, CREEP_SHAPE *aParent, const VECTOR2I &aPos)
void detachConnection(const std::shared_ptr< GRAPH_CONNECTION > &aGc)
A class used to represent the shapes for creepage calculation.
VECTOR2I GetPos() const
CREEP_SHAPE::TYPE GetType() const
virtual int GetRadius() const
const BOARD_ITEM * GetParent() const
virtual void ConnectChildren(std::shared_ptr< GRAPH_NODE > &a1, std::shared_ptr< GRAPH_NODE > &a2, CREEPAGE_GRAPH &aG) const
Creepage: a conductive arc.
VECTOR2I GetStartPoint() const override
EDA_ANGLE AngleBetweenStartAndEnd(const VECTOR2I aPoint) const
VECTOR2I GetEndPoint() const override
EDA_ANGLE GetStartAngle() const override
double GetWidth() const
CU_SHAPE_ARC(VECTOR2I aPos, double aRadius, EDA_ANGLE aStartAngle, EDA_ANGLE aEndAngle, VECTOR2D aStartPoint, VECTOR2D aEndPoint)
int GetRadius() const override
EDA_ANGLE GetEndAngle() const override
std::vector< PATH_CONNECTION > Paths(const BE_SHAPE_POINT &aS2, double aMaxWeight, double aMaxSquaredWeight) const override
Creepage: a conductive circle.
int GetRadius() const override
CU_SHAPE_CIRCLE(VECTOR2I aPos, double aRadius=0)
std::vector< PATH_CONNECTION > Paths(const BE_SHAPE_POINT &aS2, double aMaxWeight, double aMaxSquaredWeight) const override
Creepage: a conductive segment.
std::vector< PATH_CONNECTION > Paths(const BE_SHAPE_POINT &aS2, double aMaxWeight, double aMaxSquaredWeight) const override
VECTOR2I GetStart() const
double GetWidth() const
VECTOR2I GetEnd() const
CU_SHAPE_SEGMENT(VECTOR2I aStart, VECTOR2I aEnd, double aWidth=0)
double AsRadians() const
Definition eda_angle.h:119
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
void SetCenter(const VECTOR2I &aCenter)
VECTOR2I getCenter() const
std::vector< VECTOR2I > GetPolyPoints() const
Duplicate the polygon outlines into a flat list of VECTOR2I points.
void CalcArcAngles(EDA_ANGLE &aStartAngle, EDA_ANGLE &aEndAngle) const
Calc arc start and end angles such that aStartAngle < aEndAngle.
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
void RebuildBezierToSegmentsPointsList(int aMaxError)
Rebuild the m_bezierPoints vertex list that approximate the Bezier curve by a list of segments.
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:491
virtual void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
int GetCornerRadius() const
VECTOR2I GetArcMid() const
std::shared_ptr< GRAPH_NODE > n2
PATH_CONNECTION m_path
void GetShapes(std::vector< PCB_SHAPE > &aShapes)
std::shared_ptr< GRAPH_NODE > n1
std::set< std::shared_ptr< GRAPH_CONNECTION > > m_node_conns
Builder for constructing a PACKED_RTREE from a set of items.
void Add(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
Definition pad.h:61
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
void SetEnd(const VECTOR2I &aEnd) override
void SetStart(const VECTOR2I &aStart) override
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
bool IsClockwise() const
Definition shape_arc.h:323
int GetWidth() const override
Definition shape_arc.h:215
const VECTOR2I & GetP1() const
Definition shape_arc.h:115
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:96
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
bool PointOnEdge(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of some of the outlines or holes.
CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles() const
Return an iterator object, for the aOutline-th outline in the set (with holes).
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
const VECTOR2I & GetPosition() const
Definition shape_rect.h:164
const VECTOR2I GetSize() const
Definition shape_rect.h:172
const SEG & GetSeg() const
int GetWidth() const override
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
const SHAPE_LINE_CHAIN & Vertices() const
Return the list of vertices defining this simple polygon.
An abstract shape on 2D plane.
Definition shape.h:124
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:559
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:312
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
VECTOR2_TRAITS< int32_t >::extended_type extended_type
Definition vector2d.h:69
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:335
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:567
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
Handle a list of polygons defining a copper zone.
Definition zone.h:70
static bool empty(const wxTextEntryBase *aCtrl)
VECTOR2I closestPointOnSegment(const VECTOR2I &A, const VECTOR2I &B, const VECTOR2I &P)
bool SegmentIntersectsBoard(const VECTOR2I &aP1, const VECTOR2I &aP2, const std::vector< BOARD_ITEM * > &aBe, const std::vector< const BOARD_ITEM * > &aDontTestAgainst, int aMinGrooveWidth)
std::vector< PATH_CONNECTION > GetPaths(CREEP_SHAPE *aS1, CREEP_SHAPE *aS2, double aMaxWeight)
bool segmentIntersectsArc(const VECTOR2I &p1, const VECTOR2I &p2, const VECTOR2I &center, double radius, EDA_ANGLE startAngle, EDA_ANGLE endAngle, std::vector< VECTOR2I > *aIntersectionPoints=nullptr)
bool compareShapes(const CREEP_SHAPE *a, const CREEP_SHAPE *b)
bool segments_intersect(const VECTOR2I &p1, const VECTOR2I &q1, const VECTOR2I &p2, const VECTOR2I &q2, std::vector< VECTOR2I > &aIntersectionPoints)
void BuildCreepageBoardEdges(BOARD &aBoard, std::vector< BOARD_ITEM * > &aVector, std::vector< std::unique_ptr< PCB_SHAPE > > &aOwned, const std::set< const BOARD_ITEM * > *aExclude)
Collect the board-edge items used by the creepage graph.
bool areEquivalent(const CREEP_SHAPE *a, const CREEP_SHAPE *b)
bool segmentIntersectsCircle(const VECTOR2I &p1, const VECTOR2I &p2, const VECTOR2I &center, double radius, std::vector< VECTOR2I > *aIntersectPoints)
KIRTREE::PACKED_RTREE< CREEPAGE_TRACK_ENTRY *, int, 2 > TRACK_RTREE
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
@ RADIANS_T
Definition eda_angle.h:31
@ DEGREES_T
Definition eda_angle.h:30
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
@ NO_FILL
Definition eda_fill.h:30
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
std::variant< LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, SHAPE_ELLIPSE, BOX2I > INTERSECTABLE_GEOM
A variant type that can hold any of the supported geometry types for intersection calculations.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
Definition padstack.h:102
std::deque< BOARD_ITEM * > DRAWINGS
#define D(x)
Definition ptree.cpp:37
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:48
@ SH_RECT
axis-aligned rectangle
Definition shape.h:43
@ SH_CIRCLE
circle
Definition shape.h:46
@ SH_SIMPLE
simple polygon
Definition shape.h:47
@ SH_SEGMENT
line segment
Definition shape.h:44
@ SH_ARC
circular arc
Definition shape.h:50
@ SH_LINE_CHAIN
line chain (polyline)
Definition shape.h:45
@ SH_COMPOUND
compound shape, consisting of multiple simple shapes
Definition shape.h:49
int halfWidth
const PCB_TRACK * track
SEG segment
PCB_LAYER_ID layer
A visitor that visits INTERSECTABLE_GEOM variant objects with another (which is held as state: m_othe...
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
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_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707