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