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