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