KiCad PCB EDA Suite
Loading...
Searching...
No Matches
convert_shape_list_to_polygon.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <unordered_set>
23#include <deque>
24
25#include <trigo.h>
26#include <macros.h>
27
28#include <math/vector2d.h>
29#include <pcb_shape.h>
30#include <footprint.h>
31#include <pad.h>
32#include <base_units.h>
36#include <geometry/roundrect.h>
39#include <board.h>
41#include <collectors.h>
42#include <set>
43
44#include <nanoflann.hpp>
45
46#include <wx/log.h>
47
48
56const wxChar* traceBoardOutline = wxT( "KICAD_BOARD_OUTLINE" );
57
58
59class SCOPED_FLAGS_CLEANER : public std::unordered_set<EDA_ITEM*>
60{
62
63public:
64 SCOPED_FLAGS_CLEANER( const EDA_ITEM_FLAGS& aFlagsToClear ) : m_flagsToClear( aFlagsToClear ) {}
65
67 {
68 for( EDA_ITEM* item : *this )
70 }
71
72 // No copying or it can lead to double or early-clearing of the flags.
75};
76
77
86static bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit )
87{
88 return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
89}
90
91
100static bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond )
101{
102 return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm();
103}
104
105
109static VECTOR2I free_end( const PCB_SHAPE* aFirst, const PCB_SHAPE* aSecond )
110{
111 auto gap =
112 [aSecond]( const VECTOR2I& aPt )
113 {
114 return std::min( ( aPt - aSecond->GetStart() ).SquaredEuclideanNorm(),
115 ( aPt - aSecond->GetEnd() ).SquaredEuclideanNorm() );
116 };
117
118 return gap( aFirst->GetStart() ) <= gap( aFirst->GetEnd() ) ? aFirst->GetEnd()
119 : aFirst->GetStart();
120}
121
122
123static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape )
124{
125 bool padOutside = false;
126
127 for( PAD* pad : aFootprint->Pads() )
128 {
129 pad->Padstack().ForEachUniqueLayer(
130 [&]( PCB_LAYER_ID aLayer )
131 {
133
134 poly.ClearArcs();
135
136 poly.BooleanIntersection( *pad->GetEffectivePolygon( aLayer, ERROR_INSIDE ) );
137
138 if( poly.OutlineCount() == 0 )
139 {
140 VECTOR2I padPos = pad->GetPosition();
141 wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): outside" ), padPos.x, padPos.y );
142 padOutside = true;
143 }
144 } );
145
146 if( padOutside )
147 break;
148
149 VECTOR2I padPos = pad->GetPosition();
150 wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): not outside" ), padPos.x, padPos.y );
151 }
152
153 return padOutside;
154}
155
156
158{
159 std::vector<std::pair<VECTOR2I, PCB_SHAPE*>> endpoints;
160
161 PCB_SHAPE_ENDPOINTS_ADAPTOR( const std::vector<PCB_SHAPE*>& shapes )
162 {
163 endpoints.reserve( shapes.size() * 2 );
164
165 for( PCB_SHAPE* shape : shapes )
166 {
167 endpoints.emplace_back( shape->GetStart(), shape );
168 endpoints.emplace_back( shape->GetEnd(), shape );
169 }
170 }
171
172 // Required by nanoflann
173 size_t kdtree_get_point_count() const { return endpoints.size(); }
174
175 // Returns the dim'th component of the idx'th point
176 double kdtree_get_pt( const size_t idx, const size_t dim ) const
177 {
178 if( dim == 0 )
179 return static_cast<double>( endpoints[idx].first.x );
180 else
181 return static_cast<double>( endpoints[idx].first.y );
182 }
183
184 template <class BBOX>
185 bool kdtree_get_bbox( BBOX& ) const
186 {
187 return false;
188 }
189};
190
191using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PCB_SHAPE_ENDPOINTS_ADAPTOR>,
193 2 /* dim */ >;
194
195static void processClosedShape( PCB_SHAPE* aShape, SHAPE_LINE_CHAIN& aContour,
196 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*>& aShapeOwners,
197 int aErrorMax, bool aAllowUseArcsInPolygons )
198{
199 switch( aShape->GetShape() )
200 {
201 case SHAPE_T::POLY:
202 {
203 VECTOR2I prevPt;
204 bool firstPt = true;
205
206 for( auto it = aShape->GetPolyShape().CIterate(); it; it++ )
207 {
208 VECTOR2I pt = *it;
209 aContour.Append( pt );
210
211 if( firstPt )
212 firstPt = false;
213 else
214 aShapeOwners[ std::make_pair( prevPt, pt ) ] = aShape;
215
216 prevPt = pt;
217 }
218
219 aContour.SetClosed( true );
220 break;
221 }
222 case SHAPE_T::CIRCLE:
223 {
224 VECTOR2I center = aShape->GetCenter();
225 int radius = aShape->GetRadius();
226 VECTOR2I start = center;
227 start.x += radius;
228
229 SHAPE_ARC arc360( center, start, ANGLE_360, 0 );
230 aContour.Append( arc360, aErrorMax );
231 aContour.SetClosed( true );
232
233 for( int ii = 1; ii < aContour.PointCount(); ++ii )
234 aShapeOwners[ std::make_pair( aContour.CPoint( ii-1 ), aContour.CPoint( ii ) ) ] = aShape;
235
236 if( !aAllowUseArcsInPolygons )
237 aContour.ClearArcs();
238
239 break;
240 }
242 {
243 if( aShape->GetCornerRadius() > 0 )
244 {
245 ROUNDRECT rr( SHAPE_RECT( aShape->GetStart(), aShape->GetRectangleWidth(), aShape->GetRectangleHeight() ),
246 aShape->GetCornerRadius(), true /* normalize */ );
247 SHAPE_POLY_SET poly;
248 rr.TransformToPolygon( poly, aShape->GetMaxError() );
249 aContour.Append( poly.Outline( 0 ) );
250
251 for( int ii = 1; ii < aContour.PointCount(); ++ii )
252 aShapeOwners[ std::make_pair( aContour.CPoint( ii - 1 ), aContour.CPoint( ii ) ) ] = aShape;
253
254 if( !aAllowUseArcsInPolygons )
255 aContour.ClearArcs();
256
257 aContour.SetClosed( true );
258 break;
259 }
260
261 std::vector<VECTOR2I> pts = aShape->GetRectCorners();
262 VECTOR2I prevPt;
263 bool firstPt = true;
264
265 for( const VECTOR2I& pt : pts )
266 {
267 aContour.Append( pt );
268
269 if( firstPt )
270 firstPt = false;
271 else
272 aShapeOwners[ std::make_pair( prevPt, pt ) ] = aShape;
273
274 prevPt = pt;
275 }
276
277 aContour.SetClosed( true );
278 break;
279 }
280 case SHAPE_T::ELLIPSE:
281 {
282 // Tessellate the ellipse outline and append it as a closed contour.
284 aShape->GetEllipseRotation() );
285
287
288 for( int ii = 0; ii < chain.PointCount(); ++ii )
289 aContour.Append( chain.CPoint( ii ) );
290
291 aContour.SetClosed( true );
292
293 for( int ii = 1; ii < aContour.PointCount(); ++ii )
294 aShapeOwners[std::make_pair( aContour.CPoint( ii - 1 ), aContour.CPoint( ii ) )] = aShape;
295
296 break;
297 }
298 default:
299 break;
300 }
301}
302
303static void processShapeSegment( PCB_SHAPE* aShape, SHAPE_LINE_CHAIN& aContour, VECTOR2I& aPrevPt,
304 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*>& aShapeOwners,
305 int aErrorMax, int aChainingEpsilon, bool aAllowUseArcsInPolygons )
306{
307 switch( aShape->GetShape() )
308 {
309 case SHAPE_T::SEGMENT:
310 {
311 VECTOR2I nextPt;
312
313 if( closer_to_first( aPrevPt, aShape->GetStart(), aShape->GetEnd() ) )
314 nextPt = aShape->GetEnd();
315 else
316 nextPt = aShape->GetStart();
317
318 aContour.Append( nextPt );
319 aShapeOwners[ std::make_pair( aPrevPt, nextPt ) ] = aShape;
320 aPrevPt = nextPt;
321 break;
322 }
323 case SHAPE_T::ARC:
324 {
325 VECTOR2I pstart = aShape->GetStart();
326 VECTOR2I pmid = aShape->GetArcMid();
327 VECTOR2I pend = aShape->GetEnd();
328
329 if( !close_enough( aPrevPt, pstart, aChainingEpsilon ) && !close_enough( aPrevPt, pend, aChainingEpsilon ) )
330 return;
331
332 if( closer_to_first( aPrevPt, pend, pstart ) )
333 std::swap( pstart, pend );
334
335 pstart = aPrevPt;
336 SHAPE_ARC sarc( pstart, pmid, pend, 0 );
337 SHAPE_LINE_CHAIN arcChain;
338 arcChain.Append( sarc, aErrorMax );
339
340 if( !aAllowUseArcsInPolygons )
341 arcChain.ClearArcs();
342
343 for( int ii = 1; ii < arcChain.PointCount(); ++ii )
344 aShapeOwners[ std::make_pair( arcChain.CPoint( ii - 1 ), arcChain.CPoint( ii ) ) ] = aShape;
345
346 aContour.Append( arcChain );
347 aPrevPt = pend;
348 break;
349 }
350 case SHAPE_T::BEZIER:
351 {
352 VECTOR2I nextPt;
353 bool reverse = false;
354
355 if( closer_to_first( aPrevPt, aShape->GetStart(), aShape->GetEnd() ) )
356 {
357 nextPt = aShape->GetEnd();
358 }
359 else
360 {
361 nextPt = aShape->GetStart();
362 reverse = true;
363 }
364
365 aShape->RebuildBezierToSegmentsPointsList( aErrorMax );
366
367 if( reverse )
368 {
369 for( int jj = aShape->GetBezierPoints().size() - 1; jj >= 0; jj-- )
370 {
371 const VECTOR2I& pt = aShape->GetBezierPoints()[jj];
372
373 if( aPrevPt == pt )
374 continue;
375
376 aContour.Append( pt );
377 aShapeOwners[ std::make_pair( aPrevPt, pt ) ] = aShape;
378 aPrevPt = pt;
379 }
380 }
381 else
382 {
383 for( const VECTOR2I& pt : aShape->GetBezierPoints() )
384 {
385 if( aPrevPt == pt )
386 continue;
387
388 aContour.Append( pt );
389 aShapeOwners[ std::make_pair( aPrevPt, pt ) ] = aShape;
390 aPrevPt = pt;
391 }
392 }
393
394 aPrevPt = nextPt;
395 break;
396 }
398 {
399 VECTOR2I pstart = aShape->GetStart();
400 VECTOR2I pend = aShape->GetEnd();
401 bool reverse = false;
402
403 if( !close_enough( aPrevPt, pstart, aChainingEpsilon )
404 && !close_enough( aPrevPt, pend, aChainingEpsilon ) )
405 {
406 return;
407 }
408
409 // Same as the arc case above
410 if( closer_to_first( aPrevPt, pend, pstart ) )
411 {
412 reverse = true;
413 std::swap( pstart, pend );
414 }
415
417 aShape->GetEllipseRotation(), aShape->GetEllipseStartAngle(), aShape->GetEllipseEndAngle() );
418
419 SHAPE_LINE_CHAIN arcChain = e.ConvertToPolyline( aErrorMax );
420
421 if( reverse )
422 arcChain = arcChain.Reverse();
423
424 for( int ii = 0; ii < arcChain.PointCount(); ++ii )
425 {
426 const VECTOR2I& pt = arcChain.CPoint( ii );
427
428 if( pt == aPrevPt )
429 continue;
430
431 aContour.Append( pt );
432 aShapeOwners[std::make_pair( aPrevPt, pt )] = aShape;
433 aPrevPt = pt;
434 }
435
436 aPrevPt = pend;
437 break;
438 }
439 default:
440 break;
441 }
442}
443
444static std::map<int, std::vector<int>> buildContourHierarchy( const std::vector<SHAPE_LINE_CHAIN>& aContours )
445{
446 std::map<int, std::vector<int>> contourToParentIndexesMap;
447
448 for( size_t ii = 0; ii < aContours.size(); ++ii )
449 {
450 if( aContours[ii].PointCount() < 1 ) // malformed/empty SHAPE_LINE_CHAIN
451 continue;
452
453 VECTOR2I firstPt = aContours[ii].GetPoint( 0 );
454 std::vector<int> parents;
455
456 for( size_t jj = 0; jj < aContours.size(); ++jj )
457 {
458 if( jj == ii )
459 continue;
460
461 const SHAPE_LINE_CHAIN& parentCandidate = aContours[jj];
462
463 if( parentCandidate.PointInside( firstPt, 0, true ) )
464 parents.push_back( jj );
465 }
466
467 contourToParentIndexesMap[ii] = std::move( parents );
468 }
469
470 return contourToParentIndexesMap;
471}
472
473static bool addOutlinesToPolygon( const std::vector<SHAPE_LINE_CHAIN>& aContours,
474 const std::map<int, std::vector<int>>& aContourHierarchy,
475 const std::set<int>& aCrossingContours, SHAPE_POLY_SET& aPolygons,
476 bool aAllowDisjoint, OUTLINE_ERROR_HANDLER* aErrorHandler,
477 const std::function<PCB_SHAPE*( const SEG& )>& aFetchOwner,
478 std::map<int, int>& aContourToOutlineIdxMap )
479{
480 for( const auto& [ contourIndex, parentIndexes ] : aContourHierarchy )
481 {
482 if( parentIndexes.size() % 2 == 0 )
483 {
484 // A nested contour crossing another is a cutout wall, parent parity lies for it
485 if( !parentIndexes.empty() && aCrossingContours.count( contourIndex ) )
486 continue;
487
488 // Even number of parents; top-level outline
489 if( !aAllowDisjoint && !aPolygons.IsEmpty() )
490 {
491 if( aErrorHandler )
492 {
493 BOARD_ITEM* a = aFetchOwner( aPolygons.Outline( 0 ).GetSegment( 0 ) );
494 BOARD_ITEM* b = aFetchOwner( aContours[ contourIndex ].GetSegment( 0 ) );
495
496 if( a && b )
497 {
498 (*aErrorHandler)( _( "(multiple board outlines not supported)" ), a, b,
499 aContours[ contourIndex ].GetPoint( 0 ) );
500 return false;
501 }
502 }
503 }
504
505 aPolygons.AddOutline( aContours[ contourIndex ] );
506 aContourToOutlineIdxMap[ contourIndex ] = aPolygons.OutlineCount() - 1;
507 }
508 }
509 return true;
510}
511
512static void addHolesToPolygon( const std::vector<SHAPE_LINE_CHAIN>& aContours,
513 const std::map<int, std::vector<int>>& aContourHierarchy,
514 const std::map<int, int>& aContourToOutlineIdxMap, SHAPE_POLY_SET& aPolygons,
515 bool aAllowUseArcsInPolygons, const std::set<int>& aCrossingContours )
516{
517 if( aAllowUseArcsInPolygons || aCrossingContours.empty() )
518 {
519 for( const auto& [contourIndex, parentIndexes] : aContourHierarchy )
520 {
521 if( parentIndexes.size() % 2 == 1 )
522 {
523 // Odd nesting depth means a hole, attach it to its direct parent
524 const SHAPE_LINE_CHAIN& hole = aContours[contourIndex];
525
526 for( int parentContourIdx : parentIndexes )
527 {
528 if( aContourHierarchy.at( parentContourIdx ).size() == parentIndexes.size() - 1 )
529 {
530 int outlineIdx = aContourToOutlineIdxMap.at( parentContourIdx );
531 aPolygons.AddHole( hole, outlineIdx );
532 break;
533 }
534 }
535 }
536 }
537
538 return;
539 }
540
541 // Malformed overlapping contours in the polygonized path.
542 SHAPE_POLY_SET cutoutCandidates;
543 SHAPE_POLY_SET islandCandidates;
544
545 for( const auto& [contourIndex, parentIndexes] : aContourHierarchy )
546 {
547 if( parentIndexes.empty() )
548 continue;
549
550 if( parentIndexes.size() % 2 == 1 || aCrossingContours.count( contourIndex ) )
551 cutoutCandidates.AddOutline( aContours[contourIndex] );
552 else
553 islandCandidates.AddOutline( aContours[contourIndex] );
554 }
555
556 if( cutoutCandidates.OutlineCount() )
557 {
558 cutoutCandidates.Simplify();
559 aPolygons.BooleanSubtract( cutoutCandidates );
560 }
561
562 if( islandCandidates.OutlineCount() )
563 {
564 islandCandidates.Simplify();
565 aPolygons.BooleanAdd( islandCandidates );
566 }
567}
568
569static bool checkSelfIntersections( SHAPE_POLY_SET& aPolygons, OUTLINE_ERROR_HANDLER* aErrorHandler,
570 const std::function<PCB_SHAPE*(const SEG&)>& aFetchOwner )
571{
572 bool selfIntersecting = false;
573 std::vector<SEG> segments;
574 size_t total = 0;
575
576 for( int ii = 0; ii < aPolygons.OutlineCount(); ++ii )
577 {
578 const SHAPE_LINE_CHAIN& contour = aPolygons.Outline( ii );
579 total += contour.SegmentCount();
580
581 for( int jj = 0; jj < aPolygons.HoleCount( ii ); ++jj )
582 {
583 const SHAPE_LINE_CHAIN& hole = aPolygons.Hole( ii, jj );
584 total += hole.SegmentCount();
585 }
586 }
587
588 segments.reserve( total );
589
590 for( auto seg = aPolygons.IterateSegmentsWithHoles(); seg; seg++ )
591 {
592 SEG segment = *seg;
593
594 if( LexicographicalCompare( segment.A, segment.B ) > 0 )
595 std::swap( segment.A, segment.B );
596
597 segments.push_back( segment );
598 }
599
600 std::sort( segments.begin(), segments.end(),
601 []( const SEG& a, const SEG& b )
602 {
603 if( a.A != b.A )
604 return LexicographicalCompare( a.A, b.A ) < 0;
605 return LexicographicalCompare( a.B, b.B ) < 0;
606 } );
607
608 for( size_t i = 0; i < segments.size(); ++i )
609 {
610 const SEG& seg1 = segments[i];
611
612 for( size_t j = i + 1; j < segments.size(); ++j )
613 {
614 const SEG& seg2 = segments[j];
615
616 if( seg2.A > seg1.B )
617 break;
618
619 if( seg1 == seg2 || ( seg1.A == seg2.B && seg1.B == seg2.A ) )
620 {
621 if( aErrorHandler )
622 {
623 BOARD_ITEM* a = aFetchOwner( seg1 );
624 BOARD_ITEM* b = aFetchOwner( seg2 );
625 (*aErrorHandler)( _( "(self-intersecting)" ), a, b, seg1.A );
626 }
627 selfIntersecting = true;
628 }
629 else if( OPT_VECTOR2I pt = seg1.Intersect( seg2, true ) )
630 {
631 if( aErrorHandler )
632 {
633 BOARD_ITEM* a = aFetchOwner( seg1 );
634 BOARD_ITEM* b = aFetchOwner( seg2 );
635 (*aErrorHandler)( _( "(self-intersecting)" ), a, b, *pt );
636 }
637 selfIntersecting = true;
638 }
639 }
640 }
641
642 return !selfIntersecting;
643}
644
645
647{
648 PCB_SHAPE* available = nullptr;
649 PCB_SHAPE* consumed = nullptr;
650};
651
652
653static bool closerEndpoint( const nanoflann::ResultItem<uint32_t, double>& aLeft,
654 const nanoflann::ResultItem<uint32_t, double>& aRight )
655{
656 if( aLeft.second != aRight.second )
657 return aLeft.second < aRight.second;
658
659 return aLeft.first < aRight.first;
660}
661
662
674template <typename CONSUMED_FUNC>
675static CHAIN_NEIGHBOURS findNeighbours( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& aKdTree,
676 const PCB_SHAPE_ENDPOINTS_ADAPTOR& aAdaptor, double aChainingEpsilon,
677 CONSUMED_FUNC aIsConsumed )
678{
679 const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
680 const double radius_sq = aChainingEpsilon * aChainingEpsilon;
681
682 std::vector<nanoflann::ResultItem<uint32_t, double>> matches;
683 aKdTree.radiusSearch( query_pt, radius_sq, matches );
684
685 std::sort( matches.begin(), matches.end(), closerEndpoint );
686
688
689 for( const nanoflann::ResultItem<uint32_t, double>& match : matches )
690 {
691 PCB_SHAPE* candidate = aAdaptor.endpoints[match.first].second;
692
693 if( candidate == aShape )
694 continue;
695
696 PCB_SHAPE*& slot = aIsConsumed( candidate ) ? result.consumed : result.available;
697
698 if( !slot )
699 slot = candidate;
700
701 if( result.available && result.consumed )
702 break;
703 }
704
705 return result;
706}
707
708
709static std::set<int> findCrossingContours( const std::vector<SHAPE_LINE_CHAIN>& aContours )
710{
711 std::set<int> crossing;
712
713 for( size_t ii = 0; ii < aContours.size(); ++ii )
714 {
715 for( size_t jj = ii + 1; jj < aContours.size(); ++jj )
716 {
717 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
718
719 if( aContours[ii].Intersect( aContours[jj], intersections, true ) != 0 )
720 {
721 crossing.insert( ii );
722 crossing.insert( jj );
723 }
724 }
725 }
726
727 return crossing;
728}
729
730
731// Walk a chain of open shapes (segments/arcs/beziers) starting from aStart, and produce a closed
732// SHAPE_LINE_CHAIN if the chain forms a closed loop. Shapes that are consumed are removed from
733// aRemaining. Returns true and populates aContour and aOwnerShape only if a closed contour is produced.
734// Used to detect cross-contour intersections of bezier-bounded slots which would otherwise be missed
735// by the closed-shape-only intersection test.
736static bool buildChainedClosedContour( PCB_SHAPE* aStart, std::set<PCB_SHAPE*>& aRemaining,
737 const KDTree& aKdTree, const PCB_SHAPE_ENDPOINTS_ADAPTOR& aAdaptor,
738 int aErrorMax, int aChainingEpsilon, SHAPE_LINE_CHAIN& aContour,
739 PCB_SHAPE*& aOwnerShape )
740{
741 std::deque<PCB_SHAPE*> chain;
742 chain.push_back( aStart );
743
744 bool closed = false;
745 VECTOR2I frontPt = aStart->GetStart();
746 VECTOR2I backPt = aStart->GetEnd();
747
748 std::set<PCB_SHAPE*> visited;
749 visited.insert( aStart );
750
751 // The KD-tree spans the original openShapes set, so it still returns shapes already consumed by
752 // an earlier chain. Filter against aRemaining to avoid accidentally absorbing those into this chain.
753 auto isConsumed =
754 [&]( PCB_SHAPE* aCandidate )
755 {
756 return !aRemaining.contains( aCandidate ) || visited.contains( aCandidate );
757 };
758
759 auto extendChain =
760 [&]( bool forward )
761 {
762 PCB_SHAPE* curr = forward ? chain.back() : chain.front();
763 VECTOR2I prev = forward ? backPt : frontPt;
764
765 for( ;; )
766 {
767 CHAIN_NEIGHBOURS next = findNeighbours( curr, prev, aKdTree, aAdaptor, aChainingEpsilon,
768 isConsumed );
769
770 if( next.available )
771 {
772 visited.insert( next.available );
773
774 if( forward )
775 chain.push_back( next.available );
776 else
777 chain.push_front( next.available );
778
779 if( closer_to_first( prev, next.available->GetStart(), next.available->GetEnd() ) )
780 prev = next.available->GetEnd();
781 else
782 prev = next.available->GetStart();
783
784 curr = next.available;
785 continue;
786 }
787
788 // Match on position, not identity (see doConvertOutlineToPolygon)
789 VECTOR2I chainPt = forward ? frontPt : backPt;
790
791 if( chain.size() > 1 && close_enough( prev, chainPt, aChainingEpsilon ) )
792 closed = true;
793
794 if( forward )
795 backPt = prev;
796 else
797 frontPt = prev;
798
799 break;
800 }
801 };
802
803 extendChain( true );
804
805 if( !closed )
806 extendChain( false );
807
808 if( !closed )
809 return false;
810
811 // Build the contour from the closed chain, mirroring doConvertOutlineToPolygon().
812 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
813 PCB_SHAPE* first = chain.front();
814 VECTOR2I startPt;
815
816 if( chain.size() > 1 )
817 {
818 PCB_SHAPE* second = *( std::next( chain.begin() ) );
819
820 startPt = free_end( first, second );
821 }
822 else
823 {
824 startPt = first->GetStart();
825 }
826
827 aContour.Clear();
828 aContour.Append( startPt );
829 VECTOR2I prevPt = startPt;
830
831 for( PCB_SHAPE* shapeInChain : chain )
832 processShapeSegment( shapeInChain, aContour, prevPt, shapeOwners, aErrorMax, aChainingEpsilon, false );
833
834 if( aContour.PointCount() < 3 )
835 return false;
836
837 if( aContour.CPoint( 0 ) != aContour.CLastPoint() )
838 aContour.SetPoint( -1, aContour.CPoint( 0 ) );
839
840 aContour.SetClosed( true );
841
842 for( PCB_SHAPE* consumed : chain )
843 aRemaining.erase( consumed );
844
845 aOwnerShape = first;
846 return true;
847}
848
849
850bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
851 int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
852 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons,
853 SCOPED_FLAGS_CLEANER& aCleaner )
854{
855 if( aShapeList.size() == 0 )
856 return true;
857
858 bool selfIntersecting = false;
859 PCB_SHAPE* graphic = nullptr;
860
861 // Seed in list order so the polygon does not depend on addresses
862 std::unordered_set<PCB_SHAPE*> remaining( aShapeList.begin(), aShapeList.end() );
863 size_t nextSeed = 0;
864
865 // Pre-build KD-tree
866 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
867 KDTree kdTree( 2, adaptor );
868
869 // Keep a list of where the various shapes came from
870 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
871
872 auto fetchOwner =
873 [&]( const SEG& seg ) -> PCB_SHAPE*
874 {
875 auto it = shapeOwners.find( std::make_pair( seg.A, seg.B ) );
876 return it == shapeOwners.end() ? nullptr : it->second;
877 };
878
879 std::set<std::pair<PCB_SHAPE*, PCB_SHAPE*>> reportedGaps;
880 std::vector<SHAPE_LINE_CHAIN> contours;
881 contours.reserve( aShapeList.size() );
882
883 for( PCB_SHAPE* shape : aShapeList )
884 shape->ClearFlags( SKIP_STRUCT );
885
886 auto isConsumed =
887 [&]( PCB_SHAPE* aCandidate )
888 {
889 return ( aCandidate->GetFlags() & SKIP_STRUCT ) != 0;
890 };
891
892 // Process each shape to build contours
893 while( !remaining.empty() )
894 {
895 while( nextSeed < aShapeList.size() && !remaining.count( aShapeList[nextSeed] ) )
896 nextSeed++;
897
898 if( nextSeed >= aShapeList.size() )
899 break;
900
901 graphic = aShapeList[nextSeed];
902 graphic->SetFlags( SKIP_STRUCT );
903 aCleaner.insert( graphic );
904 remaining.erase( graphic );
905
906 contours.emplace_back();
907 SHAPE_LINE_CHAIN& currContour = contours.back();
908 currContour.SetWidth( graphic->GetWidth() );
909
910 // Handle closed shapes (circles, rects, polygons, ellipses)
911 if( graphic->GetShape() == SHAPE_T::POLY
912 || graphic->GetShape() == SHAPE_T::CIRCLE
913 || graphic->GetShape() == SHAPE_T::RECTANGLE
914 || graphic->GetShape() == SHAPE_T::ELLIPSE )
915 {
916 processClosedShape( graphic, currContour, shapeOwners, aErrorMax, aAllowUseArcsInPolygons );
917 }
918 else
919 {
920 // Build chains for open shapes
921 std::deque<PCB_SHAPE*> chain;
922 chain.push_back( graphic );
923
924 bool closed = false;
925 VECTOR2I frontPt = graphic->GetStart();
926 VECTOR2I backPt = graphic->GetEnd();
927
928 auto extendChain =
929 [&]( bool forward )
930 {
931 PCB_SHAPE* curr = forward ? chain.back() : chain.front();
932 VECTOR2I prev = forward ? backPt : frontPt;
933
934 for( ;; )
935 {
936 CHAIN_NEIGHBOURS next = findNeighbours( curr, prev, kdTree, adaptor, aChainingEpsilon,
937 isConsumed );
938
939 if( next.available )
940 {
941 next.available->SetFlags( SKIP_STRUCT );
942 aCleaner.insert( next.available );
943 remaining.erase( next.available );
944
945 if( forward )
946 chain.push_back( next.available );
947 else
948 chain.push_front( next.available );
949
950 if( closer_to_first( prev, next.available->GetStart(), next.available->GetEnd() ) )
951 prev = next.available->GetEnd();
952 else
953 prev = next.available->GetStart();
954
955 curr = next.available;
956 continue;
957 }
958
959 VECTOR2I chainPt = forward ? frontPt : backPt;
960
961 if( chain.size() > 1 && close_enough( prev, chainPt, aChainingEpsilon ) )
962 {
963 closed = true;
964 }
965 else if( next.consumed )
966 {
967 if( aErrorHandler )
968 ( *aErrorHandler )( _( "(self-intersecting)" ), curr, next.consumed, prev );
969
970 selfIntersecting = true;
971 }
972
973 if( forward )
974 backPt = prev;
975 else
976 frontPt = prev;
977
978 break;
979 }
980 };
981
982 extendChain( true );
983
984 if( !closed )
985 extendChain( false );
986
987 // Process the chain to build the contour
988 PCB_SHAPE* first = chain.front();
989 VECTOR2I startPt;
990
991 if( chain.size() > 1 )
992 {
993 PCB_SHAPE* second = *( std::next( chain.begin() ) );
994
995 startPt = free_end( first, second );
996 }
997 else
998 {
999 startPt = first->GetStart();
1000 }
1001
1002 currContour.Append( startPt );
1003 VECTOR2I prevPt = startPt;
1004
1005 for( PCB_SHAPE* shapeInChain : chain )
1006 {
1007 processShapeSegment( shapeInChain, currContour, prevPt, shapeOwners, aErrorMax, aChainingEpsilon,
1008 aAllowUseArcsInPolygons );
1009 }
1010
1011 // Handle contour closure
1012 if( close_enough( currContour.CPoint( 0 ), currContour.CLastPoint(), aChainingEpsilon ) )
1013 {
1014 if( currContour.CPoint( 0 ) != currContour.CLastPoint() && currContour.PointCount() > 2 )
1015 {
1016 PCB_SHAPE* owner = fetchOwner( currContour.CSegment( -1 ) );
1017
1018 if( currContour.IsArcEnd( currContour.PointCount() - 1 ) )
1019 {
1020 SHAPE_ARC arc = currContour.Arc( currContour.ArcIndex( currContour.PointCount() - 1 ) );
1021
1022 SHAPE_ARC sarc( arc.GetP0(), arc.GetArcMid(), currContour.CPoint( 0 ), 0 );
1023
1024 SHAPE_LINE_CHAIN arcChain;
1025 arcChain.Append( sarc, aErrorMax );
1026
1027 if( !aAllowUseArcsInPolygons )
1028 arcChain.ClearArcs();
1029
1030 for( int ii = 1; ii < arcChain.PointCount(); ++ii )
1031 shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ), arcChain.CPoint( ii ) )] = owner;
1032
1033 currContour.RemoveShape( currContour.PointCount() - 1 );
1034 currContour.Append( arcChain );
1035 }
1036 else
1037 {
1038 currContour.SetPoint( -1, currContour.CPoint( 0 ) );
1039
1040 shapeOwners[ std::make_pair( currContour.CPoints()[currContour.PointCount() - 2],
1041 currContour.CLastPoint() ) ] = owner;
1042 }
1043 }
1044
1045 currContour.SetClosed( true );
1046 }
1047 else
1048 {
1049 auto report_gap =
1050 [&]( const VECTOR2I& pt )
1051 {
1052 if( !aErrorHandler )
1053 return;
1054
1055 const double query_pt[2] = { static_cast<double>( pt.x ), static_cast<double>( pt.y ) };
1056
1057 // Both endpoints are in the tree, so over-fetch for a second shape
1058 uint32_t indices[8] = { 0 }; // make gcc quiet
1059 double dists[8];
1060
1061 const size_t found = kdTree.knnSearch( query_pt, 8, indices, dists );
1062
1063 if( found == 0 )
1064 return;
1065
1066 PCB_SHAPE* shapeA = adaptor.endpoints[indices[0]].second;
1067 PCB_SHAPE* shapeB = shapeA;
1068
1069 // A lone shape has no neighbour, so it pairs with itself
1070 for( size_t ii = 1; ii < found; ++ii )
1071 {
1072 if( adaptor.endpoints[indices[ii]].second != shapeA )
1073 {
1074 shapeB = adaptor.endpoints[indices[ii]].second;
1075 break;
1076 }
1077 }
1078
1079 // Avoid reporting the same pair twice
1080 auto key = std::minmax( shapeA, shapeB );
1081
1082 if( !reportedGaps.insert( key ).second )
1083 return;
1084
1085 // Find the nearest points between the two shapes and calculate midpoint
1086 std::shared_ptr<SHAPE> effectiveShapeA = shapeA->GetEffectiveShape();
1087 std::shared_ptr<SHAPE> effectiveShapeB = shapeB->GetEffectiveShape();
1088 VECTOR2I ptA, ptB;
1089 VECTOR2I midpoint = pt; // fallback to original point
1090
1091 if( effectiveShapeA
1092 && effectiveShapeB
1093 && effectiveShapeA->NearestPoints( effectiveShapeB.get(), ptA, ptB ) )
1094 {
1095 midpoint = ( ptA + ptB ) / 2;
1096 }
1097
1098 ( *aErrorHandler )( _( "(not a closed shape)" ), shapeA, shapeB, midpoint );
1099 };
1100
1101 report_gap( currContour.CPoint( 0 ) );
1102 report_gap( currContour.CLastPoint() );
1103 }
1104 }
1105 }
1106
1107 // Ensure all contours are closed
1108 for( const SHAPE_LINE_CHAIN& contour : contours )
1109 {
1110 if( !contour.IsClosed() )
1111 return false;
1112 }
1113
1114 // Generate bounding boxes for hierarchy calculations
1115 for( const SHAPE_LINE_CHAIN& contour : contours )
1116 {
1117 if( !contour.GetCachedBBox()->IsValid() )
1118 contour.GenerateBBoxCache();
1119 }
1120
1121 // Build contour hierarchy
1122 auto contourHierarchy = buildContourHierarchy( contours );
1123
1124 std::set<int> crossingContours;
1125
1126 if( !aAllowUseArcsInPolygons )
1127 crossingContours = findCrossingContours( contours );
1128
1129 // Add outlines to polygon set
1130 std::map<int, int> contourToOutlineIdxMap;
1131 if( !addOutlinesToPolygon( contours, contourHierarchy, crossingContours, aPolygons, aAllowDisjoint, aErrorHandler,
1132 fetchOwner, contourToOutlineIdxMap ) )
1133 {
1134 return false;
1135 }
1136
1137 // Add holes to polygon set
1138 addHolesToPolygon( contours, contourHierarchy, contourToOutlineIdxMap, aPolygons, aAllowUseArcsInPolygons,
1139 crossingContours );
1140
1141 // Check for self-intersections
1142 return checkSelfIntersections( aPolygons, aErrorHandler, fetchOwner );
1143}
1144
1145
1146bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
1147 int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
1148 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
1149{
1151
1152 return doConvertOutlineToPolygon( aShapeList, aPolygons, aErrorMax, aChainingEpsilon, aAllowDisjoint,
1153 aErrorHandler, aAllowUseArcsInPolygons, cleaner );
1154}
1155
1156
1157bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist, OUTLINE_ERROR_HANDLER* aErrorHandler )
1158{
1159 bool success = true;
1160 PCB_TYPE_COLLECTOR items;
1161 int min_dist = std::max( 0, aMinDist );
1162
1163 // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
1164 items.Collect( aBoard, { PCB_SHAPE_T } );
1165
1166 std::vector<PCB_SHAPE*> shapeList;
1167
1168 for( int ii = 0; ii < items.GetCount(); ii++ )
1169 {
1170 PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
1171
1172 if( seg->GetLayer() == Edge_Cuts )
1173 shapeList.push_back( seg );
1174 }
1175
1176 // Now Test validity of collected items
1177 for( PCB_SHAPE* shape : shapeList )
1178 {
1179 switch( shape->GetShape() )
1180 {
1181 case SHAPE_T::RECTANGLE:
1182 {
1183 VECTOR2I seg = shape->GetEnd() - shape->GetStart();
1184 int dim = seg.EuclideanNorm();
1185
1186 if( dim <= min_dist )
1187 {
1188 success = false;
1189
1190 if( aErrorHandler )
1191 {
1192 (*aErrorHandler)( wxString::Format( _( "(rectangle has null or very small size: %d nm)" ), dim ),
1193 shape, nullptr, shape->GetStart() );
1194 }
1195 }
1196 break;
1197 }
1198
1199 case SHAPE_T::CIRCLE:
1200 {
1201 int r = shape->GetRadius();
1202
1203 if( r <= min_dist )
1204 {
1205 success = false;
1206
1207 if( aErrorHandler )
1208 {
1209 (*aErrorHandler)( wxString::Format( _( "(circle has null or very small radius: %d nm)" ), r ),
1210 shape, nullptr, shape->GetStart() );
1211 }
1212 }
1213 break;
1214 }
1215
1216 case SHAPE_T::SEGMENT:
1217 {
1218 VECTOR2I seg = shape->GetEnd() - shape->GetStart();
1219 int dim = seg.EuclideanNorm();
1220
1221 if( dim <= min_dist )
1222 {
1223 success = false;
1224
1225 if( aErrorHandler )
1226 {
1227 (*aErrorHandler)( wxString::Format( _( "(segment has null or very small length: %d nm)" ), dim ),
1228 shape, nullptr, shape->GetStart() );
1229 }
1230 }
1231 break;
1232 }
1233
1234 case SHAPE_T::ARC:
1235 {
1236 // Arc size can be evaluated from the distance between arc middle point and arc ends
1237 // We do not need a precise value, just an idea of its size
1238 VECTOR2I arcMiddle = shape->GetArcMid();
1239 VECTOR2I seg1 = arcMiddle - shape->GetStart();
1240 VECTOR2I seg2 = shape->GetEnd() - arcMiddle;
1241 int dim = seg1.EuclideanNorm() + seg2.EuclideanNorm();
1242
1243 if( dim <= min_dist )
1244 {
1245 success = false;
1246
1247 if( aErrorHandler )
1248 {
1249 (*aErrorHandler)( wxString::Format( _( "(arc has null or very small size: %d nm)" ), dim ),
1250 shape, nullptr, shape->GetStart() );
1251 }
1252 }
1253 break;
1254 }
1255
1256 case SHAPE_T::POLY:
1257 break;
1258
1259 case SHAPE_T::BEZIER:
1260 break;
1261
1262 case SHAPE_T::ELLIPSE:
1264 {
1265 const int major = shape->GetEllipseMajorRadius();
1266 const int minor = shape->GetEllipseMinorRadius();
1267
1268 if( major <= min_dist || minor <= min_dist )
1269 {
1270 success = false;
1271
1272 if( aErrorHandler )
1273 {
1274 ( *aErrorHandler )( wxString::Format( _( "(ellipse has null or very small "
1275 "radii: major=%d nm, minor=%d nm)" ),
1276 major, minor ),
1277 shape, nullptr, shape->GetEllipseCenter() );
1278 }
1279 }
1280 break;
1281 }
1282
1283 default:
1284 UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
1285 return false;
1286 }
1287 }
1288
1289 std::vector<std::pair<PCB_SHAPE*, SHAPE_LINE_CHAIN>> closedContours;
1290 closedContours.reserve( shapeList.size() );
1291
1292 std::set<PCB_SHAPE*> openShapes;
1293
1294 for( PCB_SHAPE* shape : shapeList )
1295 {
1296 if( shape->GetShape() == SHAPE_T::POLY
1297 || shape->GetShape() == SHAPE_T::CIRCLE
1298 || shape->GetShape() == SHAPE_T::RECTANGLE
1299 || shape->GetShape() == SHAPE_T::ELLIPSE )
1300 {
1301 SHAPE_LINE_CHAIN contour;
1302 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
1303
1304 processClosedShape( shape, contour, shapeOwners, shape->GetMaxError(), true );
1305 closedContours.emplace_back( shape, std::move( contour ) );
1306 }
1307 else if( shape->GetShape() == SHAPE_T::SEGMENT
1308 || shape->GetShape() == SHAPE_T::ARC
1309 || shape->GetShape() == SHAPE_T::BEZIER
1310 || shape->GetShape() == SHAPE_T::ELLIPSE_ARC )
1311 {
1312 openShapes.insert( shape );
1313 }
1314 }
1315
1316 // Gather closed contours from chained open shapes (slots formed by segments/arcs/beziers).
1317 // Without this, malformed-outline detection misses overlaps involving such slots.
1318 if( !openShapes.empty() )
1319 {
1320 std::vector<PCB_SHAPE*> openShapeList( openShapes.begin(), openShapes.end() );
1321 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( openShapeList );
1322 KDTree kdTree( 2, adaptor );
1323
1324 int chainingEpsilon = aBoard->GetOutlinesChainingEpsilon();
1325 int maxError = aBoard->GetDesignSettings().m_MaxError;
1326
1327 while( !openShapes.empty() )
1328 {
1329 PCB_SHAPE* start = *openShapes.begin();
1330 SHAPE_LINE_CHAIN contour;
1331 PCB_SHAPE* owner = nullptr;
1332
1333 if( buildChainedClosedContour( start, openShapes, kdTree, adaptor, maxError, chainingEpsilon,
1334 contour, owner ) )
1335 {
1336 closedContours.emplace_back( owner, std::move( contour ) );
1337 }
1338 else
1339 {
1340 openShapes.erase( start );
1341 }
1342 }
1343 }
1344
1345 for( size_t ii = 0; ii < closedContours.size(); ++ii )
1346 {
1347 const SHAPE_LINE_CHAIN& contourA = closedContours[ii].second;
1348
1349 for( size_t jj = ii + 1; jj < closedContours.size(); ++jj )
1350 {
1351 const SHAPE_LINE_CHAIN& contourB = closedContours[jj].second;
1352 std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
1353
1354 // Ignore touching-only cases; report only real overlap/crossing.
1355 if( contourA.Intersect( contourB, intersections, true ) == 0 )
1356 continue;
1357
1358 success = false;
1359
1360 if( aErrorHandler )
1361 {
1362 PCB_SHAPE* shapeA = closedContours[ii].first;
1363 PCB_SHAPE* shapeB = closedContours[jj].first;
1364
1365 VECTOR2I midpoint = intersections.front().p;
1366 std::shared_ptr<SHAPE> effectiveShapeA = shapeA->GetEffectiveShape();
1367 std::shared_ptr<SHAPE> effectiveShapeB = shapeB->GetEffectiveShape();
1368
1369 if( effectiveShapeA && effectiveShapeB )
1370 {
1371 BOX2I bboxA = effectiveShapeA->BBox();
1372 BOX2I bboxB = effectiveShapeB->BBox();
1373 BOX2I overlapBox = bboxA.Intersect( bboxB );
1374
1375 if( overlapBox.GetWidth() > 0 && overlapBox.GetHeight() > 0 )
1376 midpoint = overlapBox.Centre();
1377 }
1378
1379 ( *aErrorHandler )( _( "(self-intersecting)" ), shapeA, shapeB, midpoint );
1380 }
1381 }
1382 }
1383
1384 return success;
1385}
1386
1387
1388bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
1389 int aChainingEpsilon, bool aInferOutlineIfNecessary,
1390 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
1391{
1392 PCB_TYPE_COLLECTOR items;
1393 SHAPE_POLY_SET fpHoles;
1394 bool success = false;
1395
1397
1398 // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
1399 items.Collect( aBoard, { PCB_SHAPE_T } );
1400
1401 for( int ii = 0; ii < items.GetCount(); ++ii )
1402 items[ii]->ClearFlags( SKIP_STRUCT );
1403
1404 for( FOOTPRINT* fp : aBoard->Footprints() )
1405 {
1406 PCB_TYPE_COLLECTOR fpItems;
1407 fpItems.Collect( fp, { PCB_SHAPE_T } );
1408
1409 std::vector<PCB_SHAPE*> fpSegList;
1410
1411 for( int ii = 0; ii < fpItems.GetCount(); ii++ )
1412 {
1413 PCB_SHAPE* fpSeg = static_cast<PCB_SHAPE*>( fpItems[ii] );
1414
1415 if( fpSeg->GetLayer() == Edge_Cuts )
1416 fpSegList.push_back( fpSeg );
1417 }
1418
1419 if( !fpSegList.empty() )
1420 {
1421 SHAPE_POLY_SET fpOutlines;
1422 success = doConvertOutlineToPolygon( fpSegList, fpOutlines, aErrorMax, aChainingEpsilon,
1423 false,
1424 nullptr, // don't report errors here; the second pass also
1425 // gets an opportunity to use these segments
1426 aAllowUseArcsInPolygons,
1427 cleaner );
1428
1429 // Test to see if we should make holes or outlines. Holes are made if the footprint
1430 // has copper outside of a single, closed outline. If there are multiple outlines,
1431 // we assume that the footprint edges represent holes as we do not support multiple
1432 // boards. Similarly, if any of the footprint pads are located outside of the edges,
1433 // then the edges are holes
1434 if( success && ( isCopperOutside( fp, fpOutlines ) || fpOutlines.OutlineCount() > 1 ) )
1435 {
1436 fpHoles.Append( fpOutlines );
1437 }
1438 else
1439 {
1440 // If it wasn't a closed area, or wasn't a hole, the we want to keep the fpSegs
1441 // in contention for the board outline builds.
1442 for( int ii = 0; ii < fpItems.GetCount(); ++ii )
1443 fpItems[ii]->ClearFlags( SKIP_STRUCT );
1444 }
1445 }
1446 }
1447
1448 // Make a working copy of aSegList, because the list is modified during calculations
1449 std::vector<PCB_SHAPE*> segList;
1450
1451 for( int ii = 0; ii < items.GetCount(); ii++ )
1452 {
1453 PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
1454
1455 // Skip anything already used to generate footprint holes (above)
1456 if( seg->GetFlags() & SKIP_STRUCT )
1457 continue;
1458
1459 if( seg->GetLayer() == Edge_Cuts )
1460 segList.push_back( seg );
1461 }
1462
1463 if( segList.size() )
1464 {
1465 success = doConvertOutlineToPolygon( segList, aOutlines, aErrorMax, aChainingEpsilon, true,
1466 aErrorHandler, aAllowUseArcsInPolygons, cleaner );
1467 }
1468
1469 if( ( !success || !aOutlines.OutlineCount() ) && aInferOutlineIfNecessary )
1470 {
1471 // Couldn't create a valid polygon outline. Use the board edge cuts bounding box to
1472 // create a rectangular outline, or, failing that, the bounding box of the items on
1473 // the board.
1474 BOX2I bbbox = aBoard->GetBoardEdgesBoundingBox();
1475
1476 // If null area, uses the global bounding box.
1477 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1478 bbbox = aBoard->ComputeBoundingBox( false, true );
1479
1480 // Ensure non null area. If happen, gives a minimal size.
1481 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1482 bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
1483
1484 aOutlines.RemoveAllContours();
1485 aOutlines.NewOutline();
1486
1487 VECTOR2I corner;
1488 aOutlines.Append( bbbox.GetOrigin() );
1489
1490 corner.x = bbbox.GetOrigin().x;
1491 corner.y = bbbox.GetEnd().y;
1492 aOutlines.Append( corner );
1493
1494 aOutlines.Append( bbbox.GetEnd() );
1495
1496 corner.x = bbbox.GetEnd().x;
1497 corner.y = bbbox.GetOrigin().y;
1498 aOutlines.Append( corner );
1499 }
1500
1501 if( aAllowUseArcsInPolygons )
1502 {
1503 for( int ii = 0; ii < fpHoles.OutlineCount(); ++ii )
1504 {
1505 const VECTOR2I holePt = fpHoles.Outline( ii ).CPoint( 0 );
1506
1507 for( int jj = 0; jj < aOutlines.OutlineCount(); ++jj )
1508 {
1509 if( aOutlines.Outline( jj ).PointInside( holePt ) )
1510 {
1511 aOutlines.AddHole( fpHoles.Outline( ii ), jj );
1512 break;
1513 }
1514 }
1515 }
1516 }
1517 else
1518 {
1519 fpHoles.Simplify();
1520 aOutlines.BooleanSubtract( fpHoles );
1521 }
1522
1523 return success;
1524}
1525
1526
1539void buildBoardBoundingBoxPoly( const BOARD* aBoard, SHAPE_POLY_SET& aOutline )
1540{
1541 BOX2I bbbox = aBoard->GetBoundingBox();
1543
1544 // If null area, uses the global bounding box.
1545 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1546 bbbox = aBoard->ComputeBoundingBox( false, true );
1547
1548 // Ensure non null area. If happen, gives a minimal size.
1549 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1550 bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
1551
1552 // Inflate slightly (by 1/10th the size of the box)
1553 bbbox.Inflate( bbbox.GetWidth() / 10, bbbox.GetHeight() / 10 );
1554
1555 chain.Append( bbbox.GetOrigin() );
1556 chain.Append( bbbox.GetOrigin().x, bbbox.GetEnd().y );
1557 chain.Append( bbbox.GetEnd() );
1558 chain.Append( bbbox.GetEnd().x, bbbox.GetOrigin().y );
1559 chain.SetClosed( true );
1560
1561 aOutline.RemoveAllContours();
1562 aOutline.AddOutline( chain );
1563}
1564
1565
1566VECTOR2I projectPointOnSegment( const VECTOR2I& aEndPoint, const SHAPE_POLY_SET& aOutline,
1567 int aOutlineNum = 0 )
1568{
1569 int minDistance = -1;
1570 VECTOR2I projPoint;
1571
1572 for( auto it = aOutline.CIterateSegments( aOutlineNum ); it; it++ )
1573 {
1574 auto seg = it.Get();
1575 int dis = seg.Distance( aEndPoint );
1576
1577 if( minDistance < 0 || ( dis < minDistance ) )
1578 {
1579 minDistance = dis;
1580 projPoint = seg.NearestPoint( aEndPoint );
1581 }
1582 }
1583
1584 return projPoint;
1585}
1586
1587
1588int findEndSegments( SHAPE_LINE_CHAIN& aChain, SEG& aStartSeg, SEG& aEndSeg )
1589{
1590 int foundSegs = 0;
1591
1592 for( int i = 0; i < aChain.SegmentCount(); i++ )
1593 {
1594 SEG seg = aChain.Segment( i );
1595
1596 bool foundA = false;
1597 bool foundB = false;
1598
1599 for( int j = 0; j < aChain.SegmentCount(); j++ )
1600 {
1601 // Don't test the segment against itself
1602 if( i == j )
1603 continue;
1604
1605 SEG testSeg = aChain.Segment( j );
1606
1607 if( testSeg.Contains( seg.A ) )
1608 foundA = true;
1609
1610 if( testSeg.Contains( seg.B ) )
1611 foundB = true;
1612 }
1613
1614 // This segment isn't a start or end
1615 if( foundA && foundB )
1616 continue;
1617
1618 if( foundSegs == 0 )
1619 {
1620 // The first segment we encounter is the "start" segment
1621 wxLogTrace( traceBoardOutline, wxT( "Found start segment: (%d, %d)-(%d, %d)" ),
1622 seg.A.x, seg.A.y, seg.B.x, seg.B.y );
1623 aStartSeg = seg;
1624 foundSegs++;
1625 }
1626 else
1627 {
1628 // Once we find both start and end, we can stop
1629 wxLogTrace( traceBoardOutline, wxT( "Found end segment: (%d, %d)-(%d, %d)" ),
1630 seg.A.x, seg.A.y, seg.B.x, seg.B.y );
1631 aEndSeg = seg;
1632 foundSegs++;
1633 break;
1634 }
1635 }
1636
1637 return foundSegs;
1638}
1639
1640
1641bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
1642 int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
1643
1644{
1645 FOOTPRINT* footprint = aBoard->GetFirstFootprint();
1646
1647 // No footprint loaded
1648 if( !footprint )
1649 {
1650 wxLogTrace( traceBoardOutline, wxT( "No footprint found on board" ) );
1651 return false;
1652 }
1653
1654 PCB_TYPE_COLLECTOR items;
1655 SHAPE_POLY_SET outlines;
1656 bool success = false;
1657
1659
1660 // Get all the SHAPEs into 'items', then keep only those on layer == Edge_Cuts.
1661 items.Collect( aBoard, { PCB_SHAPE_T } );
1662
1663 // Make a working copy of aSegList, because the list is modified during calculations
1664 std::vector<PCB_SHAPE*> segList;
1665
1666 for( int ii = 0; ii < items.GetCount(); ii++ )
1667 {
1668 if( items[ii]->GetLayer() == Edge_Cuts )
1669 segList.push_back( static_cast<PCB_SHAPE*>( items[ii] ) );
1670 }
1671
1672 if( !segList.empty() )
1673 {
1674 success = doConvertOutlineToPolygon( segList, outlines, aErrorMax, aChainingEpsilon, true,
1675 aErrorHandler, false, cleaner );
1676 }
1677
1678 // A closed outline was found on Edge_Cuts
1679 if( success )
1680 {
1681 wxLogTrace( traceBoardOutline, wxT( "Closed outline found" ) );
1682
1683 // If copper is outside a closed polygon, treat it as a hole
1684 // If there are multiple outlines in the footprint, they are also holes
1685 if( isCopperOutside( footprint, outlines ) || outlines.OutlineCount() > 1 )
1686 {
1687 wxLogTrace( traceBoardOutline, wxT( "Treating outline as a hole" ) );
1688
1689 buildBoardBoundingBoxPoly( aBoard, aOutlines );
1690
1691 // Copy all outlines from the conversion as holes into the new outline
1692 for( int i = 0; i < outlines.OutlineCount(); i++ )
1693 {
1694 SHAPE_LINE_CHAIN& out = outlines.Outline( i );
1695
1696 if( out.IsClosed() )
1697 aOutlines.AddHole( out, -1 );
1698
1699 for( int j = 0; j < outlines.HoleCount( i ); j++ )
1700 {
1701 SHAPE_LINE_CHAIN& hole = outlines.Hole( i, j );
1702
1703 if( hole.IsClosed() )
1704 aOutlines.AddHole( hole, -1 );
1705 }
1706 }
1707 }
1708 // If all copper is inside, then the computed outline is the board outline
1709 else
1710 {
1711 wxLogTrace( traceBoardOutline, wxT( "Treating outline as board edge" ) );
1712 aOutlines = std::move( outlines );
1713 }
1714
1715 return true;
1716 }
1717 // No board outlines were found, so use the bounding box
1718 else if( outlines.OutlineCount() == 0 )
1719 {
1720 wxLogTrace( traceBoardOutline, wxT( "Using footprint bounding box" ) );
1721 buildBoardBoundingBoxPoly( aBoard, aOutlines );
1722
1723 return true;
1724 }
1725 // There is an outline present, but it is not closed
1726 else
1727 {
1728 wxLogTrace( traceBoardOutline, wxT( "Trying to build outline" ) );
1729
1730 std::vector<SHAPE_LINE_CHAIN> closedChains;
1731 std::vector<SHAPE_LINE_CHAIN> openChains;
1732
1733 // The ConvertOutlineToPolygon function returns only one main outline and the rest as
1734 // holes, so we promote the holes and process them
1735 openChains.push_back( outlines.Outline( 0 ) );
1736
1737 for( int j = 0; j < outlines.HoleCount( 0 ); j++ )
1738 {
1739 SHAPE_LINE_CHAIN hole = outlines.Hole( 0, j );
1740
1741 if( hole.IsClosed() )
1742 {
1743 wxLogTrace( traceBoardOutline, wxT( "Found closed hole" ) );
1744 closedChains.push_back( hole );
1745 }
1746 else
1747 {
1748 wxLogTrace( traceBoardOutline, wxT( "Found open hole" ) );
1749 openChains.push_back( hole );
1750 }
1751 }
1752
1753 SHAPE_POLY_SET bbox;
1754 buildBoardBoundingBoxPoly( aBoard, bbox );
1755
1756 // Treat the open polys as the board edge
1757 SHAPE_LINE_CHAIN chain = openChains[0];
1758 SHAPE_LINE_CHAIN rect = bbox.Outline( 0 );
1759
1760 // We know the outline chain is open, so set to non-closed to get better segment count
1761 chain.SetClosed( false );
1762
1763 SEG startSeg;
1764 SEG endSeg;
1765
1766 // The two possible board outlines
1767 SHAPE_LINE_CHAIN upper;
1768 SHAPE_LINE_CHAIN lower;
1769
1770 findEndSegments( chain, startSeg, endSeg );
1771
1772 if( chain.SegmentCount() == 0 )
1773 {
1774 // Something is wrong, bail out with the overall footprint bounding box
1775 wxLogTrace( traceBoardOutline, wxT( "No line segments in provided outline" ) );
1776 aOutlines = std::move( bbox );
1777 return true;
1778 }
1779 else if( chain.SegmentCount() == 1 )
1780 {
1781 // This case means there is only 1 line segment making up the edge cuts of the
1782 // footprint, so we just need to use it to cut the bounding box in half.
1783 wxLogTrace( traceBoardOutline, wxT( "Only 1 line segment in provided outline" ) );
1784
1785 startSeg = chain.Segment( 0 );
1786
1787 // Intersect with all the sides of the rectangle
1788 OPT_VECTOR2I inter0 = startSeg.IntersectLines( rect.Segment( 0 ) );
1789 OPT_VECTOR2I inter1 = startSeg.IntersectLines( rect.Segment( 1 ) );
1790 OPT_VECTOR2I inter2 = startSeg.IntersectLines( rect.Segment( 2 ) );
1791 OPT_VECTOR2I inter3 = startSeg.IntersectLines( rect.Segment( 3 ) );
1792
1793 if( inter0 && inter2 && !inter1 && !inter3 )
1794 {
1795 // Intersects the vertical rectangle sides only
1796 wxLogTrace( traceBoardOutline, wxT( "Segment intersects only vertical bbox sides" ) );
1797
1798 // The upper half
1799 upper.Append( *inter0 );
1800 upper.Append( rect.GetPoint( 1 ) );
1801 upper.Append( rect.GetPoint( 2 ) );
1802 upper.Append( *inter2 );
1803 upper.SetClosed( true );
1804
1805 // The lower half
1806 lower.Append( *inter0 );
1807 lower.Append( rect.GetPoint( 0 ) );
1808 lower.Append( rect.GetPoint( 3 ) );
1809 lower.Append( *inter2 );
1810 lower.SetClosed( true );
1811 }
1812 else if( inter1 && inter3 && !inter0 && !inter2 )
1813 {
1814 // Intersects the horizontal rectangle sides only
1815 wxLogTrace( traceBoardOutline, wxT( "Segment intersects only horizontal bbox sides" ) );
1816
1817 // The left half
1818 upper.Append( *inter1 );
1819 upper.Append( rect.GetPoint( 1 ) );
1820 upper.Append( rect.GetPoint( 0 ) );
1821 upper.Append( *inter3 );
1822 upper.SetClosed( true );
1823
1824 // The right half
1825 lower.Append( *inter1 );
1826 lower.Append( rect.GetPoint( 2 ) );
1827 lower.Append( rect.GetPoint( 3 ) );
1828 lower.Append( *inter3 );
1829 lower.SetClosed( true );
1830 }
1831 else
1832 {
1833 // Angled line segment that cuts across a corner
1834 wxLogTrace( traceBoardOutline, wxT( "Segment intersects two perpendicular bbox sides" ) );
1835
1836 // Figure out which actual lines are intersected, since IntersectLines assumes
1837 // an infinite line
1838 bool hit0 = rect.Segment( 0 ).Contains( *inter0 );
1839 bool hit1 = rect.Segment( 1 ).Contains( *inter1 );
1840 bool hit2 = rect.Segment( 2 ).Contains( *inter2 );
1841 bool hit3 = rect.Segment( 3 ).Contains( *inter3 );
1842
1843 if( hit0 && hit1 )
1844 {
1845 // Cut across the upper left corner
1846 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
1847
1848 // The upper half
1849 upper.Append( *inter0 );
1850 upper.Append( rect.GetPoint( 1 ) );
1851 upper.Append( *inter1 );
1852 upper.SetClosed( true );
1853
1854 // The lower half
1855 lower.Append( *inter0 );
1856 lower.Append( rect.GetPoint( 0 ) );
1857 lower.Append( rect.GetPoint( 3 ) );
1858 lower.Append( rect.GetPoint( 2 ) );
1859 lower.Append( *inter1 );
1860 lower.SetClosed( true );
1861 }
1862 else if( hit1 && hit2 )
1863 {
1864 // Cut across the upper right corner
1865 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper right corner" ) );
1866
1867 // The upper half
1868 upper.Append( *inter1 );
1869 upper.Append( rect.GetPoint( 2 ) );
1870 upper.Append( *inter2 );
1871 upper.SetClosed( true );
1872
1873 // The lower half
1874 lower.Append( *inter1 );
1875 lower.Append( rect.GetPoint( 1 ) );
1876 lower.Append( rect.GetPoint( 0 ) );
1877 lower.Append( rect.GetPoint( 3 ) );
1878 lower.Append( *inter2 );
1879 lower.SetClosed( true );
1880 }
1881 else if( hit2 && hit3 )
1882 {
1883 // Cut across the lower right corner
1884 wxLogTrace( traceBoardOutline, wxT( "Segment cuts lower right corner" ) );
1885
1886 // The upper half
1887 upper.Append( *inter2 );
1888 upper.Append( rect.GetPoint( 2 ) );
1889 upper.Append( rect.GetPoint( 1 ) );
1890 upper.Append( rect.GetPoint( 0 ) );
1891 upper.Append( *inter3 );
1892 upper.SetClosed( true );
1893
1894 // The bottom half
1895 lower.Append( *inter2 );
1896 lower.Append( rect.GetPoint( 3 ) );
1897 lower.Append( *inter3 );
1898 lower.SetClosed( true );
1899 }
1900 else
1901 {
1902 // Cut across the lower left corner
1903 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
1904
1905 // The upper half
1906 upper.Append( *inter0 );
1907 upper.Append( rect.GetPoint( 1 ) );
1908 upper.Append( rect.GetPoint( 2 ) );
1909 upper.Append( rect.GetPoint( 3 ) );
1910 upper.Append( *inter3 );
1911 upper.SetClosed( true );
1912
1913 // The bottom half
1914 lower.Append( *inter0 );
1915 lower.Append( rect.GetPoint( 0 ) );
1916 lower.Append( *inter3 );
1917 lower.SetClosed( true );
1918 }
1919 }
1920 }
1921 else
1922 {
1923 // More than 1 segment
1924 wxLogTrace( traceBoardOutline, wxT( "Multiple segments in outline" ) );
1925
1926 // Just a temporary thing
1927 aOutlines = std::move( bbox );
1928 return true;
1929 }
1930
1931 // Figure out which is the correct outline
1932 SHAPE_POLY_SET poly1;
1933 SHAPE_POLY_SET poly2;
1934
1935 poly1.NewOutline();
1936 poly1.Append( upper );
1937
1938 poly2.NewOutline();
1939 poly2.Append( lower );
1940
1941 if( isCopperOutside( footprint, poly1 ) )
1942 {
1943 wxLogTrace( traceBoardOutline, wxT( "Using lower shape" ) );
1944 aOutlines = std::move( poly2 );
1945 }
1946 else
1947 {
1948 wxLogTrace( traceBoardOutline, wxT( "Using upper shape" ) );
1949 aOutlines = std::move( poly1 );
1950 }
1951
1952 // Add all closed polys as holes to the main outline
1953 for( SHAPE_LINE_CHAIN& closedChain : closedChains )
1954 {
1955 wxLogTrace( traceBoardOutline, wxT( "Adding hole to main outline" ) );
1956 aOutlines.AddHole( closedChain, -1 );
1957 }
1958
1959 return true;
1960 }
1961
1962 // We really shouldn't reach this point
1963 return false;
1964}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
int GetMaxError() const
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
const BOX2I GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition board.h:1290
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition board.h:1276
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:713
const FOOTPRINTS & Footprints() const
Definition board.h:465
int GetOutlinesChainingEpsilon()
Definition board.h:1072
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2538
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
constexpr BOX2< Vec > Intersect(const BOX2< Vec > &aRect)
Definition box2.h:344
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr const Vec & GetOrigin() const
Definition box2.h:207
int GetCount() const
Return the number of objects in the list.
Definition collector.h:79
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:158
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:160
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:167
int GetEllipseMinorRadius() const
Definition eda_shape.h:403
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:385
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:431
int GetEllipseMajorRadius() const
Definition eda_shape.h:394
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:412
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:177
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:333
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:283
std::vector< VECTOR2I > GetRectCorners() const
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:422
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:505
int GetRectangleHeight() const
int GetCornerRadius() const
VECTOR2I GetArcMid() const
std::deque< PAD * > & Pads()
Definition footprint.h:355
Definition pad.h:61
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:80
int GetWidth() const override
std::shared_ptr< SHAPE > GetEffectiveShape(PCB_LAYER_ID aLayer=UNDEFINED_LAYER, FLASHING aFlash=FLASHING::DEFAULT, DRC_CONSTRAINT_T aUsage=NULL_CONSTRAINT) const override
Make a set of SHAPE objects representing the PCB_SHAPE.
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition pcb_shape.h:70
Collect all BOARD_ITEM objects of a given set of KICAD_T type(s).
Definition collectors.h:518
void Collect(BOARD_ITEM *aBoard, const std::vector< KICAD_T > &aTypes)
Collect BOARD_ITEM objects using this class's Inspector method, which does the collection.
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:32
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:79
SCOPED_FLAGS_CLEANER(const SCOPED_FLAGS_CLEANER &)=delete
SCOPED_FLAGS_CLEANER(const EDA_ITEM_FLAGS &aFlagsToClear)
SCOPED_FLAGS_CLEANER & operator=(const SCOPED_FLAGS_CLEANER &)=delete
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition seg.cpp:401
static SEG::ecoord Square(int a)
Definition seg.h:119
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:217
bool Contains(const SEG &aSeg) const
Definition seg.h:321
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError) const
Build a polyline approximation of the ellipse or arc.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
int Intersect(const SEG &aSeg, std::vector< INTERSECTION > &aIp) const
Find all intersection points between our line chain and the segment aSeg.
const SHAPE_LINE_CHAIN Reverse() const
Reverse point order in the line chain.
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
bool IsArcEnd(size_t aIndex) const
void ClearArcs()
Remove all arc references in the line chain, resulting in a chain formed only of straight segments.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
void Clear()
Remove all points from the line chain.
void SetWidth(int aWidth) override
Set the width of all segments in the chain.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
virtual const SEG GetSegment(int aIndex) const override
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
void RemoveShape(int aPointIndex)
Remove the shape at the given index from the line chain.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
CONST_ITERATOR CIterate(int aFirst, int aLast, bool aIterateHoles=false) const
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
CONST_SEGMENT_ITERATOR CIterateSegments(int aFirst, int aLast, bool aIterateHoles=false) const
Return an iterator object, for iterating between aFirst and aLast outline, with or without holes (def...
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
SEGMENT_ITERATOR IterateSegmentsWithHoles()
Returns an iterator object, for all outlines in the set (with holes)
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
VECTOR2I projectPointOnSegment(const VECTOR2I &aEndPoint, const SHAPE_POLY_SET &aOutline, int aOutlineNum=0)
static void addHolesToPolygon(const std::vector< SHAPE_LINE_CHAIN > &aContours, const std::map< int, std::vector< int > > &aContourHierarchy, const std::map< int, int > &aContourToOutlineIdxMap, SHAPE_POLY_SET &aPolygons, bool aAllowUseArcsInPolygons, const std::set< int > &aCrossingContours)
bool BuildBoardPolygonOutlines(BOARD *aBoard, SHAPE_POLY_SET &aOutlines, int aErrorMax, int aChainingEpsilon, bool aInferOutlineIfNecessary, OUTLINE_ERROR_HANDLER *aErrorHandler, bool aAllowUseArcsInPolygons)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
static bool addOutlinesToPolygon(const std::vector< SHAPE_LINE_CHAIN > &aContours, const std::map< int, std::vector< int > > &aContourHierarchy, const std::set< int > &aCrossingContours, SHAPE_POLY_SET &aPolygons, bool aAllowDisjoint, OUTLINE_ERROR_HANDLER *aErrorHandler, const std::function< PCB_SHAPE *(const SEG &)> &aFetchOwner, std::map< int, int > &aContourToOutlineIdxMap)
static bool closerEndpoint(const nanoflann::ResultItem< uint32_t, double > &aLeft, const nanoflann::ResultItem< uint32_t, double > &aRight)
static bool isCopperOutside(const FOOTPRINT *aFootprint, SHAPE_POLY_SET &aShape)
static void processClosedShape(PCB_SHAPE *aShape, SHAPE_LINE_CHAIN &aContour, std::map< std::pair< VECTOR2I, VECTOR2I >, PCB_SHAPE * > &aShapeOwners, int aErrorMax, bool aAllowUseArcsInPolygons)
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PCB_SHAPE_ENDPOINTS_ADAPTOR >, PCB_SHAPE_ENDPOINTS_ADAPTOR, 2 > KDTree
bool ConvertOutlineToPolygon(std::vector< PCB_SHAPE * > &aShapeList, SHAPE_POLY_SET &aPolygons, int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint, OUTLINE_ERROR_HANDLER *aErrorHandler, bool aAllowUseArcsInPolygons)
Build a polygon set with holes from a PCB_SHAPE list.
bool TestBoardOutlinesGraphicItems(BOARD *aBoard, int aMinDist, OUTLINE_ERROR_HANDLER *aErrorHandler)
Test a board graphic items on edge cut layer for validity.
static std::set< int > findCrossingContours(const std::vector< SHAPE_LINE_CHAIN > &aContours)
void buildBoardBoundingBoxPoly(const BOARD *aBoard, SHAPE_POLY_SET &aOutline)
Get the complete bounding box of the board (including all items).
int findEndSegments(SHAPE_LINE_CHAIN &aChain, SEG &aStartSeg, SEG &aEndSeg)
static bool buildChainedClosedContour(PCB_SHAPE *aStart, std::set< PCB_SHAPE * > &aRemaining, const KDTree &aKdTree, const PCB_SHAPE_ENDPOINTS_ADAPTOR &aAdaptor, int aErrorMax, int aChainingEpsilon, SHAPE_LINE_CHAIN &aContour, PCB_SHAPE *&aOwnerShape)
static bool close_enough(VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit)
Local and tunable method of qualifying the proximity of two points.
static bool checkSelfIntersections(SHAPE_POLY_SET &aPolygons, OUTLINE_ERROR_HANDLER *aErrorHandler, const std::function< PCB_SHAPE *(const SEG &)> &aFetchOwner)
static std::map< int, std::vector< int > > buildContourHierarchy(const std::vector< SHAPE_LINE_CHAIN > &aContours)
static CHAIN_NEIGHBOURS findNeighbours(PCB_SHAPE *aShape, const VECTOR2I &aPoint, const KDTree &aKdTree, const PCB_SHAPE_ENDPOINTS_ADAPTOR &aAdaptor, double aChainingEpsilon, CONSUMED_FUNC aIsConsumed)
Find the shapes that could continue a chain at aPoint.
static bool closer_to_first(VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond)
Local method which qualifies whether the start or end point of a segment is closest to a point.
bool BuildFootprintPolygonOutlines(BOARD *aBoard, SHAPE_POLY_SET &aOutlines, int aErrorMax, int aChainingEpsilon, OUTLINE_ERROR_HANDLER *aErrorHandler)
Extract a board outline for a footprint view.
static void processShapeSegment(PCB_SHAPE *aShape, SHAPE_LINE_CHAIN &aContour, VECTOR2I &aPrevPt, std::map< std::pair< VECTOR2I, VECTOR2I >, PCB_SHAPE * > &aShapeOwners, int aErrorMax, int aChainingEpsilon, bool aAllowUseArcsInPolygons)
static VECTOR2I free_end(const PCB_SHAPE *aFirst, const PCB_SHAPE *aSecond)
Return the end of aFirst that does not join aSecond.
bool doConvertOutlineToPolygon(std::vector< PCB_SHAPE * > &aShapeList, SHAPE_POLY_SET &aPolygons, int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint, OUTLINE_ERROR_HANDLER *aErrorHandler, bool aAllowUseArcsInPolygons, SCOPED_FLAGS_CLEANER &aCleaner)
const std::function< void(const wxString &msg, BOARD_ITEM *itemA, BOARD_ITEM *itemB, const VECTOR2I &pt)> OUTLINE_ERROR_HANDLER
#define _(s)
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
#define SKIP_STRUCT
flag indicating that the structure should be ignored
std::uint32_t EDA_ITEM_FLAGS
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
a few functions useful in geometry calculations.
const wxChar * traceBoardOutline
Flag to enable debug tracing for the board outline creation.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Edge_Cuts
Definition layer_ids.h:108
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:92
CITER next(CITER it)
Definition ptree.cpp:120
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:35
PCB_SHAPE * available
nearest unchained
PCB_SHAPE * consumed
nearest already chained
std::vector< std::pair< VECTOR2I, PCB_SHAPE * > > endpoints
PCB_SHAPE_ENDPOINTS_ADAPTOR(const std::vector< PCB_SHAPE * > &shapes)
double kdtree_get_pt(const size_t idx, const size_t dim) const
VECTOR2I center
const SHAPE_LINE_CHAIN chain
int radius
wxString result
Test unit parsing edge cases and error handling.
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
constexpr int LexicographicalCompare(const VECTOR2< T > &aA, const VECTOR2< T > &aB)
Definition vector2d.h:657