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