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.

710 lines
25 KiB

  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-2017 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. /**
  26. * @file convert_drawsegment_list_to_polygon.cpp
  27. * @brief functions to convert a shape built with DRAWSEGMENTS to a polygon.
  28. * expecting the shape describes shape similar to a polygon
  29. */
  30. #include <trigo.h>
  31. #include <macros.h>
  32. #include <class_drawsegment.h>
  33. #include <base_units.h>
  34. #include <convert_basic_shapes_to_polygon.h>
  35. #include <geometry/geometry_utils.h>
  36. /**
  37. * Function close_ness
  38. * is a non-exact distance (also called Manhattan distance) used to approximate
  39. * the distance between two points.
  40. * The distance is very in-exact, but can be helpful when used
  41. * to pick between alternative neighboring points.
  42. * @param aLeft is the first point
  43. * @param aRight is the second point
  44. * @return unsigned - a measure of proximity that the caller knows about, in BIU,
  45. * but remember it is only an approximation.
  46. */
  47. static unsigned close_ness( const wxPoint& aLeft, const wxPoint& aRight )
  48. {
  49. // Don't need an accurate distance calculation, just something
  50. // approximating it, for relative ordering.
  51. return unsigned( std::abs( aLeft.x - aRight.x ) + abs( aLeft.y - aRight.y ) );
  52. }
  53. /**
  54. * Function close_enough
  55. * is a local and tunable method of qualifying the proximity of two points.
  56. *
  57. * @param aLeft is the first point
  58. * @param aRight is the second point
  59. * @param aLimit is a measure of proximity that the caller knows about.
  60. * @return bool - true if the two points are close enough, else false.
  61. */
  62. inline bool close_enough( const wxPoint& aLeft, const wxPoint& aRight, unsigned aLimit )
  63. {
  64. // We don't use an accurate distance calculation, just something
  65. // approximating it, since aLimit is non-exact anyway except when zero.
  66. return close_ness( aLeft, aRight ) <= aLimit;
  67. }
  68. /**
  69. * Function close_st
  70. * is a local method of qualifying if either the start of end point of a segment is closest to a point.
  71. *
  72. * @param aReference is the reference point
  73. * @param aFirst is the first point
  74. * @param aSecond is the second point
  75. * @return bool - true if the the first point is closest to the reference, otherwise false.
  76. */
  77. inline bool close_st( const wxPoint& aReference, const wxPoint& aFirst, const wxPoint& aSecond )
  78. {
  79. // We don't use an accurate distance calculation, just something
  80. // approximating to find the closest to the reference.
  81. return close_ness( aReference, aFirst ) <= close_ness( aReference, aSecond );
  82. }
  83. /**
  84. * Searches for a DRAWSEGMENT matching a given end point or start point in a list, and
  85. * if found, removes it from the TYPE_COLLECTOR and returns it, else returns NULL.
  86. * @param aPoint The starting or ending point to search for.
  87. * @param aList The list to remove from.
  88. * @param aLimit is the distance from \a aPoint that still constitutes a valid find.
  89. * @return DRAWSEGMENT* - The first DRAWSEGMENT that has a start or end point matching
  90. * aPoint, otherwise NULL if none.
  91. */
  92. static DRAWSEGMENT* findPoint( const wxPoint& aPoint, std::vector< DRAWSEGMENT* >& aList, unsigned aLimit )
  93. {
  94. unsigned min_d = INT_MAX;
  95. int ndx_min = 0;
  96. // find the point closest to aPoint and perhaps exactly matching aPoint.
  97. for( size_t i = 0; i < aList.size(); ++i )
  98. {
  99. DRAWSEGMENT* graphic = aList[i];
  100. unsigned d;
  101. switch( graphic->GetShape() )
  102. {
  103. case S_ARC:
  104. if( aPoint == graphic->GetArcStart() || aPoint == graphic->GetArcEnd() )
  105. {
  106. aList.erase( aList.begin() + i );
  107. return graphic;
  108. }
  109. d = close_ness( aPoint, graphic->GetArcStart() );
  110. if( d < min_d )
  111. {
  112. min_d = d;
  113. ndx_min = i;
  114. }
  115. d = close_ness( aPoint, graphic->GetArcEnd() );
  116. if( d < min_d )
  117. {
  118. min_d = d;
  119. ndx_min = i;
  120. }
  121. break;
  122. default:
  123. if( aPoint == graphic->GetStart() || aPoint == graphic->GetEnd() )
  124. {
  125. aList.erase( aList.begin() + i );
  126. return graphic;
  127. }
  128. d = close_ness( aPoint, graphic->GetStart() );
  129. if( d < min_d )
  130. {
  131. min_d = d;
  132. ndx_min = i;
  133. }
  134. d = close_ness( aPoint, graphic->GetEnd() );
  135. if( d < min_d )
  136. {
  137. min_d = d;
  138. ndx_min = i;
  139. }
  140. }
  141. }
  142. if( min_d <= aLimit )
  143. {
  144. DRAWSEGMENT* graphic = aList[ndx_min];
  145. aList.erase( aList.begin() + ndx_min );
  146. return graphic;
  147. }
  148. return NULL;
  149. }
  150. /**
  151. * Function ConvertOutlineToPolygon
  152. * build a polygon (with holes) from a DRAWSEGMENT list, which is expected to be
  153. * a outline, therefore a closed main outline with perhaps closed inner outlines.
  154. * These closed inner outlines are considered as holes in the main outline
  155. * @param aSegList the initial list of drawsegments (only lines, circles and arcs).
  156. * @param aPolygons will contain the complex polygon.
  157. * @param aTolerance is the max distance between points that is still accepted as connected (internal units)
  158. * @param aErrorText is a wxString to return error message.
  159. */
  160. bool ConvertOutlineToPolygon( std::vector<DRAWSEGMENT*>& aSegList, SHAPE_POLY_SET& aPolygons,
  161. wxString* aErrorText, unsigned int aTolerance )
  162. {
  163. if( aSegList.size() == 0 )
  164. return true;
  165. wxString msg;
  166. // Make a working copy of aSegList, because the list is modified during calculations
  167. std::vector< DRAWSEGMENT* > segList = aSegList;
  168. DRAWSEGMENT* graphic;
  169. wxPoint prevPt;
  170. // Find edge point with minimum x, this should be in the outer polygon
  171. // which will define the perimeter Edge.Cuts polygon.
  172. wxPoint xmin = wxPoint( INT_MAX, 0 );
  173. int xmini = 0;
  174. for( size_t i = 0; i < segList.size(); i++ )
  175. {
  176. graphic = (DRAWSEGMENT*) segList[i];
  177. switch( graphic->GetShape() )
  178. {
  179. case S_SEGMENT:
  180. {
  181. if( graphic->GetStart().x < xmin.x )
  182. {
  183. xmin = graphic->GetStart();
  184. xmini = i;
  185. }
  186. if( graphic->GetEnd().x < xmin.x )
  187. {
  188. xmin = graphic->GetEnd();
  189. xmini = i;
  190. }
  191. }
  192. break;
  193. case S_ARC:
  194. // Freerouter does not yet understand arcs, so approximate
  195. // an arc with a series of short lines and put those
  196. // line segments into the !same! PATH.
  197. {
  198. wxPoint pstart = graphic->GetArcStart();
  199. wxPoint center = graphic->GetCenter();
  200. double angle = -graphic->GetAngle();
  201. double radius = graphic->GetRadius();
  202. int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 );
  203. wxPoint pt;
  204. for( int step = 1; step<=steps; ++step )
  205. {
  206. double rotation = ( angle * step ) / steps;
  207. pt = pstart;
  208. RotatePoint( &pt, center, rotation );
  209. if( pt.x < xmin.x )
  210. {
  211. xmin = pt;
  212. xmini = i;
  213. }
  214. }
  215. }
  216. break;
  217. case S_CIRCLE:
  218. {
  219. wxPoint pt = graphic->GetCenter();
  220. // pt has minimum x point
  221. pt.x -= graphic->GetRadius();
  222. // when the radius <= 0, this is a mal-formed circle. Skip it
  223. if( graphic->GetRadius() > 0 && pt.x < xmin.x )
  224. {
  225. xmin = pt;
  226. xmini = i;
  227. }
  228. }
  229. break;
  230. case S_CURVE:
  231. {
  232. graphic->RebuildBezierToSegmentsPointsList( graphic->GetWidth() );
  233. for( unsigned int jj = 0; jj < graphic->GetBezierPoints().size(); jj++ )
  234. {
  235. wxPoint pt = graphic->GetBezierPoints()[jj];
  236. if( pt.x < xmin.x )
  237. {
  238. xmin = pt;
  239. xmini = i;
  240. }
  241. }
  242. }
  243. break;
  244. default:
  245. break;
  246. }
  247. }
  248. // Grab the left most point, assume its on the board's perimeter, and see if we
  249. // can put enough graphics together by matching endpoints to formulate a cohesive
  250. // polygon.
  251. graphic = (DRAWSEGMENT*) segList[xmini];
  252. // The first DRAWSEGMENT is in 'graphic', ok to remove it from 'items'
  253. segList.erase( segList.begin() + xmini );
  254. // Output the Edge.Cuts perimeter as circle or polygon.
  255. if( graphic->GetShape() == S_CIRCLE )
  256. {
  257. int steps = GetArcToSegmentCount( graphic->GetRadius(), ARC_LOW_DEF, 360.0 );
  258. TransformCircleToPolygon( aPolygons, graphic->GetCenter(), graphic->GetRadius(), steps );
  259. }
  260. else
  261. {
  262. // Polygon start point. Arbitrarily chosen end of the
  263. // segment and build the poly from here.
  264. wxPoint startPt = wxPoint( graphic->GetEnd() );
  265. prevPt = graphic->GetEnd();
  266. aPolygons.NewOutline();
  267. aPolygons.Append( prevPt );
  268. // Do not append the other end point yet of this 'graphic', this first
  269. // 'graphic' might be an arc or a curve.
  270. for(;;)
  271. {
  272. switch( graphic->GetShape() )
  273. {
  274. case S_SEGMENT:
  275. {
  276. wxPoint nextPt;
  277. // Use the line segment end point furthest away from
  278. // prevPt as we assume the other end to be ON prevPt or
  279. // very close to it.
  280. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
  281. nextPt = graphic->GetEnd();
  282. else
  283. nextPt = graphic->GetStart();
  284. aPolygons.Append( nextPt );
  285. prevPt = nextPt;
  286. }
  287. break;
  288. case S_ARC:
  289. // We do not support arcs in polygons, so approximate
  290. // an arc with a series of short lines and put those
  291. // line segments into the !same! PATH.
  292. {
  293. wxPoint pstart = graphic->GetArcStart();
  294. wxPoint pend = graphic->GetArcEnd();
  295. wxPoint pcenter = graphic->GetCenter();
  296. double angle = -graphic->GetAngle();
  297. double radius = graphic->GetRadius();
  298. int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 );
  299. if( !close_enough( prevPt, pstart, aTolerance ) )
  300. {
  301. wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) );
  302. angle = -angle;
  303. std::swap( pstart, pend );
  304. }
  305. wxPoint nextPt;
  306. for( int step = 1; step<=steps; ++step )
  307. {
  308. double rotation = ( angle * step ) / steps;
  309. nextPt = pstart;
  310. RotatePoint( &nextPt, pcenter, rotation );
  311. aPolygons.Append( nextPt );
  312. }
  313. prevPt = nextPt;
  314. }
  315. break;
  316. case S_CURVE:
  317. // We do not support Bezier curves in polygons, so approximate
  318. // with a series of short lines and put those
  319. // line segments into the !same! PATH.
  320. {
  321. wxPoint nextPt;
  322. bool reverse = false;
  323. // Use the end point furthest away from
  324. // prevPt as we assume the other end to be ON prevPt or
  325. // very close to it.
  326. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
  327. nextPt = graphic->GetEnd();
  328. else
  329. {
  330. nextPt = graphic->GetStart();
  331. reverse = true;
  332. }
  333. if( reverse )
  334. {
  335. for( int jj = graphic->GetBezierPoints().size()-1;
  336. jj >= 0; jj-- )
  337. aPolygons.Append( graphic->GetBezierPoints()[jj] );
  338. }
  339. else
  340. {
  341. for( unsigned int jj = 0;
  342. jj < graphic->GetBezierPoints().size(); jj++ )
  343. aPolygons.Append( graphic->GetBezierPoints()[jj] );
  344. }
  345. prevPt = nextPt;
  346. }
  347. break;
  348. default:
  349. if( aErrorText )
  350. {
  351. msg.Printf( _( "Unsupported DRAWSEGMENT type %s" ),
  352. GetChars( BOARD_ITEM::ShowShape( graphic->GetShape() ) ) );
  353. *aErrorText << msg << "\n";
  354. }
  355. return false;
  356. }
  357. // Get next closest segment.
  358. graphic = findPoint( prevPt, segList, aTolerance );
  359. // If there are no more close segments, check if the board
  360. // outline polygon can be closed.
  361. if( !graphic )
  362. {
  363. if( close_enough( startPt, prevPt, aTolerance ) )
  364. {
  365. // Close the polygon back to start point
  366. // aPolygons.Append( startPt ); // not needed
  367. }
  368. else
  369. {
  370. if( aErrorText )
  371. {
  372. msg.Printf(
  373. _( "Unable to find the next boundary segment with an endpoint of (%s mm, %s mm). "
  374. "graphic outline must form a contiguous, closed polygon." ),
  375. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
  376. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
  377. );
  378. *aErrorText << msg << "\n";
  379. }
  380. return false;
  381. }
  382. break;
  383. }
  384. }
  385. }
  386. while( segList.size() )
  387. {
  388. // emit a signal layers keepout for every interior polygon left...
  389. int hole = aPolygons.NewHole();
  390. graphic = (DRAWSEGMENT*) segList[0];
  391. segList.erase( segList.begin() );
  392. if( graphic->GetShape() == S_CIRCLE )
  393. {
  394. // make a circle by segments;
  395. wxPoint center = graphic->GetCenter();
  396. double angle = 3600.0;
  397. wxPoint start = center;
  398. int radius = graphic->GetRadius();
  399. int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, 360.0 );
  400. wxPoint nextPt;
  401. start.x += radius;
  402. for( int step = 0; step < steps; ++step )
  403. {
  404. double rotation = ( angle * step ) / steps;
  405. nextPt = start;
  406. RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation );
  407. aPolygons.Append( nextPt, -1, hole );
  408. }
  409. }
  410. else
  411. {
  412. // Polygon start point. Arbitrarily chosen end of the
  413. // segment and build the poly from here.
  414. wxPoint startPt( graphic->GetEnd() );
  415. prevPt = graphic->GetEnd();
  416. aPolygons.Append( prevPt, -1, hole );
  417. // do not append the other end point yet, this first 'graphic' might be an arc
  418. for(;;)
  419. {
  420. switch( graphic->GetShape() )
  421. {
  422. case S_SEGMENT:
  423. {
  424. wxPoint nextPt;
  425. // Use the line segment end point furthest away from
  426. // prevPt as we assume the other end to be ON prevPt or
  427. // very close to it.
  428. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
  429. {
  430. nextPt = graphic->GetEnd();
  431. }
  432. else
  433. {
  434. nextPt = graphic->GetStart();
  435. }
  436. prevPt = nextPt;
  437. aPolygons.Append( prevPt, -1, hole );
  438. }
  439. break;
  440. case S_ARC:
  441. // Freerouter does not yet understand arcs, so approximate
  442. // an arc with a series of short lines and put those
  443. // line segments into the !same! PATH.
  444. {
  445. wxPoint pstart = graphic->GetArcStart();
  446. wxPoint pend = graphic->GetArcEnd();
  447. wxPoint pcenter = graphic->GetCenter();
  448. double angle = -graphic->GetAngle();
  449. int radius = graphic->GetRadius();
  450. int steps = GetArcToSegmentCount( radius, ARC_LOW_DEF, angle / 10.0 );
  451. if( !close_enough( prevPt, pstart, aTolerance ) )
  452. {
  453. wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), aTolerance ) );
  454. angle = -angle;
  455. std::swap( pstart, pend );
  456. }
  457. wxPoint nextPt;
  458. for( int step = 1; step <= steps; ++step )
  459. {
  460. double rotation = ( angle * step ) / steps;
  461. nextPt = pstart;
  462. RotatePoint( &nextPt, pcenter, rotation );
  463. aPolygons.Append( nextPt, -1, hole );
  464. }
  465. prevPt = nextPt;
  466. }
  467. break;
  468. case S_CURVE:
  469. // We do not support Bezier curves in polygons, so approximate
  470. // with a series of short lines and put those
  471. // line segments into the !same! PATH.
  472. {
  473. wxPoint nextPt;
  474. bool reverse = false;
  475. // Use the end point furthest away from
  476. // prevPt as we assume the other end to be ON prevPt or
  477. // very close to it.
  478. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
  479. nextPt = graphic->GetEnd();
  480. else
  481. {
  482. nextPt = graphic->GetStart();
  483. reverse = true;
  484. }
  485. if( reverse )
  486. {
  487. for( int jj = graphic->GetBezierPoints().size()-1;
  488. jj >= 0; jj-- )
  489. aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole );
  490. }
  491. else
  492. {
  493. for( unsigned int jj = 0;
  494. jj < graphic->GetBezierPoints().size(); jj++ )
  495. aPolygons.Append( graphic->GetBezierPoints()[jj], -1, hole );
  496. }
  497. prevPt = nextPt;
  498. }
  499. break;
  500. default:
  501. if( aErrorText )
  502. {
  503. msg.Printf( _( "Unsupported DRAWSEGMENT type %s" ),
  504. GetChars( BOARD_ITEM::ShowShape( graphic->GetShape() ) ) );
  505. *aErrorText << msg << "\n";
  506. }
  507. return false;
  508. }
  509. // Get next closest segment.
  510. graphic = findPoint( prevPt, segList, aTolerance );
  511. // If there are no more close segments, check if polygon
  512. // can be closed.
  513. if( !graphic )
  514. {
  515. if( close_enough( startPt, prevPt, aTolerance ) )
  516. {
  517. // Close the polygon back to start point
  518. // aPolygons.Append( startPt, -1, hole ); // not needed
  519. }
  520. else
  521. {
  522. if( aErrorText )
  523. {
  524. msg.Printf(
  525. _( "Unable to find the next graphic segment with an endpoint of (%s mm, %s mm).\n"
  526. "Edit graphics, making them contiguous polygons each." ),
  527. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
  528. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
  529. );
  530. *aErrorText << msg << "\n";
  531. }
  532. return false;
  533. }
  534. break;
  535. }
  536. }
  537. }
  538. }
  539. return true;
  540. }
  541. #include <class_board.h>
  542. #include <collectors.h>
  543. /* This function is used to extract a board outlines (3D view, automatic zones build ...)
  544. * Any closed outline inside the main outline is a hole
  545. * All contours should be closed, i.e. valid closed polygon vertices
  546. */
  547. bool BuildBoardPolygonOutlines( BOARD* aBoard, SHAPE_POLY_SET& aOutlines,
  548. wxString* aErrorText, unsigned int aTolerance )
  549. {
  550. PCB_TYPE_COLLECTOR items;
  551. // Get all the DRAWSEGMENTS and module graphics into 'items',
  552. // then keep only those on layer == Edge_Cuts.
  553. static const KICAD_T scan_graphics[] = { PCB_LINE_T, PCB_MODULE_EDGE_T, EOT };
  554. items.Collect( aBoard, scan_graphics );
  555. // Make a working copy of aSegList, because the list is modified during calculations
  556. std::vector< DRAWSEGMENT* > segList;
  557. for( int ii = 0; ii < items.GetCount(); ii++ )
  558. {
  559. if( items[ii]->GetLayer() == Edge_Cuts )
  560. segList.push_back( static_cast< DRAWSEGMENT* >( items[ii] ) );
  561. }
  562. bool success = ConvertOutlineToPolygon( segList, aOutlines, aErrorText, aTolerance );
  563. if( !success || !aOutlines.OutlineCount() )
  564. {
  565. // Creates a valid polygon outline is not possible.
  566. // So uses the board edge cuts bounding box to create a
  567. // rectangular outline
  568. // When no edge cuts items, build a contour
  569. // from global bounding box
  570. EDA_RECT bbbox = aBoard->GetBoardEdgesBoundingBox();
  571. // If null area, uses the global bounding box.
  572. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  573. bbbox = aBoard->ComputeBoundingBox();
  574. // Ensure non null area. If happen, gives a minimal size.
  575. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  576. bbbox.Inflate( Millimeter2iu( 1.0 ) );
  577. aOutlines.RemoveAllContours();
  578. aOutlines.NewOutline();
  579. wxPoint corner;
  580. aOutlines.Append( bbbox.GetOrigin() );
  581. corner.x = bbbox.GetOrigin().x;
  582. corner.y = bbbox.GetEnd().y;
  583. aOutlines.Append( corner );
  584. aOutlines.Append( bbbox.GetEnd() );
  585. corner.x = bbbox.GetEnd().x;
  586. corner.y = bbbox.GetOrigin().y;
  587. aOutlines.Append( corner );
  588. }
  589. return success;
  590. }