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.

2081 lines
58 KiB

4 years ago
4 years ago
3 years ago
4 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
  6. * Copyright (C) 2011 Wayne Stambaugh <stambaughw@gmail.com>
  7. * Copyright (C) 2023 CERN
  8. * Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
  9. *
  10. * This program is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU General Public License
  12. * as published by the Free Software Foundation; either version 2
  13. * of the License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License
  21. * along with this program; if not, you may find one here:
  22. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  23. * or you may search the http://www.gnu.org website for the version 2 license,
  24. * or you may write to the Free Software Foundation, Inc.,
  25. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  26. */
  27. #include <bezier_curves.h>
  28. #include <convert_basic_shapes_to_polygon.h>
  29. #include <eda_draw_frame.h>
  30. #include <geometry/shape_simple.h>
  31. #include <geometry/shape_segment.h>
  32. #include <geometry/shape_circle.h>
  33. #include <macros.h>
  34. #include <math/util.h> // for KiROUND
  35. #include <eda_shape.h>
  36. #include <plotters/plotter.h>
  37. EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
  38. m_endsSwapped( false ),
  39. m_shape( aType ),
  40. m_stroke( aLineWidth, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ),
  41. m_fill( aFill ),
  42. m_fillColor( COLOR4D::UNSPECIFIED ),
  43. m_rectangleHeight( 0 ),
  44. m_rectangleWidth( 0 ),
  45. m_segmentLength( 0 ),
  46. m_editState( 0 ),
  47. m_proxyItem( false )
  48. {
  49. }
  50. EDA_SHAPE::~EDA_SHAPE()
  51. {
  52. }
  53. wxString EDA_SHAPE::ShowShape() const
  54. {
  55. if( IsProxyItem() )
  56. {
  57. switch( m_shape )
  58. {
  59. case SHAPE_T::SEGMENT: return _( "Thermal Spoke" );
  60. case SHAPE_T::RECTANGLE: return _( "Number Box" );
  61. default: return wxT( "??" );
  62. }
  63. }
  64. else
  65. {
  66. switch( m_shape )
  67. {
  68. case SHAPE_T::SEGMENT: return _( "Line" );
  69. case SHAPE_T::RECTANGLE: return _( "Rect" );
  70. case SHAPE_T::ARC: return _( "Arc" );
  71. case SHAPE_T::CIRCLE: return _( "Circle" );
  72. case SHAPE_T::BEZIER: return _( "Bezier Curve" );
  73. case SHAPE_T::POLY: return _( "Polygon" );
  74. default: return wxT( "??" );
  75. }
  76. }
  77. }
  78. wxString EDA_SHAPE::SHAPE_T_asString() const
  79. {
  80. switch( m_shape )
  81. {
  82. case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
  83. case SHAPE_T::RECTANGLE: return wxS( "S_RECT" );
  84. case SHAPE_T::ARC: return wxS( "S_ARC" );
  85. case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
  86. case SHAPE_T::POLY: return wxS( "S_POLYGON" );
  87. case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
  88. }
  89. return wxEmptyString; // Just to quiet GCC.
  90. }
  91. void EDA_SHAPE::setPosition( const VECTOR2I& aPos )
  92. {
  93. move( aPos - getPosition() );
  94. }
  95. VECTOR2I EDA_SHAPE::getPosition() const
  96. {
  97. if( m_shape == SHAPE_T::ARC )
  98. return getCenter();
  99. else if( m_shape == SHAPE_T::POLY )
  100. return m_poly.CVertex( 0 );
  101. else
  102. return m_start;
  103. }
  104. double EDA_SHAPE::GetLength() const
  105. {
  106. double length = 0.0;
  107. switch( m_shape )
  108. {
  109. case SHAPE_T::BEZIER:
  110. for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
  111. length += GetLineLength( m_bezierPoints[ ii - 1], m_bezierPoints[ii] );
  112. return length;
  113. case SHAPE_T::SEGMENT:
  114. return GetLineLength( GetStart(), GetEnd() );
  115. case SHAPE_T::POLY:
  116. for( int ii = 0; ii < m_poly.COutline( 0 ).SegmentCount(); ii++ )
  117. length += m_poly.COutline( 0 ).CSegment( ii ).Length();
  118. return length;
  119. case SHAPE_T::ARC:
  120. return GetRadius() * GetArcAngle().AsRadians();
  121. default:
  122. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  123. return 0.0;
  124. }
  125. }
  126. int EDA_SHAPE::GetRectangleHeight() const
  127. {
  128. switch( m_shape )
  129. {
  130. case SHAPE_T::RECTANGLE: return GetEndY() - GetStartY();
  131. default:
  132. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  133. return 0;
  134. }
  135. }
  136. int EDA_SHAPE::GetRectangleWidth() const
  137. {
  138. switch( m_shape )
  139. {
  140. case SHAPE_T::RECTANGLE: return GetEndX() - GetStartX();
  141. default:
  142. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  143. return 0;
  144. }
  145. }
  146. void EDA_SHAPE::SetLength( const double& aLength )
  147. {
  148. switch( m_shape )
  149. {
  150. case SHAPE_T::SEGMENT: m_segmentLength = aLength; break;
  151. default:
  152. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  153. }
  154. }
  155. void EDA_SHAPE::SetRectangle( const long long int& aHeight, const long long int& aWidth )
  156. {
  157. switch ( m_shape )
  158. {
  159. case SHAPE_T::RECTANGLE:
  160. m_rectangleHeight = aHeight;
  161. m_rectangleWidth = aWidth;
  162. break;
  163. default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  164. }
  165. }
  166. void EDA_SHAPE::SetSegmentAngle( const EDA_ANGLE& aAngle )
  167. {
  168. switch( m_shape )
  169. {
  170. case SHAPE_T::SEGMENT: m_segmentAngle = aAngle; break;
  171. default: UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  172. }
  173. }
  174. bool EDA_SHAPE::IsClosed() const
  175. {
  176. switch( m_shape )
  177. {
  178. case SHAPE_T::CIRCLE:
  179. case SHAPE_T::RECTANGLE:
  180. return true;
  181. case SHAPE_T::ARC:
  182. case SHAPE_T::SEGMENT:
  183. return false;
  184. case SHAPE_T::POLY:
  185. if( m_poly.IsEmpty() )
  186. return false;
  187. else
  188. return m_poly.Outline( 0 ).IsClosed();
  189. case SHAPE_T::BEZIER:
  190. if( m_bezierPoints.size() < 3 )
  191. return false;
  192. else
  193. return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
  194. default:
  195. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  196. return false;
  197. }
  198. }
  199. void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
  200. {
  201. switch ( m_shape )
  202. {
  203. case SHAPE_T::ARC:
  204. case SHAPE_T::SEGMENT:
  205. case SHAPE_T::RECTANGLE:
  206. case SHAPE_T::CIRCLE:
  207. m_start += aMoveVector;
  208. m_end += aMoveVector;
  209. m_arcCenter += aMoveVector;
  210. break;
  211. case SHAPE_T::POLY:
  212. m_poly.Move( aMoveVector );
  213. break;
  214. case SHAPE_T::BEZIER:
  215. m_start += aMoveVector;
  216. m_end += aMoveVector;
  217. m_bezierC1 += aMoveVector;
  218. m_bezierC2 += aMoveVector;
  219. for( VECTOR2I& pt : m_bezierPoints )
  220. pt += aMoveVector;
  221. break;
  222. default:
  223. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  224. break;
  225. }
  226. }
  227. void EDA_SHAPE::scale( double aScale )
  228. {
  229. auto scalePt = [&]( VECTOR2I& pt )
  230. {
  231. pt.x = KiROUND( pt.x * aScale );
  232. pt.y = KiROUND( pt.y * aScale );
  233. };
  234. switch( m_shape )
  235. {
  236. case SHAPE_T::ARC:
  237. case SHAPE_T::SEGMENT:
  238. case SHAPE_T::RECTANGLE:
  239. scalePt( m_start );
  240. scalePt( m_end );
  241. scalePt( m_arcCenter );
  242. break;
  243. case SHAPE_T::CIRCLE: // ring or circle
  244. scalePt( m_start );
  245. m_end.x = m_start.x + KiROUND( GetRadius() * aScale );
  246. m_end.y = m_start.y;
  247. break;
  248. case SHAPE_T::POLY: // polygon
  249. {
  250. std::vector<VECTOR2I> pts;
  251. for( int ii = 0; ii < m_poly.OutlineCount(); ++ ii )
  252. {
  253. for( const VECTOR2I& pt : m_poly.Outline( ii ).CPoints() )
  254. {
  255. pts.emplace_back( pt );
  256. scalePt( pts.back() );
  257. }
  258. }
  259. SetPolyPoints( pts );
  260. }
  261. break;
  262. case SHAPE_T::BEZIER:
  263. scalePt( m_start );
  264. scalePt( m_end );
  265. scalePt( m_bezierC1 );
  266. scalePt( m_bezierC2 );
  267. break;
  268. default:
  269. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  270. break;
  271. }
  272. }
  273. void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  274. {
  275. switch( m_shape )
  276. {
  277. case SHAPE_T::SEGMENT:
  278. case SHAPE_T::CIRCLE:
  279. RotatePoint( m_start, aRotCentre, aAngle );
  280. RotatePoint( m_end, aRotCentre, aAngle );
  281. break;
  282. case SHAPE_T::ARC:
  283. RotatePoint( m_start, aRotCentre, aAngle );
  284. RotatePoint( m_end, aRotCentre, aAngle );
  285. RotatePoint( m_arcCenter, aRotCentre, aAngle );
  286. break;
  287. case SHAPE_T::RECTANGLE:
  288. if( aAngle.IsCardinal() )
  289. {
  290. RotatePoint( m_start, aRotCentre, aAngle );
  291. RotatePoint( m_end, aRotCentre, aAngle );
  292. break;
  293. }
  294. // Convert non-cardinally-rotated rect to a diamond
  295. m_shape = SHAPE_T::POLY;
  296. m_poly.RemoveAllContours();
  297. m_poly.NewOutline();
  298. m_poly.Append( m_start );
  299. m_poly.Append( m_end.x, m_start.y );
  300. m_poly.Append( m_end );
  301. m_poly.Append( m_start.x, m_end.y );
  302. KI_FALLTHROUGH;
  303. case SHAPE_T::POLY:
  304. m_poly.Rotate( aAngle, aRotCentre );
  305. break;
  306. case SHAPE_T::BEZIER:
  307. RotatePoint( m_start, aRotCentre, aAngle );
  308. RotatePoint( m_end, aRotCentre, aAngle );
  309. RotatePoint( m_bezierC1, aRotCentre, aAngle );
  310. RotatePoint( m_bezierC2, aRotCentre, aAngle );
  311. for( VECTOR2I& pt : m_bezierPoints )
  312. RotatePoint( pt, aRotCentre, aAngle);
  313. break;
  314. default:
  315. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  316. break;
  317. }
  318. }
  319. void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
  320. {
  321. switch ( m_shape )
  322. {
  323. case SHAPE_T::SEGMENT:
  324. case SHAPE_T::RECTANGLE:
  325. if( aFlipLeftRight )
  326. {
  327. m_start.x = aCentre.x - ( m_start.x - aCentre.x );
  328. m_end.x = aCentre.x - ( m_end.x - aCentre.x );
  329. }
  330. else
  331. {
  332. m_start.y = aCentre.y - ( m_start.y - aCentre.y );
  333. m_end.y = aCentre.y - ( m_end.y - aCentre.y );
  334. }
  335. std::swap( m_start, m_end );
  336. break;
  337. case SHAPE_T::CIRCLE:
  338. if( aFlipLeftRight )
  339. {
  340. m_start.x = aCentre.x - ( m_start.x - aCentre.x );
  341. m_end.x = aCentre.x - ( m_end.x - aCentre.x );
  342. }
  343. else
  344. {
  345. m_start.y = aCentre.y - ( m_start.y - aCentre.y );
  346. m_end.y = aCentre.y - ( m_end.y - aCentre.y );
  347. }
  348. break;
  349. case SHAPE_T::ARC:
  350. if( aFlipLeftRight )
  351. {
  352. m_start.x = aCentre.x - ( m_start.x - aCentre.x );
  353. m_end.x = aCentre.x - ( m_end.x - aCentre.x );
  354. m_arcCenter.x = aCentre.x - ( m_arcCenter.x - aCentre.x );
  355. }
  356. else
  357. {
  358. m_start.y = aCentre.y - ( m_start.y - aCentre.y );
  359. m_end.y = aCentre.y - ( m_end.y - aCentre.y );
  360. m_arcCenter.y = aCentre.y - ( m_arcCenter.y - aCentre.y );
  361. }
  362. std::swap( m_start, m_end );
  363. break;
  364. case SHAPE_T::POLY:
  365. m_poly.Mirror( aFlipLeftRight, !aFlipLeftRight, aCentre );
  366. break;
  367. case SHAPE_T::BEZIER:
  368. if( aFlipLeftRight )
  369. {
  370. m_start.x = aCentre.x - ( m_start.x - aCentre.x );
  371. m_end.x = aCentre.x - ( m_end.x - aCentre.x );
  372. m_bezierC1.x = aCentre.x - ( m_bezierC1.x - aCentre.x );
  373. m_bezierC2.x = aCentre.x - ( m_bezierC2.x - aCentre.x );
  374. }
  375. else
  376. {
  377. m_start.y = aCentre.y - ( m_start.y - aCentre.y );
  378. m_end.y = aCentre.y - ( m_end.y - aCentre.y );
  379. m_bezierC1.y = aCentre.y - ( m_bezierC1.y - aCentre.y );
  380. m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
  381. }
  382. // Rebuild the poly points shape
  383. {
  384. std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
  385. BEZIER_POLY converter( ctrlPoints );
  386. converter.GetPoly( m_bezierPoints, m_stroke.GetWidth() );
  387. }
  388. break;
  389. default:
  390. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  391. break;
  392. }
  393. }
  394. void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen )
  395. {
  396. // Has meaning only for SHAPE_T::BEZIER
  397. if( m_shape != SHAPE_T::BEZIER )
  398. {
  399. m_bezierPoints.clear();
  400. return;
  401. }
  402. // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
  403. m_bezierPoints = buildBezierToSegmentsPointsList( aMinSegLen );
  404. // Ensure last point respects aMinSegLen parameter
  405. if( m_bezierPoints.size() > 2 )
  406. {
  407. int idx = m_bezierPoints.size() - 1;
  408. if( VECTOR2I( m_bezierPoints[idx] - m_bezierPoints[idx] - 1 ).EuclideanNorm() < aMinSegLen )
  409. {
  410. m_bezierPoints[idx - 1] = m_bezierPoints[idx];
  411. m_bezierPoints.pop_back();
  412. }
  413. }
  414. }
  415. const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen ) const
  416. {
  417. std::vector<VECTOR2I> bezierPoints;
  418. // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
  419. std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
  420. BEZIER_POLY converter( ctrlPoints );
  421. converter.GetPoly( bezierPoints, aMinSegLen );
  422. return bezierPoints;
  423. }
  424. VECTOR2I EDA_SHAPE::getCenter() const
  425. {
  426. switch( m_shape )
  427. {
  428. case SHAPE_T::ARC:
  429. return m_arcCenter;
  430. case SHAPE_T::CIRCLE:
  431. return m_start;
  432. case SHAPE_T::SEGMENT:
  433. // Midpoint of the line
  434. return ( m_start + m_end ) / 2;
  435. case SHAPE_T::POLY:
  436. case SHAPE_T::RECTANGLE:
  437. case SHAPE_T::BEZIER:
  438. return getBoundingBox().Centre();
  439. default:
  440. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  441. return VECTOR2I();
  442. }
  443. }
  444. void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
  445. {
  446. switch( m_shape )
  447. {
  448. case SHAPE_T::ARC:
  449. m_arcCenter = aCenter;
  450. break;
  451. case SHAPE_T::CIRCLE:
  452. m_start = aCenter;
  453. break;
  454. default:
  455. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  456. }
  457. }
  458. VECTOR2I EDA_SHAPE::GetArcMid() const
  459. {
  460. // If none of the input data have changed since we loaded the arc,
  461. // keep the original mid point data to minimize churn
  462. if( m_arcMidData.start == m_start && m_arcMidData.end == m_end
  463. && m_arcMidData.center == m_arcCenter )
  464. return m_arcMidData.mid;
  465. VECTOR2I mid = m_start;
  466. RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
  467. return mid;
  468. }
  469. void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
  470. {
  471. VECTOR2D startRadial( GetStart() - getCenter() );
  472. VECTOR2D endRadial( GetEnd() - getCenter() );
  473. aStartAngle = EDA_ANGLE( startRadial );
  474. aEndAngle = EDA_ANGLE( endRadial );
  475. if( aEndAngle == aStartAngle )
  476. aEndAngle = aStartAngle + ANGLE_360; // ring, not null
  477. while( aEndAngle < aStartAngle )
  478. aEndAngle += ANGLE_360;
  479. }
  480. int EDA_SHAPE::GetRadius() const
  481. {
  482. double radius = 0.0;
  483. switch( m_shape )
  484. {
  485. case SHAPE_T::ARC:
  486. radius = GetLineLength( m_arcCenter, m_start );
  487. break;
  488. case SHAPE_T::CIRCLE:
  489. radius = GetLineLength( m_start, m_end );
  490. break;
  491. default:
  492. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  493. }
  494. // don't allow degenerate circles/arcs
  495. return std::max( 1, KiROUND( radius ) );
  496. }
  497. void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd, const VECTOR2I& aCenter )
  498. {
  499. m_arcMidData.start = aStart;
  500. m_arcMidData.end = aEnd;
  501. m_arcMidData.center = aCenter;
  502. m_arcMidData.mid = aMid;
  503. }
  504. void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
  505. {
  506. m_arcMidData = {};
  507. m_start = aStart;
  508. m_end = aEnd;
  509. m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
  510. VECTOR2I new_mid = GetArcMid();
  511. m_endsSwapped = false;
  512. // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
  513. // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
  514. SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
  515. /*
  516. * If the input winding doesn't match our internal winding, the calculated midpoint will end
  517. * up on the other side of the arc. In this case, we need to flip the start/end points and
  518. * flag this change for the system.
  519. */
  520. VECTOR2D dist( new_mid - aMid );
  521. VECTOR2D dist2( new_mid - m_arcCenter );
  522. if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
  523. {
  524. std::swap( m_start, m_end );
  525. m_endsSwapped = true;
  526. }
  527. }
  528. EDA_ANGLE EDA_SHAPE::GetSegmentAngle() const
  529. {
  530. EDA_ANGLE angle( atan2( static_cast<double>( GetStart().y - GetEnd().y ),
  531. static_cast<double>( GetEnd().x - GetStart().x ) ), RADIANS_T );
  532. return angle;
  533. }
  534. EDA_ANGLE EDA_SHAPE::GetArcAngle() const
  535. {
  536. EDA_ANGLE startAngle;
  537. EDA_ANGLE endAngle;
  538. CalcArcAngles( startAngle, endAngle );
  539. return endAngle - startAngle;
  540. }
  541. bool EDA_SHAPE::IsClockwiseArc() const
  542. {
  543. if( m_shape == SHAPE_T::ARC )
  544. {
  545. VECTOR2D mid = GetArcMid();
  546. double orient = ( mid.x - m_start.x ) * ( m_end.y - m_start.y )
  547. - ( mid.y - m_start.y ) * ( m_end.x - m_start.x );
  548. return orient < 0;
  549. }
  550. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  551. return false;
  552. }
  553. void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
  554. {
  555. EDA_ANGLE angle( aAngle );
  556. m_end = m_start;
  557. RotatePoint( m_end, m_arcCenter, -angle.Normalize720() );
  558. if( aCheckNegativeAngle && aAngle < ANGLE_0 )
  559. {
  560. std::swap( m_start, m_end );
  561. m_endsSwapped = true;
  562. }
  563. }
  564. wxString EDA_SHAPE::GetFriendlyName() const
  565. {
  566. if( IsProxyItem() )
  567. {
  568. switch( m_shape )
  569. {
  570. case SHAPE_T::RECTANGLE: return _( "Pad Number Box" );
  571. case SHAPE_T::SEGMENT: return _( "Thermal Spoke Template" );
  572. default: return _( "Unrecognized" );
  573. }
  574. }
  575. else
  576. {
  577. switch( m_shape )
  578. {
  579. case SHAPE_T::CIRCLE: return _( "Circle" );
  580. case SHAPE_T::ARC: return _( "Arc" );
  581. case SHAPE_T::BEZIER: return _( "Curve" );
  582. case SHAPE_T::POLY: return _( "Polygon" );
  583. case SHAPE_T::RECTANGLE: return _( "Rectangle" );
  584. case SHAPE_T::SEGMENT: return _( "Segment" );
  585. default: return _( "Unrecognized" );
  586. }
  587. }
  588. }
  589. void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  590. {
  591. wxString msg;
  592. wxString shape = _( "Shape" );
  593. aList.emplace_back( shape, GetFriendlyName() );
  594. switch( m_shape )
  595. {
  596. case SHAPE_T::CIRCLE:
  597. aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
  598. break;
  599. case SHAPE_T::ARC:
  600. msg = EDA_UNIT_UTILS::UI::MessageTextFromValue( GetArcAngle() );
  601. aList.emplace_back( _( "Angle" ), msg );
  602. aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
  603. break;
  604. case SHAPE_T::BEZIER:
  605. aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
  606. break;
  607. case SHAPE_T::POLY:
  608. msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
  609. aList.emplace_back( _( "Points" ), msg );
  610. break;
  611. case SHAPE_T::RECTANGLE:
  612. aList.emplace_back( _( "Width" ),
  613. aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
  614. aList.emplace_back( _( "Height" ),
  615. aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
  616. break;
  617. case SHAPE_T::SEGMENT:
  618. {
  619. aList.emplace_back( _( "Length" ),
  620. aFrame->MessageTextFromValue( GetLineLength( GetStart(), GetEnd() ) ));
  621. // angle counter-clockwise from 3'o-clock
  622. EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ),
  623. (double)( GetEnd().x - GetStart().x ) ), RADIANS_T );
  624. aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
  625. break;
  626. }
  627. default:
  628. break;
  629. }
  630. m_stroke.GetMsgPanelInfo( aFrame, aList );
  631. }
  632. const BOX2I EDA_SHAPE::getBoundingBox() const
  633. {
  634. BOX2I bbox;
  635. switch( m_shape )
  636. {
  637. case SHAPE_T::RECTANGLE:
  638. for( VECTOR2I& pt : GetRectCorners() )
  639. bbox.Merge( pt );
  640. break;
  641. case SHAPE_T::SEGMENT:
  642. bbox.SetOrigin( GetStart() );
  643. bbox.SetEnd( GetEnd() );
  644. break;
  645. case SHAPE_T::CIRCLE:
  646. bbox.SetOrigin( GetStart() );
  647. bbox.Inflate( GetRadius() );
  648. break;
  649. case SHAPE_T::ARC:
  650. computeArcBBox( bbox );
  651. break;
  652. case SHAPE_T::POLY:
  653. if( m_poly.IsEmpty() )
  654. break;
  655. for( auto iter = m_poly.CIterate(); iter; iter++ )
  656. bbox.Merge( *iter );
  657. break;
  658. case SHAPE_T::BEZIER:
  659. bbox.SetOrigin( GetStart() );
  660. bbox.Merge( GetBezierC1() );
  661. bbox.Merge( GetBezierC2() );
  662. bbox.Merge( GetEnd() );
  663. break;
  664. default:
  665. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  666. break;
  667. }
  668. bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
  669. bbox.Normalize();
  670. return bbox;
  671. }
  672. bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  673. {
  674. int maxdist = aAccuracy;
  675. if( GetWidth() > 0 )
  676. maxdist += GetWidth() / 2;
  677. switch( m_shape )
  678. {
  679. case SHAPE_T::CIRCLE:
  680. {
  681. int radius = GetRadius();
  682. VECTOR2I::extended_type dist = KiROUND<double, VECTOR2I::extended_type>(
  683. EuclideanNorm( aPosition - getCenter() ) );
  684. if( IsFilled() )
  685. return dist <= radius + maxdist; // Filled circle hit-test
  686. else
  687. return abs( radius - dist ) <= maxdist; // Ring hit-test
  688. }
  689. case SHAPE_T::ARC:
  690. {
  691. if( EuclideanNorm( aPosition - m_start ) <= maxdist )
  692. return true;
  693. if( EuclideanNorm( aPosition - m_end ) <= maxdist )
  694. return true;
  695. VECTOR2I relPos = aPosition - getCenter();
  696. int radius = GetRadius();
  697. VECTOR2I::extended_type dist =
  698. KiROUND<double, VECTOR2I::extended_type>( EuclideanNorm( relPos ) );
  699. if( IsFilled() )
  700. {
  701. // Check distance from arc center
  702. if( dist > radius + maxdist )
  703. return false;
  704. }
  705. else
  706. {
  707. // Check distance from arc circumference
  708. if( abs( radius - dist ) > maxdist )
  709. return false;
  710. }
  711. // Finally, check to see if it's within arc's swept angle.
  712. EDA_ANGLE startAngle;
  713. EDA_ANGLE endAngle;
  714. CalcArcAngles( startAngle, endAngle );
  715. EDA_ANGLE relPosAngle( relPos );
  716. startAngle.Normalize();
  717. endAngle.Normalize();
  718. relPosAngle.Normalize();
  719. if( endAngle > startAngle )
  720. return relPosAngle >= startAngle && relPosAngle <= endAngle;
  721. else
  722. return relPosAngle >= startAngle || relPosAngle <= endAngle;
  723. }
  724. case SHAPE_T::BEZIER:
  725. const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( GetWidth() );
  726. for( unsigned int i= 1; i < m_bezierPoints.size(); i++)
  727. {
  728. if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) )
  729. return true;
  730. }
  731. return false;
  732. case SHAPE_T::SEGMENT:
  733. return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
  734. case SHAPE_T::RECTANGLE:
  735. if( IsProxyItem() || IsFilled() ) // Filled rect hit-test
  736. {
  737. SHAPE_POLY_SET poly;
  738. poly.NewOutline();
  739. for( const VECTOR2I& pt : GetRectCorners() )
  740. poly.Append( pt );
  741. return poly.Collide( aPosition, maxdist );
  742. }
  743. else // Open rect hit-test
  744. {
  745. std::vector<VECTOR2I> pts = GetRectCorners();
  746. return TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
  747. || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
  748. || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
  749. || TestSegmentHit( aPosition, pts[3], pts[0], maxdist );
  750. }
  751. case SHAPE_T::POLY:
  752. if( IsFilled() )
  753. {
  754. if( !m_poly.COutline( 0 ).IsClosed() )
  755. {
  756. SHAPE_POLY_SET copy( m_poly );
  757. copy.Outline( 0 ).Append( copy.Outline( 0 ).CPoint( 0 ) );
  758. return copy.Collide( aPosition, maxdist );
  759. }
  760. else
  761. {
  762. return m_poly.Collide( aPosition, maxdist );
  763. }
  764. }
  765. else
  766. {
  767. return m_poly.CollideEdge( aPosition, nullptr, maxdist );
  768. }
  769. default:
  770. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  771. return false;
  772. }
  773. }
  774. bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  775. {
  776. BOX2I arect = aRect;
  777. arect.Normalize();
  778. arect.Inflate( aAccuracy );
  779. BOX2I bbox = getBoundingBox();
  780. switch( m_shape )
  781. {
  782. case SHAPE_T::CIRCLE:
  783. // Test if area intersects or contains the circle:
  784. if( aContained )
  785. {
  786. return arect.Contains( bbox );
  787. }
  788. else
  789. {
  790. // If the rectangle does not intersect the bounding box, this is a much quicker test
  791. if( !arect.Intersects( bbox ) )
  792. return false;
  793. else
  794. return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
  795. }
  796. case SHAPE_T::ARC:
  797. // Test for full containment of this arc in the rect
  798. if( aContained )
  799. {
  800. return arect.Contains( bbox );
  801. }
  802. // Test if the rect crosses the arc
  803. else
  804. {
  805. if( !arect.Intersects( bbox ) )
  806. return false;
  807. if( IsFilled() )
  808. {
  809. return ( arect.Intersects( getCenter(), GetStart() )
  810. || arect.Intersects( getCenter(), GetEnd() )
  811. || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
  812. }
  813. else
  814. {
  815. return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
  816. }
  817. }
  818. case SHAPE_T::RECTANGLE:
  819. if( aContained )
  820. {
  821. return arect.Contains( bbox );
  822. }
  823. else
  824. {
  825. std::vector<VECTOR2I> pts = GetRectCorners();
  826. // Account for the width of the lines
  827. arect.Inflate( GetWidth() / 2 );
  828. return ( arect.Intersects( pts[0], pts[1] )
  829. || arect.Intersects( pts[1], pts[2] )
  830. || arect.Intersects( pts[2], pts[3] )
  831. || arect.Intersects( pts[3], pts[0] ) );
  832. }
  833. case SHAPE_T::SEGMENT:
  834. if( aContained )
  835. {
  836. return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
  837. }
  838. else
  839. {
  840. // Account for the width of the line
  841. arect.Inflate( GetWidth() / 2 );
  842. return arect.Intersects( GetStart(), GetEnd() );
  843. }
  844. case SHAPE_T::POLY:
  845. if( aContained )
  846. {
  847. return arect.Contains( bbox );
  848. }
  849. else
  850. {
  851. // Fast test: if aRect is outside the polygon bounding box,
  852. // rectangles cannot intersect
  853. if( !arect.Intersects( bbox ) )
  854. return false;
  855. // Account for the width of the line
  856. arect.Inflate( GetWidth() / 2 );
  857. for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
  858. {
  859. const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
  860. int count = poly.GetPointCount();
  861. for( int jj = 0; jj < count; jj++ )
  862. {
  863. VECTOR2I vertex = poly.GetPoint( jj );
  864. // Test if the point is within aRect
  865. if( arect.Contains( vertex ) )
  866. return true;
  867. if( jj + 1 < count )
  868. {
  869. VECTOR2I vertexNext = poly.GetPoint( jj + 1 );
  870. // Test if this edge intersects aRect
  871. if( arect.Intersects( vertex, vertexNext ) )
  872. return true;
  873. }
  874. else if( poly.IsClosed() )
  875. {
  876. VECTOR2I vertexNext = poly.GetPoint( 0 );
  877. // Test if this edge intersects aRect
  878. if( arect.Intersects( vertex, vertexNext ) )
  879. return true;
  880. }
  881. }
  882. }
  883. return false;
  884. }
  885. case SHAPE_T::BEZIER:
  886. if( aContained )
  887. {
  888. return arect.Contains( bbox );
  889. }
  890. else
  891. {
  892. // Fast test: if aRect is outside the polygon bounding box,
  893. // rectangles cannot intersect
  894. if( !arect.Intersects( bbox ) )
  895. return false;
  896. // Account for the width of the line
  897. arect.Inflate( GetWidth() / 2 );
  898. unsigned count = m_bezierPoints.size();
  899. for( unsigned ii = 1; ii < count; ii++ )
  900. {
  901. VECTOR2I vertex = m_bezierPoints[ii - 1];
  902. VECTOR2I vertexNext = m_bezierPoints[ii];
  903. // Test if the point is within aRect
  904. if( arect.Contains( vertex ) )
  905. return true;
  906. // Test if this edge intersects aRect
  907. if( arect.Intersects( vertex, vertexNext ) )
  908. return true;
  909. }
  910. return false;
  911. }
  912. default:
  913. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  914. return false;
  915. }
  916. }
  917. std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
  918. {
  919. std::vector<VECTOR2I> pts;
  920. VECTOR2I topLeft = GetStart();
  921. VECTOR2I botRight = GetEnd();
  922. pts.emplace_back( topLeft );
  923. pts.emplace_back( botRight.x, topLeft.y );
  924. pts.emplace_back( botRight );
  925. pts.emplace_back( topLeft.x, botRight.y );
  926. return pts;
  927. }
  928. void EDA_SHAPE::computeArcBBox( BOX2I& aBBox ) const
  929. {
  930. // Start, end, and each inflection point the arc crosses will enclose the entire arc.
  931. // Only include the center when filled; it's not necessarily inside the BB of an unfilled
  932. // arc with a small included angle.
  933. aBBox.SetOrigin( m_start );
  934. aBBox.Merge( m_end );
  935. if( IsFilled() )
  936. aBBox.Merge( m_arcCenter );
  937. int radius = GetRadius();
  938. EDA_ANGLE t1, t2;
  939. CalcArcAngles( t1, t2 );
  940. t1.Normalize();
  941. t2.Normalize();
  942. if( t2 > t1 )
  943. {
  944. if( t1 < ANGLE_0 && t2 > ANGLE_0 )
  945. aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
  946. if( t1 < ANGLE_90 && t2 > ANGLE_90 )
  947. aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
  948. if( t1 < ANGLE_180 && t2 > ANGLE_180 )
  949. aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
  950. if( t1 < ANGLE_270 && t2 > ANGLE_270 )
  951. aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
  952. }
  953. else
  954. {
  955. if( t1 < ANGLE_0 || t2 > ANGLE_0 )
  956. aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
  957. if( t1 < ANGLE_90 || t2 > ANGLE_90 )
  958. aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
  959. if( t1 < ANGLE_180 || t2 > ANGLE_180 )
  960. aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
  961. if( t1 < ANGLE_270 || t2 > ANGLE_270 )
  962. aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
  963. }
  964. }
  965. void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
  966. {
  967. m_poly.RemoveAllContours();
  968. m_poly.NewOutline();
  969. for( const VECTOR2I& p : aPoints )
  970. m_poly.Append( p.x, p.y );
  971. }
  972. std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly ) const
  973. {
  974. std::vector<SHAPE*> effectiveShapes;
  975. int width = GetEffectiveWidth();
  976. switch( m_shape )
  977. {
  978. case SHAPE_T::ARC:
  979. effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
  980. break;
  981. case SHAPE_T::SEGMENT:
  982. effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
  983. break;
  984. case SHAPE_T::RECTANGLE:
  985. {
  986. std::vector<VECTOR2I> pts = GetRectCorners();
  987. if( ( IsFilled() || IsProxyItem() ) && !aEdgeOnly )
  988. effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
  989. if( width > 0 || !IsFilled() || aEdgeOnly )
  990. {
  991. effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
  992. effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
  993. effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
  994. effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
  995. }
  996. }
  997. break;
  998. case SHAPE_T::CIRCLE:
  999. {
  1000. if( IsFilled() && !aEdgeOnly )
  1001. effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
  1002. if( width > 0 || !IsFilled() || aEdgeOnly )
  1003. effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
  1004. break;
  1005. }
  1006. case SHAPE_T::BEZIER:
  1007. {
  1008. std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width );
  1009. VECTOR2I start_pt = bezierPoints[0];
  1010. for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
  1011. {
  1012. VECTOR2I end_pt = bezierPoints[jj];
  1013. effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
  1014. start_pt = end_pt;
  1015. }
  1016. break;
  1017. }
  1018. case SHAPE_T::POLY:
  1019. {
  1020. if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
  1021. break;
  1022. for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
  1023. {
  1024. const SHAPE_LINE_CHAIN& l = GetPolyShape().COutline( ii );
  1025. if( IsFilled() && !aEdgeOnly )
  1026. effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
  1027. if( width > 0 || !IsFilled() || aEdgeOnly )
  1028. {
  1029. int segCount = l.SegmentCount();
  1030. if( aLineChainOnly && l.IsClosed() )
  1031. segCount--; // Treat closed chain as open
  1032. for( int jj = 0; jj < segCount; jj++ )
  1033. effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.CSegment( jj ), width ) );
  1034. }
  1035. }
  1036. }
  1037. break;
  1038. default:
  1039. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  1040. break;
  1041. }
  1042. return effectiveShapes;
  1043. }
  1044. void EDA_SHAPE::DupPolyPointsList( std::vector<VECTOR2I>& aBuffer ) const
  1045. {
  1046. for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
  1047. {
  1048. int pointCount = m_poly.COutline( ii ).PointCount();
  1049. if( pointCount )
  1050. {
  1051. aBuffer.reserve( pointCount );
  1052. for ( auto iter = m_poly.CIterate(); iter; iter++ )
  1053. aBuffer.emplace_back( iter->x, iter->y );
  1054. }
  1055. }
  1056. }
  1057. bool EDA_SHAPE::IsPolyShapeValid() const
  1058. {
  1059. // return true if the polygonal shape is valid (has more than 2 points)
  1060. if( GetPolyShape().OutlineCount() == 0 )
  1061. return false;
  1062. const SHAPE_LINE_CHAIN& outline = static_cast<const SHAPE_POLY_SET&>( GetPolyShape() ).Outline( 0 );
  1063. return outline.PointCount() > 2;
  1064. }
  1065. int EDA_SHAPE::GetPointCount() const
  1066. {
  1067. // return the number of corners of the polygonal shape
  1068. // this shape is expected to be only one polygon without hole
  1069. if( GetPolyShape().OutlineCount() )
  1070. return GetPolyShape().VertexCount( 0 );
  1071. return 0;
  1072. }
  1073. void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
  1074. {
  1075. switch( GetShape() )
  1076. {
  1077. case SHAPE_T::SEGMENT:
  1078. case SHAPE_T::CIRCLE:
  1079. case SHAPE_T::RECTANGLE:
  1080. SetStart( aPosition );
  1081. SetEnd( aPosition );
  1082. break;
  1083. case SHAPE_T::ARC:
  1084. SetArcGeometry( aPosition, aPosition, aPosition );
  1085. m_editState = 1;
  1086. break;
  1087. case SHAPE_T::BEZIER:
  1088. SetStart( aPosition );
  1089. SetEnd( aPosition );
  1090. SetBezierC1( aPosition );
  1091. SetBezierC2( aPosition );
  1092. m_editState = 1;
  1093. RebuildBezierToSegmentsPointsList( GetWidth() );
  1094. break;
  1095. case SHAPE_T::POLY:
  1096. m_poly.NewOutline();
  1097. m_poly.Outline( 0 ).SetClosed( false );
  1098. // Start and end of the first segment (co-located for now)
  1099. m_poly.Outline( 0 ).Append( aPosition );
  1100. m_poly.Outline( 0 ).Append( aPosition, true );
  1101. break;
  1102. default:
  1103. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  1104. }
  1105. }
  1106. bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
  1107. {
  1108. switch( GetShape() )
  1109. {
  1110. case SHAPE_T::ARC:
  1111. case SHAPE_T::SEGMENT:
  1112. case SHAPE_T::CIRCLE:
  1113. case SHAPE_T::RECTANGLE:
  1114. return false;
  1115. case SHAPE_T::BEZIER:
  1116. if( m_editState == 3 )
  1117. return false;
  1118. m_editState++;
  1119. return true;
  1120. case SHAPE_T::POLY:
  1121. {
  1122. SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
  1123. // do not add zero-length segments
  1124. if( poly.CPoint( poly.GetPointCount() - 2 ) != poly.CLastPoint() )
  1125. poly.Append( aPosition, true );
  1126. }
  1127. return true;
  1128. default:
  1129. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  1130. return false;
  1131. }
  1132. }
  1133. void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
  1134. {
  1135. #define sq( x ) pow( x, 2 )
  1136. switch( GetShape() )
  1137. {
  1138. case SHAPE_T::SEGMENT:
  1139. case SHAPE_T::CIRCLE:
  1140. case SHAPE_T::RECTANGLE:
  1141. SetEnd( aPosition );
  1142. break;
  1143. case SHAPE_T::BEZIER:
  1144. {
  1145. switch( m_editState )
  1146. {
  1147. case 0:
  1148. SetStart( aPosition );
  1149. SetEnd( aPosition );
  1150. SetBezierC1( aPosition );
  1151. SetBezierC2( aPosition );
  1152. break;
  1153. case 1:
  1154. SetBezierC2( aPosition );
  1155. SetEnd( aPosition );
  1156. break;
  1157. case 2: SetBezierC1( aPosition ); break;
  1158. case 3: SetBezierC2( aPosition ); break;
  1159. }
  1160. RebuildBezierToSegmentsPointsList( GetWidth() );
  1161. }
  1162. break;
  1163. case SHAPE_T::ARC:
  1164. {
  1165. double radius = GetRadius();
  1166. EDA_ANGLE lastAngle = GetArcAngle();
  1167. // Edit state 0: drawing: place start
  1168. // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
  1169. // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
  1170. // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
  1171. // Edit state 4: point edit: move center
  1172. // Edit state 5: point edit: move arc-mid-point
  1173. switch( m_editState )
  1174. {
  1175. case 0:
  1176. SetArcGeometry( aPosition, aPosition, aPosition );
  1177. return;
  1178. case 1:
  1179. m_end = aPosition;
  1180. radius = sqrt( sq( GetLineLength( m_start, m_end ) ) / 2.0 );
  1181. break;
  1182. case 2:
  1183. case 3:
  1184. {
  1185. VECTOR2I v = m_start - m_end;
  1186. double chordBefore = sq( v.x ) + sq( v.y );
  1187. if( m_editState == 2 )
  1188. m_start = aPosition;
  1189. else
  1190. m_end = aPosition;
  1191. v = m_start - m_end;
  1192. double chordAfter = sq( v.x ) + sq( v.y );
  1193. double ratio = 0.0;
  1194. if( chordBefore > 0 )
  1195. ratio = chordAfter / chordBefore;
  1196. if( ratio != 0 )
  1197. radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
  1198. }
  1199. break;
  1200. case 4:
  1201. {
  1202. double radialA = GetLineLength( m_start, aPosition );
  1203. double radialB = GetLineLength( m_end, aPosition );
  1204. radius = ( radialA + radialB ) / 2.0;
  1205. }
  1206. break;
  1207. case 5:
  1208. SetArcGeometry( GetStart(), aPosition, GetEnd() );
  1209. return;
  1210. }
  1211. // Calculate center based on start, end, and radius
  1212. //
  1213. // Let 'l' be the length of the chord and 'm' the middle point of the chord
  1214. double l = GetLineLength( m_start, m_end );
  1215. VECTOR2D m = ( m_start + m_end ) / 2;
  1216. double sqRadDiff = sq( radius ) - sq( l / 2 );
  1217. // Calculate 'd', the vector from the chord midpoint to the center
  1218. VECTOR2D d;
  1219. if( l > 0 && sqRadDiff >= 0 )
  1220. {
  1221. d.x = sqrt( sqRadDiff ) * ( m_start.y - m_end.y ) / l;
  1222. d.y = sqrt( sqRadDiff ) * ( m_end.x - m_start.x ) / l;
  1223. }
  1224. VECTOR2I c1 = KiROUND( m + d );
  1225. VECTOR2I c2 = KiROUND( m - d );
  1226. // Solution gives us 2 centers; we need to pick one:
  1227. switch( m_editState )
  1228. {
  1229. case 1:
  1230. // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
  1231. // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
  1232. m_arcCenter = c1; // first trial
  1233. if( GetArcAngle() > ANGLE_180 )
  1234. m_arcCenter = c2;
  1235. break;
  1236. case 2:
  1237. case 3:
  1238. // Pick the one of c1, c2 to keep arc on the same side
  1239. m_arcCenter = c1; // first trial
  1240. if( ( lastAngle < ANGLE_180 ) != ( GetArcAngle() < ANGLE_180 ) )
  1241. m_arcCenter = c2;
  1242. break;
  1243. case 4:
  1244. // Pick the one closer to the mouse position
  1245. m_arcCenter = GetLineLength( c1, aPosition ) < GetLineLength( c2, aPosition ) ? c1 : c2;
  1246. break;
  1247. }
  1248. }
  1249. break;
  1250. case SHAPE_T::POLY:
  1251. m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
  1252. break;
  1253. default:
  1254. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  1255. }
  1256. }
  1257. void EDA_SHAPE::endEdit( bool aClosed )
  1258. {
  1259. switch( GetShape() )
  1260. {
  1261. case SHAPE_T::ARC:
  1262. case SHAPE_T::SEGMENT:
  1263. case SHAPE_T::CIRCLE:
  1264. case SHAPE_T::RECTANGLE:
  1265. case SHAPE_T::BEZIER:
  1266. break;
  1267. case SHAPE_T::POLY:
  1268. {
  1269. SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
  1270. // do not include last point twice
  1271. if( poly.GetPointCount() > 2 )
  1272. {
  1273. if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
  1274. {
  1275. poly.SetClosed( aClosed );
  1276. }
  1277. else
  1278. {
  1279. poly.SetClosed( false );
  1280. poly.Remove( poly.GetPointCount() - 1 );
  1281. }
  1282. }
  1283. }
  1284. break;
  1285. default:
  1286. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  1287. }
  1288. }
  1289. void EDA_SHAPE::SwapShape( EDA_SHAPE* aImage )
  1290. {
  1291. EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
  1292. assert( image );
  1293. #define SWAPITEM( x ) std::swap( x, image->x )
  1294. SWAPITEM( m_stroke );
  1295. SWAPITEM( m_start );
  1296. SWAPITEM( m_end );
  1297. SWAPITEM( m_arcCenter );
  1298. SWAPITEM( m_shape );
  1299. SWAPITEM( m_bezierC1 );
  1300. SWAPITEM( m_bezierC2 );
  1301. SWAPITEM( m_bezierPoints );
  1302. SWAPITEM( m_poly );
  1303. SWAPITEM( m_fill );
  1304. SWAPITEM( m_fillColor );
  1305. SWAPITEM( m_editState );
  1306. SWAPITEM( m_endsSwapped );
  1307. #undef SWAPITEM
  1308. }
  1309. int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
  1310. {
  1311. #define EPSILON 2 // Should be enough for rounding errors on calculated items
  1312. #define TEST( a, b ) { if( a != b ) return a - b; }
  1313. #define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
  1314. #define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
  1315. TEST_PT( m_start, aOther->m_start );
  1316. TEST_PT( m_end, aOther->m_end );
  1317. TEST( (int) m_shape, (int) aOther->m_shape );
  1318. if( m_shape == SHAPE_T::ARC )
  1319. {
  1320. TEST_PT( m_arcCenter, aOther->m_arcCenter );
  1321. }
  1322. else if( m_shape == SHAPE_T::BEZIER )
  1323. {
  1324. TEST_PT( m_bezierC1, aOther->m_bezierC1 );
  1325. TEST_PT( m_bezierC2, aOther->m_bezierC2 );
  1326. }
  1327. else if( m_shape == SHAPE_T::POLY )
  1328. {
  1329. TEST( m_poly.TotalVertices(), aOther->m_poly.TotalVertices() );
  1330. }
  1331. for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
  1332. TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
  1333. for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
  1334. TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
  1335. TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
  1336. TEST( (int) m_stroke.GetLineStyle(), (int) aOther->m_stroke.GetLineStyle() );
  1337. TEST( (int) m_fill, (int) aOther->m_fill );
  1338. return 0;
  1339. }
  1340. void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
  1341. ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const
  1342. {
  1343. int width = ignoreLineWidth ? 0 : GetWidth();
  1344. width += 2 * aClearance;
  1345. switch( m_shape )
  1346. {
  1347. case SHAPE_T::CIRCLE:
  1348. {
  1349. int r = GetRadius();
  1350. if( IsFilled() )
  1351. TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
  1352. else
  1353. TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
  1354. break;
  1355. }
  1356. case SHAPE_T::RECTANGLE:
  1357. {
  1358. std::vector<VECTOR2I> pts = GetRectCorners();
  1359. if( IsFilled() || IsProxyItem() )
  1360. {
  1361. aBuffer.NewOutline();
  1362. for( const VECTOR2I& pt : pts )
  1363. aBuffer.Append( pt );
  1364. }
  1365. if( width > 0 || !IsFilled() )
  1366. {
  1367. // Add in segments
  1368. TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
  1369. TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
  1370. TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
  1371. TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
  1372. }
  1373. break;
  1374. }
  1375. case SHAPE_T::ARC:
  1376. TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
  1377. break;
  1378. case SHAPE_T::SEGMENT:
  1379. TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
  1380. break;
  1381. case SHAPE_T::POLY:
  1382. {
  1383. if( !IsPolyShapeValid() )
  1384. break;
  1385. if( IsFilled() )
  1386. {
  1387. for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
  1388. {
  1389. const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
  1390. SHAPE_POLY_SET tmp;
  1391. tmp.NewOutline();
  1392. for( int jj = 0; jj < (int) poly.GetPointCount(); ++jj )
  1393. tmp.Append( poly.GetPoint( jj ) );
  1394. if( width > 0 )
  1395. {
  1396. int inflate = width / 2;
  1397. if( aErrorLoc == ERROR_OUTSIDE )
  1398. inflate += aError;
  1399. tmp.Inflate( inflate, CORNER_STRATEGY::ROUND_ALL_CORNERS, aError );
  1400. }
  1401. aBuffer.Append( tmp );
  1402. }
  1403. }
  1404. else
  1405. {
  1406. for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
  1407. {
  1408. const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
  1409. for( int jj = 0; jj < (int) poly.SegmentCount(); ++jj )
  1410. {
  1411. const SEG& seg = poly.GetSegment( jj );
  1412. TransformOvalToPolygon( aBuffer, seg.A, seg.B, width, aError, aErrorLoc );
  1413. }
  1414. }
  1415. }
  1416. break;
  1417. }
  1418. case SHAPE_T::BEZIER:
  1419. {
  1420. std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
  1421. BEZIER_POLY converter( ctrlPts );
  1422. std::vector<VECTOR2I> poly;
  1423. converter.GetPoly( poly, GetWidth() );
  1424. for( unsigned ii = 1; ii < poly.size(); ii++ )
  1425. TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
  1426. break;
  1427. }
  1428. default:
  1429. UNIMPLEMENTED_FOR( SHAPE_T_asString() );
  1430. break;
  1431. }
  1432. }
  1433. void EDA_SHAPE::SetLineStyle( const LINE_STYLE aStyle )
  1434. {
  1435. m_stroke.SetLineStyle( aStyle );
  1436. }
  1437. LINE_STYLE EDA_SHAPE::GetLineStyle() const
  1438. {
  1439. if( m_stroke.GetLineStyle() != LINE_STYLE::DEFAULT )
  1440. return m_stroke.GetLineStyle();
  1441. return LINE_STYLE::SOLID;
  1442. }
  1443. bool EDA_SHAPE::operator==( const EDA_SHAPE& aOther ) const
  1444. {
  1445. if( GetShape() != aOther.GetShape() )
  1446. return false;
  1447. if( m_fill != aOther.m_fill )
  1448. return false;
  1449. if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
  1450. return false;
  1451. if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
  1452. return false;
  1453. if( m_fillColor != aOther.m_fillColor )
  1454. return false;
  1455. if( m_start != aOther.m_start )
  1456. return false;
  1457. if( m_end != aOther.m_end )
  1458. return false;
  1459. if( m_arcCenter != aOther.m_arcCenter )
  1460. return false;
  1461. if( m_bezierC1 != aOther.m_bezierC1 )
  1462. return false;
  1463. if( m_bezierC2 != aOther.m_bezierC2 )
  1464. return false;
  1465. if( m_bezierPoints != aOther.m_bezierPoints )
  1466. return false;
  1467. for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
  1468. {
  1469. if( m_poly.CVertex( ii ) != aOther.m_poly.CVertex( ii ) )
  1470. return false;
  1471. }
  1472. return true;
  1473. }
  1474. double EDA_SHAPE::Similarity( const EDA_SHAPE& aOther ) const
  1475. {
  1476. if( GetShape() != aOther.GetShape() )
  1477. return 0.0;
  1478. double similarity = 1.0;
  1479. if( m_fill != aOther.m_fill )
  1480. similarity *= 0.9;
  1481. if( m_stroke.GetWidth() != aOther.m_stroke.GetWidth() )
  1482. similarity *= 0.9;
  1483. if( m_stroke.GetLineStyle() != aOther.m_stroke.GetLineStyle() )
  1484. similarity *= 0.9;
  1485. if( m_fillColor != aOther.m_fillColor )
  1486. similarity *= 0.9;
  1487. if( m_start != aOther.m_start )
  1488. similarity *= 0.9;
  1489. if( m_end != aOther.m_end )
  1490. similarity *= 0.9;
  1491. if( m_arcCenter != aOther.m_arcCenter )
  1492. similarity *= 0.9;
  1493. if( m_bezierC1 != aOther.m_bezierC1 )
  1494. similarity *= 0.9;
  1495. if( m_bezierC2 != aOther.m_bezierC2 )
  1496. similarity *= 0.9;
  1497. {
  1498. int m = m_bezierPoints.size();
  1499. int n = aOther.m_bezierPoints.size();
  1500. size_t longest = alg::longest_common_subset( m_bezierPoints, aOther.m_bezierPoints );
  1501. similarity *= std::pow( 0.9, m + n - 2 * longest );
  1502. }
  1503. {
  1504. int m = m_poly.TotalVertices();
  1505. int n = aOther.m_poly.TotalVertices();
  1506. std::vector<VECTOR2I> poly;
  1507. std::vector<VECTOR2I> otherPoly;
  1508. VECTOR2I lastPt( 0, 0 );
  1509. // We look for the longest common subset of the two polygons, but we need to
  1510. // offset each point because we're actually looking for overall similarity, not just
  1511. // exact matches. So if the zone is moved by 1IU, we only want one point to be
  1512. // considered "moved" rather than the entire polygon. In this case, the first point
  1513. // will not be a match but the rest of the sequence will.
  1514. for( int ii = 0; ii < m; ++ii )
  1515. {
  1516. poly.emplace_back( lastPt - m_poly.CVertex( ii ) );
  1517. lastPt = m_poly.CVertex( ii );
  1518. }
  1519. lastPt = VECTOR2I( 0, 0 );
  1520. for( int ii = 0; ii < n; ++ii )
  1521. {
  1522. otherPoly.emplace_back( lastPt - aOther.m_poly.CVertex( ii ) );
  1523. lastPt = aOther.m_poly.CVertex( ii );
  1524. }
  1525. size_t longest = alg::longest_common_subset( poly, otherPoly );
  1526. similarity *= std::pow( 0.9, m + n - 2 * longest );
  1527. }
  1528. return similarity;
  1529. }
  1530. IMPLEMENT_ENUM_TO_WXANY( SHAPE_T )
  1531. IMPLEMENT_ENUM_TO_WXANY( LINE_STYLE )
  1532. static struct EDA_SHAPE_DESC
  1533. {
  1534. EDA_SHAPE_DESC()
  1535. {
  1536. ENUM_MAP<SHAPE_T>::Instance()
  1537. .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
  1538. .Map( SHAPE_T::RECTANGLE, _HKI( "Rectangle" ) )
  1539. .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
  1540. .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
  1541. .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
  1542. .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
  1543. auto& plotDashTypeEnum = ENUM_MAP<LINE_STYLE>::Instance();
  1544. if( plotDashTypeEnum.Choices().GetCount() == 0 )
  1545. {
  1546. plotDashTypeEnum.Map( LINE_STYLE::DEFAULT, _HKI( "Default" ) )
  1547. .Map( LINE_STYLE::SOLID, _HKI( "Solid" ) )
  1548. .Map( LINE_STYLE::DASH, _HKI( "Dashed" ) )
  1549. .Map( LINE_STYLE::DOT, _HKI( "Dotted" ) )
  1550. .Map( LINE_STYLE::DASHDOT, _HKI( "Dash-Dot" ) )
  1551. .Map( LINE_STYLE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
  1552. }
  1553. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  1554. REGISTER_TYPE( EDA_SHAPE );
  1555. auto isNotPolygonOrCircle = []( INSPECTABLE* aItem ) -> bool
  1556. {
  1557. // Polygons, unlike other shapes, have no meaningful start or end coordinates
  1558. if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
  1559. return shape->GetShape() != SHAPE_T::POLY && shape->GetShape() != SHAPE_T::CIRCLE;
  1560. return false;
  1561. };
  1562. auto isCircle = []( INSPECTABLE* aItem ) -> bool
  1563. {
  1564. // Polygons, unlike other shapes, have no meaningful start or end coordinates
  1565. if( EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
  1566. return shape->GetShape() == SHAPE_T::CIRCLE;
  1567. return false;
  1568. };
  1569. const wxString shapeProps = _HKI( "Shape Properties" );
  1570. auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
  1571. NO_SETTER( EDA_SHAPE, SHAPE_T ), &EDA_SHAPE::GetShape );
  1572. propMgr.AddProperty( shape, shapeProps );
  1573. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
  1574. &EDA_SHAPE::SetStartX, &EDA_SHAPE::GetStartX, PROPERTY_DISPLAY::PT_COORD,
  1575. ORIGIN_TRANSFORMS::ABS_X_COORD ),
  1576. shapeProps )
  1577. .SetAvailableFunc( isNotPolygonOrCircle );
  1578. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
  1579. &EDA_SHAPE::SetStartY, &EDA_SHAPE::GetStartY, PROPERTY_DISPLAY::PT_COORD,
  1580. ORIGIN_TRANSFORMS::ABS_Y_COORD ),
  1581. shapeProps )
  1582. .SetAvailableFunc( isNotPolygonOrCircle );
  1583. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center X" ),
  1584. &EDA_SHAPE::SetStartX, &EDA_SHAPE::GetStartX, PROPERTY_DISPLAY::PT_COORD,
  1585. ORIGIN_TRANSFORMS::ABS_X_COORD ),
  1586. shapeProps )
  1587. .SetAvailableFunc( isCircle );
  1588. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Center Y" ),
  1589. &EDA_SHAPE::SetStartY, &EDA_SHAPE::GetStartY, PROPERTY_DISPLAY::PT_COORD,
  1590. ORIGIN_TRANSFORMS::ABS_Y_COORD ),
  1591. shapeProps )
  1592. .SetAvailableFunc( isCircle );
  1593. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Radius" ),
  1594. &EDA_SHAPE::SetRadius, &EDA_SHAPE::GetRadius, PROPERTY_DISPLAY::PT_SIZE,
  1595. ORIGIN_TRANSFORMS::NOT_A_COORD ),
  1596. shapeProps )
  1597. .SetAvailableFunc( isCircle );
  1598. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
  1599. &EDA_SHAPE::SetEndX, &EDA_SHAPE::GetEndX, PROPERTY_DISPLAY::PT_COORD,
  1600. ORIGIN_TRANSFORMS::ABS_X_COORD ),
  1601. shapeProps )
  1602. .SetAvailableFunc( isNotPolygonOrCircle );
  1603. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
  1604. &EDA_SHAPE::SetEndY, &EDA_SHAPE::GetEndY, PROPERTY_DISPLAY::PT_COORD,
  1605. ORIGIN_TRANSFORMS::ABS_Y_COORD ),
  1606. shapeProps )
  1607. .SetAvailableFunc( isNotPolygonOrCircle );
  1608. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
  1609. &EDA_SHAPE::SetWidth, &EDA_SHAPE::GetWidth, PROPERTY_DISPLAY::PT_SIZE ),
  1610. shapeProps );
  1611. void ( EDA_SHAPE::*lineStyleSetter )( LINE_STYLE ) = &EDA_SHAPE::SetLineStyle;
  1612. propMgr.AddProperty( new PROPERTY_ENUM<EDA_SHAPE, LINE_STYLE>( _HKI( "Line Style" ),
  1613. lineStyleSetter, &EDA_SHAPE::GetLineStyle ),
  1614. shapeProps );
  1615. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Line Color" ),
  1616. &EDA_SHAPE::SetLineColor, &EDA_SHAPE::GetLineColor ),
  1617. shapeProps )
  1618. .SetIsHiddenFromRulesEditor();
  1619. auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
  1620. NO_SETTER( EDA_SHAPE, EDA_ANGLE ), &EDA_SHAPE::GetArcAngle,
  1621. PROPERTY_DISPLAY::PT_DECIDEGREE );
  1622. angle->SetAvailableFunc(
  1623. [=]( INSPECTABLE* aItem ) -> bool
  1624. {
  1625. if( EDA_SHAPE* curr_shape = dynamic_cast<EDA_SHAPE*>( aItem ) )
  1626. return curr_shape->GetShape() == SHAPE_T::ARC;
  1627. return false;
  1628. } );
  1629. propMgr.AddProperty( angle, shapeProps );
  1630. auto fillAvailable =
  1631. [=]( INSPECTABLE* aItem ) -> bool
  1632. {
  1633. if( EDA_SHAPE* edaShape = dynamic_cast<EDA_SHAPE*>( aItem ) )
  1634. {
  1635. switch( edaShape->GetShape() )
  1636. {
  1637. case SHAPE_T::POLY:
  1638. case SHAPE_T::RECTANGLE:
  1639. case SHAPE_T::CIRCLE:
  1640. return true;
  1641. default:
  1642. return false;
  1643. }
  1644. }
  1645. return false;
  1646. };
  1647. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, bool>( _HKI( "Filled" ),
  1648. &EDA_SHAPE::SetFilled, &EDA_SHAPE::IsFilled ),
  1649. shapeProps )
  1650. .SetAvailableFunc( fillAvailable );
  1651. propMgr.AddProperty( new PROPERTY<EDA_SHAPE, COLOR4D>( _HKI( "Fill Color" ),
  1652. &EDA_SHAPE::SetFillColor, &EDA_SHAPE::GetFillColor ),
  1653. shapeProps )
  1654. .SetAvailableFunc( fillAvailable )
  1655. .SetIsHiddenFromRulesEditor();
  1656. }
  1657. } _EDA_SHAPE_DESC;