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