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.

619 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-2020 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. DIALOG_PAD_PRIMITIVES_PROPERTIES::DIALOG_PAD_PRIMITIVES_PROPERTIES( wxWindow* aParent,
  39. PCB_BASE_FRAME* aFrame,
  40. PCB_SHAPE* aShape ) :
  41. DIALOG_PAD_PRIMITIVES_PROPERTIES_BASE( aParent ),
  42. m_shape( aShape ),
  43. m_startX( aFrame, m_startXLabel, m_startXCtrl, m_startXUnits, true ),
  44. m_startY( aFrame, m_startYLabel, m_startYCtrl, m_startYUnits, true ),
  45. m_ctrl1X( aFrame, m_ctrl1XLabel, m_ctrl1XCtrl, m_ctrl1XUnits, true ),
  46. m_ctrl1Y( aFrame, m_ctrl1YLabel, m_ctrl1YCtrl, m_ctrl1YUnits, true ),
  47. m_ctrl2X( aFrame, m_ctrl2XLabel, m_ctrl2XCtrl, m_ctrl2XUnits, true ),
  48. m_ctrl2Y( aFrame, m_ctrl2YLabel, m_ctrl2YCtrl, m_ctrl2YUnits, true ),
  49. m_endX( aFrame, m_endXLabel, m_endXCtrl, m_endXUnits, true ),
  50. m_endY( aFrame, m_endYLabel, m_endYCtrl, m_endYUnits, true ),
  51. m_radius( aFrame, m_radiusLabel, m_radiusCtrl, m_radiusUnits, true ),
  52. m_thickness( aFrame, m_thicknessLabel, m_thicknessCtrl, m_thicknessUnits, true )
  53. {
  54. SetInitialFocus( m_startXCtrl );
  55. TransferDataToWindow();
  56. m_sdbSizerOK->SetDefault();
  57. FinishDialogSettings();
  58. }
  59. bool DIALOG_PAD_PRIMITIVES_PROPERTIES::TransferDataToWindow()
  60. {
  61. if( m_shape == NULL )
  62. return false;
  63. m_thickness.SetValue( m_shape->GetWidth() );
  64. m_filledCtrl->SetValue( m_shape->IsFilled() );
  65. switch( m_shape->GetShape() )
  66. {
  67. case S_SEGMENT: // Segment with rounded ends
  68. SetTitle( _( "Segment" ) );
  69. m_startX.SetValue( m_shape->GetStart().x );
  70. m_startY.SetValue( m_shape->GetStart().y );
  71. m_endX.SetValue( m_shape->GetEnd().x );
  72. m_endY.SetValue( m_shape->GetEnd().y );
  73. m_ctrl1X.Show( false, true );
  74. m_ctrl1Y.Show( false, true );
  75. m_ctrl2X.Show( false, true );
  76. m_ctrl2Y.Show( false, true );
  77. m_staticTextPosCtrl1->Show( false );
  78. m_staticTextPosCtrl1->SetSize( 0, 0 );
  79. m_staticTextPosCtrl2->Show( false );
  80. m_staticTextPosCtrl2->SetSize( 0, 0 );
  81. m_radius.Show( false );
  82. m_filledCtrl->Show( false );
  83. break;
  84. case S_CURVE: // Bezier line
  85. SetTitle( _( "Bezier" ) );
  86. m_startX.SetValue( m_shape->GetStart().x );
  87. m_startY.SetValue( m_shape->GetStart().y );
  88. m_endX.SetValue( m_shape->GetEnd().x );
  89. m_endY.SetValue( m_shape->GetEnd().y );
  90. m_ctrl1X.SetValue( m_shape->GetBezControl1().x );
  91. m_ctrl1Y.SetValue( m_shape->GetBezControl1().y );
  92. m_ctrl2X.SetValue( m_shape->GetBezControl2().x );
  93. m_ctrl2Y.SetValue( m_shape->GetBezControl2().y );
  94. m_radius.Show( false );
  95. m_filledCtrl->Show( false );
  96. break;
  97. case S_ARC: // Arc with rounded ends
  98. SetTitle( _( "Arc" ) );
  99. m_startX.SetValue( m_shape->GetEnd().x ); // confusingly, the start point of the arc
  100. m_startY.SetValue( m_shape->GetEnd().y );
  101. m_staticTextPosEnd->SetLabel( _( "Center" ) );
  102. m_endX.SetValue( m_shape->GetStart().x ); // arc center
  103. m_endY.SetValue( m_shape->GetStart().y );
  104. m_radiusLabel->SetLabel( _( "Angle:" ) );
  105. m_radius.SetUnits( EDA_UNITS::DEGREES );
  106. m_radius.SetValue( m_shape->GetAngle() );
  107. m_ctrl1X.Show( false, true );
  108. m_ctrl1Y.Show( false, true );
  109. m_ctrl2X.Show( false, true );
  110. m_ctrl2Y.Show( false, true );
  111. m_staticTextPosCtrl1->Show( false );
  112. m_staticTextPosCtrl1->SetSize( 0, 0 );
  113. m_staticTextPosCtrl2->Show( false );
  114. m_staticTextPosCtrl2->SetSize( 0, 0 );
  115. m_filledCtrl->Show( false );
  116. break;
  117. case S_CIRCLE: // ring or circle
  118. if( m_shape->GetWidth() )
  119. SetTitle( _( "Ring" ) );
  120. else
  121. SetTitle( _( "Circle" ) );
  122. // End point does not exist for a circle or ring:
  123. m_staticTextPosEnd->Show( false );
  124. m_endX.Show( false );
  125. m_endY.Show( false );
  126. // Circle center uses position controls:
  127. m_staticTextPosStart->SetLabel( _( "Center:" ) );
  128. m_startX.SetValue( m_shape->GetStart().x );
  129. m_startY.SetValue( m_shape->GetStart().y );
  130. m_radius.SetValue( m_shape->GetRadius() );
  131. m_ctrl1X.Show( false, true );
  132. m_ctrl1Y.Show( false, true );
  133. m_ctrl2X.Show( false, true );
  134. m_ctrl2Y.Show( false, true );
  135. m_staticTextPosCtrl1->Show( false );
  136. m_staticTextPosCtrl1->SetSize( 0, 0 );
  137. m_staticTextPosCtrl2->Show( false );
  138. m_staticTextPosCtrl2->SetSize( 0, 0 );
  139. m_filledCtrl->Show( true );
  140. break;
  141. case S_POLYGON: // polygon
  142. // polygon has a specific dialog editor. So nothing here
  143. break;
  144. default:
  145. SetTitle( "Unknown basic shape" );
  146. break;
  147. }
  148. return true;
  149. }
  150. bool DIALOG_PAD_PRIMITIVES_PROPERTIES::TransferDataFromWindow()
  151. {
  152. if( m_thickness.GetValue() == 0 && !m_filledCtrl->GetValue() )
  153. {
  154. DisplayError( this, _( "Line width may not be 0 for unfilled shapes." ) );
  155. m_thicknessCtrl->SetFocus();
  156. return false;
  157. }
  158. // Transfer data out of the GUI.
  159. m_shape->SetWidth( m_thickness.GetValue() );
  160. m_shape->SetFilled( m_filledCtrl->GetValue() );
  161. switch( m_shape->GetShape() )
  162. {
  163. case S_SEGMENT: // Segment with rounded ends
  164. m_shape->SetStart( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  165. m_shape->SetEnd( wxPoint( m_endX.GetValue(), m_endY.GetValue() ) );
  166. break;
  167. case S_CURVE: // Segment with rounded ends
  168. m_shape->SetStart( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  169. m_shape->SetEnd( wxPoint( m_endX.GetValue(), m_endY.GetValue() ) );
  170. m_shape->SetBezControl1( wxPoint( m_ctrl1X.GetValue(), m_ctrl1Y.GetValue() ) );
  171. m_shape->SetBezControl1( wxPoint( m_ctrl2X.GetValue(), m_ctrl2Y.GetValue() ) );
  172. break;
  173. case S_ARC: // Arc with rounded ends
  174. // NB: we store the center of the arc in m_Start, and, confusingly,
  175. // the start point in m_End
  176. m_shape->SetStart( wxPoint( m_endX.GetValue(), m_endY.GetValue() ) );
  177. m_shape->SetEnd( wxPoint( m_startX.GetValue(), m_startY.GetValue() ) );
  178. // arc angle
  179. m_shape->SetAngle( m_radius.GetValue() );
  180. break;
  181. case S_CIRCLE: // ring or 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 S_POLYGON: // polygon
  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, true )
  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( small_plus_xpm ) );
  207. m_deleteButton->SetBitmap( KiBitmap( trash_xpm ) );
  208. m_warningIcon->SetBitmap( KiBitmap( dialog_warning_xpm ) );
  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, wxGridEventHandler( DIALOG_PAD_PRIMITIVE_POLY_PROPS::onCellChanging ), NULL, this );
  216. // Now all widgets have the size fixed, call FinishDialogSettings
  217. FinishDialogSettings();
  218. }
  219. DIALOG_PAD_PRIMITIVE_POLY_PROPS::~DIALOG_PAD_PRIMITIVE_POLY_PROPS()
  220. {
  221. m_gridCornersList->Disconnect( wxEVT_GRID_CELL_CHANGING, wxGridEventHandler( DIALOG_PAD_PRIMITIVE_POLY_PROPS::onCellChanging ), NULL, this );
  222. }
  223. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::TransferDataToWindow()
  224. {
  225. if( m_shape == NULL )
  226. return false;
  227. m_thickness.SetValue( m_shape->GetWidth() );
  228. m_filledCtrl->SetValue( m_shape->IsFilled() );
  229. // Populates the list of corners
  230. int extra_rows = m_currPoints.size() - m_gridCornersList->GetNumberRows();
  231. if( extra_rows > 0 )
  232. {
  233. m_gridCornersList->AppendRows( extra_rows );
  234. }
  235. else if( extra_rows < 0 )
  236. {
  237. extra_rows = -extra_rows;
  238. m_gridCornersList->DeleteRows( 0, extra_rows );
  239. }
  240. // enter others corner coordinates
  241. wxString msg;
  242. for( unsigned row = 0; row < m_currPoints.size(); ++row )
  243. {
  244. // Row label is "Corner x"
  245. msg.Printf( "Corner %d", row+1 );
  246. m_gridCornersList->SetRowLabelValue( row, msg );
  247. msg = StringFromValue( GetUserUnits(), m_currPoints[row].x );
  248. m_gridCornersList->SetCellValue( row, 0, msg );
  249. msg = StringFromValue( GetUserUnits(), m_currPoints[row].y );
  250. m_gridCornersList->SetCellValue( row, 1, msg );
  251. }
  252. return true;
  253. }
  254. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::TransferDataFromWindow()
  255. {
  256. if( !Validate() )
  257. return false;
  258. m_shape->SetPolyPoints( m_currPoints );
  259. m_shape->SetWidth( m_thickness.GetValue() );
  260. m_shape->SetFilled( m_filledCtrl->GetValue() );
  261. return true;
  262. }
  263. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::Validate()
  264. {
  265. // Don't remove redundant corners while user is editing corner list
  266. return doValidate( false );
  267. }
  268. // test for a valid polygon (a not self intersectiong polygon)
  269. bool DIALOG_PAD_PRIMITIVE_POLY_PROPS::doValidate( bool aRemoveRedundantCorners )
  270. {
  271. if( !m_gridCornersList->CommitPendingChanges() )
  272. return false;
  273. if( m_currPoints.size() < 3 )
  274. {
  275. m_warningText->SetLabel( _("Polygon must have at least 3 corners" ) );
  276. m_warningText->Show( true );
  277. m_warningIcon->Show( true );
  278. return false;
  279. }
  280. bool valid = true;
  281. SHAPE_LINE_CHAIN polyline( m_currPoints, true );
  282. // Remove redundant corners:
  283. polyline.Simplify();
  284. if( polyline.PointCount() < 3 )
  285. {
  286. m_warningText->SetLabel( _( "Polygon must have at least 3 corners after simplification" ) );
  287. valid = false;
  288. }
  289. if( valid && polyline.SelfIntersecting() )
  290. {
  291. m_warningText->SetLabel( _( "Polygon can not be self-intersecting" ) );
  292. valid = false;
  293. }
  294. m_warningIcon->Show( !valid );
  295. m_warningText->Show( !valid );
  296. if( aRemoveRedundantCorners )
  297. {
  298. if( polyline.PointCount() != (int) m_currPoints.size() )
  299. { // Happens after simplification
  300. m_currPoints.clear();
  301. for( const VECTOR2I& pt : polyline.CPoints() )
  302. m_currPoints.emplace_back( pt );
  303. m_warningIcon->Show( true );
  304. m_warningText->Show( true );
  305. m_warningText->SetLabel( _( "Note: redundant corners removed" ) );
  306. }
  307. }
  308. return valid;
  309. }
  310. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::OnButtonAdd( wxCommandEvent& event )
  311. {
  312. if( !m_gridCornersList->CommitPendingChanges() )
  313. return;
  314. // Insert a new corner after the currently selected:
  315. wxArrayInt selections = m_gridCornersList->GetSelectedRows();
  316. int row = -1;
  317. if( m_gridCornersList->GetNumberRows() == 0 )
  318. row = 0;
  319. else if( selections.size() > 0 )
  320. row = selections[ selections.size() - 1 ] + 1;
  321. else
  322. row = m_gridCornersList->GetGridCursorRow() + 1;
  323. if( row < 0 )
  324. {
  325. wxMessageBox( _( "Select a corner to add the new corner after." ) );
  326. return;
  327. }
  328. if( m_currPoints.size() == 0 || row >= (int) m_currPoints.size() )
  329. m_currPoints.emplace_back( 0, 0 );
  330. else
  331. m_currPoints.insert( m_currPoints.begin() + row, wxPoint( 0, 0 ) );
  332. Validate();
  333. TransferDataToWindow();
  334. m_gridCornersList->ForceRefresh();
  335. // Select the new row
  336. m_gridCornersList->SelectRow( row, false );
  337. m_panelPoly->Refresh();
  338. }
  339. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::OnButtonDelete( wxCommandEvent& event )
  340. {
  341. if( !m_gridCornersList->CommitPendingChanges() )
  342. return;
  343. wxArrayInt selections = m_gridCornersList->GetSelectedRows();
  344. if( m_gridCornersList->GetNumberRows() == 0 )
  345. return;
  346. if( selections.size() == 0 && m_gridCornersList->GetGridCursorRow() >= 0 )
  347. selections.push_back( m_gridCornersList->GetGridCursorRow() );
  348. if( selections.size() == 0 )
  349. {
  350. wxMessageBox( _( "Select a corner to delete." ) );
  351. return;
  352. }
  353. // remove corners:
  354. std::sort( selections.begin(), selections.end() );
  355. for( int ii = selections.size()-1; ii >= 0 ; --ii )
  356. m_currPoints.erase( m_currPoints.begin() + selections[ii] );
  357. Validate();
  358. TransferDataToWindow();
  359. m_gridCornersList->ForceRefresh();
  360. // select the row previous to the last deleted row
  361. m_gridCornersList->SelectRow( std::max( 0, selections[ 0 ] - 1 ) );
  362. m_panelPoly->Refresh();
  363. }
  364. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onPaintPolyPanel( wxPaintEvent& event )
  365. {
  366. wxPaintDC dc( m_panelPoly );
  367. wxSize dc_size = dc.GetSize();
  368. dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 );
  369. // Calculate a suitable scale to fit the available draw area
  370. int minsize( Millimeter2iu( 0.5 ) );
  371. for( unsigned ii = 0; ii < m_currPoints.size(); ++ii )
  372. {
  373. minsize = std::max( minsize, std::abs( m_currPoints[ii].x ) );
  374. minsize = std::max( minsize, std::abs( m_currPoints[ii].y ) );
  375. }
  376. // The draw origin is the center of the window.
  377. // Therefore the window size is twice the minsize just calculated
  378. minsize *= 2;
  379. minsize += m_thickness.GetValue();
  380. // Give a margin
  381. double scale = std::min( double( dc_size.x ) / minsize, double( dc_size.y ) / minsize ) * 0.9;
  382. GRResetPenAndBrush( &dc );
  383. // Draw X and Y axis. This is particularly useful to show the
  384. // reference position of basic shape
  385. // Axis are drawn before the polygon to avoid masking segments on axis
  386. GRLine( NULL, &dc, -dc_size.x, 0, dc_size.x, 0, 0, LIGHTBLUE ); // X axis
  387. GRLine( NULL, &dc, 0, -dc_size.y, 0, dc_size.y, 0, LIGHTBLUE ); // Y axis
  388. // Draw polygon.
  389. // The selected edge(s) are shown in selectcolor, the others in normalcolor.
  390. EDA_COLOR_T normalcolor = WHITE;
  391. EDA_COLOR_T selectcolor = RED;
  392. for( unsigned ii = 0; ii < m_currPoints.size(); ++ii )
  393. {
  394. EDA_COLOR_T color = normalcolor;
  395. if( m_gridCornersList->IsInSelection (ii, 0) ||
  396. m_gridCornersList->IsInSelection (ii, 1) ||
  397. m_gridCornersList->GetGridCursorRow() == (int)ii )
  398. color = selectcolor;
  399. unsigned jj = ii + 1;
  400. if( jj >= m_currPoints.size() )
  401. jj = 0;
  402. GRLine( NULL, &dc, m_currPoints[ii] * scale, m_currPoints[jj] * scale, m_thickness.GetValue() * scale, color );
  403. }
  404. event.Skip();
  405. }
  406. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onPolyPanelResize( wxSizeEvent& event )
  407. {
  408. m_panelPoly->Refresh();
  409. event.Skip();
  410. }
  411. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onGridSelect( wxGridRangeSelectEvent& event )
  412. {
  413. m_panelPoly->Refresh();
  414. }
  415. void DIALOG_PAD_PRIMITIVE_POLY_PROPS::onCellChanging( wxGridEvent& event )
  416. {
  417. int row = event.GetRow();
  418. int col = event.GetCol();
  419. wxString msg = event.GetString();
  420. if( msg.IsEmpty() )
  421. return;
  422. if( col == 0 ) // Set the X value
  423. m_currPoints[row].x = ValueFromString( GetUserUnits(), msg );
  424. else // Set the Y value
  425. m_currPoints[row].y = ValueFromString( GetUserUnits(), msg );
  426. Validate();
  427. m_panelPoly->Refresh();
  428. }
  429. // A dialog to apply geometry transforms to a shape or set of shapes
  430. // (move, rotate around origin, scaling factor, duplication).
  431. DIALOG_PAD_PRIMITIVES_TRANSFORM::DIALOG_PAD_PRIMITIVES_TRANSFORM( wxWindow* aParent,
  432. PCB_BASE_FRAME* aFrame,
  433. std::vector<std::shared_ptr<PCB_SHAPE>>& aList,
  434. bool aShowDuplicate ) :
  435. DIALOG_PAD_PRIMITIVES_TRANSFORM_BASE( aParent ),
  436. m_list( aList ),
  437. m_vectorX( aFrame, m_xLabel, m_xCtrl, m_xUnits, true ),
  438. m_vectorY( aFrame, m_yLabel, m_yCtrl, m_yUnits, true ),
  439. m_rotation( aFrame, m_rotationLabel, m_rotationCtrl, m_rotationUnits )
  440. {
  441. m_rotation.SetUnits( EDA_UNITS::DEGREES );
  442. if( !aShowDuplicate ) // means no duplicate transform
  443. {
  444. m_staticTextDupCnt->Show( false );
  445. m_spinCtrlDuplicateCount->Show( false );
  446. }
  447. m_sdbSizerOK->SetDefault();
  448. GetSizer()->SetSizeHints( this );
  449. }
  450. // A helper function in geometry transform
  451. inline void geom_transf( wxPoint& aCoord, wxPoint& aMove, double aScale, double aRotation )
  452. {
  453. aCoord.x = KiROUND( aCoord.x * aScale );
  454. aCoord.y = KiROUND( aCoord.y * aScale );
  455. aCoord += aMove;
  456. RotatePoint( &aCoord, aRotation );
  457. }
  458. void DIALOG_PAD_PRIMITIVES_TRANSFORM::Transform( std::vector<std::shared_ptr<PCB_SHAPE>>* aList,
  459. int aDuplicateCount )
  460. {
  461. wxPoint move_vect( m_vectorX.GetValue(), m_vectorY.GetValue() );
  462. double rotation = m_rotation.GetValue();
  463. double scale = DoubleValueFromString( EDA_UNITS::UNSCALED, m_scaleCtrl->GetValue() );
  464. // Avoid too small / too large scale, which could create issues:
  465. if( scale < 0.01 )
  466. scale = 0.01;
  467. if( scale > 100.0 )
  468. scale = 100.0;
  469. // Transform shapes
  470. // shapes are scaled, then moved then rotated.
  471. // if aList != NULL, the initial shape will be duplicated, and transform
  472. // applied to the duplicated shape
  473. wxPoint currMoveVect = move_vect;
  474. double curr_rotation = rotation;
  475. do {
  476. for( unsigned idx = 0; idx < m_list.size(); ++idx )
  477. {
  478. std::shared_ptr<PCB_SHAPE> shape;
  479. if( aList == NULL )
  480. shape = m_list[idx];
  481. else
  482. {
  483. aList->emplace_back( std::make_shared<PCB_SHAPE>( *m_list[idx] ) );
  484. shape = aList->back();
  485. }
  486. // Transform parameters common to all shape types (some can be unused)
  487. shape->SetWidth( KiROUND( shape->GetWidth() * scale ) );
  488. shape->Move( currMoveVect );
  489. shape->Scale( scale );
  490. shape->Rotate( wxPoint( 0, 0 ), curr_rotation );
  491. }
  492. // Prepare new transform on duplication:
  493. // Each new item is rotated (or moved) by the transform from the last duplication
  494. curr_rotation += rotation;
  495. currMoveVect += move_vect;
  496. } while( aList && --aDuplicateCount > 0 );
  497. }