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