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