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.

404 lines
12 KiB

  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) 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. /**
  25. * @file class_pad_custom_shape_functions.cpp
  26. * class D_PAD functions specific to custom shaped pads.
  27. */
  28. #include <fctsys.h>
  29. #include <trigo.h>
  30. #include <pcbnew.h>
  31. #include <class_pad.h>
  32. #include <class_drawsegment.h>
  33. #include <class_edge_mod.h>
  34. #include <convert_basic_shapes_to_polygon.h>
  35. #include <geometry/shape_rect.h>
  36. #include <geometry/convex_hull.h>
  37. void PAD_CS_PRIMITIVE::ExportTo( DRAWSEGMENT* aTarget )
  38. {
  39. aTarget->SetShape( m_Shape );
  40. aTarget->SetWidth( m_Thickness );
  41. aTarget->SetStart( m_Start );
  42. aTarget->SetEnd( m_End );
  43. // in a DRAWSEGMENT the radius of a circle is calculated from the
  44. // center and one point on the circle outline (stored in m_End)
  45. if( m_Shape == S_CIRCLE )
  46. {
  47. wxPoint end = m_Start;
  48. end.x += m_Radius;
  49. aTarget->SetEnd( end );
  50. }
  51. aTarget->SetAngle( m_ArcAngle );
  52. aTarget->SetPolyPoints( m_Poly );
  53. }
  54. void PAD_CS_PRIMITIVE::ExportTo( EDGE_MODULE* aTarget )
  55. {
  56. ExportTo( static_cast<DRAWSEGMENT*>( aTarget ) );
  57. // Initialize coordinates specific to the EDGE_MODULE (m_Start0 and m_End0)
  58. aTarget->SetLocalCoord();
  59. }
  60. void PAD_CS_PRIMITIVE::Move( wxPoint aMoveVector )
  61. {
  62. m_Start += aMoveVector;
  63. m_End += aMoveVector;
  64. for( auto& corner : m_Poly )
  65. {
  66. corner += aMoveVector;
  67. }
  68. }
  69. /*
  70. * Has meaning only for free shape pads.
  71. * add a free shape to the shape list.
  72. * the shape is a polygon (can be with thick outline), segment, circle or arc
  73. */
  74. void D_PAD::AddPrimitive( const SHAPE_POLY_SET& aPoly, int aThickness )
  75. {
  76. std::vector<wxPoint> points;
  77. // If aPoly has holes, convert it to a polygon with no holes.
  78. SHAPE_POLY_SET poly_no_hole;
  79. poly_no_hole.Append( aPoly );
  80. poly_no_hole.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  81. for( auto iter = poly_no_hole.CIterate(); iter; iter++ )
  82. points.push_back( wxPoint( iter->x, iter->y ) );
  83. AddPrimitive( points, aThickness );
  84. }
  85. void D_PAD::AddPrimitive( const std::vector<wxPoint>& aPoly, int aThickness )
  86. {
  87. PAD_CS_PRIMITIVE shape( S_POLYGON );
  88. shape.m_Poly = aPoly;
  89. shape.m_Thickness = aThickness;
  90. m_basicShapes.push_back( shape );
  91. MergePrimitivesAsPolygon();
  92. }
  93. void D_PAD::AddPrimitive( wxPoint aStart, wxPoint aEnd, int aThickness )
  94. {
  95. PAD_CS_PRIMITIVE shape( S_SEGMENT );
  96. shape.m_Start = aStart;
  97. shape.m_End = aEnd;
  98. shape.m_Thickness = aThickness;
  99. m_basicShapes.push_back( shape );
  100. MergePrimitivesAsPolygon();
  101. }
  102. void D_PAD::AddPrimitive( wxPoint aCenter, wxPoint aStart, int aArcAngle, int aThickness )
  103. {
  104. PAD_CS_PRIMITIVE shape( S_ARC );
  105. shape.m_Start = aCenter;
  106. shape.m_End = aStart;
  107. shape.m_ArcAngle = aArcAngle;
  108. shape.m_Thickness = aThickness;
  109. m_basicShapes.push_back( shape );
  110. MergePrimitivesAsPolygon();
  111. }
  112. void D_PAD::AddPrimitive( wxPoint aCenter, int aRadius, int aThickness )
  113. {
  114. PAD_CS_PRIMITIVE shape( S_CIRCLE );
  115. shape.m_Start = aCenter;
  116. shape.m_Radius = aRadius;
  117. shape.m_Thickness = aThickness;
  118. m_basicShapes.push_back( shape );
  119. MergePrimitivesAsPolygon();
  120. }
  121. bool D_PAD::SetPrimitives( const std::vector<PAD_CS_PRIMITIVE>& aPrimitivesList )
  122. {
  123. // clear old list
  124. m_basicShapes.clear();
  125. // Import to the basic shape list
  126. if( aPrimitivesList.size() )
  127. m_basicShapes = aPrimitivesList;
  128. // Only one polygon is expected (pad area = only one copper area)
  129. return MergePrimitivesAsPolygon();
  130. }
  131. bool D_PAD::AddPrimitives( const std::vector<PAD_CS_PRIMITIVE>& aPrimitivesList )
  132. {
  133. for( const auto& prim : aPrimitivesList )
  134. m_basicShapes.push_back( prim );
  135. return MergePrimitivesAsPolygon();
  136. }
  137. // clear the basic shapes list and associated data
  138. void D_PAD::DeletePrimitivesList()
  139. {
  140. m_basicShapes.clear();
  141. m_customShapeAsPolygon.RemoveAllContours();
  142. }
  143. bool D_PAD::buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon,
  144. int aCircleToSegmentsCount )
  145. {
  146. SHAPE_POLY_SET aux_polyset;
  147. for( unsigned cnt = 0; cnt < m_basicShapes.size(); ++cnt )
  148. {
  149. const PAD_CS_PRIMITIVE& bshape = m_basicShapes[cnt];
  150. switch( bshape.m_Shape )
  151. {
  152. case S_SEGMENT: // usual segment : line with rounded ends
  153. TransformRoundedEndsSegmentToPolygon( aux_polyset,
  154. bshape.m_Start, bshape.m_End, aCircleToSegmentsCount, bshape.m_Thickness );
  155. break;
  156. case S_ARC: // Arc with rounded ends
  157. TransformArcToPolygon( aux_polyset,
  158. bshape.m_Start, bshape.m_End, bshape.m_ArcAngle,
  159. aCircleToSegmentsCount, bshape.m_Thickness );
  160. break;
  161. case S_CIRCLE: // ring or circle
  162. if( bshape.m_Thickness ) // ring
  163. TransformRingToPolygon( aux_polyset,
  164. bshape.m_Start, bshape.m_Radius,
  165. aCircleToSegmentsCount, bshape.m_Thickness ) ;
  166. else // Filled circle
  167. TransformCircleToPolygon( aux_polyset,
  168. bshape.m_Start, bshape.m_Radius,
  169. aCircleToSegmentsCount ) ;
  170. break;
  171. case S_POLYGON: // polygon
  172. if( bshape.m_Poly.size() < 2 )
  173. break; // Malformed polygon.
  174. {
  175. // Insert the polygon:
  176. const std::vector< wxPoint>& poly = bshape.m_Poly;
  177. aux_polyset.NewOutline();
  178. if( bshape.m_Thickness )
  179. {
  180. SHAPE_POLY_SET polyset;
  181. polyset.NewOutline();
  182. for( unsigned ii = 0; ii < poly.size(); ii++ )
  183. {
  184. polyset.Append( poly[ii].x, poly[ii].y );
  185. }
  186. polyset.Inflate( bshape.m_Thickness/2, ARC_APPROX_SEGMENTS_COUNT_HIGH_DEF );
  187. aux_polyset.Append( polyset );
  188. }
  189. else
  190. for( unsigned ii = 0; ii < poly.size(); ii++ )
  191. aux_polyset.Append( poly[ii].x, poly[ii].y );
  192. }
  193. break;
  194. default:
  195. break;
  196. }
  197. }
  198. aux_polyset.Simplify( SHAPE_POLY_SET::PM_FAST );
  199. // Merge all polygons with the initial pad anchor shape
  200. if( aux_polyset.OutlineCount() )
  201. {
  202. aMergedPolygon->BooleanAdd( aux_polyset, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  203. aMergedPolygon->Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
  204. }
  205. return aMergedPolygon->OutlineCount() <= 1;
  206. }
  207. /* Merge all basic shapes, converted to a polygon in one polygon,
  208. * return true if OK, false in there is more than one polygon
  209. * in aMergedPolygon
  210. */
  211. bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon,
  212. int aCircleToSegmentsCount )
  213. {
  214. // if aMergedPolygon == NULL, use m_customShapeAsPolygon as target
  215. if( !aMergedPolygon )
  216. aMergedPolygon = &m_customShapeAsPolygon;
  217. aMergedPolygon->RemoveAllContours();
  218. // Add the anchor pad shape in aMergedPolygon, others in aux_polyset:
  219. // The anchor pad is always at 0,0
  220. switch( GetAnchorPadShape() )
  221. {
  222. default:
  223. case PAD_SHAPE_CIRCLE:
  224. TransformCircleToPolygon( *aMergedPolygon, wxPoint( 0,0 ), GetSize().x/2,
  225. aCircleToSegmentsCount );
  226. break;
  227. case PAD_SHAPE_RECT:
  228. {
  229. SHAPE_RECT rect( -GetSize().x/2, -GetSize().y/2, GetSize().x, GetSize().y );
  230. aMergedPolygon->AddOutline( rect.Outline() );
  231. }
  232. break;
  233. }
  234. if ( !buildCustomPadPolygon( aMergedPolygon, aCircleToSegmentsCount ) )
  235. return false;
  236. m_boundingRadius = -1; // The current bouding radius is no more valid.
  237. return aMergedPolygon->OutlineCount() <= 1;
  238. }
  239. void D_PAD::CustomShapeAsPolygonToBoardPosition( SHAPE_POLY_SET * aMergedPolygon,
  240. wxPoint aPosition, double aRotation ) const
  241. {
  242. if( aMergedPolygon->OutlineCount() == 0 )
  243. return;
  244. // Move, rotate, ... coordinates in aMergedPolygon according to the
  245. // pad position and orientation
  246. for( int cnt = 0; cnt < aMergedPolygon->OutlineCount(); ++cnt )
  247. {
  248. SHAPE_LINE_CHAIN& poly = aMergedPolygon->Outline( cnt );
  249. for( int ii = 0; ii < poly.PointCount(); ++ii )
  250. {
  251. wxPoint corner( poly.Point( ii ).x, poly.Point( ii ).y );
  252. RotatePoint( &corner, aRotation );
  253. corner += aPosition;
  254. poly.Point( ii ).x = corner.x;
  255. poly.Point( ii ).y = corner.y;
  256. }
  257. }
  258. }
  259. bool D_PAD::GetBestAnchorPosition( VECTOR2I& aPos )
  260. {
  261. SHAPE_POLY_SET poly;
  262. if ( !buildCustomPadPolygon( &poly, ARC_APPROX_SEGMENTS_COUNT_LOW_DEF ) )
  263. return false;
  264. const int minSteps = 10;
  265. const int maxSteps = 50;
  266. int stepsX, stepsY;
  267. auto bbox = poly.BBox();
  268. if( bbox.GetWidth() < bbox.GetHeight() )
  269. {
  270. stepsX = minSteps;
  271. stepsY = minSteps * (double) bbox.GetHeight() / (double )(bbox.GetWidth() + 1);
  272. }
  273. else
  274. {
  275. stepsY = minSteps;
  276. stepsX = minSteps * (double) bbox.GetWidth() / (double )(bbox.GetHeight() + 1);
  277. }
  278. stepsX = std::max(minSteps, std::min( maxSteps, stepsX ) );
  279. stepsY = std::max(minSteps, std::min( maxSteps, stepsY ) );
  280. auto center = bbox.Centre();
  281. auto minDist = std::numeric_limits<int64_t>::max();
  282. int64_t minDistEdge;
  283. if( GetAnchorPadShape() == PAD_SHAPE_CIRCLE )
  284. {
  285. minDistEdge = GetSize().x;
  286. }
  287. else
  288. {
  289. minDistEdge = std::max( GetSize().x, GetSize().y );
  290. }
  291. OPT<VECTOR2I> bestAnchor( []()->OPT<VECTOR2I> { return NULLOPT; }() );
  292. for ( int y = 0; y < stepsY ; y++ )
  293. {
  294. for ( int x = 0; x < stepsX; x++ )
  295. {
  296. VECTOR2I p = bbox.GetPosition();
  297. p.x += rescale( x, bbox.GetWidth(), (stepsX - 1) );
  298. p.y += rescale( y, bbox.GetHeight(), (stepsY - 1) );
  299. if ( poly.Contains(p) )
  300. {
  301. auto dist = (center - p).EuclideanNorm();
  302. auto distEdge = poly.COutline(0).Distance( p, true );
  303. if ( distEdge >= minDistEdge )
  304. {
  305. if ( dist < minDist )
  306. {
  307. bestAnchor = p;
  308. minDist = dist;
  309. }
  310. }
  311. }
  312. }
  313. }
  314. if ( bestAnchor )
  315. {
  316. aPos = *bestAnchor;
  317. return true;
  318. }
  319. return false;
  320. }