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.

1380 lines
46 KiB

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