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