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.

645 lines
22 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. /**
  36. * Function close_ness
  37. * is a non-exact distance (also called Manhattan distance) used to approximate
  38. * the distance between two points.
  39. * The distance is very in-exact, but can be helpful when used
  40. * to pick between alternative neighboring points.
  41. * @param aLeft is the first point
  42. * @param aRight is the second point
  43. * @return unsigned - a measure of proximity that the caller knows about, in BIU,
  44. * but remember it is only an approximation.
  45. */
  46. static unsigned close_ness( const wxPoint& aLeft, const wxPoint& aRight )
  47. {
  48. // Don't need an accurate distance calculation, just something
  49. // approximating it, for relative ordering.
  50. return unsigned( std::abs( aLeft.x - aRight.x ) + abs( aLeft.y - aRight.y ) );
  51. }
  52. /**
  53. * Function close_enough
  54. * is a local and tunable method of qualifying the proximity of two points.
  55. *
  56. * @param aLeft is the first point
  57. * @param aRight is the second point
  58. * @param aLimit is a measure of proximity that the caller knows about.
  59. * @return bool - true if the two points are close enough, else false.
  60. */
  61. inline bool close_enough( const wxPoint& aLeft, const wxPoint& aRight, unsigned aLimit )
  62. {
  63. // We don't use an accurate distance calculation, just something
  64. // approximating it, since aLimit is non-exact anyway except when zero.
  65. return close_ness( aLeft, aRight ) <= aLimit;
  66. }
  67. /**
  68. * Function close_st
  69. * is a local method of qualifying if either the start of end point of a segment is closest to a point.
  70. *
  71. * @param aReference is the reference point
  72. * @param aFirst is the first point
  73. * @param aSecond is the second point
  74. * @return bool - true if the the first point is closest to the reference, otherwise false.
  75. */
  76. inline bool close_st( const wxPoint& aReference, const wxPoint& aFirst, const wxPoint& aSecond )
  77. {
  78. // We don't use an accurate distance calculation, just something
  79. // approximating to find the closest to the reference.
  80. return close_ness( aReference, aFirst ) <= close_ness( aReference, aSecond );
  81. }
  82. /**
  83. * Function findPoint
  84. * searches for a DRAWSEGMENT with an end point or start point of aPoint, 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 items 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 aSegmentsByCircle is the number of segments to approximate a circle.
  158. * @param aErrorText is a wxString to return error message.
  159. */
  160. bool ConvertOutlineToPolygon( std::vector< DRAWSEGMENT* >& aSegList,
  161. SHAPE_POLY_SET& aPolygons, int aSegmentsByCircle,
  162. wxString* aErrorText )
  163. {
  164. if( aSegList.size() == 0 )
  165. return true;
  166. wxString msg;
  167. // Make a working copy of aSegList, because the list is modified during calculations
  168. std::vector< DRAWSEGMENT* > segList = aSegList;
  169. unsigned prox; // a proximity BIU metric, not an accurate distance
  170. DRAWSEGMENT* graphic;
  171. wxPoint prevPt;
  172. // Find edge point with minimum x, this should be in the outer polygon
  173. // which will define the perimeter Edge.Cuts polygon.
  174. wxPoint xmin = wxPoint( INT_MAX, 0 );
  175. int xmini = 0;
  176. for( size_t i = 0; i < segList.size(); i++ )
  177. {
  178. graphic = (DRAWSEGMENT*) segList[i];
  179. switch( graphic->GetShape() )
  180. {
  181. case S_SEGMENT:
  182. {
  183. if( graphic->GetStart().x < xmin.x )
  184. {
  185. xmin = graphic->GetStart();
  186. xmini = i;
  187. }
  188. if( graphic->GetEnd().x < xmin.x )
  189. {
  190. xmin = graphic->GetEnd();
  191. xmini = i;
  192. }
  193. }
  194. break;
  195. case S_ARC:
  196. // Freerouter does not yet understand arcs, so approximate
  197. // an arc with a series of short lines and put those
  198. // line segments into the !same! PATH.
  199. {
  200. wxPoint pstart = graphic->GetArcStart();
  201. wxPoint center = graphic->GetCenter();
  202. double angle = -graphic->GetAngle();
  203. int steps = aSegmentsByCircle * fabs(angle) /3600.0;
  204. if( steps == 0 )
  205. steps = 1;
  206. wxPoint pt;
  207. for( int step = 1; step<=steps; ++step )
  208. {
  209. double rotation = ( angle * step ) / steps;
  210. pt = pstart;
  211. RotatePoint( &pt, center, rotation );
  212. if( pt.x < xmin.x )
  213. {
  214. xmin = pt;
  215. xmini = i;
  216. }
  217. }
  218. }
  219. break;
  220. case S_CIRCLE:
  221. {
  222. wxPoint pt = graphic->GetCenter();
  223. // pt has minimum x point
  224. pt.x -= graphic->GetRadius();
  225. // when the radius <= 0, this is a mal-formed circle. Skip it
  226. if( graphic->GetRadius() > 0 && pt.x < xmin.x )
  227. {
  228. xmin = pt;
  229. xmini = i;
  230. }
  231. }
  232. break;
  233. default:
  234. break;
  235. }
  236. }
  237. // Grab the left most point, assume its on the board's perimeter, and see if we
  238. // can put enough graphics together by matching endpoints to formulate a cohesive
  239. // polygon.
  240. graphic = (DRAWSEGMENT*) segList[xmini];
  241. // The first DRAWSEGMENT is in 'graphic', ok to remove it from 'items'
  242. segList.erase( segList.begin() + xmini );
  243. // Set maximum proximity threshold for point to point nearness metric for
  244. // board perimeter only, not interior keepouts yet.
  245. prox = Millimeter2iu( 0.01 ); // should be enough to fix rounding issues
  246. // is arc start and end point calculations
  247. // Output the Edge.Cuts perimeter as circle or polygon.
  248. if( graphic->GetShape() == S_CIRCLE )
  249. {
  250. TransformCircleToPolygon( aPolygons, graphic->GetCenter(),
  251. graphic->GetRadius(), aSegmentsByCircle );
  252. }
  253. else
  254. {
  255. // Polygon start point. Arbitrarily chosen end of the
  256. // segment and build the poly from here.
  257. wxPoint startPt = wxPoint( graphic->GetEnd() );
  258. prevPt = graphic->GetEnd();
  259. aPolygons.NewOutline();
  260. aPolygons.Append( prevPt );
  261. // Do not append the other end point yet of this 'graphic', this first
  262. // 'graphic' might be an arc.
  263. for(;;)
  264. {
  265. switch( graphic->GetShape() )
  266. {
  267. case S_SEGMENT:
  268. {
  269. wxPoint nextPt;
  270. // Use the line segment end point furthest away from
  271. // prevPt as we assume the other end to be ON prevPt or
  272. // very close to it.
  273. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
  274. {
  275. nextPt = graphic->GetEnd();
  276. }
  277. else
  278. {
  279. nextPt = graphic->GetStart();
  280. }
  281. aPolygons.Append( nextPt );
  282. prevPt = nextPt;
  283. }
  284. break;
  285. case S_ARC:
  286. // We do not support arcs in polygons, so approximate
  287. // an arc with a series of short lines and put those
  288. // line segments into the !same! PATH.
  289. {
  290. wxPoint pstart = graphic->GetArcStart();
  291. wxPoint pend = graphic->GetArcEnd();
  292. wxPoint pcenter = graphic->GetCenter();
  293. double angle = -graphic->GetAngle();
  294. int steps = aSegmentsByCircle * fabs(angle) /3600.0;
  295. if( steps == 0 )
  296. steps = 1;
  297. if( !close_enough( prevPt, pstart, prox ) )
  298. {
  299. wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), prox ) );
  300. angle = -angle;
  301. std::swap( pstart, pend );
  302. }
  303. wxPoint nextPt;
  304. for( int step = 1; step<=steps; ++step )
  305. {
  306. double rotation = ( angle * step ) / steps;
  307. nextPt = pstart;
  308. RotatePoint( &nextPt, pcenter, rotation );
  309. aPolygons.Append( nextPt );
  310. }
  311. prevPt = nextPt;
  312. }
  313. break;
  314. default:
  315. if( aErrorText )
  316. {
  317. msg.Printf( _( "Unsupported DRAWSEGMENT type %s" ),
  318. GetChars( BOARD_ITEM::ShowShape( graphic->GetShape() ) ) );
  319. *aErrorText << msg << "\n";
  320. }
  321. return false;
  322. }
  323. // Get next closest segment.
  324. graphic = findPoint( prevPt, segList, prox );
  325. // If there are no more close segments, check if the board
  326. // outline polygon can be closed.
  327. if( !graphic )
  328. {
  329. if( close_enough( startPt, prevPt, prox ) )
  330. {
  331. // Close the polygon back to start point
  332. // aPolygons.Append( startPt ); // not needed
  333. }
  334. else
  335. {
  336. if( aErrorText )
  337. {
  338. msg.Printf(
  339. _( "Unable to find the next boundary segment with an endpoint of (%s mm, %s mm). "
  340. "graphic outline must form a contiguous, closed polygon." ),
  341. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
  342. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
  343. );
  344. *aErrorText << msg << "\n";
  345. }
  346. return false;
  347. }
  348. break;
  349. }
  350. }
  351. }
  352. // Output the interior Edge.Cuts graphics as keepouts, using same nearness
  353. // metric as the board edge as otherwise we have trouble completing complex
  354. // polygons.
  355. prox = Millimeter2iu( 0.05 );
  356. while( segList.size() )
  357. {
  358. // emit a signal layers keepout for every interior polygon left...
  359. int hole = aPolygons.NewHole();
  360. graphic = (DRAWSEGMENT*) segList[0];
  361. segList.erase( segList.begin() );
  362. if( graphic->GetShape() == S_CIRCLE )
  363. {
  364. // make a circle by segments;
  365. wxPoint center = graphic->GetCenter();
  366. double angle = 3600.0;
  367. wxPoint start = center;
  368. int radius = graphic->GetRadius();
  369. start.x += radius;
  370. wxPoint nextPt;
  371. for( int step = 0; step<aSegmentsByCircle; ++step )
  372. {
  373. double rotation = ( angle * step ) / aSegmentsByCircle;
  374. nextPt = start;
  375. RotatePoint( &nextPt.x, &nextPt.y, center.x, center.y, rotation );
  376. aPolygons.Append( nextPt, -1, hole );
  377. }
  378. }
  379. else
  380. {
  381. // Polygon start point. Arbitrarily chosen end of the
  382. // segment and build the poly from here.
  383. wxPoint startPt( graphic->GetEnd() );
  384. prevPt = graphic->GetEnd();
  385. aPolygons.Append( prevPt, -1, hole );
  386. // do not append the other end point yet, this first 'graphic' might be an arc
  387. for(;;)
  388. {
  389. switch( graphic->GetShape() )
  390. {
  391. case S_SEGMENT:
  392. {
  393. wxPoint nextPt;
  394. // Use the line segment end point furthest away from
  395. // prevPt as we assume the other end to be ON prevPt or
  396. // very close to it.
  397. if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) )
  398. {
  399. nextPt = graphic->GetEnd();
  400. }
  401. else
  402. {
  403. nextPt = graphic->GetStart();
  404. }
  405. prevPt = nextPt;
  406. aPolygons.Append( prevPt, -1, hole );
  407. }
  408. break;
  409. case S_ARC:
  410. // Freerouter does not yet understand arcs, so approximate
  411. // an arc with a series of short lines and put those
  412. // line segments into the !same! PATH.
  413. {
  414. wxPoint pstart = graphic->GetArcStart();
  415. wxPoint pend = graphic->GetArcEnd();
  416. wxPoint pcenter = graphic->GetCenter();
  417. double angle = -graphic->GetAngle();
  418. int steps = aSegmentsByCircle * fabs(angle) /3600.0;
  419. if( steps == 0 )
  420. steps = 1;
  421. if( !close_enough( prevPt, pstart, prox ) )
  422. {
  423. wxASSERT( close_enough( prevPt, graphic->GetArcEnd(), prox ) );
  424. angle = -angle;
  425. std::swap( pstart, pend );
  426. }
  427. wxPoint nextPt;
  428. for( int step = 1; step <= steps; ++step )
  429. {
  430. double rotation = ( angle * step ) / steps;
  431. nextPt = pstart;
  432. RotatePoint( &nextPt, pcenter, rotation );
  433. aPolygons.Append( nextPt, -1, hole );
  434. }
  435. prevPt = nextPt;
  436. }
  437. break;
  438. default:
  439. if( aErrorText )
  440. {
  441. msg.Printf(
  442. _( "Unsupported DRAWSEGMENT type %s" ),
  443. GetChars( BOARD_ITEM::ShowShape( graphic->GetShape() ) ) );
  444. *aErrorText << msg << "\n";
  445. }
  446. return false;
  447. }
  448. // Get next closest segment.
  449. graphic = findPoint( prevPt, segList, prox );
  450. // If there are no more close segments, check if polygon
  451. // can be closed.
  452. if( !graphic )
  453. {
  454. if( close_enough( startPt, prevPt, prox ) )
  455. {
  456. // Close the polygon back to start point
  457. // aPolygons.Append( startPt, -1, hole ); // not needed
  458. }
  459. else
  460. {
  461. if( aErrorText )
  462. {
  463. msg.Printf(
  464. _( "Unable to find the next graphic segment with an endpoint of (%s mm, %s mm).\n"
  465. "Edit graphics, making them contiguous polygons each." ),
  466. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.x ).c_str() ) ),
  467. GetChars( FROM_UTF8( BOARD_ITEM::FormatInternalUnits( prevPt.y ).c_str() ) )
  468. );
  469. *aErrorText << msg << "\n";
  470. }
  471. return false;
  472. }
  473. break;
  474. }
  475. }
  476. }
  477. }
  478. return true;
  479. }
  480. #include <class_board.h>
  481. #include <collectors.h>
  482. /* This function is used to extract a board outlines (3D view, automatic zones build ...)
  483. * Any closed outline inside the main outline is a hole
  484. * All contours should be closed, i.e. valid closed polygon vertices
  485. */
  486. bool BuildBoardPolygonOutlines( BOARD* aBoard,
  487. SHAPE_POLY_SET& aOutlines,
  488. wxString* aErrorText )
  489. {
  490. PCB_TYPE_COLLECTOR items;
  491. // Get all the DRAWSEGMENTS and module graphics into 'items',
  492. // then keep only those on layer == Edge_Cuts.
  493. static const KICAD_T scan_graphics[] = { PCB_LINE_T, PCB_MODULE_EDGE_T, EOT };
  494. items.Collect( aBoard, scan_graphics );
  495. // Make a working copy of aSegList, because the list is modified during calculations
  496. std::vector< DRAWSEGMENT* > segList;
  497. for( int ii = 0; ii < items.GetCount(); ii++ )
  498. {
  499. if( items[ii]->GetLayer() == Edge_Cuts )
  500. segList.push_back( static_cast< DRAWSEGMENT* >( items[ii] ) );
  501. }
  502. const int STEPS = 36; // for a segmentation of an arc of 360 degrees
  503. bool success = ConvertOutlineToPolygon( segList, aOutlines, STEPS, aErrorText );
  504. if( !success || !aOutlines.OutlineCount() )
  505. {
  506. // Creates a valid polygon outline is not possible.
  507. // So uses the board edge cuts bounding box to create a
  508. // rectangular outline
  509. // When no edge cuts items, build a contour
  510. // from global bounding box
  511. EDA_RECT bbbox = aBoard->GetBoardEdgesBoundingBox();
  512. // If null area, uses the global bounding box.
  513. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  514. bbbox = aBoard->ComputeBoundingBox();
  515. // Ensure non null area. If happen, gives a minimal size.
  516. if( ( bbbox.GetWidth() ) == 0 || ( bbbox.GetHeight() == 0 ) )
  517. bbbox.Inflate( Millimeter2iu( 1.0 ) );
  518. aOutlines.RemoveAllContours();
  519. aOutlines.NewOutline();
  520. wxPoint corner;
  521. aOutlines.Append( bbbox.GetOrigin() );
  522. corner.x = bbbox.GetOrigin().x;
  523. corner.y = bbbox.GetEnd().y;
  524. aOutlines.Append( corner );
  525. aOutlines.Append( bbbox.GetEnd() );
  526. corner.x = bbbox.GetEnd().x;
  527. corner.y = bbbox.GetOrigin().y;
  528. aOutlines.Append( corner );
  529. }
  530. return success;
  531. }