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