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,
439 SHAPE_POLY_SET& aPolygons )
440{
441 for( const auto& [ contourIndex, parentIndexes ] : aContourHierarchy )
442 {
443 if( parentIndexes.size() % 2 == 1 )
444 {
445 // Odd number of parents; we're a hole in the parent which has one fewer parents
446 const SHAPE_LINE_CHAIN& hole = aContours[ contourIndex ];
447
448 for( int parentContourIdx : parentIndexes )
449 {
450 if( aContourHierarchy.at( parentContourIdx ).size() == parentIndexes.size() - 1 )
451 {
452 int outlineIdx = aContourToOutlineIdxMap.at( parentContourIdx );
453 aPolygons.AddHole( hole, outlineIdx );
454 break;
455 }
456 }
457 }
458 }
459}
460
462 OUTLINE_ERROR_HANDLER* aErrorHandler,
463 const std::function<PCB_SHAPE*(const SEG&)>& aFetchOwner )
464{
465 bool selfIntersecting = false;
466 std::vector<SEG> segments;
467 size_t total = 0;
468
469 for( int ii = 0; ii < aPolygons.OutlineCount(); ++ii )
470 {
471 const SHAPE_LINE_CHAIN& contour = aPolygons.Outline( ii );
472 total += contour.SegmentCount();
473
474 for( int jj = 0; jj < aPolygons.HoleCount( ii ); ++jj )
475 {
476 const SHAPE_LINE_CHAIN& hole = aPolygons.Hole( ii, jj );
477 total += hole.SegmentCount();
478 }
479 }
480
481 segments.reserve( total );
482
483 for( auto seg = aPolygons.IterateSegmentsWithHoles(); seg; seg++ )
484 {
485 SEG segment = *seg;
486
487 if( LexicographicalCompare( segment.A, segment.B ) > 0 )
488 std::swap( segment.A, segment.B );
489
490 segments.push_back( segment );
491 }
492
493 std::sort( segments.begin(), segments.end(),
494 []( const SEG& a, const SEG& b )
495 {
496 if( a.A != b.A )
497 return LexicographicalCompare( a.A, b.A ) < 0;
498 return LexicographicalCompare( a.B, b.B ) < 0;
499 } );
500
501 for( size_t i = 0; i < segments.size(); ++i )
502 {
503 const SEG& seg1 = segments[i];
504
505 for( size_t j = i + 1; j < segments.size(); ++j )
506 {
507 const SEG& seg2 = segments[j];
508
509 if( seg2.A > seg1.B )
510 break;
511
512 if( seg1 == seg2 || ( seg1.A == seg2.B && seg1.B == seg2.A ) )
513 {
514 if( aErrorHandler )
515 {
516 BOARD_ITEM* a = aFetchOwner( seg1 );
517 BOARD_ITEM* b = aFetchOwner( seg2 );
518 (*aErrorHandler)( _( "(self-intersecting)" ), a, b, seg1.A );
519 }
520 selfIntersecting = true;
521 }
522 else if( OPT_VECTOR2I pt = seg1.Intersect( seg2, true ) )
523 {
524 if( aErrorHandler )
525 {
526 BOARD_ITEM* a = aFetchOwner( seg1 );
527 BOARD_ITEM* b = aFetchOwner( seg2 );
528 (*aErrorHandler)( _( "(self-intersecting)" ), a, b, *pt );
529 }
530 selfIntersecting = true;
531 }
532 }
533 }
534
535 return !selfIntersecting;
536}
537
538// Helper function to find next shape using KD-tree
539static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree,
540 const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, double aChainingEpsilon )
541{
542 const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
543
544 uint32_t indices[2];
545 double distances[2];
546 kdTree.knnSearch( query_pt, 2, indices, distances );
547
548 if( distances[0] == std::numeric_limits<double>::max() )
549 return nullptr;
550
551 // Find the closest valid candidate
552 PCB_SHAPE* closest_graphic = nullptr;
553 double closest_dist_sq = aChainingEpsilon * aChainingEpsilon;
554
555 for( size_t i = 0; i < 2; ++i )
556 {
557 if( distances[i] == std::numeric_limits<double>::max() )
558 continue;
559
560 PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second;
561
562 if( candidate == aShape )
563 continue;
564
565 if( distances[i] < closest_dist_sq )
566 {
567 closest_dist_sq = distances[i];
568 closest_graphic = candidate;
569 }
570 }
571
572 return closest_graphic;
573}
574
575bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
576 int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
577 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons,
578 SCOPED_FLAGS_CLEANER& aCleaner )
579{
580 if( aShapeList.size() == 0 )
581 return true;
582
583 bool selfIntersecting = false;
584 PCB_SHAPE* graphic = nullptr;
585
586 std::set<PCB_SHAPE*> startCandidates( aShapeList.begin(), aShapeList.end() );
587
588 // Pre-build KD-tree
589 PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
590 KDTree kdTree( 2, adaptor );
591
592 // Keep a list of where the various shapes came from
593 std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
594
595 auto fetchOwner =
596 [&]( const SEG& seg ) -> PCB_SHAPE*
597 {
598 auto it = shapeOwners.find( std::make_pair( seg.A, seg.B ) );
599 return it == shapeOwners.end() ? nullptr : it->second;
600 };
601
602 std::set<std::pair<PCB_SHAPE*, PCB_SHAPE*>> reportedGaps;
603 std::vector<SHAPE_LINE_CHAIN> contours;
604 contours.reserve( startCandidates.size() );
605
606 for( PCB_SHAPE* shape : startCandidates )
607 shape->ClearFlags( SKIP_STRUCT );
608
609 // Process each shape to build contours
610 while( startCandidates.size() )
611 {
612 graphic = *startCandidates.begin();
613 graphic->SetFlags( SKIP_STRUCT );
614 aCleaner.insert( graphic );
615 startCandidates.erase( startCandidates.begin() );
616
617 contours.emplace_back();
618 SHAPE_LINE_CHAIN& currContour = contours.back();
619 currContour.SetWidth( graphic->GetWidth() );
620
621 // Handle closed shapes (circles, rects, polygons)
622 if( graphic->GetShape() == SHAPE_T::POLY || graphic->GetShape() == SHAPE_T::CIRCLE
623 || graphic->GetShape() == SHAPE_T::RECTANGLE )
624 {
625 processClosedShape( graphic, currContour, shapeOwners, aErrorMax, aAllowUseArcsInPolygons );
626 }
627 else
628 {
629 // Build chains for open shapes
630 std::deque<PCB_SHAPE*> chain;
631 chain.push_back( graphic );
632
633 bool closed = false;
634 VECTOR2I frontPt = graphic->GetStart();
635 VECTOR2I backPt = graphic->GetEnd();
636
637 auto extendChain = [&]( bool forward )
638 {
639 PCB_SHAPE* curr = forward ? chain.back() : chain.front();
640 VECTOR2I prev = forward ? backPt : frontPt;
641
642 for( ;; )
643 {
644 PCB_SHAPE* next = findNext( curr, prev, kdTree, adaptor, aChainingEpsilon );
645
646 if( next && !( next->GetFlags() & SKIP_STRUCT ) )
647 {
648 next->SetFlags( SKIP_STRUCT );
649 aCleaner.insert( next );
650 startCandidates.erase( next );
651
652 if( forward )
653 chain.push_back( next );
654 else
655 chain.push_front( next );
656
657 if( closer_to_first( prev, next->GetStart(), next->GetEnd() ) )
658 prev = next->GetEnd();
659 else
660 prev = next->GetStart();
661
662 curr = next;
663 continue;
664 }
665
666 if( next )
667 {
668 PCB_SHAPE* chainEnd = forward ? chain.front() : chain.back();
669 VECTOR2I chainPt = forward ? frontPt : backPt;
670
671 if( next == chainEnd && close_enough( prev, chainPt, aChainingEpsilon ) )
672 {
673 closed = true;
674 }
675 else
676 {
677 if( aErrorHandler )
678 ( *aErrorHandler )( _( "(self-intersecting)" ), curr, next, prev );
679
680 selfIntersecting = true;
681 }
682 }
683
684 if( forward )
685 backPt = prev;
686 else
687 frontPt = prev;
688
689 break;
690 }
691 };
692
693 extendChain( true );
694
695 if( !closed )
696 extendChain( false );
697
698 // Process the chain to build the contour
699 PCB_SHAPE* first = chain.front();
700 VECTOR2I startPt;
701
702 if( chain.size() > 1 )
703 {
704 PCB_SHAPE* second = *( std::next( chain.begin() ) );
705
706 if( close_enough( first->GetStart(), second->GetStart(), aChainingEpsilon )
707 || close_enough( first->GetStart(), second->GetEnd(), aChainingEpsilon ) )
708 startPt = first->GetEnd();
709 else
710 startPt = first->GetStart();
711 }
712 else
713 {
714 startPt = first->GetStart();
715 }
716
717 currContour.Append( startPt );
718 VECTOR2I prevPt = startPt;
719
720 for( PCB_SHAPE* shapeInChain : chain )
721 {
722 processShapeSegment( shapeInChain, currContour, prevPt, shapeOwners,
723 aErrorMax, aChainingEpsilon, aAllowUseArcsInPolygons );
724 }
725
726 // Handle contour closure
727 if( close_enough( currContour.CPoint( 0 ), currContour.CLastPoint(), aChainingEpsilon ) )
728 {
729 if( currContour.CPoint( 0 ) != currContour.CLastPoint() && currContour.PointCount() > 2 )
730 {
731 PCB_SHAPE* owner = fetchOwner( currContour.CSegment( -1 ) );
732
733 if( currContour.IsArcEnd( currContour.PointCount() - 1 ) )
734 {
735 SHAPE_ARC arc = currContour.Arc( currContour.ArcIndex( currContour.PointCount() - 1 ) );
736
737 SHAPE_ARC sarc( arc.GetP0(), arc.GetArcMid(), currContour.CPoint( 0 ), 0 );
738
739 SHAPE_LINE_CHAIN arcChain;
740 arcChain.Append( sarc, aErrorMax );
741
742 if( !aAllowUseArcsInPolygons )
743 arcChain.ClearArcs();
744
745 for( int ii = 1; ii < arcChain.PointCount(); ++ii )
746 shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ), arcChain.CPoint( ii ) )] = owner;
747
748 currContour.RemoveShape( currContour.PointCount() - 1 );
749 currContour.Append( arcChain );
750 }
751 else
752 {
753 currContour.SetPoint( -1, currContour.CPoint( 0 ) );
754
755 shapeOwners[ std::make_pair( currContour.CPoints()[currContour.PointCount() - 2],
756 currContour.CLastPoint() ) ] = owner;
757 }
758 }
759
760 currContour.SetClosed( true );
761 }
762 else
763 {
764 auto report_gap = [&]( const VECTOR2I& pt )
765 {
766 if( !aErrorHandler )
767 return;
768
769 const double query_pt[2] = { static_cast<double>( pt.x ), static_cast<double>( pt.y ) };
770 uint32_t indices[2] = { 0, 0 }; // make gcc quiet
771 double dists[2];
772
773 // Find the two closest items to the given point using kdtree
774 kdTree.knnSearch( query_pt, 2, indices, dists );
775
776 PCB_SHAPE* shapeA = adaptor.endpoints[indices[0]].second;
777 PCB_SHAPE* shapeB = adaptor.endpoints[indices[1]].second;
778
779 // Avoid reporting the same pair twice
780 auto key = std::minmax( shapeA, shapeB );
781
782 if( !reportedGaps.insert( key ).second )
783 return;
784
785 // Find the nearest points between the two shapes and calculate midpoint
786 std::shared_ptr<SHAPE> effectiveShapeA = shapeA->GetEffectiveShape();
787 std::shared_ptr<SHAPE> effectiveShapeB = shapeB->GetEffectiveShape();
788 VECTOR2I ptA, ptB;
789 VECTOR2I midpoint = pt; // fallback to original point
790
791 if( effectiveShapeA && effectiveShapeB
792 && effectiveShapeA->NearestPoints( effectiveShapeB.get(), ptA, ptB ) )
793 {
794 midpoint = ( ptA + ptB ) / 2;
795 }
796
797 ( *aErrorHandler )( _( "(not a closed shape)" ), shapeA, shapeB, midpoint );
798 };
799
800 report_gap( currContour.CPoint( 0 ) );
801 report_gap( currContour.CLastPoint() );
802 }
803 }
804 }
805
806 // Ensure all contours are closed
807 for( const SHAPE_LINE_CHAIN& contour : contours )
808 {
809 if( !contour.IsClosed() )
810 return false;
811 }
812
813 // Generate bounding boxes for hierarchy calculations
814 for( size_t ii = 0; ii < contours.size(); ++ii )
815 {
816 SHAPE_LINE_CHAIN& contour = contours[ii];
817
818 if( !contour.GetCachedBBox()->IsValid() )
819 contour.GenerateBBoxCache();
820 }
821
822 // Build contour hierarchy
823 auto contourHierarchy = buildContourHierarchy( contours );
824
825 // Add outlines to polygon set
826 std::map<int, int> contourToOutlineIdxMap;
827 if( !addOutlinesToPolygon( contours, contourHierarchy, aPolygons, aAllowDisjoint,
828 aErrorHandler, fetchOwner, contourToOutlineIdxMap ) )
829 {
830 return false;
831 }
832
833 // Add holes to polygon set
834 addHolesToPolygon( contours, contourHierarchy, contourToOutlineIdxMap, aPolygons );
835
836 // Check for self-intersections
837 return checkSelfIntersections( aPolygons, aErrorHandler, fetchOwner );
838}
839
840
841bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
842 int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
843 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
844{
846
847 return doConvertOutlineToPolygon( aShapeList, aPolygons, aErrorMax, aChainingEpsilon,
848 aAllowDisjoint, aErrorHandler, aAllowUseArcsInPolygons,
849 cleaner );
850}
851
852
853bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
854 OUTLINE_ERROR_HANDLER* aErrorHandler )
855{
856 bool success = true;
857 PCB_TYPE_COLLECTOR items;
858 int min_dist = std::max( 0, aMinDist );
859
860 // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
861 items.Collect( aBoard, { PCB_SHAPE_T } );
862
863 std::vector<PCB_SHAPE*> shapeList;
864
865 for( int ii = 0; ii < items.GetCount(); ii++ )
866 {
867 PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
868
869 if( seg->GetLayer() == Edge_Cuts )
870 shapeList.push_back( seg );
871 }
872
873 // Now Test validity of collected items
874 for( PCB_SHAPE* shape : shapeList )
875 {
876 switch( shape->GetShape() )
877 {
879 {
880 VECTOR2I seg = shape->GetEnd() - shape->GetStart();
881 int dim = seg.EuclideanNorm();
882
883 if( dim <= min_dist )
884 {
885 success = false;
886
887 if( aErrorHandler )
888 {
889 (*aErrorHandler)( wxString::Format( _( "(rectangle has null or very small "
890 "size: %d nm)" ), dim ),
891 shape, nullptr, shape->GetStart() );
892 }
893 }
894 break;
895 }
896
897 case SHAPE_T::CIRCLE:
898 {
899 int r = shape->GetRadius();
900
901 if( r <= min_dist )
902 {
903 success = false;
904
905 if( aErrorHandler )
906 {
907 (*aErrorHandler)( wxString::Format( _( "(circle has null or very small "
908 "radius: %d nm)" ), r ),
909 shape, nullptr, shape->GetStart() );
910 }
911 }
912 break;
913 }
914
915 case SHAPE_T::SEGMENT:
916 {
917 VECTOR2I seg = shape->GetEnd() - shape->GetStart();
918 int dim = seg.EuclideanNorm();
919
920 if( dim <= min_dist )
921 {
922 success = false;
923
924 if( aErrorHandler )
925 {
926 (*aErrorHandler)( wxString::Format( _( "(segment has null or very small "
927 "length: %d nm)" ), dim ),
928 shape, nullptr, shape->GetStart() );
929 }
930 }
931 break;
932 }
933
934 case SHAPE_T::ARC:
935 {
936 // Arc size can be evaluated from the distance between arc middle point and arc ends
937 // We do not need a precise value, just an idea of its size
938 VECTOR2I arcMiddle = shape->GetArcMid();
939 VECTOR2I seg1 = arcMiddle - shape->GetStart();
940 VECTOR2I seg2 = shape->GetEnd() - arcMiddle;
941 int dim = seg1.EuclideanNorm() + seg2.EuclideanNorm();
942
943 if( dim <= min_dist )
944 {
945 success = false;
946
947 if( aErrorHandler )
948 {
949 (*aErrorHandler)( wxString::Format( _( "(arc has null or very small size: "
950 "%d nm)" ), dim ),
951 shape, nullptr, shape->GetStart() );
952 }
953 }
954 break;
955 }
956
957 case SHAPE_T::POLY:
958 break;
959
960 case SHAPE_T::BEZIER:
961 break;
962
963 default:
964 UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
965 return false;
966 }
967 }
968
969 return success;
970}
971
972
973bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
974 int aChainingEpsilon, bool aInferOutlineIfNecessary,
975 OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
976{
977 PCB_TYPE_COLLECTOR items;
978 SHAPE_POLY_SET fpHoles;
979 bool success = false;
980
982
983 // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
984 items.Collect( aBoard, { PCB_SHAPE_T } );
985
986 for( int ii = 0; ii < items.GetCount(); ++ii )
987 items[ii]->ClearFlags( SKIP_STRUCT );
988
989 for( FOOTPRINT* fp : aBoard->Footprints() )
990 {
991 PCB_TYPE_COLLECTOR fpItems;
992 fpItems.Collect( fp, { PCB_SHAPE_T } );
993
994 std::vector<PCB_SHAPE*> fpSegList;
995
996 for( int ii = 0; ii < fpItems.GetCount(); ii++ )
997 {
998 PCB_SHAPE* fpSeg = static_cast<PCB_SHAPE*>( fpItems[ii] );
999
1000 if( fpSeg->GetLayer() == Edge_Cuts )
1001 fpSegList.push_back( fpSeg );
1002 }
1003
1004 if( !fpSegList.empty() )
1005 {
1006 SHAPE_POLY_SET fpOutlines;
1007 success = doConvertOutlineToPolygon( fpSegList, fpOutlines, aErrorMax, aChainingEpsilon,
1008 false,
1009 nullptr, // don't report errors here; the second pass also
1010 // gets an opportunity to use these segments
1011 aAllowUseArcsInPolygons,
1012 cleaner );
1013
1014 // Test to see if we should make holes or outlines. Holes are made if the footprint
1015 // has copper outside of a single, closed outline. If there are multiple outlines,
1016 // we assume that the footprint edges represent holes as we do not support multiple
1017 // boards. Similarly, if any of the footprint pads are located outside of the edges,
1018 // then the edges are holes
1019 if( success && ( isCopperOutside( fp, fpOutlines ) || fpOutlines.OutlineCount() > 1 ) )
1020 {
1021 fpHoles.Append( fpOutlines );
1022 }
1023 else
1024 {
1025 // If it wasn't a closed area, or wasn't a hole, the we want to keep the fpSegs
1026 // in contention for the board outline builds.
1027 for( int ii = 0; ii < fpItems.GetCount(); ++ii )
1028 fpItems[ii]->ClearFlags( SKIP_STRUCT );
1029 }
1030 }
1031 }
1032
1033 // Make a working copy of aSegList, because the list is modified during calculations
1034 std::vector<PCB_SHAPE*> segList;
1035
1036 for( int ii = 0; ii < items.GetCount(); ii++ )
1037 {
1038 PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
1039
1040 // Skip anything already used to generate footprint holes (above)
1041 if( seg->GetFlags() & SKIP_STRUCT )
1042 continue;
1043
1044 if( seg->GetLayer() == Edge_Cuts )
1045 segList.push_back( seg );
1046 }
1047
1048 if( segList.size() )
1049 {
1050 success = doConvertOutlineToPolygon( segList, aOutlines, aErrorMax, aChainingEpsilon, true,
1051 aErrorHandler, aAllowUseArcsInPolygons, cleaner );
1052 }
1053
1054 if( ( !success || !aOutlines.OutlineCount() ) && aInferOutlineIfNecessary )
1055 {
1056 // Couldn't create a valid polygon outline. Use the board edge cuts bounding box to
1057 // create a rectangular outline, or, failing that, the bounding box of the items on
1058 // the board.
1059 BOX2I bbbox = aBoard->GetBoardEdgesBoundingBox();
1060
1061 // If null area, uses the global bounding box.
1062 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1063 bbbox = aBoard->ComputeBoundingBox( false );
1064
1065 // Ensure non null area. If happen, gives a minimal size.
1066 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1067 bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
1068
1069 aOutlines.RemoveAllContours();
1070 aOutlines.NewOutline();
1071
1072 VECTOR2I corner;
1073 aOutlines.Append( bbbox.GetOrigin() );
1074
1075 corner.x = bbbox.GetOrigin().x;
1076 corner.y = bbbox.GetEnd().y;
1077 aOutlines.Append( corner );
1078
1079 aOutlines.Append( bbbox.GetEnd() );
1080
1081 corner.x = bbbox.GetEnd().x;
1082 corner.y = bbbox.GetOrigin().y;
1083 aOutlines.Append( corner );
1084 }
1085
1086 if( aAllowUseArcsInPolygons )
1087 {
1088 for( int ii = 0; ii < fpHoles.OutlineCount(); ++ii )
1089 {
1090 const VECTOR2I holePt = fpHoles.Outline( ii ).CPoint( 0 );
1091
1092 for( int jj = 0; jj < aOutlines.OutlineCount(); ++jj )
1093 {
1094 if( aOutlines.Outline( jj ).PointInside( holePt ) )
1095 {
1096 aOutlines.AddHole( fpHoles.Outline( ii ), jj );
1097 break;
1098 }
1099 }
1100 }
1101 }
1102 else
1103 {
1104 aOutlines.BooleanSubtract( fpHoles );
1105 }
1106
1107 return success;
1108}
1109
1110
1123void buildBoardBoundingBoxPoly( const BOARD* aBoard, SHAPE_POLY_SET& aOutline )
1124{
1125 BOX2I bbbox = aBoard->GetBoundingBox();
1127
1128 // If null area, uses the global bounding box.
1129 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1130 bbbox = aBoard->ComputeBoundingBox( false );
1131
1132 // Ensure non null area. If happen, gives a minimal size.
1133 if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
1134 bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
1135
1136 // Inflate slightly (by 1/10th the size of the box)
1137 bbbox.Inflate( bbbox.GetWidth() / 10, bbbox.GetHeight() / 10 );
1138
1139 chain.Append( bbbox.GetOrigin() );
1140 chain.Append( bbbox.GetOrigin().x, bbbox.GetEnd().y );
1141 chain.Append( bbbox.GetEnd() );
1142 chain.Append( bbbox.GetEnd().x, bbbox.GetOrigin().y );
1143 chain.SetClosed( true );
1144
1145 aOutline.RemoveAllContours();
1146 aOutline.AddOutline( chain );
1147}
1148
1149
1150VECTOR2I projectPointOnSegment( const VECTOR2I& aEndPoint, const SHAPE_POLY_SET& aOutline,
1151 int aOutlineNum = 0 )
1152{
1153 int minDistance = -1;
1154 VECTOR2I projPoint;
1155
1156 for( auto it = aOutline.CIterateSegments( aOutlineNum ); it; it++ )
1157 {
1158 auto seg = it.Get();
1159 int dis = seg.Distance( aEndPoint );
1160
1161 if( minDistance < 0 || ( dis < minDistance ) )
1162 {
1163 minDistance = dis;
1164 projPoint = seg.NearestPoint( aEndPoint );
1165 }
1166 }
1167
1168 return projPoint;
1169}
1170
1171
1172int findEndSegments( SHAPE_LINE_CHAIN& aChain, SEG& aStartSeg, SEG& aEndSeg )
1173{
1174 int foundSegs = 0;
1175
1176 for( int i = 0; i < aChain.SegmentCount(); i++ )
1177 {
1178 SEG seg = aChain.Segment( i );
1179
1180 bool foundA = false;
1181 bool foundB = false;
1182
1183 for( int j = 0; j < aChain.SegmentCount(); j++ )
1184 {
1185 // Don't test the segment against itself
1186 if( i == j )
1187 continue;
1188
1189 SEG testSeg = aChain.Segment( j );
1190
1191 if( testSeg.Contains( seg.A ) )
1192 foundA = true;
1193
1194 if( testSeg.Contains( seg.B ) )
1195 foundB = true;
1196 }
1197
1198 // This segment isn't a start or end
1199 if( foundA && foundB )
1200 continue;
1201
1202 if( foundSegs == 0 )
1203 {
1204 // The first segment we encounter is the "start" segment
1205 wxLogTrace( traceBoardOutline, wxT( "Found start segment: (%d, %d)-(%d, %d)" ),
1206 seg.A.x, seg.A.y, seg.B.x, seg.B.y );
1207 aStartSeg = seg;
1208 foundSegs++;
1209 }
1210 else
1211 {
1212 // Once we find both start and end, we can stop
1213 wxLogTrace( traceBoardOutline, wxT( "Found end segment: (%d, %d)-(%d, %d)" ),
1214 seg.A.x, seg.A.y, seg.B.x, seg.B.y );
1215 aEndSeg = seg;
1216 foundSegs++;
1217 break;
1218 }
1219 }
1220
1221 return foundSegs;
1222}
1223
1224
1225bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
1226 int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
1227
1228{
1229 FOOTPRINT* footprint = aBoard->GetFirstFootprint();
1230
1231 // No footprint loaded
1232 if( !footprint )
1233 {
1234 wxLogTrace( traceBoardOutline, wxT( "No footprint found on board" ) );
1235 return false;
1236 }
1237
1238 PCB_TYPE_COLLECTOR items;
1239 SHAPE_POLY_SET outlines;
1240 bool success = false;
1241
1243
1244 // Get all the SHAPEs into 'items', then keep only those on layer == Edge_Cuts.
1245 items.Collect( aBoard, { PCB_SHAPE_T } );
1246
1247 // Make a working copy of aSegList, because the list is modified during calculations
1248 std::vector<PCB_SHAPE*> segList;
1249
1250 for( int ii = 0; ii < items.GetCount(); ii++ )
1251 {
1252 if( items[ii]->GetLayer() == Edge_Cuts )
1253 segList.push_back( static_cast<PCB_SHAPE*>( items[ii] ) );
1254 }
1255
1256 if( !segList.empty() )
1257 {
1258 success = doConvertOutlineToPolygon( segList, outlines, aErrorMax, aChainingEpsilon, true,
1259 aErrorHandler, false, cleaner );
1260 }
1261
1262 // A closed outline was found on Edge_Cuts
1263 if( success )
1264 {
1265 wxLogTrace( traceBoardOutline, wxT( "Closed outline found" ) );
1266
1267 // If copper is outside a closed polygon, treat it as a hole
1268 // If there are multiple outlines in the footprint, they are also holes
1269 if( isCopperOutside( footprint, outlines ) || outlines.OutlineCount() > 1 )
1270 {
1271 wxLogTrace( traceBoardOutline, wxT( "Treating outline as a hole" ) );
1272
1273 buildBoardBoundingBoxPoly( aBoard, aOutlines );
1274
1275 // Copy all outlines from the conversion as holes into the new outline
1276 for( int i = 0; i < outlines.OutlineCount(); i++ )
1277 {
1278 SHAPE_LINE_CHAIN& out = outlines.Outline( i );
1279
1280 if( out.IsClosed() )
1281 aOutlines.AddHole( out, -1 );
1282
1283 for( int j = 0; j < outlines.HoleCount( i ); j++ )
1284 {
1285 SHAPE_LINE_CHAIN& hole = outlines.Hole( i, j );
1286
1287 if( hole.IsClosed() )
1288 aOutlines.AddHole( hole, -1 );
1289 }
1290 }
1291 }
1292 // If all copper is inside, then the computed outline is the board outline
1293 else
1294 {
1295 wxLogTrace( traceBoardOutline, wxT( "Treating outline as board edge" ) );
1296 aOutlines = std::move( outlines );
1297 }
1298
1299 return true;
1300 }
1301 // No board outlines were found, so use the bounding box
1302 else if( outlines.OutlineCount() == 0 )
1303 {
1304 wxLogTrace( traceBoardOutline, wxT( "Using footprint bounding box" ) );
1305 buildBoardBoundingBoxPoly( aBoard, aOutlines );
1306
1307 return true;
1308 }
1309 // There is an outline present, but it is not closed
1310 else
1311 {
1312 wxLogTrace( traceBoardOutline, wxT( "Trying to build outline" ) );
1313
1314 std::vector<SHAPE_LINE_CHAIN> closedChains;
1315 std::vector<SHAPE_LINE_CHAIN> openChains;
1316
1317 // The ConvertOutlineToPolygon function returns only one main outline and the rest as
1318 // holes, so we promote the holes and process them
1319 openChains.push_back( outlines.Outline( 0 ) );
1320
1321 for( int j = 0; j < outlines.HoleCount( 0 ); j++ )
1322 {
1323 SHAPE_LINE_CHAIN hole = outlines.Hole( 0, j );
1324
1325 if( hole.IsClosed() )
1326 {
1327 wxLogTrace( traceBoardOutline, wxT( "Found closed hole" ) );
1328 closedChains.push_back( hole );
1329 }
1330 else
1331 {
1332 wxLogTrace( traceBoardOutline, wxT( "Found open hole" ) );
1333 openChains.push_back( hole );
1334 }
1335 }
1336
1337 SHAPE_POLY_SET bbox;
1338 buildBoardBoundingBoxPoly( aBoard, bbox );
1339
1340 // Treat the open polys as the board edge
1341 SHAPE_LINE_CHAIN chain = openChains[0];
1342 SHAPE_LINE_CHAIN rect = bbox.Outline( 0 );
1343
1344 // We know the outline chain is open, so set to non-closed to get better segment count
1345 chain.SetClosed( false );
1346
1347 SEG startSeg;
1348 SEG endSeg;
1349
1350 // The two possible board outlines
1351 SHAPE_LINE_CHAIN upper;
1352 SHAPE_LINE_CHAIN lower;
1353
1354 findEndSegments( chain, startSeg, endSeg );
1355
1356 if( chain.SegmentCount() == 0 )
1357 {
1358 // Something is wrong, bail out with the overall footprint bounding box
1359 wxLogTrace( traceBoardOutline, wxT( "No line segments in provided outline" ) );
1360 aOutlines = std::move( bbox );
1361 return true;
1362 }
1363 else if( chain.SegmentCount() == 1 )
1364 {
1365 // This case means there is only 1 line segment making up the edge cuts of the
1366 // footprint, so we just need to use it to cut the bounding box in half.
1367 wxLogTrace( traceBoardOutline, wxT( "Only 1 line segment in provided outline" ) );
1368
1369 startSeg = chain.Segment( 0 );
1370
1371 // Intersect with all the sides of the rectangle
1372 OPT_VECTOR2I inter0 = startSeg.IntersectLines( rect.Segment( 0 ) );
1373 OPT_VECTOR2I inter1 = startSeg.IntersectLines( rect.Segment( 1 ) );
1374 OPT_VECTOR2I inter2 = startSeg.IntersectLines( rect.Segment( 2 ) );
1375 OPT_VECTOR2I inter3 = startSeg.IntersectLines( rect.Segment( 3 ) );
1376
1377 if( inter0 && inter2 && !inter1 && !inter3 )
1378 {
1379 // Intersects the vertical rectangle sides only
1380 wxLogTrace( traceBoardOutline, wxT( "Segment intersects only vertical bbox sides" ) );
1381
1382 // The upper half
1383 upper.Append( *inter0 );
1384 upper.Append( rect.GetPoint( 1 ) );
1385 upper.Append( rect.GetPoint( 2 ) );
1386 upper.Append( *inter2 );
1387 upper.SetClosed( true );
1388
1389 // The lower half
1390 lower.Append( *inter0 );
1391 lower.Append( rect.GetPoint( 0 ) );
1392 lower.Append( rect.GetPoint( 3 ) );
1393 lower.Append( *inter2 );
1394 lower.SetClosed( true );
1395 }
1396 else if( inter1 && inter3 && !inter0 && !inter2 )
1397 {
1398 // Intersects the horizontal rectangle sides only
1399 wxLogTrace( traceBoardOutline, wxT( "Segment intersects only horizontal bbox sides" ) );
1400
1401 // The left half
1402 upper.Append( *inter1 );
1403 upper.Append( rect.GetPoint( 1 ) );
1404 upper.Append( rect.GetPoint( 0 ) );
1405 upper.Append( *inter3 );
1406 upper.SetClosed( true );
1407
1408 // The right half
1409 lower.Append( *inter1 );
1410 lower.Append( rect.GetPoint( 2 ) );
1411 lower.Append( rect.GetPoint( 3 ) );
1412 lower.Append( *inter3 );
1413 lower.SetClosed( true );
1414 }
1415 else
1416 {
1417 // Angled line segment that cuts across a corner
1418 wxLogTrace( traceBoardOutline, wxT( "Segment intersects two perpendicular bbox sides" ) );
1419
1420 // Figure out which actual lines are intersected, since IntersectLines assumes
1421 // an infinite line
1422 bool hit0 = rect.Segment( 0 ).Contains( *inter0 );
1423 bool hit1 = rect.Segment( 1 ).Contains( *inter1 );
1424 bool hit2 = rect.Segment( 2 ).Contains( *inter2 );
1425 bool hit3 = rect.Segment( 3 ).Contains( *inter3 );
1426
1427 if( hit0 && hit1 )
1428 {
1429 // Cut across the upper left corner
1430 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
1431
1432 // The upper half
1433 upper.Append( *inter0 );
1434 upper.Append( rect.GetPoint( 1 ) );
1435 upper.Append( *inter1 );
1436 upper.SetClosed( true );
1437
1438 // The lower half
1439 lower.Append( *inter0 );
1440 lower.Append( rect.GetPoint( 0 ) );
1441 lower.Append( rect.GetPoint( 3 ) );
1442 lower.Append( rect.GetPoint( 2 ) );
1443 lower.Append( *inter1 );
1444 lower.SetClosed( true );
1445 }
1446 else if( hit1 && hit2 )
1447 {
1448 // Cut across the upper right corner
1449 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper right corner" ) );
1450
1451 // The upper half
1452 upper.Append( *inter1 );
1453 upper.Append( rect.GetPoint( 2 ) );
1454 upper.Append( *inter2 );
1455 upper.SetClosed( true );
1456
1457 // The lower half
1458 lower.Append( *inter1 );
1459 lower.Append( rect.GetPoint( 1 ) );
1460 lower.Append( rect.GetPoint( 0 ) );
1461 lower.Append( rect.GetPoint( 3 ) );
1462 lower.Append( *inter2 );
1463 lower.SetClosed( true );
1464 }
1465 else if( hit2 && hit3 )
1466 {
1467 // Cut across the lower right corner
1468 wxLogTrace( traceBoardOutline, wxT( "Segment cuts lower right corner" ) );
1469
1470 // The upper half
1471 upper.Append( *inter2 );
1472 upper.Append( rect.GetPoint( 2 ) );
1473 upper.Append( rect.GetPoint( 1 ) );
1474 upper.Append( rect.GetPoint( 0 ) );
1475 upper.Append( *inter3 );
1476 upper.SetClosed( true );
1477
1478 // The bottom half
1479 lower.Append( *inter2 );
1480 lower.Append( rect.GetPoint( 3 ) );
1481 lower.Append( *inter3 );
1482 lower.SetClosed( true );
1483 }
1484 else
1485 {
1486 // Cut across the lower left corner
1487 wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
1488
1489 // The upper half
1490 upper.Append( *inter0 );
1491 upper.Append( rect.GetPoint( 1 ) );
1492 upper.Append( rect.GetPoint( 2 ) );
1493 upper.Append( rect.GetPoint( 3 ) );
1494 upper.Append( *inter3 );
1495 upper.SetClosed( true );
1496
1497 // The bottom half
1498 lower.Append( *inter0 );
1499 lower.Append( rect.GetPoint( 0 ) );
1500 lower.Append( *inter3 );
1501 lower.SetClosed( true );
1502 }
1503 }
1504 }
1505 else
1506 {
1507 // More than 1 segment
1508 wxLogTrace( traceBoardOutline, wxT( "Multiple segments in outline" ) );
1509
1510 // Just a temporary thing
1511 aOutlines = std::move( bbox );
1512 return true;
1513 }
1514
1515 // Figure out which is the correct outline
1516 SHAPE_POLY_SET poly1;
1517 SHAPE_POLY_SET poly2;
1518
1519 poly1.NewOutline();
1520 poly1.Append( upper );
1521
1522 poly2.NewOutline();
1523 poly2.Append( lower );
1524
1525 if( isCopperOutside( footprint, poly1 ) )
1526 {
1527 wxLogTrace( traceBoardOutline, wxT( "Using lower shape" ) );
1528 aOutlines = std::move( poly2 );
1529 }
1530 else
1531 {
1532 wxLogTrace( traceBoardOutline, wxT( "Using upper shape" ) );
1533 aOutlines = std::move( poly1 );
1534 }
1535
1536 // Add all closed polys as holes to the main outline
1537 for( SHAPE_LINE_CHAIN& closedChain : closedChains )
1538 {
1539 wxLogTrace( traceBoardOutline, wxT( "Adding hole to main outline" ) );
1540 aOutlines.AddHole( closedChain, -1 );
1541 }
1542
1543 return true;
1544 }
1545
1546 // We really shouldn't reach this point
1547 return false;
1548}
@ 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:322
const BOX2I GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition board.h:1064
const BOX2I GetBoundingBox() const override
Return the orthogonal bounding box of this object for display purposes.
Definition board.h:1050
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:530
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2116
const FOOTPRINTS & Footprints() const
Definition board.h:363
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 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:99
void SetFlags(EDA_ITEM_FLAGS aMask)
Definition eda_item.h:148
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition eda_item.h:150
EDA_ITEM_FLAGS GetFlags() const
Definition eda_item.h:151
int GetRectangleWidth() const
SHAPE_POLY_SET & GetPolyShape()
Definition eda_shape.h:337
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:169
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:216
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:174
std::vector< VECTOR2I > GetRectCorners() const
const std::vector< VECTOR2I > & GetBezierPoints() const
Definition eda_shape.h:321
int GetRectangleHeight() const
int GetCornerRadius() const
VECTOR2I GetArcMid() const
std::deque< PAD * > & Pads()
Definition footprint.h:306
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
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
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 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.
const std::vector< VECTOR2I > & CPoints() const
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
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)
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)
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
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 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)
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:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
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:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
constexpr int LexicographicalCompare(const VECTOR2< T > &aA, const VECTOR2< T > &aB)
Definition vector2d.h:644