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.

252 lines
7.1 KiB

  1. /*
  2. * This program source code file is part of KICAD, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017-2023 Kicad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <limits>
  24. #include <vector>
  25. #include <preview_items/polygon_geom_manager.h>
  26. #include <geometry/geometry_utils.h>
  27. #include <geometry/shape_line_chain.h>
  28. POLYGON_GEOM_MANAGER::POLYGON_GEOM_MANAGER( CLIENT& aClient ):
  29. m_client( aClient ),
  30. m_leaderMode( LEADER_MODE::DIRECT ),
  31. m_intersectionsAllowed( true )
  32. {}
  33. bool POLYGON_GEOM_MANAGER::AddPoint( const VECTOR2I& aPt )
  34. {
  35. // if this is the first point, make sure the client is happy
  36. // for us to continue
  37. if( !IsPolygonInProgress() && !m_client.OnFirstPoint( *this ) )
  38. return false;
  39. if( m_leaderPts.PointCount() > 1 )
  40. {
  41. // there are enough leader points - the next
  42. // locked-in point is the end of the last leader
  43. // segment
  44. m_lockedPoints.Append( m_leaderPts.CPoint( -2 ) );
  45. m_lockedPoints.Append( m_leaderPts.CPoint( -1 ) );
  46. }
  47. else
  48. {
  49. // no leader lines, directly add the cursor
  50. m_lockedPoints.Append( aPt );
  51. }
  52. // check for self-intersections
  53. if( !m_intersectionsAllowed && IsSelfIntersecting( false ) )
  54. {
  55. m_lockedPoints.Remove( m_lockedPoints.PointCount() - 1 );
  56. return false;
  57. }
  58. if( m_lockedPoints.PointCount() > 0 )
  59. updateTemporaryLines( aPt );
  60. m_client.OnGeometryChange( *this );
  61. return true;
  62. }
  63. void POLYGON_GEOM_MANAGER::SetFinished()
  64. {
  65. m_client.OnComplete( *this );
  66. }
  67. void POLYGON_GEOM_MANAGER::SetLeaderMode( LEADER_MODE aMode )
  68. {
  69. m_leaderMode = aMode;
  70. }
  71. bool POLYGON_GEOM_MANAGER::IsSelfIntersecting( bool aIncludeLeaderPts ) const
  72. {
  73. SHAPE_LINE_CHAIN pts( m_lockedPoints );
  74. if( aIncludeLeaderPts )
  75. {
  76. for( int i = 0; i < m_leaderPts.PointCount(); ++i )
  77. {
  78. if( m_leaderPts.CPoint( i ) != pts.CPoint( 0 ) )
  79. pts.Append( m_leaderPts.CPoint( i ) );
  80. }
  81. }
  82. // line chain needs to be set as closed for proper checks
  83. pts.SetClosed( true );
  84. return !!pts.SelfIntersecting();
  85. }
  86. void POLYGON_GEOM_MANAGER::SetCursorPosition( const VECTOR2I& aPos )
  87. {
  88. updateTemporaryLines( aPos );
  89. }
  90. bool POLYGON_GEOM_MANAGER::IsPolygonInProgress() const
  91. {
  92. return m_lockedPoints.PointCount() > 0;
  93. }
  94. int POLYGON_GEOM_MANAGER::PolygonPointCount() const
  95. {
  96. return m_lockedPoints.PointCount();
  97. }
  98. bool POLYGON_GEOM_MANAGER::NewPointClosesOutline( const VECTOR2I& aPt ) const
  99. {
  100. return m_lockedPoints.PointCount() > 0 && m_lockedPoints.CPoint( 0 ) == aPt;
  101. }
  102. std::optional<VECTOR2I> POLYGON_GEOM_MANAGER::DeleteLastCorner()
  103. {
  104. std::optional<VECTOR2I> last;
  105. if( m_lockedPoints.PointCount() > 0 )
  106. {
  107. last = m_lockedPoints.GetPoint( m_lockedPoints.PointCount() - 1 );
  108. m_lockedPoints.Remove( m_lockedPoints.PointCount() - 1 );
  109. }
  110. // update the new last segment (was previously
  111. // locked in), reusing last constraints
  112. if( m_lockedPoints.PointCount() > 0 )
  113. updateTemporaryLines( m_leaderPts.CLastPoint() );
  114. m_client.OnGeometryChange( *this );
  115. return last;
  116. }
  117. void POLYGON_GEOM_MANAGER::Reset()
  118. {
  119. m_lockedPoints.Clear();
  120. m_leaderPts.Clear();
  121. m_loopPts.Clear();
  122. m_client.OnGeometryChange( *this );
  123. }
  124. static SHAPE_LINE_CHAIN build45DegLeader( const VECTOR2I& aEndPoint, SHAPE_LINE_CHAIN aLastPoints )
  125. {
  126. if( aLastPoints.PointCount() < 1 )
  127. return SHAPE_LINE_CHAIN();
  128. const VECTOR2I lastPt = aLastPoints.CPoint( -1 );
  129. const VECTOR2D endpointD = aEndPoint;
  130. const VECTOR2D lineVec = endpointD - lastPt;
  131. if( aLastPoints.SegmentCount() < 1 )
  132. return SHAPE_LINE_CHAIN(
  133. std::vector<VECTOR2I>{ lastPt, lastPt + GetVectorSnapped45( lineVec ) } );
  134. EDA_ANGLE lineA( lineVec );
  135. EDA_ANGLE prevA( GetVectorSnapped45( lastPt - aLastPoints.CPoint( -2 ) ) );
  136. bool vertical = std::abs( lineVec.y ) > std::abs( lineVec.x );
  137. bool horizontal = std::abs( lineVec.y ) < std::abs( lineVec.x );
  138. double angDiff = std::abs( ( lineA - prevA ).Normalize180().AsDegrees() );
  139. bool bendEnd = ( angDiff < 45 ) || ( angDiff > 90 && angDiff < 135 );
  140. if( prevA.Normalize90() == ANGLE_45 || prevA.Normalize90() == -ANGLE_45 )
  141. bendEnd = !bendEnd;
  142. VECTOR2D mid = endpointD;
  143. if( bendEnd )
  144. {
  145. if( vertical )
  146. {
  147. if( lineVec.y > 0 )
  148. mid = VECTOR2D( lastPt.x, endpointD.y - std::abs( lineVec.x ) );
  149. else
  150. mid = VECTOR2D( lastPt.x, endpointD.y + std::abs( lineVec.x ) );
  151. }
  152. else if( horizontal )
  153. {
  154. if( lineVec.x > 0 )
  155. mid = VECTOR2D( endpointD.x - std::abs( lineVec.y ), lastPt.y );
  156. else
  157. mid = VECTOR2D( endpointD.x + std::abs( lineVec.y ), lastPt.y );
  158. }
  159. }
  160. else
  161. {
  162. if( vertical )
  163. {
  164. if( lineVec.y > 0 )
  165. mid = VECTOR2D( endpointD.x, lastPt.y + std::abs( lineVec.x ) );
  166. else
  167. mid = VECTOR2D( endpointD.x, lastPt.y - std::abs( lineVec.x ) );
  168. }
  169. else if( horizontal )
  170. {
  171. if( lineVec.x > 0 )
  172. mid = VECTOR2D( lastPt.x + std::abs( lineVec.y ), endpointD.y );
  173. else
  174. mid = VECTOR2D( lastPt.x - std::abs( lineVec.y ), endpointD.y );
  175. }
  176. }
  177. const VECTOR2I midInt = { KiROUND( mid.x ), KiROUND( mid.y ) };
  178. return SHAPE_LINE_CHAIN( std::vector<VECTOR2I>{ lastPt, midInt, aEndPoint } );
  179. }
  180. void POLYGON_GEOM_MANAGER::updateTemporaryLines( const VECTOR2I& aEndPoint, LEADER_MODE aModifier )
  181. {
  182. wxCHECK( m_lockedPoints.PointCount() > 0, /*void*/ );
  183. const VECTOR2I& last_pt = m_lockedPoints.CLastPoint();
  184. if( m_leaderMode == LEADER_MODE::DEG45 || aModifier == LEADER_MODE::DEG45 )
  185. {
  186. if( m_lockedPoints.PointCount() > 0 )
  187. {
  188. m_leaderPts = build45DegLeader( aEndPoint, m_lockedPoints );
  189. m_loopPts = build45DegLeader( aEndPoint, m_lockedPoints.Reverse() ).Reverse();
  190. }
  191. }
  192. else
  193. {
  194. // direct segment
  195. m_leaderPts = SHAPE_LINE_CHAIN( { last_pt, aEndPoint } );
  196. m_loopPts.Clear();
  197. }
  198. m_client.OnGeometryChange( *this );
  199. }