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.

417 lines
12 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2016 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 1992-2016 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 <PolyLine.h>
  30. #include <trigo.h>
  31. #include <pcbnew.h>
  32. #include <class_pad.h>
  33. #include <class_drawsegment.h>
  34. #include <class_edge_mod.h>
  35. #include <convert_basic_shapes_to_polygon.h>
  36. #include <geometry/shape_rect.h>
  37. #include <geometry/convex_hull.h>
  38. void PAD_CS_PRIMITIVE::ExportTo( DRAWSEGMENT* aTarget )
  39. {
  40. aTarget->SetShape( m_Shape );
  41. aTarget->SetWidth( m_Thickness );
  42. aTarget->SetStart( m_Start );
  43. aTarget->SetEnd( m_End );
  44. // in a DRAWSEGMENT the radius of a circle is calculated from the
  45. // center and one point on the circle outline (stored in m_End)
  46. if( m_Shape == S_CIRCLE )
  47. {
  48. wxPoint end = m_Start;
  49. end.x += m_Radius;
  50. aTarget->SetEnd( end );
  51. }
  52. aTarget->SetAngle( m_ArcAngle );
  53. aTarget->SetPolyPoints( m_Poly );
  54. }
  55. void PAD_CS_PRIMITIVE::ExportTo( EDGE_MODULE* aTarget )
  56. {
  57. ExportTo( static_cast<DRAWSEGMENT*>( aTarget ) );
  58. // Initialize coordinates specific to the EDGE_MODULE (m_Start0 and m_End0)
  59. aTarget->SetLocalCoord();
  60. }
  61. void PAD_CS_PRIMITIVE::Move( wxPoint aMoveVector )
  62. {
  63. m_Start += aMoveVector;
  64. m_End += aMoveVector;
  65. for( auto& corner : m_Poly )
  66. {
  67. corner += aMoveVector;
  68. }
  69. }
  70. /*
  71. * Has meaning only for free shape pads.
  72. * add a free shape to the shape list.
  73. * the shape is a polygon (can be with thick outline), segment, circle or arc
  74. */
  75. void D_PAD::AddPrimitive( const SHAPE_POLY_SET& aPoly, int aThickness )
  76. {
  77. std::vector<wxPoint> points;
  78. for( auto iter = aPoly.CIterate(); iter; iter++ )
  79. points.push_back( wxPoint( iter->x, iter->y ) );
  80. AddPrimitive( points, aThickness );
  81. }
  82. void D_PAD::AddPrimitive( const std::vector<wxPoint>& aPoly, int aThickness )
  83. {
  84. PAD_CS_PRIMITIVE shape( S_POLYGON );
  85. shape.m_Poly = aPoly;
  86. shape.m_Thickness = aThickness;
  87. m_basicShapes.push_back( shape );
  88. MergePrimitivesAsPolygon();
  89. }
  90. void D_PAD::AddPrimitive( wxPoint aStart, wxPoint aEnd, int aThickness )
  91. {
  92. PAD_CS_PRIMITIVE shape( S_SEGMENT );
  93. shape.m_Start = aStart;
  94. shape.m_End = aEnd;
  95. shape.m_Thickness = aThickness;
  96. m_basicShapes.push_back( shape );
  97. MergePrimitivesAsPolygon();
  98. }
  99. void D_PAD::AddPrimitive( wxPoint aCenter, wxPoint aStart, int aArcAngle, int aThickness )
  100. {
  101. PAD_CS_PRIMITIVE shape( S_ARC );
  102. shape.m_Start = aCenter;
  103. shape.m_End = aStart;
  104. shape.m_ArcAngle = aArcAngle;
  105. shape.m_Thickness = aThickness;
  106. m_basicShapes.push_back( shape );
  107. MergePrimitivesAsPolygon();
  108. }
  109. void D_PAD::AddPrimitive( wxPoint aCenter, int aRadius, int aThickness )
  110. {
  111. PAD_CS_PRIMITIVE shape( S_CIRCLE );
  112. shape.m_Start = aCenter;
  113. shape.m_Radius = aRadius;
  114. shape.m_Thickness = aThickness;
  115. m_basicShapes.push_back( shape );
  116. MergePrimitivesAsPolygon();
  117. }
  118. bool D_PAD::SetPrimitives( const std::vector<PAD_CS_PRIMITIVE>& aPrimitivesList )
  119. {
  120. // clear old list
  121. m_basicShapes.clear();
  122. // Import to the basic shape list
  123. if( aPrimitivesList.size() )
  124. m_basicShapes = aPrimitivesList;
  125. // Only one polygon is expected (pad area = only one copper area)
  126. return MergePrimitivesAsPolygon();
  127. }
  128. // clear the basic shapes list and associated data
  129. void D_PAD::DeletePrimitivesList()
  130. {
  131. m_basicShapes.clear();
  132. m_customShapeAsPolygon.RemoveAllContours();
  133. }
  134. bool D_PAD::buildCustomPadPolygon( SHAPE_POLY_SET* aMergedPolygon,
  135. int aCircleToSegmentsCount )
  136. {
  137. SHAPE_POLY_SET aux_polyset;
  138. for( unsigned cnt = 0; cnt < m_basicShapes.size(); ++cnt )
  139. {
  140. const PAD_CS_PRIMITIVE& bshape = m_basicShapes[cnt];
  141. switch( bshape.m_Shape )
  142. {
  143. case S_SEGMENT: // usual segment : line with rounded ends
  144. TransformRoundedEndsSegmentToPolygon( aux_polyset,
  145. bshape.m_Start, bshape.m_End, aCircleToSegmentsCount, bshape.m_Thickness );
  146. break;
  147. case S_ARC: // Arc with rounded ends
  148. TransformArcToPolygon( aux_polyset,
  149. bshape.m_Start, bshape.m_End, bshape.m_ArcAngle,
  150. aCircleToSegmentsCount, bshape.m_Thickness );
  151. break;
  152. case S_CIRCLE: // ring or circle
  153. if( bshape.m_Thickness ) // ring
  154. TransformRingToPolygon( aux_polyset,
  155. bshape.m_Start, bshape.m_Radius,
  156. aCircleToSegmentsCount, bshape.m_Thickness ) ;
  157. else // Filled circle
  158. TransformCircleToPolygon( aux_polyset,
  159. bshape.m_Start, bshape.m_Radius,
  160. aCircleToSegmentsCount ) ;
  161. break;
  162. case S_POLYGON: // polygon
  163. if( bshape.m_Poly.size() < 2 )
  164. break; // Malformed polygon.
  165. {
  166. // Insert the polygon:
  167. const std::vector< wxPoint>& poly = bshape.m_Poly;
  168. aux_polyset.NewOutline();
  169. if( bshape.m_Thickness )
  170. {
  171. SHAPE_POLY_SET polyset;
  172. polyset.NewOutline();
  173. for( unsigned ii = 0; ii < poly.size(); ii++ )
  174. {
  175. polyset.Append( poly[ii].x, poly[ii].y );
  176. }
  177. polyset.Inflate( bshape.m_Thickness/2, 32 );
  178. aux_polyset.Append( polyset );
  179. }
  180. else
  181. for( unsigned ii = 0; ii < poly.size(); ii++ )
  182. aux_polyset.Append( poly[ii].x, poly[ii].y );
  183. }
  184. break;
  185. default:
  186. break;
  187. }
  188. }
  189. aux_polyset.Simplify( SHAPE_POLY_SET::PM_FAST );
  190. // Merge all polygons, if more than one, pick the largest (area-wise)
  191. if( aux_polyset.OutlineCount() )
  192. {
  193. if( aux_polyset.OutlineCount() >= 2)
  194. {
  195. int bestOutline = 0;
  196. double maxArea = 0.0;
  197. for( int i = 0; i < aux_polyset.OutlineCount(); i++ )
  198. {
  199. double area = aux_polyset.COutline(i).Area();
  200. if ( area > maxArea )
  201. {
  202. maxArea = area;
  203. bestOutline = i;
  204. }
  205. }
  206. if( bestOutline != 0 )
  207. aux_polyset.Polygon( 0 ) = aux_polyset.Polygon( bestOutline );
  208. for (int i = 1; i < aux_polyset.OutlineCount(); i++ )
  209. {
  210. aux_polyset.DeletePolygon( i );
  211. }
  212. }
  213. aMergedPolygon->BooleanAdd( aux_polyset, SHAPE_POLY_SET::PM_FAST );
  214. aMergedPolygon->Fracture( SHAPE_POLY_SET::PM_FAST );
  215. }
  216. return aMergedPolygon->OutlineCount() <= 1;
  217. }
  218. /* Merge all basic shapes, converted to a polygon in one polygon,
  219. * return true if OK, false in there is more than one polygon
  220. * in aMergedPolygon
  221. */
  222. bool D_PAD::MergePrimitivesAsPolygon( SHAPE_POLY_SET* aMergedPolygon,
  223. int aCircleToSegmentsCount )
  224. {
  225. // if aMergedPolygon == NULL, use m_customShapeAsPolygon as target
  226. if( !aMergedPolygon )
  227. aMergedPolygon = &m_customShapeAsPolygon;
  228. aMergedPolygon->RemoveAllContours();
  229. // Add the anchor pad shape in aMergedPolygon, others in aux_polyset:
  230. // The anchor pad is always at 0,0
  231. switch( GetAnchorPadShape() )
  232. {
  233. default:
  234. case PAD_SHAPE_CIRCLE:
  235. TransformCircleToPolygon( *aMergedPolygon, wxPoint( 0,0 ), GetSize().x/2,
  236. aCircleToSegmentsCount );
  237. break;
  238. case PAD_SHAPE_RECT:
  239. {
  240. SHAPE_RECT rect( -GetSize().x/2, -GetSize().y/2, GetSize().x, GetSize().y );
  241. aMergedPolygon->AddOutline( rect.Outline() );
  242. }
  243. break;
  244. }
  245. if ( !buildCustomPadPolygon( aMergedPolygon, aCircleToSegmentsCount ) )
  246. return false;
  247. m_boundingRadius = -1; // The current bouding radius is no more valid.
  248. return aMergedPolygon->OutlineCount() <= 1;
  249. }
  250. void D_PAD::CustomShapeAsPolygonToBoardPosition( SHAPE_POLY_SET * aMergedPolygon,
  251. wxPoint aPosition, double aRotation ) const
  252. {
  253. if( aMergedPolygon->OutlineCount() == 0 )
  254. return;
  255. // Move, rotate, ... coordinates in aMergedPolygon according to the
  256. // pad position and orientation
  257. for( int cnt = 0; cnt < aMergedPolygon->OutlineCount(); ++cnt )
  258. {
  259. SHAPE_LINE_CHAIN& poly = aMergedPolygon->Outline( cnt );
  260. for( int ii = 0; ii < poly.PointCount(); ++ii )
  261. {
  262. wxPoint corner( poly.Point( ii ).x, poly.Point( ii ).y );
  263. RotatePoint( &corner, aRotation );
  264. corner += aPosition;
  265. poly.Point( ii ).x = corner.x;
  266. poly.Point( ii ).y = corner.y;
  267. }
  268. }
  269. }
  270. bool D_PAD::GetBestAnchorPosition( VECTOR2I& aPos )
  271. {
  272. SHAPE_POLY_SET poly;
  273. if ( !buildCustomPadPolygon( &poly, 16 ) )
  274. return false;
  275. const int minSteps = 10;
  276. const int maxSteps = 50;
  277. int stepsX, stepsY;
  278. auto bbox = poly.BBox();
  279. if( bbox.GetWidth() < bbox.GetHeight() )
  280. {
  281. stepsX = minSteps;
  282. stepsY = minSteps * (double) bbox.GetHeight() / (double )(bbox.GetWidth() + 1);
  283. }
  284. else
  285. {
  286. stepsY = minSteps;
  287. stepsX = minSteps * (double) bbox.GetWidth() / (double )(bbox.GetHeight() + 1);
  288. }
  289. stepsX = std::max(minSteps, std::min( maxSteps, stepsX ) );
  290. stepsY = std::max(minSteps, std::min( maxSteps, stepsY ) );
  291. auto center = bbox.Centre();
  292. auto minDist = std::numeric_limits<int64_t>::max();
  293. int64_t minDistEdge;
  294. if( GetAnchorPadShape() == PAD_SHAPE_CIRCLE )
  295. {
  296. minDistEdge = GetSize().x;
  297. }
  298. else
  299. {
  300. minDistEdge = std::max( GetSize().x, GetSize().y );
  301. }
  302. OPT<VECTOR2I> bestAnchor( []()->OPT<VECTOR2I> { return NULLOPT; }() );
  303. for ( int y = 0; y < stepsY ; y++ )
  304. {
  305. for ( int x = 0; x < stepsX; x++ )
  306. {
  307. VECTOR2I p = bbox.GetPosition();
  308. p.x += rescale( x, bbox.GetWidth(), (stepsX - 1) );
  309. p.y += rescale( y, bbox.GetHeight(), (stepsY - 1) );
  310. if ( poly.Contains(p) )
  311. {
  312. auto dist = (center - p).EuclideanNorm();
  313. auto distEdge = poly.COutline(0).Distance( p, true );
  314. if ( distEdge >= minDistEdge )
  315. {
  316. if ( dist < minDist )
  317. {
  318. bestAnchor = p;
  319. minDist = dist;
  320. }
  321. }
  322. }
  323. }
  324. }
  325. if ( bestAnchor )
  326. {
  327. aPos = *bestAnchor;
  328. return true;
  329. }
  330. return false;
  331. }