You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1413 lines
48 KiB

5 years ago
5 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
5 years ago
3 years ago
4 years ago
5 years ago
3 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  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 <dick@softplc.com>
  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. #include <unordered_set>
  26. #include <trigo.h>
  27. #include <macros.h>
  28. #include <math/vector2d.h>
  29. #include <pcb_shape.h>
  30. #include <footprint.h>
  31. #include <pad.h>
  32. #include <base_units.h>
  33. #include <convert_basic_shapes_to_polygon.h>
  34. #include <geometry/shape_poly_set.h>
  35. #include <geometry/geometry_utils.h>
  36. #include <convert_shape_list_to_polygon.h>
  37. #include <board.h>
  38. #include <collectors.h>
  39. #include <nanoflann.hpp>
  40. #include <wx/log.h>
  41. /**
  42. * Flag to enable debug tracing for the board outline creation
  43. *
  44. * Use "KICAD_BOARD_OUTLINE" to enable.
  45. *
  46. * @ingroup trace_env_vars
  47. */
  48. const wxChar* traceBoardOutline = wxT( "KICAD_BOARD_OUTLINE" );
  49. class SCOPED_FLAGS_CLEANER : public std::unordered_set<EDA_ITEM*>
  50. {
  51. EDA_ITEM_FLAGS m_flagsToClear;
  52. public:
  53. SCOPED_FLAGS_CLEANER( const EDA_ITEM_FLAGS& aFlagsToClear ) : m_flagsToClear( aFlagsToClear ) {}
  54. ~SCOPED_FLAGS_CLEANER()
  55. {
  56. for( EDA_ITEM* item : *this )
  57. item->ClearFlags( m_flagsToClear );
  58. }
  59. };
  60. /**
  61. * Local and tunable method of qualifying the proximity of two points.
  62. *
  63. * @param aLeft is the first point.
  64. * @param aRight is the second point.
  65. * @param aLimit is a measure of proximity that the caller knows about.
  66. * @return true if the two points are close enough, else false.
  67. */
  68. static bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit )
  69. {
  70. return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit );
  71. }
  72. /**
  73. * Local method which qualifies whether the start or end point of a segment is closest to a point.
  74. *
  75. * @param aRef is the reference point
  76. * @param aFirst is the first point
  77. * @param aSecond is the second point
  78. * @return true if the first point is closest to the reference, otherwise false.
  79. */
  80. static bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond )
  81. {
  82. return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm();
  83. }
  84. static bool isCopperOutside( const FOOTPRINT* aFootprint, SHAPE_POLY_SET& aShape )
  85. {
  86. bool padOutside = false;
  87. for( PAD* pad : aFootprint->Pads() )
  88. {
  89. pad->Padstack().ForEachUniqueLayer(
  90. [&]( PCB_LAYER_ID aLayer )
  91. {
  92. SHAPE_POLY_SET poly = aShape.CloneDropTriangulation();
  93. poly.ClearArcs();
  94. poly.BooleanIntersection( *pad->GetEffectivePolygon( aLayer, ERROR_INSIDE ) );
  95. if( poly.OutlineCount() == 0 )
  96. {
  97. VECTOR2I padPos = pad->GetPosition();
  98. wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): outside" ),
  99. padPos.x, padPos.y );
  100. padOutside = true;
  101. }
  102. } );
  103. if( padOutside )
  104. break;
  105. VECTOR2I padPos = pad->GetPosition();
  106. wxLogTrace( traceBoardOutline, wxT( "Tested pad (%d, %d): not outside" ),
  107. padPos.x, padPos.y );
  108. }
  109. return padOutside;
  110. }
  111. struct PCB_SHAPE_ENDPOINTS_ADAPTOR
  112. {
  113. std::vector<std::pair<VECTOR2I, PCB_SHAPE*>> endpoints;
  114. PCB_SHAPE_ENDPOINTS_ADAPTOR( const std::vector<PCB_SHAPE*>& shapes )
  115. {
  116. endpoints.reserve( shapes.size() * 2 );
  117. for( PCB_SHAPE* shape : shapes )
  118. {
  119. endpoints.emplace_back( shape->GetStart(), shape );
  120. endpoints.emplace_back( shape->GetEnd(), shape );
  121. }
  122. }
  123. // Required by nanoflann
  124. size_t kdtree_get_point_count() const { return endpoints.size(); }
  125. // Returns the dim'th component of the idx'th point
  126. double kdtree_get_pt( const size_t idx, const size_t dim ) const
  127. {
  128. if( dim == 0 )
  129. return static_cast<double>( endpoints[idx].first.x );
  130. else
  131. return static_cast<double>( endpoints[idx].first.y );
  132. }
  133. template <class BBOX>
  134. bool kdtree_get_bbox( BBOX& ) const
  135. {
  136. return false;
  137. }
  138. };
  139. using KDTree = nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PCB_SHAPE_ENDPOINTS_ADAPTOR>,
  140. PCB_SHAPE_ENDPOINTS_ADAPTOR,
  141. 2 /* dim */ >;
  142. // Helper function to find next shape using KD-tree
  143. static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree,
  144. const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, unsigned aLimit )
  145. {
  146. const double query_pt[2] = { static_cast<double>( aPoint.x ), static_cast<double>( aPoint.y ) };
  147. // Search for points within a very small radius for exact matches
  148. std::vector<nanoflann::ResultItem<size_t, double>> matches;
  149. double search_radius_sq = static_cast<double>( aLimit * aLimit );
  150. nanoflann::RadiusResultSet<double, size_t> radiusResultSet( search_radius_sq, matches );
  151. kdTree.findNeighbors( radiusResultSet, query_pt );
  152. if( matches.empty() )
  153. return nullptr;
  154. // Find the closest valid candidate
  155. PCB_SHAPE* closest_graphic = nullptr;
  156. double closest_dist_sq = search_radius_sq;
  157. for( const auto& match : matches )
  158. {
  159. PCB_SHAPE* candidate = adaptor.endpoints[match.first].second;
  160. if( candidate == aShape )
  161. continue;
  162. if( match.second < closest_dist_sq && !candidate->HasFlag( SKIP_STRUCT ) )
  163. {
  164. closest_dist_sq = match.second;
  165. closest_graphic = candidate;
  166. }
  167. }
  168. return closest_graphic;
  169. }
  170. bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
  171. int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
  172. OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons,
  173. SCOPED_FLAGS_CLEANER& aCleaner )
  174. {
  175. if( aShapeList.size() == 0 )
  176. return true;
  177. bool selfIntersecting = false;
  178. PCB_SHAPE* graphic = nullptr;
  179. std::set<PCB_SHAPE*> startCandidates( aShapeList.begin(), aShapeList.end() );
  180. // Pre-build KD-tree
  181. PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList );
  182. KDTree kdTree( 2, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams( 10 ) );
  183. // Keep a list of where the various shapes came from so after doing our combined-polygon
  184. // tests we can still report errors against the individual graphic items.
  185. std::map<std::pair<VECTOR2I, VECTOR2I>, PCB_SHAPE*> shapeOwners;
  186. auto fetchOwner =
  187. [&]( const SEG& seg ) -> PCB_SHAPE*
  188. {
  189. auto it = shapeOwners.find( std::make_pair( seg.A, seg.B ) );
  190. return it == shapeOwners.end() ? nullptr : it->second;
  191. };
  192. PCB_SHAPE* prevGraphic = nullptr;
  193. VECTOR2I prevPt;
  194. std::vector<SHAPE_LINE_CHAIN> contours;
  195. contours.reserve( startCandidates.size() );
  196. for( PCB_SHAPE* shape : startCandidates )
  197. shape->ClearFlags( SKIP_STRUCT );
  198. while( startCandidates.size() )
  199. {
  200. graphic = (PCB_SHAPE*) *startCandidates.begin();
  201. graphic->SetFlags( SKIP_STRUCT );
  202. aCleaner.insert( graphic );
  203. startCandidates.erase( startCandidates.begin() );
  204. contours.emplace_back();
  205. SHAPE_LINE_CHAIN& currContour = contours.back();
  206. currContour.SetWidth( graphic->GetWidth() );
  207. bool firstPt = true;
  208. // Circles, rects and polygons are closed shapes unto themselves (and do not combine
  209. // with other shapes), so process them separately.
  210. if( graphic->GetShape() == SHAPE_T::POLY )
  211. {
  212. for( auto it = graphic->GetPolyShape().CIterate(); it; it++ )
  213. {
  214. VECTOR2I pt = *it;
  215. currContour.Append( pt );
  216. if( firstPt )
  217. firstPt = false;
  218. else
  219. shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic;
  220. prevPt = pt;
  221. }
  222. currContour.SetClosed( true );
  223. }
  224. else if( graphic->GetShape() == SHAPE_T::CIRCLE )
  225. {
  226. VECTOR2I center = graphic->GetCenter();
  227. int radius = graphic->GetRadius();
  228. VECTOR2I start = center;
  229. start.x += radius;
  230. // Add 360 deg Arc in currContour
  231. SHAPE_ARC arc360( center, start, ANGLE_360, 0 );
  232. currContour.Append( arc360, aErrorMax );
  233. currContour.SetClosed( true );
  234. // set shapeOwners for currContour points created by appending the arc360:
  235. for( int ii = 1; ii < currContour.PointCount(); ++ii )
  236. {
  237. shapeOwners[ std::make_pair( currContour.CPoint( ii-1 ),
  238. currContour.CPoint( ii ) ) ] = graphic;
  239. }
  240. if( !aAllowUseArcsInPolygons )
  241. currContour.ClearArcs();
  242. }
  243. else if( graphic->GetShape() == SHAPE_T::RECTANGLE )
  244. {
  245. std::vector<VECTOR2I> pts = graphic->GetRectCorners();
  246. for( const VECTOR2I& pt : pts )
  247. {
  248. currContour.Append( pt );
  249. if( firstPt )
  250. firstPt = false;
  251. else
  252. shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic;
  253. prevPt = pt;
  254. }
  255. currContour.SetClosed( true );
  256. }
  257. else
  258. {
  259. // Polygon start point. Arbitrarily chosen end of the segment and build the poly
  260. // from here.
  261. VECTOR2I startPt = graphic->GetEnd();
  262. prevPt = startPt;
  263. currContour.Append( prevPt );
  264. // do not append the other end point yet, this first 'graphic' might be an arc
  265. for(;;)
  266. {
  267. switch( graphic->GetShape() )
  268. {
  269. case SHAPE_T::RECTANGLE:
  270. case SHAPE_T::CIRCLE:
  271. {
  272. // As a non-first item, closed shapes can't be anything but self-intersecting
  273. if( aErrorHandler )
  274. {
  275. wxASSERT( prevGraphic );
  276. (*aErrorHandler)( _( "(self-intersecting)" ), prevGraphic, graphic,
  277. prevPt );
  278. }
  279. selfIntersecting = true;
  280. // A closed shape will finish where it started, so no point in updating prevPt
  281. break;
  282. }
  283. case SHAPE_T::SEGMENT:
  284. {
  285. VECTOR2I nextPt;
  286. // Use the line segment end point furthest away from prevPt as we assume
  287. // the other end to be ON prevPt or very close to it.
  288. if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) )
  289. nextPt = graphic->GetEnd();
  290. else
  291. nextPt = graphic->GetStart();
  292. currContour.Append( nextPt );
  293. shapeOwners[ std::make_pair( prevPt, nextPt ) ] = graphic;
  294. prevPt = nextPt;
  295. }
  296. break;
  297. case SHAPE_T::ARC:
  298. {
  299. VECTOR2I pstart = graphic->GetStart();
  300. VECTOR2I pmid = graphic->GetArcMid();
  301. VECTOR2I pend = graphic->GetEnd();
  302. if( !close_enough( prevPt, pstart, aChainingEpsilon ) )
  303. {
  304. wxASSERT( close_enough( prevPt, graphic->GetEnd(), aChainingEpsilon ) );
  305. std::swap( pstart, pend );
  306. }
  307. // Snap the arc start point to avoid potential self-intersections
  308. pstart = prevPt;
  309. SHAPE_ARC sarc( pstart, pmid, pend, 0 );
  310. SHAPE_LINE_CHAIN arcChain;
  311. arcChain.Append( sarc, aErrorMax );
  312. if( !aAllowUseArcsInPolygons )
  313. arcChain.ClearArcs();
  314. // set shapeOwners for arcChain points created by appending the sarc:
  315. for( int ii = 1; ii < arcChain.PointCount(); ++ii )
  316. {
  317. shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ),
  318. arcChain.CPoint( ii ) )] = graphic;
  319. }
  320. currContour.Append( arcChain );
  321. prevPt = pend;
  322. }
  323. break;
  324. case SHAPE_T::BEZIER:
  325. {
  326. // We do not support Bezier curves in polygons, so approximate with a series
  327. // of short lines and put those line segments into the !same! PATH.
  328. VECTOR2I nextPt;
  329. bool reverse = false;
  330. // Use the end point furthest away from prevPt as we assume the other
  331. // end to be ON prevPt or very close to it.
  332. if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) )
  333. {
  334. nextPt = graphic->GetEnd();
  335. }
  336. else
  337. {
  338. nextPt = graphic->GetStart();
  339. reverse = true;
  340. }
  341. // Ensure the approximated Bezier shape is built
  342. graphic->RebuildBezierToSegmentsPointsList( aErrorMax );
  343. if( reverse )
  344. {
  345. for( int jj = graphic->GetBezierPoints().size()-1; jj >= 0; jj-- )
  346. {
  347. const VECTOR2I& pt = graphic->GetBezierPoints()[jj];
  348. if( prevPt == pt )
  349. continue;
  350. currContour.Append( pt );
  351. shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic;
  352. prevPt = pt;
  353. }
  354. }
  355. else
  356. {
  357. for( const VECTOR2I& pt : graphic->GetBezierPoints() )
  358. {
  359. if( prevPt == pt )
  360. continue;
  361. currContour.Append( pt );
  362. shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic;
  363. prevPt = pt;
  364. }
  365. }
  366. prevPt = nextPt;
  367. }
  368. break;
  369. default:
  370. UNIMPLEMENTED_FOR( graphic->SHAPE_T_asString() );
  371. return false;
  372. }
  373. PCB_SHAPE* nextGraphic = findNext( graphic, prevPt, kdTree, adaptor, aChainingEpsilon );
  374. if( nextGraphic && !( nextGraphic->GetFlags() & SKIP_STRUCT ) )
  375. {
  376. prevGraphic = graphic;
  377. graphic = nextGraphic;
  378. graphic->SetFlags( SKIP_STRUCT );
  379. aCleaner.insert( graphic );
  380. startCandidates.erase( graphic );
  381. continue;
  382. }
  383. // Finished, or ran into trouble...
  384. if( close_enough( startPt, prevPt, aChainingEpsilon ) )
  385. {
  386. if( startPt != prevPt && currContour.PointCount() > 2 )
  387. {
  388. // Snap the last shape's endpoint to the outline startpoint
  389. PCB_SHAPE* owner = fetchOwner( currContour.CSegment( -1 ) );
  390. if( currContour.IsArcEnd( currContour.PointCount() - 1 ) )
  391. {
  392. SHAPE_ARC arc = currContour.Arc(
  393. currContour.ArcIndex( currContour.PointCount() - 1 ) );
  394. // Snap the arc endpoint
  395. SHAPE_ARC sarc( arc.GetP0(), arc.GetArcMid(), startPt, 0 );
  396. SHAPE_LINE_CHAIN arcChain;
  397. arcChain.Append( sarc, aErrorMax );
  398. if( !aAllowUseArcsInPolygons )
  399. arcChain.ClearArcs();
  400. // Set shapeOwners for arcChain points created by appending the sarc:
  401. for( int ii = 1; ii < arcChain.PointCount(); ++ii )
  402. {
  403. shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ),
  404. arcChain.CPoint( ii ) )] = owner;
  405. }
  406. currContour.RemoveShape( currContour.PointCount() - 1 );
  407. currContour.Append( arcChain );
  408. }
  409. else
  410. {
  411. // Snap the segment endpoint
  412. currContour.SetPoint( -1, startPt );
  413. shapeOwners[std::make_pair( currContour.CPoint( -2 ),
  414. currContour.CPoint( -1 ) )] = owner;
  415. }
  416. prevPt = startPt;
  417. }
  418. currContour.SetClosed( true );
  419. break;
  420. }
  421. else if( nextGraphic ) // encountered already-used segment, but not at the start
  422. {
  423. if( aErrorHandler )
  424. (*aErrorHandler)( _( "(self-intersecting)" ), graphic, nextGraphic,
  425. prevPt );
  426. break;
  427. }
  428. else // encountered discontinuity
  429. {
  430. if( aErrorHandler )
  431. (*aErrorHandler)( _( "(not a closed shape)" ), graphic, nullptr, prevPt );
  432. break;
  433. }
  434. }
  435. }
  436. }
  437. for( const SHAPE_LINE_CHAIN& contour : contours )
  438. {
  439. if( !contour.IsClosed() )
  440. return false;
  441. }
  442. std::map<int, std::vector<int>> contourToParentIndexesMap;
  443. for( size_t ii = 0; ii < contours.size(); ++ii )
  444. {
  445. SHAPE_LINE_CHAIN& contour = contours[ii];
  446. if( !contour.GetCachedBBox()->IsValid() )
  447. contour.GenerateBBoxCache();
  448. }
  449. for( size_t ii = 0; ii < contours.size(); ++ii )
  450. {
  451. VECTOR2I firstPt = contours[ii].GetPoint( 0 );
  452. std::vector<int> parents;
  453. for( size_t jj = 0; jj < contours.size(); ++jj )
  454. {
  455. if( jj == ii )
  456. continue;
  457. const SHAPE_LINE_CHAIN& parentCandidate = contours[jj];
  458. if( parentCandidate.PointInside( firstPt, 0, true ) )
  459. parents.push_back( jj );
  460. }
  461. contourToParentIndexesMap[ii] = std::move( parents );
  462. }
  463. // Next add those that are top-level outlines to the SHAPE_POLY_SET
  464. std::map<int, int> contourToOutlineIdxMap;
  465. for( const auto& [ contourIndex, parentIndexes ] : contourToParentIndexesMap )
  466. {
  467. if( parentIndexes.size() %2 == 0 )
  468. {
  469. // Even number of parents; top-level outline
  470. if( !aAllowDisjoint && !aPolygons.IsEmpty() )
  471. {
  472. if( aErrorHandler )
  473. {
  474. BOARD_ITEM* a = fetchOwner( aPolygons.Outline( 0 ).GetSegment( 0 ) );
  475. BOARD_ITEM* b = fetchOwner( contours[ contourIndex ].GetSegment( 0 ) );
  476. if( a && b )
  477. {
  478. (*aErrorHandler)( _( "(multiple board outlines not supported)" ), a, b,
  479. contours[ contourIndex ].GetPoint( 0 ) );
  480. return false;
  481. }
  482. }
  483. }
  484. aPolygons.AddOutline( contours[ contourIndex ] );
  485. contourToOutlineIdxMap[ contourIndex ] = aPolygons.OutlineCount() - 1;
  486. }
  487. }
  488. // And finally add the holes
  489. for( const auto& [ contourIndex, parentIndexes ] : contourToParentIndexesMap )
  490. {
  491. if( parentIndexes.size() %2 == 1 )
  492. {
  493. // Odd number of parents; we're a hole in the parent which has one fewer parents
  494. // than we have.
  495. const SHAPE_LINE_CHAIN& hole = contours[ contourIndex ];
  496. for( int parentContourIdx : parentIndexes )
  497. {
  498. if( contourToParentIndexesMap[ parentContourIdx ].size() == parentIndexes.size() - 1 )
  499. {
  500. int outlineIdx = contourToOutlineIdxMap[ parentContourIdx ];
  501. aPolygons.AddHole( hole, outlineIdx );
  502. break;
  503. }
  504. }
  505. }
  506. }
  507. // All of the silliness that follows is to work around the segment iterator while checking
  508. // for collisions.
  509. // TODO: Implement proper segment and point iterators that follow std
  510. std::vector<SEG> segments;
  511. size_t total = 0;
  512. for( int ii = 0; ii < aPolygons.OutlineCount(); ++ii )
  513. {
  514. const SHAPE_LINE_CHAIN& contour = aPolygons.Outline( ii );
  515. total += contour.SegmentCount();
  516. for( int jj = 0; jj < aPolygons.HoleCount( ii ); ++jj )
  517. {
  518. const SHAPE_LINE_CHAIN& hole = aPolygons.Hole( ii, jj );
  519. total += hole.SegmentCount();
  520. }
  521. }
  522. segments.reserve( total );
  523. for( auto seg = aPolygons.IterateSegmentsWithHoles(); seg; seg++ )
  524. {
  525. SEG segment = *seg;
  526. // Ensure segments are always ordered A < B
  527. if( LexicographicalCompare( segment.A, segment.B ) > 0 )
  528. {
  529. std::swap( segment.A, segment.B );
  530. }
  531. segments.push_back( segment );
  532. }
  533. std::sort( segments.begin(), segments.end(),
  534. []( const SEG& a, const SEG& b )
  535. {
  536. if( a.A != b.A )
  537. return LexicographicalCompare( a.A, b.A ) < 0;
  538. return LexicographicalCompare( a.B, b.B ) < 0;
  539. } );
  540. for( size_t i = 0; i < segments.size(); ++i )
  541. {
  542. const SEG& seg1 = segments[i];
  543. for( size_t j = i + 1; j < segments.size(); ++j )
  544. {
  545. const SEG& seg2 = segments[j];
  546. if( seg2.A > seg1.B )
  547. {
  548. // No more segments to check, as they are sorted by A and B.
  549. break;
  550. }
  551. // Check for exact overlapping segments.
  552. if( seg1 == seg2 || ( seg1.A == seg2.B && seg1.B == seg2.A ) )
  553. {
  554. if( aErrorHandler )
  555. {
  556. BOARD_ITEM* a = fetchOwner( seg1 );
  557. BOARD_ITEM* b = fetchOwner( seg2 );
  558. (*aErrorHandler)( _( "(self-intersecting)" ), a, b, seg1.A );
  559. }
  560. selfIntersecting = true;
  561. }
  562. else if( OPT_VECTOR2I pt = seg1.Intersect( seg2, true ) )
  563. {
  564. if( aErrorHandler )
  565. {
  566. BOARD_ITEM* a = fetchOwner( seg1 );
  567. BOARD_ITEM* b = fetchOwner( seg2 );
  568. (*aErrorHandler)( _( "(self-intersecting)" ), a, b, *pt );
  569. }
  570. selfIntersecting = true;
  571. }
  572. }
  573. }
  574. return !selfIntersecting;
  575. }
  576. bool ConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_SET& aPolygons,
  577. int aErrorMax, int aChainingEpsilon, bool aAllowDisjoint,
  578. OUTLINE_ERROR_HANDLER* aErrorHandler, bool aAllowUseArcsInPolygons )
  579. {
  580. SCOPED_FLAGS_CLEANER cleaner( SKIP_STRUCT );
  581. return doConvertOutlineToPolygon( aShapeList, aPolygons, aErrorMax, aChainingEpsilon,
  582. aAllowDisjoint, aErrorHandler, aAllowUseArcsInPolygons,
  583. cleaner );
  584. }
  585. bool TestBoardOutlinesGraphicItems( BOARD* aBoard, int aMinDist,
  586. OUTLINE_ERROR_HANDLER* aErrorHandler )
  587. {
  588. bool success = true;
  589. PCB_TYPE_COLLECTOR items;
  590. int min_dist = std::max( 0, aMinDist );
  591. // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
  592. items.Collect( aBoard, { PCB_SHAPE_T } );
  593. std::vector<PCB_SHAPE*> shapeList;
  594. for( int ii = 0; ii < items.GetCount(); ii++ )
  595. {
  596. PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
  597. if( seg->GetLayer() == Edge_Cuts )
  598. shapeList.push_back( seg );
  599. }
  600. // Now Test validity of collected items
  601. for( PCB_SHAPE* shape : shapeList )
  602. {
  603. switch( shape->GetShape() )
  604. {
  605. case SHAPE_T::RECTANGLE:
  606. {
  607. VECTOR2I seg = shape->GetEnd() - shape->GetStart();
  608. int dim = seg.EuclideanNorm();
  609. if( dim <= min_dist )
  610. {
  611. success = false;
  612. if( aErrorHandler )
  613. {
  614. (*aErrorHandler)( wxString::Format( _( "(rectangle has null or very small "
  615. "size: %d nm)" ), dim ),
  616. shape, nullptr, shape->GetStart() );
  617. }
  618. }
  619. break;
  620. }
  621. case SHAPE_T::CIRCLE:
  622. {
  623. int r = shape->GetRadius();
  624. if( r <= min_dist )
  625. {
  626. success = false;
  627. if( aErrorHandler )
  628. {
  629. (*aErrorHandler)( wxString::Format( _( "(circle has null or very small "
  630. "radius: %d nm)" ), r ),
  631. shape, nullptr, shape->GetStart() );
  632. }
  633. }
  634. break;
  635. }
  636. case SHAPE_T::SEGMENT:
  637. {
  638. VECTOR2I seg = shape->GetEnd() - shape->GetStart();
  639. int dim = seg.EuclideanNorm();
  640. if( dim <= min_dist )
  641. {
  642. success = false;
  643. if( aErrorHandler )
  644. {
  645. (*aErrorHandler)( wxString::Format( _( "(segment has null or very small "
  646. "length: %d nm)" ), dim ),
  647. shape, nullptr, shape->GetStart() );
  648. }
  649. }
  650. break;
  651. }
  652. case SHAPE_T::ARC:
  653. {
  654. // Arc size can be evaluated from the distance between arc middle point and arc ends
  655. // We do not need a precise value, just an idea of its size
  656. VECTOR2I arcMiddle = shape->GetArcMid();
  657. VECTOR2I seg1 = arcMiddle - shape->GetStart();
  658. VECTOR2I seg2 = shape->GetEnd() - arcMiddle;
  659. int dim = seg1.EuclideanNorm() + seg2.EuclideanNorm();
  660. if( dim <= min_dist )
  661. {
  662. success = false;
  663. if( aErrorHandler )
  664. {
  665. (*aErrorHandler)( wxString::Format( _( "(arc has null or very small size: "
  666. "%d nm)" ), dim ),
  667. shape, nullptr, shape->GetStart() );
  668. }
  669. }
  670. break;
  671. }
  672. case SHAPE_T::POLY:
  673. break;
  674. case SHAPE_T::BEZIER:
  675. break;
  676. default:
  677. UNIMPLEMENTED_FOR( shape->SHAPE_T_asString() );
  678. return false;
  679. }
  680. }
  681. return success;
  682. }
  683. bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
  684. int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler,
  685. bool aAllowUseArcsInPolygons )
  686. {
  687. PCB_TYPE_COLLECTOR items;
  688. SHAPE_POLY_SET fpHoles;
  689. bool success = false;
  690. SCOPED_FLAGS_CLEANER cleaner( SKIP_STRUCT );
  691. // Get all the shapes into 'items', then keep only those on layer == Edge_Cuts.
  692. items.Collect( aBoard, { PCB_SHAPE_T } );
  693. for( int ii = 0; ii < items.GetCount(); ++ii )
  694. items[ii]->ClearFlags( SKIP_STRUCT );
  695. for( FOOTPRINT* fp : aBoard->Footprints() )
  696. {
  697. PCB_TYPE_COLLECTOR fpItems;
  698. fpItems.Collect( fp, { PCB_SHAPE_T } );
  699. std::vector<PCB_SHAPE*> fpSegList;
  700. for( int ii = 0; ii < fpItems.GetCount(); ii++ )
  701. {
  702. PCB_SHAPE* fpSeg = static_cast<PCB_SHAPE*>( fpItems[ii] );
  703. if( fpSeg->GetLayer() == Edge_Cuts )
  704. fpSegList.push_back( fpSeg );
  705. }
  706. if( !fpSegList.empty() )
  707. {
  708. SHAPE_POLY_SET fpOutlines;
  709. success = doConvertOutlineToPolygon( fpSegList, fpOutlines, aErrorMax, aChainingEpsilon,
  710. false,
  711. // don't report errors here; the second pass also
  712. // gets an opportunity to use these segments
  713. nullptr, aAllowUseArcsInPolygons, cleaner );
  714. // Test to see if we should make holes or outlines. Holes are made if the footprint
  715. // has copper outside of a single, closed outline. If there are multiple outlines,
  716. // we assume that the footprint edges represent holes as we do not support multiple
  717. // boards. Similarly, if any of the footprint pads are located outside of the edges,
  718. // then the edges are holes
  719. if( success && ( isCopperOutside( fp, fpOutlines ) || fpOutlines.OutlineCount() > 1 ) )
  720. {
  721. fpHoles.Append( fpOutlines );
  722. }
  723. else
  724. {
  725. // If it wasn't a closed area, or wasn't a hole, the we want to keep the fpSegs
  726. // in contention for the board outline builds.
  727. for( int ii = 0; ii < fpItems.GetCount(); ++ii )
  728. fpItems[ii]->ClearFlags( SKIP_STRUCT );
  729. }
  730. }
  731. }
  732. // Make a working copy of aSegList, because the list is modified during calculations
  733. std::vector<PCB_SHAPE*> segList;
  734. for( int ii = 0; ii < items.GetCount(); ii++ )
  735. {
  736. PCB_SHAPE* seg = static_cast<PCB_SHAPE*>( items[ii] );
  737. // Skip anything already used to generate footprint holes (above)
  738. if( seg->GetFlags() & SKIP_STRUCT )
  739. continue;
  740. if( seg->GetLayer() == Edge_Cuts )
  741. segList.push_back( seg );
  742. }
  743. if( segList.size() )
  744. {
  745. success = doConvertOutlineToPolygon( segList, aOutlines, aErrorMax, aChainingEpsilon, true,
  746. aErrorHandler, aAllowUseArcsInPolygons, cleaner );
  747. }
  748. if( !success || !aOutlines.OutlineCount() )
  749. {
  750. // Couldn't create a valid polygon outline. Use the board edge cuts bounding box to
  751. // create a rectangular outline, or, failing that, the bounding box of the items on
  752. // the board.
  753. BOX2I bbbox = aBoard->GetBoardEdgesBoundingBox();
  754. // If null area, uses the global bounding box.
  755. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  756. bbbox = aBoard->ComputeBoundingBox( false );
  757. // Ensure non null area. If happen, gives a minimal size.
  758. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  759. bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
  760. aOutlines.RemoveAllContours();
  761. aOutlines.NewOutline();
  762. VECTOR2I corner;
  763. aOutlines.Append( bbbox.GetOrigin() );
  764. corner.x = bbbox.GetOrigin().x;
  765. corner.y = bbbox.GetEnd().y;
  766. aOutlines.Append( corner );
  767. aOutlines.Append( bbbox.GetEnd() );
  768. corner.x = bbbox.GetEnd().x;
  769. corner.y = bbbox.GetOrigin().y;
  770. aOutlines.Append( corner );
  771. }
  772. for( int ii = 0; ii < fpHoles.OutlineCount(); ++ii )
  773. {
  774. const VECTOR2I holePt = fpHoles.Outline( ii ).CPoint( 0 );
  775. for( int jj = 0; jj < aOutlines.OutlineCount(); ++jj )
  776. {
  777. if( aOutlines.Outline( jj ).PointInside( holePt ) )
  778. {
  779. aOutlines.AddHole( fpHoles.Outline( ii ), jj );
  780. break;
  781. }
  782. }
  783. }
  784. return success;
  785. }
  786. /**
  787. * Get the complete bounding box of the board (including all items).
  788. *
  789. * The vertex numbers and segment numbers of the rectangle returned.
  790. * 1
  791. * *---------------*
  792. * |1 2|
  793. * 0| |2
  794. * |0 3|
  795. * *---------------*
  796. * 3
  797. */
  798. void buildBoardBoundingBoxPoly( const BOARD* aBoard, SHAPE_POLY_SET& aOutline )
  799. {
  800. BOX2I bbbox = aBoard->GetBoundingBox();
  801. SHAPE_LINE_CHAIN chain;
  802. // If null area, uses the global bounding box.
  803. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  804. bbbox = aBoard->ComputeBoundingBox( false );
  805. // Ensure non null area. If happen, gives a minimal size.
  806. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  807. bbbox.Inflate( pcbIUScale.mmToIU( 1.0 ) );
  808. // Inflate slightly (by 1/10th the size of the box)
  809. bbbox.Inflate( bbbox.GetWidth() / 10, bbbox.GetHeight() / 10 );
  810. chain.Append( bbbox.GetOrigin() );
  811. chain.Append( bbbox.GetOrigin().x, bbbox.GetEnd().y );
  812. chain.Append( bbbox.GetEnd() );
  813. chain.Append( bbbox.GetEnd().x, bbbox.GetOrigin().y );
  814. chain.SetClosed( true );
  815. aOutline.RemoveAllContours();
  816. aOutline.AddOutline( chain );
  817. }
  818. VECTOR2I projectPointOnSegment( const VECTOR2I& aEndPoint, const SHAPE_POLY_SET& aOutline,
  819. int aOutlineNum = 0 )
  820. {
  821. int minDistance = -1;
  822. VECTOR2I projPoint;
  823. for( auto it = aOutline.CIterateSegments( aOutlineNum ); it; it++ )
  824. {
  825. auto seg = it.Get();
  826. int dis = seg.Distance( aEndPoint );
  827. if( minDistance < 0 || ( dis < minDistance ) )
  828. {
  829. minDistance = dis;
  830. projPoint = seg.NearestPoint( aEndPoint );
  831. }
  832. }
  833. return projPoint;
  834. }
  835. int findEndSegments( SHAPE_LINE_CHAIN& aChain, SEG& aStartSeg, SEG& aEndSeg )
  836. {
  837. int foundSegs = 0;
  838. for( int i = 0; i < aChain.SegmentCount(); i++ )
  839. {
  840. SEG seg = aChain.Segment( i );
  841. bool foundA = false;
  842. bool foundB = false;
  843. for( int j = 0; j < aChain.SegmentCount(); j++ )
  844. {
  845. // Don't test the segment against itself
  846. if( i == j )
  847. continue;
  848. SEG testSeg = aChain.Segment( j );
  849. if( testSeg.Contains( seg.A ) )
  850. foundA = true;
  851. if( testSeg.Contains( seg.B ) )
  852. foundB = true;
  853. }
  854. // This segment isn't a start or end
  855. if( foundA && foundB )
  856. continue;
  857. if( foundSegs == 0 )
  858. {
  859. // The first segment we encounter is the "start" segment
  860. wxLogTrace( traceBoardOutline, wxT( "Found start segment: (%d, %d)-(%d, %d)" ),
  861. seg.A.x, seg.A.y, seg.B.x, seg.B.y );
  862. aStartSeg = seg;
  863. foundSegs++;
  864. }
  865. else
  866. {
  867. // Once we find both start and end, we can stop
  868. wxLogTrace( traceBoardOutline, wxT( "Found end segment: (%d, %d)-(%d, %d)" ),
  869. seg.A.x, seg.A.y, seg.B.x, seg.B.y );
  870. aEndSeg = seg;
  871. foundSegs++;
  872. break;
  873. }
  874. }
  875. return foundSegs;
  876. }
  877. bool BuildFootprintPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines, int aErrorMax,
  878. int aChainingEpsilon, OUTLINE_ERROR_HANDLER* aErrorHandler )
  879. {
  880. FOOTPRINT* footprint = aBoard->GetFirstFootprint();
  881. // No footprint loaded
  882. if( !footprint )
  883. {
  884. wxLogTrace( traceBoardOutline, wxT( "No footprint found on board" ) );
  885. return false;
  886. }
  887. PCB_TYPE_COLLECTOR items;
  888. SHAPE_POLY_SET outlines;
  889. bool success = false;
  890. SCOPED_FLAGS_CLEANER cleaner( SKIP_STRUCT );
  891. // Get all the SHAPEs into 'items', then keep only those on layer == Edge_Cuts.
  892. items.Collect( aBoard, { PCB_SHAPE_T } );
  893. // Make a working copy of aSegList, because the list is modified during calculations
  894. std::vector<PCB_SHAPE*> segList;
  895. for( int ii = 0; ii < items.GetCount(); ii++ )
  896. {
  897. if( items[ii]->GetLayer() == Edge_Cuts )
  898. segList.push_back( static_cast<PCB_SHAPE*>( items[ii] ) );
  899. }
  900. if( !segList.empty() )
  901. {
  902. success = doConvertOutlineToPolygon( segList, outlines, aErrorMax, aChainingEpsilon, true,
  903. aErrorHandler, false, cleaner );
  904. }
  905. // A closed outline was found on Edge_Cuts
  906. if( success )
  907. {
  908. wxLogTrace( traceBoardOutline, wxT( "Closed outline found" ) );
  909. // If copper is outside a closed polygon, treat it as a hole
  910. // If there are multiple outlines in the footprint, they are also holes
  911. if( isCopperOutside( footprint, outlines ) || outlines.OutlineCount() > 1 )
  912. {
  913. wxLogTrace( traceBoardOutline, wxT( "Treating outline as a hole" ) );
  914. buildBoardBoundingBoxPoly( aBoard, aOutlines );
  915. // Copy all outlines from the conversion as holes into the new outline
  916. for( int i = 0; i < outlines.OutlineCount(); i++ )
  917. {
  918. SHAPE_LINE_CHAIN& out = outlines.Outline( i );
  919. if( out.IsClosed() )
  920. aOutlines.AddHole( out, -1 );
  921. for( int j = 0; j < outlines.HoleCount( i ); j++ )
  922. {
  923. SHAPE_LINE_CHAIN& hole = outlines.Hole( i, j );
  924. if( hole.IsClosed() )
  925. aOutlines.AddHole( hole, -1 );
  926. }
  927. }
  928. }
  929. // If all copper is inside, then the computed outline is the board outline
  930. else
  931. {
  932. wxLogTrace( traceBoardOutline, wxT( "Treating outline as board edge" ) );
  933. aOutlines = std::move( outlines );
  934. }
  935. return true;
  936. }
  937. // No board outlines were found, so use the bounding box
  938. else if( outlines.OutlineCount() == 0 )
  939. {
  940. wxLogTrace( traceBoardOutline, wxT( "Using footprint bounding box" ) );
  941. buildBoardBoundingBoxPoly( aBoard, aOutlines );
  942. return true;
  943. }
  944. // There is an outline present, but it is not closed
  945. else
  946. {
  947. wxLogTrace( traceBoardOutline, wxT( "Trying to build outline" ) );
  948. std::vector<SHAPE_LINE_CHAIN> closedChains;
  949. std::vector<SHAPE_LINE_CHAIN> openChains;
  950. // The ConvertOutlineToPolygon function returns only one main outline and the rest as
  951. // holes, so we promote the holes and process them
  952. openChains.push_back( outlines.Outline( 0 ) );
  953. for( int j = 0; j < outlines.HoleCount( 0 ); j++ )
  954. {
  955. SHAPE_LINE_CHAIN hole = outlines.Hole( 0, j );
  956. if( hole.IsClosed() )
  957. {
  958. wxLogTrace( traceBoardOutline, wxT( "Found closed hole" ) );
  959. closedChains.push_back( hole );
  960. }
  961. else
  962. {
  963. wxLogTrace( traceBoardOutline, wxT( "Found open hole" ) );
  964. openChains.push_back( hole );
  965. }
  966. }
  967. SHAPE_POLY_SET bbox;
  968. buildBoardBoundingBoxPoly( aBoard, bbox );
  969. // Treat the open polys as the board edge
  970. SHAPE_LINE_CHAIN chain = openChains[0];
  971. SHAPE_LINE_CHAIN rect = bbox.Outline( 0 );
  972. // We know the outline chain is open, so set to non-closed to get better segment count
  973. chain.SetClosed( false );
  974. SEG startSeg;
  975. SEG endSeg;
  976. // The two possible board outlines
  977. SHAPE_LINE_CHAIN upper;
  978. SHAPE_LINE_CHAIN lower;
  979. findEndSegments( chain, startSeg, endSeg );
  980. if( chain.SegmentCount() == 0 )
  981. {
  982. // Something is wrong, bail out with the overall footprint bounding box
  983. wxLogTrace( traceBoardOutline, wxT( "No line segments in provided outline" ) );
  984. aOutlines = std::move( bbox );
  985. return true;
  986. }
  987. else if( chain.SegmentCount() == 1 )
  988. {
  989. // This case means there is only 1 line segment making up the edge cuts of the
  990. // footprint, so we just need to use it to cut the bounding box in half.
  991. wxLogTrace( traceBoardOutline, wxT( "Only 1 line segment in provided outline" ) );
  992. startSeg = chain.Segment( 0 );
  993. // Intersect with all the sides of the rectangle
  994. OPT_VECTOR2I inter0 = startSeg.IntersectLines( rect.Segment( 0 ) );
  995. OPT_VECTOR2I inter1 = startSeg.IntersectLines( rect.Segment( 1 ) );
  996. OPT_VECTOR2I inter2 = startSeg.IntersectLines( rect.Segment( 2 ) );
  997. OPT_VECTOR2I inter3 = startSeg.IntersectLines( rect.Segment( 3 ) );
  998. if( inter0 && inter2 && !inter1 && !inter3 )
  999. {
  1000. // Intersects the vertical rectangle sides only
  1001. wxLogTrace( traceBoardOutline, wxT( "Segment intersects only vertical bbox sides" ) );
  1002. // The upper half
  1003. upper.Append( *inter0 );
  1004. upper.Append( rect.GetPoint( 1 ) );
  1005. upper.Append( rect.GetPoint( 2 ) );
  1006. upper.Append( *inter2 );
  1007. upper.SetClosed( true );
  1008. // The lower half
  1009. lower.Append( *inter0 );
  1010. lower.Append( rect.GetPoint( 0 ) );
  1011. lower.Append( rect.GetPoint( 3 ) );
  1012. lower.Append( *inter2 );
  1013. lower.SetClosed( true );
  1014. }
  1015. else if( inter1 && inter3 && !inter0 && !inter2 )
  1016. {
  1017. // Intersects the horizontal rectangle sides only
  1018. wxLogTrace( traceBoardOutline, wxT( "Segment intersects only horizontal bbox sides" ) );
  1019. // The left half
  1020. upper.Append( *inter1 );
  1021. upper.Append( rect.GetPoint( 1 ) );
  1022. upper.Append( rect.GetPoint( 0 ) );
  1023. upper.Append( *inter3 );
  1024. upper.SetClosed( true );
  1025. // The right half
  1026. lower.Append( *inter1 );
  1027. lower.Append( rect.GetPoint( 2 ) );
  1028. lower.Append( rect.GetPoint( 3 ) );
  1029. lower.Append( *inter3 );
  1030. lower.SetClosed( true );
  1031. }
  1032. else
  1033. {
  1034. // Angled line segment that cuts across a corner
  1035. wxLogTrace( traceBoardOutline, wxT( "Segment intersects two perpendicular bbox sides" ) );
  1036. // Figure out which actual lines are intersected, since IntersectLines assumes
  1037. // an infinite line
  1038. bool hit0 = rect.Segment( 0 ).Contains( *inter0 );
  1039. bool hit1 = rect.Segment( 1 ).Contains( *inter1 );
  1040. bool hit2 = rect.Segment( 2 ).Contains( *inter2 );
  1041. bool hit3 = rect.Segment( 3 ).Contains( *inter3 );
  1042. if( hit0 && hit1 )
  1043. {
  1044. // Cut across the upper left corner
  1045. wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
  1046. // The upper half
  1047. upper.Append( *inter0 );
  1048. upper.Append( rect.GetPoint( 1 ) );
  1049. upper.Append( *inter1 );
  1050. upper.SetClosed( true );
  1051. // The lower half
  1052. lower.Append( *inter0 );
  1053. lower.Append( rect.GetPoint( 0 ) );
  1054. lower.Append( rect.GetPoint( 3 ) );
  1055. lower.Append( rect.GetPoint( 2 ) );
  1056. lower.Append( *inter1 );
  1057. lower.SetClosed( true );
  1058. }
  1059. else if( hit1 && hit2 )
  1060. {
  1061. // Cut across the upper right corner
  1062. wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper right corner" ) );
  1063. // The upper half
  1064. upper.Append( *inter1 );
  1065. upper.Append( rect.GetPoint( 2 ) );
  1066. upper.Append( *inter2 );
  1067. upper.SetClosed( true );
  1068. // The lower half
  1069. lower.Append( *inter1 );
  1070. lower.Append( rect.GetPoint( 1 ) );
  1071. lower.Append( rect.GetPoint( 0 ) );
  1072. lower.Append( rect.GetPoint( 3 ) );
  1073. lower.Append( *inter2 );
  1074. lower.SetClosed( true );
  1075. }
  1076. else if( hit2 && hit3 )
  1077. {
  1078. // Cut across the lower right corner
  1079. wxLogTrace( traceBoardOutline, wxT( "Segment cuts lower right corner" ) );
  1080. // The upper half
  1081. upper.Append( *inter2 );
  1082. upper.Append( rect.GetPoint( 2 ) );
  1083. upper.Append( rect.GetPoint( 1 ) );
  1084. upper.Append( rect.GetPoint( 0 ) );
  1085. upper.Append( *inter3 );
  1086. upper.SetClosed( true );
  1087. // The bottom half
  1088. lower.Append( *inter2 );
  1089. lower.Append( rect.GetPoint( 3 ) );
  1090. lower.Append( *inter3 );
  1091. lower.SetClosed( true );
  1092. }
  1093. else
  1094. {
  1095. // Cut across the lower left corner
  1096. wxLogTrace( traceBoardOutline, wxT( "Segment cuts upper left corner" ) );
  1097. // The upper half
  1098. upper.Append( *inter0 );
  1099. upper.Append( rect.GetPoint( 1 ) );
  1100. upper.Append( rect.GetPoint( 2 ) );
  1101. upper.Append( rect.GetPoint( 3 ) );
  1102. upper.Append( *inter3 );
  1103. upper.SetClosed( true );
  1104. // The bottom half
  1105. lower.Append( *inter0 );
  1106. lower.Append( rect.GetPoint( 0 ) );
  1107. lower.Append( *inter3 );
  1108. lower.SetClosed( true );
  1109. }
  1110. }
  1111. }
  1112. else
  1113. {
  1114. // More than 1 segment
  1115. wxLogTrace( traceBoardOutline, wxT( "Multiple segments in outline" ) );
  1116. // Just a temporary thing
  1117. aOutlines = std::move( bbox );
  1118. return true;
  1119. }
  1120. // Figure out which is the correct outline
  1121. SHAPE_POLY_SET poly1;
  1122. SHAPE_POLY_SET poly2;
  1123. poly1.NewOutline();
  1124. poly1.Append( upper );
  1125. poly2.NewOutline();
  1126. poly2.Append( lower );
  1127. if( isCopperOutside( footprint, poly1 ) )
  1128. {
  1129. wxLogTrace( traceBoardOutline, wxT( "Using lower shape" ) );
  1130. aOutlines = std::move( poly2 );
  1131. }
  1132. else
  1133. {
  1134. wxLogTrace( traceBoardOutline, wxT( "Using upper shape" ) );
  1135. aOutlines = std::move( poly1 );
  1136. }
  1137. // Add all closed polys as holes to the main outline
  1138. for( SHAPE_LINE_CHAIN& closedChain : closedChains )
  1139. {
  1140. wxLogTrace( traceBoardOutline, wxT( "Adding hole to main outline" ) );
  1141. aOutlines.AddHole( closedChain, -1 );
  1142. }
  1143. return true;
  1144. }
  1145. // We really shouldn't reach this point
  1146. return false;
  1147. }