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
674template <typename CONSUMED_FUNC>
675static CHAIN_NEIGHBOURS findNeighbours( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& aKdTree,
676 const PCB_SHAPE_ENDPOINTS_ADAPTOR& aAdaptor, double aChainingEpsilon,
677 CONSUMED_FUNC aIsConsumed )
678{
679 const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
680 const double radius_sq = aChainingEpsilon * aChainingEpsilon;
681
682 std::vector<nanoflann::ResultItem<uint32_t, double>> matches;
683 aKdTree.radiusSearch( query_pt, radius_sq, matches );
684
685 std::sort( matches.begin(), matches.end(), closerEndpoint );
686
688
689 for( const nanoflann::ResultItem<uint32_t, double>& match : matches )
690 {
691 PCB_SHAPE* candidate = aAdaptor.endpoints[match.first].second;
692
693 if( candidate == aShape )
694 continue;
695
696 PCB_SHAPE*& slot = aIsConsumed( candidate ) ? result.consumed : result.available;
697
698 if( !slot )
699 slot = candidate;
700
701 if( result.available && result.consumed )
702 break;
703 }
704
705 return result;
706}
707
708
709static std::set<int> findCrossingContours( const std::vector<SHAPE_LINE_CHAIN>& aContours )
710{
711 std::set<int> crossing;
712
713 for( size_t ii = 0; ii < aContours.size(); ++ii )
714 {
715 for( size_t jj = ii + 1; jj < aContours.size(); ++jj )
716 {
718
719 if( aContours[ii].Intersect( aContours[jj], intersections, true ) != 0 )
720 {
721 crossing.insert( ii );
722 crossing.insert( jj );
723 }
724 }
725 }
726
727 return crossing;
728}
729
730
731// Walk a chain of open shapes (segments/arcs/beziers) starting from aStart, and produce a
732// closed SHAPE_LINE_CHAIN if the chain forms a closed loop. Shapes that are consumed are
733// removed from aRemaining. Returns true and populates aContour and aOwnerShape only if a
734// closed contour is produced. Used to detect cross-contour intersections of bezier-bounded
735// slots which would otherwise be missed by the closed-shape-only intersection test.
736static bool buildChainedClosedContour( PCB_SHAPE* aStart, std::set<PCB_SHAPE*>& aRemaining,
737 const KDTree& aKdTree,
738 const PCB_SHAPE_ENDPOINTS_ADAPTOR& aAdaptor,
739 int aErrorMax, int aChainingEpsilon,
740 SHAPE_LINE_CHAIN& aContour, PCB_SHAPE*& aOwnerShape )
741{
742 std::deque<PCB_SHAPE*> chain;
743 chain.push_back( aStart );
744
745 bool closed = false;
746 VECTOR2I frontPt = aStart->GetStart();
747 VECTOR2I backPt = aStart->GetEnd();
748
749 std::set<PCB_SHAPE*> visited;
750 visited.insert( aStart );
751
752 auto extendChain = [&]( bool forward )
753 {
754 PCB_SHAPE* curr = forward ? chain.back() : chain.front();
755 VECTOR2I prev = forward ? backPt : frontPt;
756
757 for( ;; )
758 {
759 // The KD-tree spans the original openShapes set, so it still returns shapes
760 // already consumed by an earlier chain. Filter against aRemaining to avoid
761 // accidentally absorbing those into this chain.
762 auto isConsumed =
763 [&]( PCB_SHAPE* aCandidate )
764 {
765 return aRemaining.find( aCandidate ) == aRemaining.end()
766 || visited.find( aCandidate ) != visited.end();
767 };
768
769 CHAIN_NEIGHBOURS next = findNeighbours( curr, prev, aKdTree, aAdaptor, aChainingEpsilon,
770 isConsumed );
771
772 if( next.available )
773 {
774 visited.insert( next.available );
775
776 if( forward )
777 chain.push_back( next.available );
778 else
779 chain.push_front( next.available );
780
781 if( closer_to_first( prev, next.available->GetStart(), next.available->GetEnd() ) )
782 prev = next.available->GetEnd();
783 else
784 prev = next.available->GetStart();
785
786 curr = next.available;
787 continue;
788 }
789
790 // Match on position, not identity (see doConvertOutlineToPolygon)
791 VECTOR2I chainPt = forward ? frontPt : backPt;
792
793 if( chain.size() > 1 && close_enough( prev, chainPt, aChainingEpsilon ) )
794 closed = true;
795
796 if( forward )
797 backPt = prev;
798 else
799 frontPt = prev;
800
801 break;
802 }
803 };
804
805 extendChain( true );
806
807 if( !closed )
808 extendChain( false );
809
810 if( !closed )
811 return false;
812
813 // Build the contour from the closed chain, mirroring doConvertOutlineToPolygon().
814 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
815 PCB_SHAPE* first = chain.front();
816 VECTOR2I startPt;
817
818 if( chain.size() > 1 )
819 {
820 PCB_SHAPE* second = *( std::next( chain.begin() ) );
821
822 startPt = free_end( first, second );
823 }
824 else
825 {
826 startPt = first->GetStart();
827 }
828
829 aContour.Clear();
830 aContour.Append( startPt );
831 VECTOR2I prevPt = startPt;
832
833 for( PCB_SHAPE* shapeInChain : chain )
834 processShapeSegment( shapeInChain, aContour, prevPt, shapeOwners, aErrorMax, aChainingEpsilon, false );
835
836 if( aContour.PointCount() < 3 )
837 return false;
838
839 if( aContour.CPoint( 0 ) != aContour.CLastPoint() )
840 aContour.SetPoint( -1, aContour.CPoint( 0 ) );
841
842 aContour.SetClosed( true );
843
844 for( PCB_SHAPE* consumed : chain )
845 aRemaining.erase( consumed );
846
847 aOwnerShape = first;
848 return true;
849}
850
851
852bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
853 int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
854 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons,
855 SCOPED_FLAGS_CLEANER& aCleaner )
856{
857 if( aShapeList.size() == 0 )
858 return true;
859
860 bool selfIntersecting = false;
861 PCB_SHAPE* graphic = nullptr;
862
863 // Seed in list order so the polygon does not depend on addresses
864 std::unordered_set<PCB_SHAPE*> remaining( aShapeList.begin(), aShapeList.end() );
865 size_t nextSeed = 0;
866
867 // Pre-build KD-tree
868 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
869 KDTree kdTree( 2, adaptor );
870
871 // Keep a list of where the various shapes came from
872 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
873
874 auto fetchOwner =
875 [&]( const SEG& seg ) -> PCB_SHAPE*
876 {
877 auto it = shapeOwners.find( std::make_pair( seg.A, seg.B ) );
878 return it == shapeOwners.end() ? nullptr : it->second;
879 };
880
881 std::set<std::pair<PCB_SHAPE*, PCB_SHAPE*>> reportedGaps;
882 std::vector<SHAPE_LINE_CHAIN> contours;
883 contours.reserve( aShapeList.size() );
884
885 for( PCB_SHAPE* shape : aShapeList )
886 shape->ClearFlags( SKIP_STRUCT );
887
888 // Process each shape to build contours
889 while( !remaining.empty() )
890 {
891 while( nextSeed < aShapeList.size() && !remaining.count( aShapeList[nextSeed] ) )
892 nextSeed++;
893
894 if( nextSeed >= aShapeList.size() )
895 break;
896
897 graphic = aShapeList[nextSeed];
898 graphic->SetFlags( SKIP_STRUCT );
899 aCleaner.insert( graphic );
900 remaining.erase( graphic );
901
902 contours.emplace_back();
903 SHAPE_LINE_CHAIN& currContour = contours.back();
904 currContour.SetWidth( graphic->GetWidth() );
905
906 // Handle closed shapes (circles, rects, polygons, ellipses)
907 if( graphic->GetShape() == SHAPE_T::POLY || graphic->GetShape() == SHAPE_T::CIRCLE
908 || graphic->GetShape() == SHAPE_T::RECTANGLE || graphic->GetShape() == SHAPE_T::ELLIPSE )
909 {
910 processClosedShape( graphic, currContour, shapeOwners, aErrorMax, aAllowUseArcsInPolygons );
911 }
912 else
913 {
914 // Build chains for open shapes
915 std::deque<PCB_SHAPE*> chain;
916 chain.push_back( graphic );
917
918 bool closed = false;
919 VECTOR2I frontPt = graphic->GetStart();
920 VECTOR2I backPt = graphic->GetEnd();
921
922 auto extendChain = [&]( bool forward )
923 {
924 PCB_SHAPE* curr = forward ? chain.back() : chain.front();
925 VECTOR2I prev = forward ? backPt : frontPt;
926
927 for( ;; )
928 {
929 CHAIN_NEIGHBOURS next = findNeighbours( curr, prev, kdTree, adaptor, aChainingEpsilon,
930 []( PCB_SHAPE* aCandidate )
931 {
932 return ( aCandidate->GetFlags() & SKIP_STRUCT ) != 0;
933 } );
934
935 if( next.available )
936 {
937 next.available->SetFlags( SKIP_STRUCT );
938 aCleaner.insert( next.available );
939 remaining.erase( next.available );
940
941 if( forward )
942 chain.push_back( next.available );
943 else
944 chain.push_front( next.available );
945
946 if( closer_to_first( prev, next.available->GetStart(), next.available->GetEnd() ) )
947 prev = next.available->GetEnd();
948 else
949 prev = next.available->GetStart();
950
951 curr = next.available;
952 continue;
953 }
954
955 VECTOR2I chainPt = forward ? frontPt : backPt;
956
957 if( chain.size() > 1 && close_enough( prev, chainPt, aChainingEpsilon ) )
958 {
959 closed = true;
960 }
961 else if( next.consumed )
962 {
963 if( aErrorHandler )
964 ( *aErrorHandler )( _( "(self-intersecting)" ), curr, next.consumed, prev );
965
966 selfIntersecting = true;
967 }
968
969 if( forward )
970 backPt = prev;
971 else
972 frontPt = prev;
973
974 break;
975 }
976 };
977
978 extendChain( true );
979
980 if( !closed )
981 extendChain( false );
982
983 // Process the chain to build the contour
984 PCB_SHAPE* first = chain.front();
985 VECTOR2I startPt;
986
987 if( chain.size() > 1 )
988 {
989 PCB_SHAPE* second = *( std::next( chain.begin() ) );
990
991 startPt = free_end( first, second );
992 }
993 else
994 {
995 startPt = first->GetStart();
996 }
997
998 currContour.Append( startPt );
999 VECTOR2I prevPt = startPt;
1000
1001 for( PCB_SHAPE* shapeInChain : chain )
1002 {
1003 processShapeSegment( shapeInChain, currContour, prevPt, shapeOwners,
1004 aErrorMax, aChainingEpsilon, aAllowUseArcsInPolygons );
1005 }
1006
1007 // Handle contour closure
1008 if( close_enough( currContour.CPoint( 0 ), currContour.CLastPoint(), aChainingEpsilon ) )
1009 {
1010 if( currContour.CPoint( 0 ) != currContour.CLastPoint() && currContour.PointCount() > 2 )
1011 {
1012 PCB_SHAPE* owner = fetchOwner( currContour.CSegment( -1 ) );
1013
1014 if( currContour.IsArcEnd( currContour.PointCount() - 1 ) )
1015 {
1016 SHAPE_ARC arc = currContour.Arc( currContour.ArcIndex( currContour.PointCount() - 1 ) );
1017
1018 SHAPE_ARC sarc( arc.GetP0(), arc.GetArcMid(), currContour.CPoint( 0 ), 0 );
1019
1020 SHAPE_LINE_CHAIN arcChain;
1021 arcChain.Append( sarc, aErrorMax );
1022
1023 if( !aAllowUseArcsInPolygons )
1024 arcChain.ClearArcs();
1025
1026 for( int ii = 1; ii < arcChain.PointCount(); ++ii )
1027 shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ), arcChain.CPoint( ii ) )] = owner;
1028
1029 currContour.RemoveShape( currContour.PointCount() - 1 );
1030 currContour.Append( arcChain );
1031 }
1032 else
1033 {
1034 currContour.SetPoint( -1, currContour.CPoint( 0 ) );
1035
1036 shapeOwners[ std::make_pair( currContour.CPoints()[currContour.PointCount() - 2],
1037 currContour.CLastPoint() ) ] = owner;
1038 }
1039 }
1040
1041 currContour.SetClosed( true );
1042 }
1043 else
1044 {
1045 auto report_gap = [&]( const VECTOR2I& pt )
1046 {
1047 if( !aErrorHandler )
1048 return;
1049
1050 const double query_pt[2] = { static_cast<double>( pt.x ), static_cast<double>( pt.y ) };
1051
1052 // Both endpoints are in the tree, so over-fetch for a second shape
1053 uint32_t indices[8] = { 0 }; // make gcc quiet
1054 double dists[8];
1055
1056 const size_t found = kdTree.knnSearch( query_pt, 8, indices, dists );
1057
1058 if( found == 0 )
1059 return;
1060
1061 PCB_SHAPE* shapeA = adaptor.endpoints[indices[0]].second;
1062 PCB_SHAPE* shapeB = shapeA;
1063
1064 // A lone shape has no neighbour, so it pairs with itself
1065 for( size_t ii = 1; ii < found; ++ii )
1066 {
1067 if( adaptor.endpoints[indices[ii]].second != shapeA )
1068 {
1069 shapeB = adaptor.endpoints[indices[ii]].second;
1070 break;
1071 }
1072 }
1073
1074 // Avoid reporting the same pair twice
1075 auto key = std::minmax( shapeA, shapeB );
1076
1077 if( !reportedGaps.insert( key ).second )
1078 return;
1079
1080 // Find the nearest points between the two shapes and calculate midpoint
1081 std::shared_ptr<SHAPE> effectiveShapeA = shapeA->GetEffectiveShape();
1082 std::shared_ptr<SHAPE> effectiveShapeB = shapeB->GetEffectiveShape();
1083 VECTOR2I ptA, ptB;
1084 VECTOR2I midpoint = pt; // fallback to original point
1085
1086 if( effectiveShapeA && effectiveShapeB
1087 && effectiveShapeA->NearestPoints( effectiveShapeB.get(), ptA, ptB ) )
1088 {
1089 midpoint = ( ptA + ptB ) / 2;
1090 }
1091
1092 ( *aErrorHandler )( _( "(not a closed shape)" ), shapeA, shapeB, midpoint );
1093 };
1094
1095 report_gap( currContour.CPoint( 0 ) );
1096 report_gap( currContour.CLastPoint() );
1097 }
1098 }
1099 }
1100
1101 // Ensure all contours are closed
1102 for( const SHAPE_LINE_CHAIN& contour : contours )
1103 {
1104 if( !contour.IsClosed() )
1105 return false;
1106 }
1107
1108 // Generate bounding boxes for hierarchy calculations
1109 for( size_t ii = 0; ii < contours.size(); ++ii )
1110 {
1111 SHAPE_LINE_CHAIN& contour = contours[ii];
1112
1113 if( !contour.GetCachedBBox()->IsValid() )
1114 contour.GenerateBBoxCache();
1115 }
1116
1117 // Build contour hierarchy
1118 auto contourHierarchy = buildContourHierarchy( contours );
1119
1120 std::set<int> crossingContours;
1121
1122 if( !aAllowUseArcsInPolygons )
1123 crossingContours = findCrossingContours( contours );
1124
1125 // Add outlines to polygon set
1126 std::map<int, int> contourToOutlineIdxMap;
1127 if( !addOutlinesToPolygon( contours, contourHierarchy, crossingContours, aPolygons, aAllowDisjoint, aErrorHandler,
1128 fetchOwner, contourToOutlineIdxMap ) )
1129 {
1130 return false;
1131 }
1132
1133 // Add holes to polygon set
1134 addHolesToPolygon( contours, contourHierarchy, contourToOutlineIdxMap, aPolygons, aAllowUseArcsInPolygons,
1135 crossingContours );
1136
1137 // Check for self-intersections
1138 return checkSelfIntersections( aPolygons, aErrorHandler, fetchOwner );
1139}
1140
1141
1142bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
1143 int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
1144 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
1145{
1147
1148 return doConvertOutlineToPolygon( aShapeList, aPolygons, aErrorMax, aChainingEpsilon,
1149 aAllowDisjoint, aErrorHandler, aAllowUseArcsInPolygons,
1150 cleaner );
1151}
1152
1153
1154bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
1155 OUTLINE_ERROR_HANDLER* aErrorHandler )
1156{
1157 bool success = true;
1158 PCB_TYPE_COLLECTOR items;
1159 int min_dist = std::max( 0, aMinDist );
1160
1161 // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
1162 items.Collect( aBoard, { PCB_SHAPE_T } );
1163
1164 std::vector<PCB_SHAPE*> shapeList;
1165
1166 for( int ii = 0; ii < items.GetCount(); ii++ )
1167 {
1168 PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
1169
1170 if( seg->GetLayer() == Edge_Cuts )
1171 shapeList.push_back( seg );
1172 }
1173
1174 // Now Test validity of collected items
1175 for( PCB_SHAPE* shape : shapeList )
1176 {
1177 switch( shape->GetShape() )
1178 {
1179 case SHAPE_T::RECTANGLE:
1180 {
1181 VECTOR2I seg = shape->GetEnd() - shape->GetStart();
1182 int dim = seg.EuclideanNorm();
1183
1184 if( dim <= min_dist )
1185 {
1186 success = false;
1187
1188 if( aErrorHandler )
1189 {
1190 (*aErrorHandler)( wxString::Format( _( "(rectangle has null or very small "
1191 "size: %d nm)" ), dim ),
1192 shape, nullptr, shape->GetStart() );
1193 }
1194 }
1195 break;
1196 }
1197
1198 case SHAPE_T::CIRCLE:
1199 {
1200 int r = shape->GetRadius();
1201
1202 if( r <= min_dist )
1203 {
1204 success = false;
1205
1206 if( aErrorHandler )
1207 {
1208 (*aErrorHandler)( wxString::Format( _( "(circle has null or very small "
1209 "radius: %d nm)" ), r ),
1210 shape, nullptr, shape->GetStart() );
1211 }
1212 }
1213 break;
1214 }
1215
1216 case SHAPE_T::SEGMENT:
1217 {
1218 VECTOR2I seg = shape->GetEnd() - shape->GetStart();
1219 int dim = seg.EuclideanNorm();
1220
1221 if( dim <= min_dist )
1222 {
1223 success = false;
1224
1225 if( aErrorHandler )
1226 {
1227 (*aErrorHandler)( wxString::Format( _( "(segment has null or very small "
1228 "length: %d nm)" ), dim ),
1229 shape, nullptr, shape->GetStart() );
1230 }
1231 }
1232 break;
1233 }
1234
1235 case SHAPE_T::ARC:
1236 {
1237 // Arc size can be evaluated from the distance between arc middle point and arc ends
1238 // We do not need a precise value, just an idea of its size
1239 VECTOR2I arcMiddle = shape->GetArcMid();
1240 VECTOR2I seg1 = arcMiddle - shape->GetStart();
1241 VECTOR2I seg2 = shape->GetEnd() - arcMiddle;
1242 int dim = seg1.EuclideanNorm() + seg2.EuclideanNorm();
1243
1244 if( dim <= min_dist )
1245 {
1246 success = false;
1247
1248 if( aErrorHandler )
1249 {
1250 (*aErrorHandler)( wxString::Format( _( "(arc has null or very small size: "
1251 "%d nm)" ), dim ),
1252 shape, nullptr, shape->GetStart() );
1253 }
1254 }
1255 break;
1256 }
1257
1258 case SHAPE_T::POLY:
1259 break;
1260
1261 case SHAPE_T::BEZIER:
1262 break;
1263
1264 case SHAPE_T::ELLIPSE:
1266 {
1267 const int major = shape->GetEllipseMajorRadius();
1268 const int minor = shape->GetEllipseMinorRadius();
1269
1270 if( major <= min_dist || minor <= min_dist )
1271 {
1272 success = false;
1273
1274 if( aErrorHandler )
1275 {
1276 ( *aErrorHandler )( wxString::Format( _( "(ellipse has null or very small "
1277 "radii: major=%d nm, minor=%d nm)" ),
1278 major, minor ),
1279 shape, nullptr, shape->GetEllipseCenter() );
1280 }
1281 }
1282 break;
1283 }
1284
1285 default:
1286 UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
1287 return false;
1288 }
1289 }
1290
1291 std::vector<std::pair<PCB_SHAPE*, SHAPE_LINE_CHAIN>> closedContours;
1292 closedContours.reserve( shapeList.size() );
1293
1294 std::set<PCB_SHAPE*> openShapes;
1295
1296 for( PCB_SHAPE* shape : shapeList )
1297 {
1298 if( shape->GetShape() == SHAPE_T::POLY || shape->GetShape() == SHAPE_T::CIRCLE
1299 || shape->GetShape() == SHAPE_T::RECTANGLE || shape->GetShape() == SHAPE_T::ELLIPSE )
1300 {
1301 SHAPE_LINE_CHAIN contour;
1302 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
1303
1304 processClosedShape( shape, contour, shapeOwners, shape->GetMaxError(), true );
1305 closedContours.emplace_back( shape, std::move( contour ) );
1306 }
1307 else if( shape->GetShape() == SHAPE_T::SEGMENT || shape->GetShape() == SHAPE_T::ARC
1308 || shape->GetShape() == SHAPE_T::BEZIER || shape->GetShape() == SHAPE_T::ELLIPSE_ARC )
1309 {
1310 openShapes.insert( shape );
1311 }
1312 }
1313
1314 // Gather closed contours from chained open shapes (slots formed by segments/arcs/beziers).
1315 // Without this, malformed-outline detection misses overlaps involving such slots.
1316 if( !openShapes.empty() )
1317 {
1318 std::vector<PCB_SHAPE*> openShapeList( openShapes.begin(), openShapes.end() );
1319 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( openShapeList );
1320 KDTree kdTree( 2, adaptor );
1321
1322 int chainingEpsilon = aBoard->GetOutlinesChainingEpsilon();
1323 int maxError = aBoard->GetDesignSettings().m_MaxError;
1324
1325 while( !openShapes.empty() )
1326 {
1327 PCB_SHAPE* start = *openShapes.begin();
1328 SHAPE_LINE_CHAIN contour;
1329 PCB_SHAPE* owner = nullptr;
1330
1331 if( buildChainedClosedContour( start, openShapes, kdTree, adaptor, maxError,
1332 chainingEpsilon, contour, owner ) )
1333 {
1334 closedContours.emplace_back( owner, std::move( contour ) );
1335 }
1336 else
1337 {
1338 openShapes.erase( start );
1339 }
1340 }
1341 }
1342
1343 for( size_t ii = 0; ii < closedContours.size(); ++ii )
1344 {
1345 const SHAPE_LINE_CHAIN& contourA = closedContours[ii].second;
1346
1347 for( size_t jj = ii + 1; jj < closedContours.size(); ++jj )
1348 {
1349 const SHAPE_LINE_CHAIN& contourB = closedContours[jj].second;
1350 SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
1351
1352 // Ignore touching-only cases; report only real overlap/crossing.
1353 if( contourA.Intersect( contourB, intersections, true ) == 0 )
1354 continue;
1355
1356 success = false;
1357
1358 if( aErrorHandler )
1359 {
1360 PCB_SHAPE* shapeA = closedContours[ii].first;
1361 PCB_SHAPE* shapeB = closedContours[jj].first;
1362
1363 VECTOR2I midpoint = intersections.front().p;
1364 std::shared_ptr<SHAPE> effectiveShapeA = shapeA->GetEffectiveShape();
1365 std::shared_ptr<SHAPE> effectiveShapeB = shapeB->GetEffectiveShape();
1366
1367 if( effectiveShapeA && effectiveShapeB )
1368 {
1369 BOX2I bboxA = effectiveShapeA->BBox();
1370 BOX2I bboxB = effectiveShapeB->BBox();
1371 BOX2I overlapBox = bboxA.Intersect( bboxB );
1372
1373 if( overlapBox.GetWidth() > 0 && overlapBox.GetHeight() > 0 )
1374 midpoint = overlapBox.Centre();
1375 }
1376
1377 ( *aErrorHandler )( _( "(self-intersecting)" ), shapeA, shapeB, midpoint );
1378 }
1379 }
1380 }
1381
1382 return success;
1383}
1384
1385
1386bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
1387 int aChainingEpsilon, bool aInferOutlineIfNecessary,
1388 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
1389{
1390 PCB_TYPE_COLLECTOR items;
1391 SHAPE_POLY_SET fpHoles;
1392 bool success = false;
1393
1395
1396 // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
1397 items.Collect( aBoard, { PCB_SHAPE_T } );
1398
1399 for( int ii = 0; ii < items.GetCount(); ++ii )
1400 items[ii]->ClearFlags( SKIP_STRUCT );
1401
1402 for( FOOTPRINT* fp : aBoard->Footprints() )
1403 {
1404 PCB_TYPE_COLLECTOR fpItems;
1405 fpItems.Collect( fp, { PCB_SHAPE_T } );
1406
1407 std::vector<PCB_SHAPE*> fpSegList;
1408
1409 for( int ii = 0; ii < fpItems.GetCount(); ii++ )
1410 {
1411 PCB_SHAPE* fpSeg = static_cast<PCB_SHAPE*>( fpItems[ii] );
1412
1413 if( fpSeg->GetLayer() == Edge_Cuts )
1414 fpSegList.push_back( fpSeg );
1415 }
1416
1417 if( !fpSegList.empty() )
1418 {
1419 SHAPE_POLY_SET fpOutlines;
1420 success = doConvertOutlineToPolygon( fpSegList, fpOutlines, aErrorMax, aChainingEpsilon,
1421 false,
1422 nullptr, // don't report errors here; the second pass also
1423 // gets an opportunity to use these segments
1424 aAllowUseArcsInPolygons,
1425 cleaner );
1426
1427 // Test to see if we should make holes or outlines. Holes are made if the footprint
1428 // has copper outside of a single, closed outline. If there are multiple outlines,
1429 // we assume that the footprint edges represent holes as we do not support multiple
1430 // boards. Similarly, if any of the footprint pads are located outside of the edges,
1431 // then the edges are holes
1432 if( success && ( isCopperOutside( fp, fpOutlines ) || fpOutlines.OutlineCount() > 1 ) )
1433 {
1434 fpHoles.Append( fpOutlines );
1435 }
1436 else
1437 {
1438 // If it wasn't a closed area, or wasn't a hole, the we want to keep the fpSegs
1439 // in contention for the board outline builds.
1440 for( int ii = 0; ii < fpItems.GetCount(); ++ii )
1441 fpItems[ii]->ClearFlags( SKIP_STRUCT );
1442 }
1443 }
1444 }
1445
1446 // Make a working copy of aSegList, because the list is modified during calculations
1447 std::vector<PCB_SHAPE*> segList;
1448
1449 for( int ii = 0; ii < items.GetCount(); ii++ )
1450 {
1451 PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
1452
1453 // Skip anything already used to generate footprint holes (above)
1454 if( seg->GetFlags() & SKIP_STRUCT )
1455 continue;
1456
1457 if( seg->GetLayer() == Edge_Cuts )
1458 segList.push_back( seg );
1459 }
1460
1461 if( segList.size() )
1462 {
1463 success = doConvertOutlineToPolygon( segList, aOutlines, aErrorMax, aChainingEpsilon, true,
1464 aErrorHandler, aAllowUseArcsInPolygons, cleaner );
1465 }
1466
1467 if( ( !success || !aOutlines.OutlineCount() ) && aInferOutlineIfNecessary )
1468 {
1469 // Couldn't create a valid polygon outline. Use the board edge cuts bounding box to
1470 // create a rectangular outline, or, failing that, the bounding box of the items on
1471 // the board.
1472 BOX2I bbbox = aBoard->GetBoardEdgesBoundingBox();
1473
1474 // If null area, uses the global bounding box.
1475 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1476 bbbox = aBoard->ComputeBoundingBox( false, true );
1477
1478 // Ensure non null area. If happen, gives a minimal size.
1479 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1480 bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
1481
1482 aOutlines.RemoveAllContours();
1483 aOutlines.NewOutline();
1484
1485 VECTOR2I corner;
1486 aOutlines.Append( bbbox.GetOrigin() );
1487
1488 corner.x = bbbox.GetOrigin().x;
1489 corner.y = bbbox.GetEnd().y;
1490 aOutlines.Append( corner );
1491
1492 aOutlines.Append( bbbox.GetEnd() );
1493
1494 corner.x = bbbox.GetEnd().x;
1495 corner.y = bbbox.GetOrigin().y;
1496 aOutlines.Append( corner );
1497 }
1498
1499 if( aAllowUseArcsInPolygons )
1500 {
1501 for( int ii = 0; ii < fpHoles.OutlineCount(); ++ii )
1502 {
1503 const VECTOR2I holePt = fpHoles.Outline( ii ).CPoint( 0 );
1504
1505 for( int jj = 0; jj < aOutlines.OutlineCount(); ++jj )
1506 {
1507 if( aOutlines.Outline( jj ).PointInside( holePt ) )
1508 {
1509 aOutlines.AddHole( fpHoles.Outline( ii ), jj );
1510 break;
1511 }
1512 }
1513 }
1514 }
1515 else
1516 {
1517 fpHoles.Simplify();
1518 aOutlines.BooleanSubtract( fpHoles );
1519 }
1520
1521 return success;
1522}
1523
1524
1537void buildBoardBoundingBoxPoly( const BOARD* aBoard, SHAPE_POLY_SET& aOutline )
1538{
1539 BOX2I bbbox = aBoard->GetBoundingBox();
1541
1542 // If null area, uses the global bounding box.
1543 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1544 bbbox = aBoard->ComputeBoundingBox( false, true );
1545
1546 // Ensure non null area. If happen, gives a minimal size.
1547 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1548 bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
1549
1550 // Inflate slightly (by 1/10th the size of the box)
1551 bbbox.Inflate( bbbox.GetWidth() / 10, bbbox.GetHeight() / 10 );
1552
1553 chain.Append( bbbox.GetOrigin() );
1554 chain.Append( bbbox.GetOrigin().x, bbbox.GetEnd().y );
1555 chain.Append( bbbox.GetEnd() );
1556 chain.Append( bbbox.GetEnd().x, bbbox.GetOrigin().y );
1557 chain.SetClosed( true );
1558
1559 aOutline.RemoveAllContours();
1560 aOutline.AddOutline( chain );
1561}
1562
1563
1564VECTOR2I projectPointOnSegment( const VECTOR2I& aEndPoint, const SHAPE_POLY_SET& aOutline,
1565 int aOutlineNum = 0 )
1566{
1567 int minDistance = -1;
1568 VECTOR2I projPoint;
1569
1570 for( auto it = aOutline.CIterateSegments( aOutlineNum ); it; it++ )
1571 {
1572 auto seg = it.Get();
1573 int dis = seg.Distance( aEndPoint );
1574
1575 if( minDistance < 0 || ( dis < minDistance ) )
1576 {
1577 minDistance = dis;
1578 projPoint = seg.NearestPoint( aEndPoint );
1579 }
1580 }
1581
1582 return projPoint;
1583}
1584
1585
1586int findEndSegments( SHAPE_LINE_CHAIN& aChain, SEG& aStartSeg, SEG& aEndSeg )
1587{
1588 int foundSegs = 0;
1589
1590 for( int i = 0; i < aChain.SegmentCount(); i++ )
1591 {
1592 SEG seg = aChain.Segment( i );
1593
1594 bool foundA = false;
1595 bool foundB = false;
1596
1597 for( int j = 0; j < aChain.SegmentCount(); j++ )
1598 {
1599 // Don't test the segment against itself
1600 if( i == j )
1601 continue;
1602
1603 SEG testSeg = aChain.Segment( j );
1604
1605 if( testSeg.Contains( seg.A ) )
1606 foundA = true;
1607
1608 if( testSeg.Contains( seg.B ) )
1609 foundB = true;
1610 }
1611
1612 // This segment isn't a start or end
1613 if( foundA && foundB )
1614 continue;
1615
1616 if( foundSegs == 0 )
1617 {
1618 // The first segment we encounter is the "start" segment
1619 wxLogTrace( traceBoardOutline, wxT( "Found start segment: (%d, %d)-(%d, %d)" ),
1620 seg.A.x, seg.A.y, seg.B.x, seg.B.y );
1621 aStartSeg = seg;
1622 foundSegs++;
1623 }
1624 else
1625 {
1626 // Once we find both start and end, we can stop
1627 wxLogTrace( traceBoardOutline, wxT( "Found end segment: (%d, %d)-(%d, %d)" ),
1628 seg.A.x, seg.A.y, seg.B.x, seg.B.y );
1629 aEndSeg = seg;
1630 foundSegs++;
1631 break;
1632 }
1633 }
1634
1635 return foundSegs;
1636}
1637
1638
1639bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
1640 int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
1641
1642{
1643 FOOTPRINT* footprint = aBoard->GetFirstFootprint();
1644
1645 // No footprint loaded
1646 if( !footprint )
1647 {
1648 wxLogTrace( traceBoardOutline, wxT( "No footprint found on board" ) );
1649 return false;
1650 }
1651
1652 PCB_TYPE_COLLECTOR items;
1653 SHAPE_POLY_SET outlines;
1654 bool success = false;
1655
1657
1658 // Get all the SHAPEs into 'items', then keep only those on layer == Edge_Cuts.
1659 items.Collect( aBoard, { PCB_SHAPE_T } );
1660
1661 // Make a working copy of aSegList, because the list is modified during calculations
1662 std::vector<PCB_SHAPE*> segList;
1663
1664 for( int ii = 0; ii < items.GetCount(); ii++ )
1665 {
1666 if( items[ii]->GetLayer() == Edge_Cuts )
1667 segList.push_back( static_cast<PCB_SHAPE*>( items[ii] ) );
1668 }
1669
1670 if( !segList.empty() )
1671 {
1672 success = doConvertOutlineToPolygon( segList, outlines, aErrorMax, aChainingEpsilon, true,
1673 aErrorHandler, false, cleaner );
1674 }
1675
1676 // A closed outline was found on Edge_Cuts
1677 if( success )
1678 {
1679 wxLogTrace( traceBoardOutline, wxT( "Closed outline found" ) );
1680
1681 // If copper is outside a closed polygon, treat it as a hole
1682 // If there are multiple outlines in the footprint, they are also holes
1683 if( isCopperOutside( footprint, outlines ) || outlines.OutlineCount() > 1 )
1684 {
1685 wxLogTrace( traceBoardOutline, wxT( "Treating outline as a hole" ) );
1686
1687 buildBoardBoundingBoxPoly( aBoard, aOutlines );
1688
1689 // Copy all outlines from the conversion as holes into the new outline
1690 for( int i = 0; i < outlines.OutlineCount(); i++ )
1691 {
1692 SHAPE_LINE_CHAIN& out = outlines.Outline( i );
1693
1694 if( out.IsClosed() )
1695 aOutlines.AddHole( out, -1 );
1696
1697 for( int j = 0; j < outlines.HoleCount( i ); j++ )
1698 {
1699 SHAPE_LINE_CHAIN& hole = outlines.Hole( i, j );
1700
1701 if( hole.IsClosed() )
1702 aOutlines.AddHole( hole, -1 );
1703 }
1704 }
1705 }
1706 // If all copper is inside, then the computed outline is the board outline
1707 else
1708 {
1709 wxLogTrace( traceBoardOutline, wxT( "Treating outline as board edge" ) );
1710 aOutlines = std::move( outlines );
1711 }
1712
1713 return true;
1714 }
1715 // No board outlines were found, so use the bounding box
1716 else if( outlines.OutlineCount() == 0 )
1717 {
1718 wxLogTrace( traceBoardOutline, wxT( "Using footprint bounding box" ) );
1719 buildBoardBoundingBoxPoly( aBoard, aOutlines );
1720
1721 return true;
1722 }
1723 // There is an outline present, but it is not closed
1724 else
1725 {
1726 wxLogTrace( traceBoardOutline, wxT( "Trying to build outline" ) );
1727
1728 std::vector<SHAPE_LINE_CHAIN> closedChains;
1729 std::vector<SHAPE_LINE_CHAIN> openChains;
1730
1731 // The ConvertOutlineToPolygon function returns only one main outline and the rest as
1732 // holes, so we promote the holes and process them
1733 openChains.push_back( outlines.Outline( 0 ) );
1734
1735 for( int j = 0; j < outlines.HoleCount( 0 ); j++ )
1736 {
1737 SHAPE_LINE_CHAIN hole = outlines.Hole( 0, j );
1738
1739 if( hole.IsClosed() )
1740 {
1741 wxLogTrace( traceBoardOutline, wxT( "Found closed hole" ) );
1742 closedChains.push_back( hole );
1743 }
1744 else
1745 {
1746 wxLogTrace( traceBoardOutline, wxT( "Found open hole" ) );
1747 openChains.push_back( hole );
1748 }
1749 }
1750
1751 SHAPE_POLY_SET bbox;
1752 buildBoardBoundingBoxPoly( aBoard, bbox );
1753
1754 // Treat the open polys as the board edge
1755 SHAPE_LINE_CHAIN chain = openChains[0];
1756 SHAPE_LINE_CHAIN rect = bbox.Outline( 0 );
1757
1758 // We know the outline chain is open, so set to non-closed to get better segment count
1759 chain.SetClosed( false );
1760
1761 SEG startSeg;
1762 SEG endSeg;
1763
1764 // The two possible board outlines
1765 SHAPE_LINE_CHAIN upper;
1766 SHAPE_LINE_CHAIN lower;
1767
1768 findEndSegments( chain, startSeg, endSeg );
1769
1770 if( chain.SegmentCount() == 0 )
1771 {
1772 // Something is wrong, bail out with the overall footprint bounding box
1773 wxLogTrace( traceBoardOutline, wxT( "No line segments in provided outline" ) );
1774 aOutlines = std::move( bbox );
1775 return true;
1776 }
1777 else if( chain.SegmentCount() == 1 )
1778 {
1779 // This case means there is only 1 line segment making up the edge cuts of the
1780 // footprint, so we just need to use it to cut the bounding box in half.
1781 wxLogTrace( traceBoardOutline, wxT( "Only 1 line segment in provided outline" ) );
1782
1783 startSeg = chain.Segment( 0 );
1784
1785 // Intersect with all the sides of the rectangle
1786 OPT_VECTOR2I inter0 = startSeg.IntersectLines( rect.Segment( 0 ) );
1787 OPT_VECTOR2I inter1 = startSeg.IntersectLines( rect.Segment( 1 ) );
1788 OPT_VECTOR2I inter2 = startSeg.IntersectLines( rect.Segment( 2 ) );
1789 OPT_VECTOR2I inter3 = startSeg.IntersectLines( rect.Segment( 3 ) );
1790
1791 if( inter0 && inter2 && !inter1 && !inter3 )
1792 {
1793 // Intersects the vertical rectangle sides only
1794 wxLogTrace( traceBoardOutline, wxT( "Segment intersects only vertical bbox sides" ) );
1795
1796 // The upper half
1797 upper.Append( *inter0 );
1798 upper.Append( rect.GetPoint( 1 ) );
1799 upper.Append( rect.GetPoint( 2 ) );
1800 upper.Append( *inter2 );
1801 upper.SetClosed( true );
1802
1803 // The lower half
1804 lower.Append( *inter0 );
1805 lower.Append( rect.GetPoint( 0 ) );
1806 lower.Append( rect.GetPoint( 3 ) );
1807 lower.Append( *inter2 );
1808 lower.SetClosed( true );
1809 }
1810 else if( inter1 && inter3 && !inter0 && !inter2 )
1811 {
1812 // Intersects the horizontal rectangle sides only
1813 wxLogTrace( traceBoardOutline, wxT( "Segment intersects only horizontal bbox sides" ) );
1814
1815 // The left half
1816 upper.Append( *inter1 );
1817 upper.Append( rect.GetPoint( 1 ) );
1818 upper.Append( rect.GetPoint( 0 ) );
1819 upper.Append( *inter3 );
1820 upper.SetClosed( true );
1821
1822 // The right half
1823 lower.Append( *inter1 );
1824 lower.Append( rect.GetPoint( 2 ) );
1825 lower.Append( rect.GetPoint( 3 ) );
1826 lower.Append( *inter3 );
1827 lower.SetClosed( true );
1828 }
1829 else
1830 {
1831 // Angled line segment that cuts across a corner
1832 wxLogTrace( traceBoardOutline, wxT( "Segment intersects two perpendicular bbox sides" ) );
1833
1834 // Figure out which actual lines are intersected, since IntersectLines assumes
1835 // an infinite line
1836 bool hit0 = rect.Segment( 0 ).Contains( *inter0 );
1837 bool hit1 = rect.Segment( 1 ).Contains( *inter1 );
1838 bool hit2 = rect.Segment( 2 ).Contains( *inter2 );
1839 bool hit3 = rect.Segment( 3 ).Contains( *inter3 );
1840
1841 if( hit0 && hit1 )
1842 {
1843 // Cut across the upper left corner
1844 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
1845
1846 // The upper half
1847 upper.Append( *inter0 );
1848 upper.Append( rect.GetPoint( 1 ) );
1849 upper.Append( *inter1 );
1850 upper.SetClosed( true );
1851
1852 // The lower half
1853 lower.Append( *inter0 );
1854 lower.Append( rect.GetPoint( 0 ) );
1855 lower.Append( rect.GetPoint( 3 ) );
1856 lower.Append( rect.GetPoint( 2 ) );
1857 lower.Append( *inter1 );
1858 lower.SetClosed( true );
1859 }
1860 else if( hit1 && hit2 )
1861 {
1862 // Cut across the upper right corner
1863 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper right corner" ) );
1864
1865 // The upper half
1866 upper.Append( *inter1 );
1867 upper.Append( rect.GetPoint( 2 ) );
1868 upper.Append( *inter2 );
1869 upper.SetClosed( true );
1870
1871 // The lower half
1872 lower.Append( *inter1 );
1873 lower.Append( rect.GetPoint( 1 ) );
1874 lower.Append( rect.GetPoint( 0 ) );
1875 lower.Append( rect.GetPoint( 3 ) );
1876 lower.Append( *inter2 );
1877 lower.SetClosed( true );
1878 }
1879 else if( hit2 && hit3 )
1880 {
1881 // Cut across the lower right corner
1882 wxLogTrace( traceBoardOutline, wxT( "Segment cuts lower right corner" ) );
1883
1884 // The upper half
1885 upper.Append( *inter2 );
1886 upper.Append( rect.GetPoint( 2 ) );
1887 upper.Append( rect.GetPoint( 1 ) );
1888 upper.Append( rect.GetPoint( 0 ) );
1889 upper.Append( *inter3 );
1890 upper.SetClosed( true );
1891
1892 // The bottom half
1893 lower.Append( *inter2 );
1894 lower.Append( rect.GetPoint( 3 ) );
1895 lower.Append( *inter3 );
1896 lower.SetClosed( true );
1897 }
1898 else
1899 {
1900 // Cut across the lower left corner
1901 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
1902
1903 // The upper half
1904 upper.Append( *inter0 );
1905 upper.Append( rect.GetPoint( 1 ) );
1906 upper.Append( rect.GetPoint( 2 ) );
1907 upper.Append( rect.GetPoint( 3 ) );
1908 upper.Append( *inter3 );
1909 upper.SetClosed( true );
1910
1911 // The bottom half
1912 lower.Append( *inter0 );
1913 lower.Append( rect.GetPoint( 0 ) );
1914 lower.Append( *inter3 );
1915 lower.SetClosed( true );
1916 }
1917 }
1918 }
1919 else
1920 {
1921 // More than 1 segment
1922 wxLogTrace( traceBoardOutline, wxT( "Multiple segments in outline" ) );
1923
1924 // Just a temporary thing
1925 aOutlines = std::move( bbox );
1926 return true;
1927 }
1928
1929 // Figure out which is the correct outline
1930 SHAPE_POLY_SET poly1;
1931 SHAPE_POLY_SET poly2;
1932
1933 poly1.NewOutline();
1934 poly1.Append( upper );
1935
1936 poly2.NewOutline();
1937 poly2.Append( lower );
1938
1939 if( isCopperOutside( footprint, poly1 ) )
1940 {
1941 wxLogTrace( traceBoardOutline, wxT( "Using lower shape" ) );
1942 aOutlines = std::move( poly2 );
1943 }
1944 else
1945 {
1946 wxLogTrace( traceBoardOutline, wxT( "Using upper shape" ) );
1947 aOutlines = std::move( poly1 );
1948 }
1949
1950 // Add all closed polys as holes to the main outline
1951 for( SHAPE_LINE_CHAIN& closedChain : closedChains )
1952 {
1953 wxLogTrace( traceBoardOutline, wxT( "Adding hole to main outline" ) );
1954 aOutlines.AddHole( closedChain, -1 );
1955 }
1956
1957 return true;
1958 }
1959
1960 // We really shouldn't reach this point
1961 return false;
1962}
@ ERROR_INSIDE
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
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:409
const BOX2I GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition board.h:1279
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition board.h:1265
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:704
const FOOTPRINTS & Footprints() const
Definition board.h:463
int GetOutlinesChainingEpsilon()
Definition board.h:1062
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1299
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2721
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:914
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:404
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:517
void Collect(BOARD_ITEM *aBoard, const std::vector< KICAD_T > &aTypes)
Collect BOARD_ITEM objects using this class's Inspector method, which does the collection.
A round rectangle shape, based on a rectangle and a radius.
Definition roundrect.h:32
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aMaxError) const
Get the polygonal representation of the roundrect.
Definition roundrect.cpp:79
SCOPED_FLAGS_CLEANER(const EDA_ITEM_FLAGS &aFlagsToClear)
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition seg.cpp:442
static SEG::ecoord Square(int a)
Definition seg.h:119
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:216
bool Contains(const SEG &aSeg) const
Definition seg.h:320
const VECTOR2I & GetArcMid() const
Definition shape_arc.h:116
const VECTOR2I & GetP0() const
Definition shape_arc.h:114
SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError) const
Build a polyline approximation of the ellipse or arc.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_LINE_CHAIN Reverse() const
Reverse point order in the line chain.
const SHAPE_ARC & Arc(size_t aArc) const
bool IsClosed() const override
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
void GenerateBBoxCache() const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int Intersect(const SEG &aSeg, INTERSECTIONS &aIp) const
Find all intersection points between our line chain and the segment aSeg.
int PointCount() const
Return the number of points (vertices) in this line chain.
bool IsArcEnd(size_t aIndex) const
void ClearArcs()
Remove all arc references in the line chain, resulting in a chain formed only of straight segments.
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
void Clear()
Remove all points from the line chain.
void SetWidth(int aWidth) override
Set the width of all segments in the chain.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
BOX2I * GetCachedBBox() const override
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
virtual const SEG GetSegment(int aIndex) const override
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
void RemoveShape(int aPointIndex)
Remove the shape at the given index from the line chain.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
std::vector< INTERSECTION > INTERSECTIONS
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
CONST_ITERATOR CIterate(int aFirst, int aLast, bool aIterateHoles=false) const
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
CONST_SEGMENT_ITERATOR CIterateSegments(int aFirst, int aLast, bool aIterateHoles=false) const
Return an iterator object, for iterating between aFirst and aLast outline, with or without holes (def...
int OutlineCount() const
Return the number of outlines in the set.
SHAPE_POLY_SET CloneDropTriangulation() const
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
SEGMENT_ITERATOR IterateSegmentsWithHoles()
Returns an iterator object, for all outlines in the set (with holes)
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
VECTOR2I projectPointOnSegment(const VECTOR2I &aEndPoint, const SHAPE_POLY_SET &aOutline, int aOutlineNum=0)
static void addHolesToPolygon(const std::vector< SHAPE_LINE_CHAIN > &aContours, const std::map< int, std::vector< int > > &aContourHierarchy, const std::map< int, int > &aContourToOutlineIdxMap, SHAPE_POLY_SET &aPolygons, bool aAllowUseArcsInPolygons, const std::set< int > &aCrossingContours)
bool BuildBoardPolygonOutlines(BOARD *aBoard, SHAPE_POLY_SET &aOutlines, int aErrorMax, int aChainingEpsilon, bool aInferOutlineIfNecessary, OUTLINE_ERROR_HANDLER *aErrorHandler, bool aAllowUseArcsInPolygons)
Extract the board outlines and build a closed polygon from lines, arcs and circle items on edge cut l...
static bool addOutlinesToPolygon(const std::vector< SHAPE_LINE_CHAIN > &aContours, const std::map< int, std::vector< int > > &aContourHierarchy, const std::set< int > &aCrossingContours, SHAPE_POLY_SET &aPolygons, bool aAllowDisjoint, OUTLINE_ERROR_HANDLER *aErrorHandler, const std::function< PCB_SHAPE *(const SEG &)> &aFetchOwner, std::map< int, int > &aContourToOutlineIdxMap)
static bool 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:428
#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:683
constexpr int LexicographicalCompare(const VECTOR2< T > &aA, const VECTOR2< T > &aB)
Definition vector2d.h:632