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.

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