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.

641 lines
20 KiB

  1. /**
  2. * @file dialog_pad_basicshapes_properties.cpp
  3. * @brief basic shapes for pads crude editor.
  4. */
  5. /*
  6. * This program source code file is part of KiCad, a free EDA CAD application.
  7. *
  8. * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
  9. * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
  10. *
  11. * This program is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU General Public License
  13. * as published by the Free Software Foundation; either version 2
  14. * of the License, or (at your option) any later version.
  15. *
  16. * This program is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU General Public License
  22. * along with this program; if not, you may find one here:
  23. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  24. * or you may search the http://www.gnu.org website for the version 2 license,
  25. * or you may write to the Free Software Foundation, Inc.,
  26. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  27. */
  28. #include <algorithm>
  29. #include <confirm.h>
  30. #include <trigo.h>
  31. #include <pcb_base_frame.h>
  32. #include <base_units.h>
  33. #include <widgets/wx_grid.h>
  34. #include <footprint.h>
  35. #include <math/util.h> // for KiROUND
  36. #include <dialog_pad_properties.h>
  37. #include <bitmaps.h>
  38. #include <wx/dcclient.h>
  39. DIALOG_PAD_PRIMITIVES_PROPERTIES::DIALOG_PAD_PRIMITIVES_PROPERTIES( wxWindow* aParent,
  40. PCB_BASE_FRAME* aFrame,
  41. PCB_SHAPE* aShape ) :
  42. DIALOG_PAD_PRIMITIVES_PROPERTIES_BASE( aParent ),
  43. m_shape( aShape ),
  44. m_startX( aFrame, m_startXLabel, m_startXCtrl, m_startXUnits ),
  45. m_startY( aFrame, m_startYLabel, m_startYCtrl, m_startYUnits ),
  46. m_ctrl1X( aFrame, m_ctrl1XLabel, m_ctrl1XCtrl, m_ctrl1XUnits ),
  47. m_ctrl1Y( aFrame, m_ctrl1YLabel, m_ctrl1YCtrl, m_ctrl1YUnits ),
  48. m_ctrl2X( aFrame, m_ctrl2XLabel, m_ctrl2XCtrl, m_ctrl2XUnits ),
  49. m_ctrl2Y( aFrame, m_ctrl2YLabel, m_ctrl2YCtrl, m_ctrl2YUnits ),
  50. m_endX( aFrame, m_endXLabel, m_endXCtrl, m_endXUnits ),
  51. m_endY( aFrame, m_endYLabel, m_endYCtrl, m_endYUnits ),
  52. m_radius( aFrame, m_radiusLabel, m_radiusCtrl, m_radiusUnits ),
  53. m_thickness( aFrame, m_thicknessLabel, m_thicknessCtrl, m_thicknessUnits )
  54. {
  55. SetInitialFocus( m_startXCtrl );
  56. TransferDataToWindow();
  57. m_sdbSizerOK->SetDefault();
  58. finishDialogSettings();
  59. }
  60. bool DIALOG_PAD_PRIMITIVES_PROPERTIES::TransferDataToWindow()
  61. {
  62. if( m_shape == nullptr )
  63. return false;
  64. m_thickness.SetValue( m_shape->GetWidth() );
  65. m_filledCtrl->SetValue( m_shape->IsFilled() );
  66. switch( m_shape->GetShape() )
  67. {
  68. case SHAPE_T::SEGMENT:
  69. SetTitle( _( "Segment" ) );
  70. m_startX.SetValue( m_shape->GetStart().x );
  71. m_startY.SetValue( m_shape->GetStart().y );
  72. m_endX.SetValue( m_shape->GetEnd().x );
  73. m_endY.SetValue( m_shape->GetEnd().y );
  74. m_ctrl1X.Show( false, true );
  75. m_ctrl1Y.Show( false, true );
  76. m_ctrl2X.Show( false, true );
  77. m_ctrl2Y.Show( false, true );
  78. m_staticTextPosCtrl1->Show( false );
  79. m_staticTextPosCtrl1->SetSize( 0, 0 );
  80. m_staticTextPosCtrl2->Show( false );
  81. m_staticTextPosCtrl2->SetSize( 0, 0 );
  82. m_radius.Show( false );
  83. m_filledCtrl->Show( false );
  84. break;
  85. case SHAPE_T::BEZIER:
  86. SetTitle( _( "Bezier" ) );
  87. m_startX.SetValue( m_shape->GetStart().x );
  88. m_startY.SetValue( m_shape->GetStart().y );
  89. m_endX.SetValue( m_shape->GetEnd().x );
  90. m_endY.SetValue( m_shape->GetEnd().y );
  91. m_ctrl1X.SetValue( m_shape->GetBezierC1().x );
  92. m_ctrl1Y.SetValue( m_shape->GetBezierC1().y );
  93. m_ctrl2X.SetValue( m_shape->GetBezierC2().x );
  94. m_ctrl2Y.SetValue( m_shape->GetBezierC2().y );
  95. m_radius.Show( false );
  96. m_filledCtrl->Show( false );
  97. break;
  98. case SHAPE_T::ARC:
  99. SetTitle( _( "Arc" ) );
  100. m_startX.SetValue( m_shape->GetStart().x );
  101. m_startY.SetValue( m_shape->GetStart().y );
  102. m_staticTextPosEnd->SetLabel( _( "Center" ) );
  103. m_endX.SetValue( m_shape->GetCenter().x );
  104. m_endY.SetValue( m_shape->GetCenter().y );
  105. m_radiusLabel->SetLabel( _( "Angle:" ) );
  106. m_radius.SetUnits( EDA_UNITS::DEGREES );
  107. m_radius.SetValue( m_shape->GetArcAngle() );
  108. m_ctrl1X.Show( false, true );
  109. m_ctrl1Y.Show( false, true );
  110. m_ctrl2X.Show( false, true );
  111. m_ctrl2Y.Show( false, true );
  112. m_staticTextPosCtrl1->Show( false );
  113. m_staticTextPosCtrl1->SetSize( 0, 0 );
  114. m_staticTextPosCtrl2->Show( false );
  115. m_staticTextPosCtrl2->SetSize( 0, 0 );
  116. m_filledCtrl->Show( false );
  117. break;
  118. case SHAPE_T::CIRCLE:
  119. if( m_shape->GetWidth() )
  120. SetTitle( _( "Ring" ) );
  121. else
  122. SetTitle( _( "Circle" ) );
  123. // End point does not exist for a circle or ring:
  124. m_staticTextPosEnd->Show( false );
  125. m_endX.Show( false );
  126. m_endY.Show( false );
  127. // Circle center uses position controls:
  128. m_staticTextPosStart->SetLabel( _( "Center:" ) );
  129. m_startX.SetValue( m_shape->GetStart().x );
  130. m_startY.SetValue( m_shape->GetStart().y );
  131. m_radius.SetValue( m_shape->GetRadius() );
  132. m_ctrl1X.Show( false, true );
  133. m_ctrl1Y.Show( false, true );
  134. m_ctrl2X.Show( false, true );
  135. m_ctrl2Y.Show( false, true );
  136. m_staticTextPosCtrl1->Show( false );
  137. m_staticTextPosCtrl1->SetSize( 0, 0 );
  138. m_staticTextPosCtrl2->Show( false );
  139. m_staticTextPosCtrl2->SetSize( 0, 0 );
  140. m_filledCtrl->Show( true );
  141. break;
  142. case SHAPE_T::POLY:
  143. // polygon has a specific dialog editor. So nothing here
  144. break;
  145. default:
  146. SetTitle( "Unknown basic shape" );
  147. break;
  148. }
  149. return true;
  150. }
  151. bool DIALOG_PAD_PRIMITIVES_PROPERTIES::TransferDataFromWindow()
  152. {
  153. if( m_thickness.GetValue() == 0 && !m_filledCtrl->GetValue() )
  154. {
  155. DisplayError( this, _( "Line width may not be 0 for unfilled shapes." ) );
  156. m_thicknessCtrl->SetFocus();
  157. return false;
  158. }
  159. // Transfer data out of the GUI.
  160. STROKE_PARAMS stroke = m_shape->GetStroke();
  161. stroke.SetWidth( m_thickness.GetValue() );
  162. m_shape->SetStroke( stroke );
  163. m_shape->SetFilled( m_filledCtrl->GetValue() );
  164. switch( m_shape->GetShape() )
  165. {
  166. case SHAPE_T::SEGMENT:
  167. m_shape->SetStart( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  168. m_shape->SetEnd( wxPoint( m_endX.GetValue(), m_endY.GetValue() ) );
  169. break;
  170. case SHAPE_T::BEZIER:
  171. m_shape->SetStart( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  172. m_shape->SetEnd( wxPoint( m_endX.GetValue(), m_endY.GetValue() ) );
  173. m_shape->SetBezierC1( wxPoint( m_ctrl1X.GetValue(), m_ctrl1Y.GetValue()));
  174. m_shape->SetBezierC1( wxPoint( m_ctrl2X.GetValue(), m_ctrl2Y.GetValue()));
  175. break;
  176. case SHAPE_T::ARC:
  177. m_shape->SetCenter( wxPoint( m_endX.GetValue(), m_endY.GetValue() ) );
  178. m_shape->SetStart( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  179. m_shape->SetArcAngleAndEnd( m_radius.GetValue() );
  180. break;
  181. case SHAPE_T::CIRCLE:
  182. m_shape->SetStart( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  183. m_shape->SetEnd( m_shape->GetStart() + wxPoint( m_radius.GetValue(), 0 ) );
  184. break;
  185. case SHAPE_T::POLY:
  186. // polygon has a specific dialog editor. So nothing here
  187. break;
  188. default:
  189. SetTitle( "Unknown basic shape" );
  190. break;
  191. }
  192. return true;
  193. }
  194. DIALOG_PAD_PRIMITIVE_POLY_PROPS::DIALOG_PAD_PRIMITIVE_POLY_PROPS( wxWindow* aParent,
  195. PCB_BASE_FRAME* aFrame,
  196. PCB_SHAPE* aShape ) :
  197. DIALOG_PAD_PRIMITIVE_POLY_PROPS_BASE( aParent ),
  198. m_shape( aShape ),
  199. m_thickness( aFrame, m_thicknessLabel, m_thicknessCtrl, m_thicknessUnits )
  200. {
  201. if( !m_shape->GetPolyShape().IsEmpty() )
  202. {
  203. for( const VECTOR2I& pt : m_shape->GetPolyShape().Outline( 0 ).CPoints() )
  204. m_currPoints.emplace_back( pt );
  205. }
  206. m_addButton->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
  207. m_deleteButton->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
  208. m_warningIcon->SetBitmap( KiBitmap( BITMAPS::dialog_warning ) );
  209. // Test for acceptable polygon (more than 2 corners, and not self-intersecting) and
  210. // remove any redundant corners. A warning message is displayed if not OK.
  211. doValidate( true );
  212. TransferDataToWindow();
  213. m_sdbSizerOK->SetDefault();
  214. GetSizer()->SetSizeHints( this );
  215. m_gridCornersList->Connect( wxEVT_GRID_CELL_CHANGING,
  216. wxGridEventHandler( DIALOG_PAD_PRIMITIVE_POLY_PROPS::onCellChanging ),
  217. nullptr, this );
  218. // Now all widgets have the size fixed, call FinishDialogSettings
  219. finishDialogSettings();
  220. }
  221. DIALOG_PAD_PRIMITIVE_POLY_PROPS::~DIALOG_PAD_PRIMITIVE_POLY_PROPS()
  222. {
  223. m_gridCornersList->Disconnect( wxEVT_GRID_CELL_CHANGING,
  224. wxGridEventHandler( DIALOG_PAD_PRIMITIVE_POLY_PROPS::onCellChanging ),
  225. nullptr, this );
  226. }
  227. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::TransferDataToWindow()
  228. {
  229. if( m_shape == nullptr )
  230. return false;
  231. m_thickness.SetValue( m_shape->GetWidth() );
  232. m_filledCtrl->SetValue( m_shape->IsFilled() );
  233. // Populates the list of corners
  234. int extra_rows = m_currPoints.size() - m_gridCornersList->GetNumberRows();
  235. if( extra_rows > 0 )
  236. {
  237. m_gridCornersList->AppendRows( extra_rows );
  238. }
  239. else if( extra_rows < 0 )
  240. {
  241. extra_rows = -extra_rows;
  242. m_gridCornersList->DeleteRows( 0, extra_rows );
  243. }
  244. // enter others corner coordinates
  245. wxString msg;
  246. for( unsigned row = 0; row < m_currPoints.size(); ++row )
  247. {
  248. // Row label is "Corner x"
  249. msg.Printf( "Corner %d", row+1 );
  250. m_gridCornersList->SetRowLabelValue( row, msg );
  251. msg = StringFromValue( GetUserUnits(), m_currPoints[row].x, true );
  252. m_gridCornersList->SetCellValue( row, 0, msg );
  253. msg = StringFromValue( GetUserUnits(), m_currPoints[row].y, true );
  254. m_gridCornersList->SetCellValue( row, 1, msg );
  255. }
  256. return true;
  257. }
  258. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::TransferDataFromWindow()
  259. {
  260. if( !Validate() )
  261. return false;
  262. m_shape->SetPolyPoints( m_currPoints );
  263. STROKE_PARAMS stroke = m_shape->GetStroke();
  264. stroke.SetWidth( m_thickness.GetValue() );
  265. m_shape->SetStroke( stroke );
  266. m_shape->SetFilled( m_filledCtrl->GetValue() );
  267. return true;
  268. }
  269. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::Validate()
  270. {
  271. // Don't remove redundant corners while user is editing corner list
  272. return doValidate( false );
  273. }
  274. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::doValidate( bool aRemoveRedundantCorners )
  275. {
  276. if( !m_gridCornersList->CommitPendingChanges() )
  277. return false;
  278. if( m_currPoints.size() < 3 )
  279. {
  280. m_warningText->SetLabel( _("Polygon must have at least 3 corners" ) );
  281. m_warningText->Show( true );
  282. m_warningIcon->Show( true );
  283. return false;
  284. }
  285. bool valid = true;
  286. SHAPE_LINE_CHAIN polyline( m_currPoints, true );
  287. // Remove redundant corners:
  288. polyline.Simplify();
  289. if( polyline.PointCount() < 3 )
  290. {
  291. m_warningText->SetLabel( _( "Polygon must have at least 3 corners after simplification" ) );
  292. valid = false;
  293. }
  294. if( valid && polyline.SelfIntersecting() )
  295. {
  296. m_warningText->SetLabel( _( "Polygon can not be self-intersecting" ) );
  297. valid = false;
  298. }
  299. m_warningIcon->Show( !valid );
  300. m_warningText->Show( !valid );
  301. if( aRemoveRedundantCorners )
  302. {
  303. if( polyline.PointCount() != (int) m_currPoints.size() )
  304. { // Happens after simplification
  305. m_currPoints.clear();
  306. for( const VECTOR2I& pt : polyline.CPoints() )
  307. m_currPoints.emplace_back( pt );
  308. m_warningIcon->Show( true );
  309. m_warningText->Show( true );
  310. m_warningText->SetLabel( _( "Note: redundant corners removed" ) );
  311. }
  312. }
  313. return valid;
  314. }
  315. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::OnButtonAdd( wxCommandEvent& event )
  316. {
  317. if( !m_gridCornersList->CommitPendingChanges() )
  318. return;
  319. // Insert a new corner after the currently selected:
  320. wxArrayInt selections = m_gridCornersList->GetSelectedRows();
  321. int row = -1;
  322. if( m_gridCornersList->GetNumberRows() == 0 )
  323. row = 0;
  324. else if( selections.size() > 0 )
  325. row = selections[ selections.size() - 1 ] + 1;
  326. else
  327. row = m_gridCornersList->GetGridCursorRow() + 1;
  328. if( row < 0 )
  329. {
  330. wxMessageBox( _( "Select a corner to add the new corner after." ) );
  331. return;
  332. }
  333. if( m_currPoints.size() == 0 || row >= (int) m_currPoints.size() )
  334. m_currPoints.emplace_back( 0, 0 );
  335. else
  336. m_currPoints.insert( m_currPoints.begin() + row, wxPoint( 0, 0 ) );
  337. Validate();
  338. TransferDataToWindow();
  339. m_gridCornersList->ForceRefresh();
  340. // Select the new row
  341. m_gridCornersList->SelectRow( row, false );
  342. m_panelPoly->Refresh();
  343. }
  344. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::OnButtonDelete( wxCommandEvent& event )
  345. {
  346. if( !m_gridCornersList->CommitPendingChanges() )
  347. return;
  348. wxArrayInt selections = m_gridCornersList->GetSelectedRows();
  349. if( m_gridCornersList->GetNumberRows() == 0 )
  350. return;
  351. if( selections.size() == 0 && m_gridCornersList->GetGridCursorRow() >= 0 )
  352. selections.push_back( m_gridCornersList->GetGridCursorRow() );
  353. if( selections.size() == 0 )
  354. {
  355. wxMessageBox( _( "Select a corner to delete." ) );
  356. return;
  357. }
  358. // remove corners:
  359. std::sort( selections.begin(), selections.end() );
  360. for( int ii = selections.size()-1; ii >= 0 ; --ii )
  361. m_currPoints.erase( m_currPoints.begin() + selections[ii] );
  362. Validate();
  363. TransferDataToWindow();
  364. m_gridCornersList->ForceRefresh();
  365. // select the row previous to the last deleted row
  366. m_gridCornersList->SelectRow( std::max( 0, selections[ 0 ] - 1 ) );
  367. m_panelPoly->Refresh();
  368. }
  369. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onPaintPolyPanel( wxPaintEvent& event )
  370. {
  371. wxPaintDC dc( m_panelPoly );
  372. wxSize dc_size = dc.GetSize();
  373. dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
  374. // Calculate a suitable scale to fit the available draw area
  375. int minsize( Millimeter2iu( 0.5 ) );
  376. for( unsigned ii = 0; ii < m_currPoints.size(); ++ii )
  377. {
  378. minsize = std::max( minsize, std::abs( m_currPoints[ii].x ) );
  379. minsize = std::max( minsize, std::abs( m_currPoints[ii].y ) );
  380. }
  381. // The draw origin is the center of the window.
  382. // Therefore the window size is twice the minsize just calculated
  383. minsize *= 2;
  384. minsize += m_thickness.GetValue();
  385. // Give a margin
  386. double scale = std::min( double( dc_size.x ) / minsize, double( dc_size.y ) / minsize ) * 0.9;
  387. GRResetPenAndBrush( &dc );
  388. // Draw X and Y axis. This is particularly useful to show the
  389. // reference position of basic shape
  390. // Axis are drawn before the polygon to avoid masking segments on axis
  391. GRLine( nullptr, &dc, -dc_size.x, 0, dc_size.x, 0, 0, LIGHTBLUE ); // X axis
  392. GRLine( nullptr, &dc, 0, -dc_size.y, 0, dc_size.y, 0, LIGHTBLUE ); // Y axis
  393. // Draw polygon.
  394. // The selected edge(s) are shown in selectcolor, the others in normalcolor.
  395. EDA_COLOR_T normalcolor = WHITE;
  396. EDA_COLOR_T selectcolor = RED;
  397. for( unsigned ii = 0; ii < m_currPoints.size(); ++ii )
  398. {
  399. EDA_COLOR_T color = normalcolor;
  400. if( m_gridCornersList->IsInSelection (ii, 0) ||
  401. m_gridCornersList->IsInSelection (ii, 1) ||
  402. m_gridCornersList->GetGridCursorRow() == (int)ii )
  403. color = selectcolor;
  404. unsigned jj = ii + 1;
  405. if( jj >= m_currPoints.size() )
  406. jj = 0;
  407. GRLine( nullptr, &dc, m_currPoints[ii] * scale, m_currPoints[jj] * scale,
  408. m_thickness.GetValue() * scale, color );
  409. }
  410. event.Skip();
  411. }
  412. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onPolyPanelResize( wxSizeEvent& event )
  413. {
  414. m_panelPoly->Refresh();
  415. event.Skip();
  416. }
  417. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onGridSelect( wxGridRangeSelectEvent& event )
  418. {
  419. m_panelPoly->Refresh();
  420. }
  421. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onCellChanging( wxGridEvent& event )
  422. {
  423. int row = event.GetRow();
  424. int col = event.GetCol();
  425. wxString msg = event.GetString();
  426. if( msg.IsEmpty() )
  427. return;
  428. if( col == 0 ) // Set the X value
  429. m_currPoints[row].x = ValueFromString( GetUserUnits(), msg );
  430. else // Set the Y value
  431. m_currPoints[row].y = ValueFromString( GetUserUnits(), msg );
  432. Validate();
  433. m_panelPoly->Refresh();
  434. }
  435. DIALOG_PAD_PRIMITIVES_TRANSFORM::DIALOG_PAD_PRIMITIVES_TRANSFORM( wxWindow* aParent,
  436. PCB_BASE_FRAME* aFrame,
  437. std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
  438. bool aShowDuplicate ) :
  439. DIALOG_PAD_PRIMITIVES_TRANSFORM_BASE( aParent ),
  440. m_list( aList ),
  441. m_vectorX( aFrame, m_xLabel, m_xCtrl, m_xUnits ),
  442. m_vectorY( aFrame, m_yLabel, m_yCtrl, m_yUnits ),
  443. m_rotation( aFrame, m_rotationLabel, m_rotationCtrl, m_rotationUnits )
  444. {
  445. m_rotation.SetUnits( EDA_UNITS::DEGREES );
  446. if( !aShowDuplicate ) // means no duplicate transform
  447. {
  448. m_staticTextDupCnt->Show( false );
  449. m_spinCtrlDuplicateCount->Show( false );
  450. }
  451. m_sdbSizerOK->SetDefault();
  452. GetSizer()->SetSizeHints( this );
  453. }
  454. // A helper function in geometry transform
  455. inline void geom_transf( wxPoint& aCoord, const wxPoint& aMove, double aScale, double aRotation )
  456. {
  457. aCoord.x = KiROUND( aCoord.x * aScale );
  458. aCoord.y = KiROUND( aCoord.y * aScale );
  459. aCoord += aMove;
  460. RotatePoint( &aCoord, aRotation );
  461. }
  462. void DIALOG_PAD_PRIMITIVES_TRANSFORM::Transform( std::vector<std::shared_ptr<PCB_SHAPE>>* aList,
  463. int aDuplicateCount )
  464. {
  465. wxPoint move_vect( m_vectorX.GetValue(), m_vectorY.GetValue() );
  466. double rotation = m_rotation.GetValue();
  467. double scale = DoubleValueFromString( EDA_UNITS::UNSCALED, m_scaleCtrl->GetValue() );
  468. // Avoid too small / too large scale, which could create issues:
  469. if( scale < 0.01 )
  470. scale = 0.01;
  471. if( scale > 100.0 )
  472. scale = 100.0;
  473. // Transform shapes
  474. // shapes are scaled, then moved then rotated.
  475. // if aList != NULL, the initial shape will be duplicated, and transform
  476. // applied to the duplicated shape
  477. wxPoint currMoveVect = move_vect;
  478. double curr_rotation = rotation;
  479. do {
  480. for( unsigned idx = 0; idx < m_list.size(); ++idx )
  481. {
  482. std::shared_ptr<PCB_SHAPE> shape;
  483. if( aList == nullptr )
  484. {
  485. shape = m_list[idx];
  486. }
  487. else
  488. {
  489. aList->emplace_back( std::make_shared<PCB_SHAPE>( *m_list[idx] ) );
  490. shape = aList->back();
  491. }
  492. // Transform parameters common to all shape types (some can be unused)
  493. STROKE_PARAMS stroke = shape->GetStroke();
  494. stroke.SetWidth( KiROUND( shape->GetWidth() * scale ) );
  495. shape->SetStroke( stroke );
  496. shape->Move( currMoveVect );
  497. shape->Scale( scale );
  498. shape->Rotate( wxPoint( 0, 0 ), curr_rotation );
  499. }
  500. // Prepare new transform on duplication:
  501. // Each new item is rotated (or moved) by the transform from the last duplication
  502. curr_rotation += rotation;
  503. currMoveVect += move_vect;
  504. } while( aList && --aDuplicateCount > 0 );
  505. }