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