KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcbnew/dialogs/dialog_shape_properties.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2019 Jean-Pierre Charras jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * 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, see <https://www.gnu.org/licenses/>.
19 */
20
21/*
22 * Edit properties of Lines, Circles, Arcs and Polygons for PCBNew and Footprint Editor
23 */
25
26#include <wx/valnum.h>
27
28#include <pcb_base_edit_frame.h>
29#include <pcb_edit_frame.h>
30#include <board_commit.h>
35#include <string_utils.h>
36#include <tool/tool_manager.h>
37#include <tool/actions.h>
38#include <pcb_shape.h>
39#include <macros.h>
40#include <algorithm>
41#include <cstdlib>
42#include <widgets/unit_binder.h>
43
45#include <tools/drawing_tool.h>
46#include <line_ending.h>
48
49
59class GEOM_SYNCER : public wxEvtHandler
60{
61public:
62 GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
63 m_shape( aShape ),
64 m_boundCtrls( aBoundCtrls )
65 {
66 }
67
68 void BindCtrls( size_t aFrom, size_t aTo, std::function<void()> aCb )
69 {
70 wxCHECK( aFrom < m_boundCtrls.size(), /* void */ );
71 wxCHECK( aTo < m_boundCtrls.size(), /* void */ );
72
73 for( size_t i = aFrom; i <= aTo; ++i )
74 {
75 m_boundCtrls[i].m_Ctrl->Bind( wxEVT_TEXT,
76 [aCb]( wxCommandEvent& aEvent )
77 {
78 aCb();
79 } );
80 }
81 }
82
83 void SetShape( PCB_SHAPE& aShape )
84 {
85 m_shape = aShape;
86 updateAll();
87 }
88
89 virtual bool Validate( wxArrayString& aErrs ) const { return true; }
90
91protected:
92 virtual void updateAll() = 0;
93
94 wxTextCtrl* GetCtrl( size_t aIndex ) const
95 {
96 wxCHECK( aIndex < m_boundCtrls.size(), nullptr );
97 return m_boundCtrls[aIndex].m_Ctrl;
98 }
99
100 int GetIntValue( size_t aIndex ) const
101 {
102 wxCHECK( aIndex < m_boundCtrls.size(), 0.0 );
103 return static_cast<int>( m_boundCtrls[aIndex].m_Binder->GetValue() );
104 }
105
106 EDA_ANGLE GetAngleValue( size_t aIndex ) const
107 {
108 wxCHECK( aIndex < m_boundCtrls.size(), EDA_ANGLE() );
109 return m_boundCtrls[aIndex].m_Binder->GetAngleValue();
110 }
111
112 void ChangeValue( size_t aIndex, int aValue )
113 {
114 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
115 m_boundCtrls[aIndex].m_Binder->ChangeValue( aValue );
116 }
117
118 void ChangeAngleValue( size_t aIndex, const EDA_ANGLE& aValue )
119 {
120 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
121 m_boundCtrls[aIndex].m_Binder->ChangeAngleValue( aValue );
122 }
123
125
126 const PCB_SHAPE& GetShape() const { return m_shape; }
127
128private:
130 std::vector<BOUND_CONTROL>& m_boundCtrls;
131};
132
133
138{
139public:
157
158 RECTANGLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
159 GEOM_SYNCER( aShape, aBoundCtrls )
160 {
161 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
162 wxASSERT( GetShape().GetShape() == SHAPE_T::RECTANGLE );
163
165 [this]()
166 {
168 } );
169
171 [this]()
172 {
174 } );
175
177 [this]()
178 {
180 } );
181 }
182
183 bool Validate( wxArrayString& aErrs ) const override
184 {
186 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
187
188 if( p0 == p1 )
189 {
190 aErrs.push_back( _( "Rectangle cannot be zero-sized." ) );
191 return false;
192 }
193
194 return true;
195 }
196
198
200
201 void updateAll() override
202 {
206 }
207
209 {
211 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
212
213 GetShape().SetStart( p0 );
214 GetShape().SetEnd( p1 );
215
218 }
219
221 {
222 const VECTOR2I p0 = GetShape().GetStart();
223 const VECTOR2I p1 = GetShape().GetEnd();
224
225 ChangeValue( START_X, p0.x );
226 ChangeValue( START_Y, p0.y );
227 ChangeValue( END_X, p1.x );
228 ChangeValue( END_Y, p1.y );
229 }
230
232 {
234 const VECTOR2I size{ GetIntValue( CORNER_W ), GetIntValue( CORNER_H ) };
235
236 GetShape().SetStart( p0 );
237 GetShape().SetEnd( p0 + size );
238
241 }
242
252
254 {
256 const VECTOR2I size = { GetIntValue( CENTER_W ), GetIntValue( CENTER_H ) };
257
258 GetShape().SetStart( center - size / 2 );
259 GetShape().SetEnd( center + size / 2 );
260
263 }
264
274};
275
276
278{
279public:
299
300 LINE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
301 GEOM_SYNCER( aShape, aBoundCtrls )
302 {
303 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
304 wxASSERT( GetShape().GetShape() == SHAPE_T::SEGMENT );
305
307 [this]()
308 {
309 OnEndsChange();
310 } );
311
313 [this]()
314 {
316 } );
317
319 [this]()
320 {
322 } );
323 }
324
325 void updateAll() override
326 {
327 updateEnds();
328 updatePolar();
330 }
331
333 {
335 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
336
337 GetShape().SetStart( p0 );
338 GetShape().SetEnd( p1 );
339
340 updatePolar();
342 }
343
345 {
346 const VECTOR2I p0 = GetShape().GetStart();
347 const VECTOR2I p1 = GetShape().GetEnd();
348
349 ChangeValue( START_X, p0.x );
350 ChangeValue( START_Y, p0.y );
351 ChangeValue( END_X, p1.x );
352 ChangeValue( END_Y, p1.y );
353 }
354
356 {
358 const int length = GetIntValue( LENGTH );
359 const EDA_ANGLE angle = GetAngleValue( ANGLE );
360
361 const VECTOR2I polar = GetRotated( VECTOR2I{ length, 0 }, angle );
362
363 GetShape().SetStart( p0 );
364 GetShape().SetEnd( p0 + polar );
365
366 updateEnds();
368 }
369
371 {
372 const VECTOR2I p0 = GetShape().GetStart();
373 const VECTOR2I p1 = GetShape().GetEnd();
374
377 ChangeValue( LENGTH, KiROUND( p0.Distance( p1 ) ) );
378 ChangeAngleValue( ANGLE, -EDA_ANGLE( p1 - p0 ) );
379 }
380
382 {
384 const VECTOR2I mid{ GetIntValue( MID_X ), GetIntValue( MID_Y ) };
385
386 GetShape().SetStart( start );
387 GetShape().SetEnd( mid - ( start - mid ) );
388
389 updateEnds();
390 updatePolar();
391 }
392
394 {
395 const VECTOR2I s = GetShape().GetStart();
396 const VECTOR2I c = GetShape().GetCenter();
397
398 ChangeValue( MID_X, c.x );
399 ChangeValue( MID_Y, c.y );
402 }
403};
404
405
407{
408public:
410 {
411 // CSA: center, start, angle
417
418 // SME: start, mid, end
425
426 // CRAA: center, radius, angle (start), angle (end)
432
434 };
435
436 ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
437 GEOM_SYNCER( aShape, aBoundCtrls )
438 {
439 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
440 wxASSERT( GetShape().GetShape() == SHAPE_T::ARC );
441
443 [this]()
444 {
445 OnCSAChange();
446 } );
447
449 [this]()
450 {
451 OnSMEChange();
452 } );
453
455 [this]()
456 {
457 OnCRAAChange();
458 } );
459 }
460
461 bool Validate( wxArrayString& aErrs ) const override
462 {
463 const EDA_ANGLE angle = GetAngleValue( CSA_ANGLE );
464
465 if( angle == ANGLE_0 )
466 {
467 aErrs.push_back( _( "Arc angle must be greater than 0" ) );
468 return false;
469 }
470
471 if( GetIntValue( CRAA_RADIUS ) <= 0 )
472 {
473 aErrs.push_back( _( "Radius must be greater than 0" ) );
474 return false;
475 }
476
480
481 if( start == mid || mid == end )
482 {
483 aErrs.push_back( _( "Arc must have at least 2 distinct points" ) );
484 return false;
485 }
486 else
487 {
488 const VECTOR2D center = CalcArcCenter( start, mid, end );
489
490 const double radius = ( center - start ).EuclideanNorm();
491 const double max_offset = std::max( std::abs( center.x ), std::abs( center.y ) ) + radius;
492 const VECTOR2I center_i = KiROUND( center );
493
494 if( max_offset >= ( std::numeric_limits<VECTOR2I::coord_type>::max() / 2.0 ) || center_i == start
495 || center_i == end )
496 {
497 aErrs.push_back( wxString::Format( _( "Invalid Arc with radius %f and angle %f." ),
498 radius, angle.AsDegrees() ) );
499 return false;
500 }
501 }
502
503 return true;
504 }
505
506 void updateAll() override
507 {
508 updateCSA();
509 updateSME();
510 updateCRAA();
511 }
512
514 {
517 const EDA_ANGLE angle{ GetAngleValue( CSA_ANGLE ) };
518
520 GetShape().SetStart( start );
521 GetShape().SetArcAngleAndEnd( angle );
522
523 updateSME();
524 updateCRAA();
525 }
526
528 {
529 const VECTOR2I center = GetShape().GetCenter();
530 const VECTOR2I start = GetShape().GetStart();
531
534 ChangeValue( CSA_START_X, start.x );
535 ChangeValue( CSA_START_Y, start.y );
536 ChangeAngleValue( CSA_ANGLE, GetShape().GetArcAngle() );
537 }
538
540 {
544
545 GetShape().SetArcGeometry( p0, p1, p2 );
546
547 updateCSA();
548 updateCRAA();
549 }
550
552 {
553 const VECTOR2I p0 = GetShape().GetStart();
554 const VECTOR2I p1 = GetShape().GetArcMid();
555 const VECTOR2I p2 = GetShape().GetEnd();
556
559 ChangeValue( SME_MID_X, p1.x );
560 ChangeValue( SME_MID_Y, p1.y );
561 ChangeValue( SME_END_X, p2.x );
562 ChangeValue( SME_END_Y, p2.y );
563 }
564
566 {
568 const int radius = GetIntValue( CRAA_RADIUS );
569 const EDA_ANGLE start_angle = GetAngleValue( CRAA_ANGLE_START );
570 const EDA_ANGLE end_angle = GetAngleValue( CRAA_ANGLE_END );
571
572 // The angles are the directions of the start/end radius lines, measured in the
573 // same convention as the stored arc (i.e. passing the mid point), so we account
574 // for the sweep direction automatically.
575 const VECTOR2I start_pt = center + GetRotated( VECTOR2I( radius, 0 ), -start_angle );
576 const VECTOR2I end_pt = center + GetRotated( VECTOR2I( radius, 0 ), -end_angle );
577
578 EDA_ANGLE sweep = ( end_angle - start_angle ).Normalize();
579
580 // Equal angles describe a full-circle arc.
581 if( sweep == ANGLE_0 )
582 sweep = ANGLE_360;
583
584 const VECTOR2I mid_pt = center + GetRotated( VECTOR2I( radius, 0 ), -( start_angle + sweep / 2.0 ) );
585
586 GetShape().SetArcGeometry( start_pt, mid_pt, end_pt );
587
588 updateCSA();
589 updateSME();
590 }
591
593 {
594 const PCB_SHAPE& shape = GetShape();
595
596 const VECTOR2I center = shape.GetCenter();
597 const int radius = shape.GetRadius();
598 const VECTOR2I start_pt = shape.GetStart();
599 const VECTOR2I end_pt = shape.GetEnd();
600
604 ChangeAngleValue( CRAA_ANGLE_START, EDA_ANGLE( start_pt - center ).Normalize() );
605 ChangeAngleValue( CRAA_ANGLE_END, EDA_ANGLE( end_pt - center ).Normalize() );
606 }
607};
608
609
611{
612public:
625
626 CIRCLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
627 GEOM_SYNCER( aShape, aBoundCtrls )
628 {
629 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
630 wxASSERT( GetShape().GetShape() == SHAPE_T::CIRCLE );
631
633 [this]()
634 {
636 } );
637
639 [this]()
640 {
642 } );
643 }
644
645 void updateAll() override
646 {
649 }
650
651 bool Validate( wxArrayString& aErrs ) const override
652 {
653 if( GetIntValue( RADIUS ) <= 0 )
654 {
655 aErrs.push_back( _( "Radius must be greater than 0" ) );
656 return false;
657 }
658
659 return true;
660 }
661
663 {
665 const int radius = GetIntValue( RADIUS );
666
669
671 }
672
674 {
675 const VECTOR2I center = GetShape().GetCenter();
676
679 ChangeValue( RADIUS, GetShape().GetRadius() );
680 }
681
683 {
686
688 GetShape().SetEnd( pt );
689
691 }
692
694 {
695 const VECTOR2I center = GetShape().GetCenter();
696 const VECTOR2I pt = GetShape().GetEnd();
697
700 ChangeValue( PT_PT_X, pt.x );
701 ChangeValue( PT_PT_Y, pt.y );
702 }
703};
704
705
710{
711public:
722
723 ELLIPSE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
724 GEOM_SYNCER( aShape, aBoundCtrls )
725 {
726 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
727 wxASSERT( GetShape().GetShape() == SHAPE_T::ELLIPSE );
728
730 [this]()
731 {
732 OnChange();
733 } );
734 }
735
736 bool Validate( wxArrayString& aErrs ) const override
737 {
738 if( GetIntValue( MAJOR_RADIUS ) <= 0 )
739 {
740 aErrs.push_back( _( "Major radius must be greater than 0" ) );
741 return false;
742 }
743
744 if( GetIntValue( MINOR_RADIUS ) <= 0 )
745 {
746 aErrs.push_back( _( "Minor radius must be greater than 0" ) );
747 return false;
748 }
749
750 return true;
751 }
752
753 void updateAll() override
754 {
756 const int major = GetShape().GetEllipseMajorRadius();
757 const int minor = GetShape().GetEllipseMinorRadius();
758 const EDA_ANGLE rotation = GetShape().GetEllipseRotation();
759
762 ChangeValue( MAJOR_RADIUS, major );
763 ChangeValue( MINOR_RADIUS, minor );
764 ChangeAngleValue( ROTATION, rotation );
765 }
766
767 void OnChange()
768 {
770 const int major = GetIntValue( MAJOR_RADIUS );
771 const int minor = GetIntValue( MINOR_RADIUS );
772 const EDA_ANGLE rotation = GetAngleValue( ROTATION );
773
775 GetShape().SetEllipseMajorRadius( std::max( 1, major ) );
776 GetShape().SetEllipseMinorRadius( std::max( 1, minor ) );
777 GetShape().SetEllipseRotation( rotation );
778 }
779};
780
781
786{
787public:
800
801 ELLIPSE_ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
802 GEOM_SYNCER( aShape, aBoundCtrls )
803 {
804 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
805 wxASSERT( GetShape().GetShape() == SHAPE_T::ELLIPSE_ARC );
806
808 [this]()
809 {
810 OnChange();
811 } );
812 }
813
814 bool Validate( wxArrayString& aErrs ) const override
815 {
816 if( GetIntValue( MAJOR_RADIUS ) <= 0 )
817 {
818 aErrs.push_back( _( "Major radius must be greater than 0" ) );
819 return false;
820 }
821
822 if( GetIntValue( MINOR_RADIUS ) <= 0 )
823 {
824 aErrs.push_back( _( "Minor radius must be greater than 0" ) );
825 return false;
826 }
827
828 return true;
829 }
830
831 void updateAll() override
832 {
834 const int major = GetShape().GetEllipseMajorRadius();
835 const int minor = GetShape().GetEllipseMinorRadius();
836 const EDA_ANGLE rotation = GetShape().GetEllipseRotation();
837 const EDA_ANGLE start = GetShape().GetEllipseStartAngle();
839
842 ChangeValue( MAJOR_RADIUS, major );
843 ChangeValue( MINOR_RADIUS, minor );
844 ChangeAngleValue( ROTATION, rotation );
847 }
848
849 void OnChange()
850 {
852 const int major = GetIntValue( MAJOR_RADIUS );
853 const int minor = GetIntValue( MINOR_RADIUS );
854 const EDA_ANGLE rotation = GetAngleValue( ROTATION );
855 const EDA_ANGLE start = GetAngleValue( START_ANGLE );
857
859 GetShape().SetEllipseMajorRadius( std::max( 1, major ) );
860 GetShape().SetEllipseMinorRadius( std::max( 1, minor ) );
861 GetShape().SetEllipseRotation( rotation );
864 }
865};
866
867
869{
870public:
884
885 BEZIER_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
886 GEOM_SYNCER( aShape, aBoundCtrls )
887 {
888 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
889 wxASSERT( GetShape().GetShape() == SHAPE_T::BEZIER );
890
892 [this]()
893 {
895 } );
896 }
897
898 void updateAll() override
899 {
900 updateBezier();
901 }
902
904 {
906 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
909
910 GetShape().SetStart( p0 );
911 GetShape().SetEnd( p1 );
912 GetShape().SetBezierC1( c1 );
913 GetShape().SetBezierC2( c2 );
914 }
915
917 {
918 const VECTOR2I p0 = GetShape().GetStart();
919 const VECTOR2I p1 = GetShape().GetEnd();
920 const VECTOR2I c1 = GetShape().GetBezierC1();
921 const VECTOR2I c2 = GetShape().GetBezierC2();
922
923 ChangeValue( START_X, p0.x );
924 ChangeValue( START_Y, p0.y );
925 ChangeValue( END_X, p1.x );
926 ChangeValue( END_Y, p1.y );
927 ChangeValue( CTRL1_X, c1.x );
928 ChangeValue( CTRL1_Y, c1.y );
929 ChangeValue( CTRL2_X, c2.x );
930 ChangeValue( CTRL2_Y, c2.y );
931 }
932};
933
934
936{
937public:
938 DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, PCB_SHAPE* aShape );
939 ~DIALOG_SHAPE_PROPERTIES() override = default;
940
941private:
942 bool TransferDataToWindow() override;
943 bool TransferDataFromWindow() override;
944
945 void onRoundedRectChanged( wxCommandEvent& event ) override;
946 void onCornerRadius( wxCommandEvent& event ) override;
947 void onLayerSelection( wxCommandEvent& event ) override;
948 void onTechLayersChanged( wxCommandEvent& event ) override;
949
950 bool Validate() override;
952
954 {
955 bool isCopper = IsCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
956
957 m_netSelector->Enable( isCopper );
958 m_netLabel->Enable( isCopper );
959 }
960
962 {
963 bool isExtCopper = IsExternalCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
964
965 m_techLayersLabel->Enable( isExtCopper );
966 m_hasSolderMask->Enable( isExtCopper );
967
968 bool showMaskMargin = isExtCopper && m_hasSolderMask->GetValue();
969
970 m_solderMaskMarginLabel->Enable( showMaskMargin );
971 m_solderMaskMarginCtrl->Enable( showMaskMargin );
972 m_solderMaskMarginUnit->Enable( showMaskMargin );
973 }
974
975private:
978
982
983 wxBoxSizer* m_endingsSizer;
984 wxStaticText* m_startShapeLabel;
985 wxBitmapComboBox* m_startShapeChoice;
986 wxStaticText* m_endShapeLabel;
987 wxBitmapComboBox* m_endShapeChoice;
988 wxStaticText* m_startLengthLabel;
989 wxTextCtrl* m_startLengthCtrl;
990 wxStaticText* m_startLengthUnits;
991 wxStaticText* m_endLengthLabel;
992 wxTextCtrl* m_endLengthCtrl;
993 wxStaticText* m_endLengthUnits;
994 wxStaticText* m_startWidthLabel;
995 wxTextCtrl* m_startWidthCtrl;
996 wxStaticText* m_startWidthUnits;
997 wxStaticText* m_endWidthLabel;
998 wxTextCtrl* m_endWidthCtrl;
999 wxStaticText* m_endWidthUnits;
1000 wxStaticText* m_startThicknessLabel;
1001 wxTextCtrl* m_startThicknessCtrl;
1002 wxStaticText* m_startThicknessUnits;
1003 wxStaticText* m_endThicknessLabel;
1004 wxTextCtrl* m_endThicknessCtrl;
1005 wxStaticText* m_endThicknessUnits;
1006 wxStaticText* m_endingsHelpLabel;
1007
1008 std::unique_ptr<UNIT_BINDER> m_startLength;
1009 std::unique_ptr<UNIT_BINDER> m_startWidth;
1010 std::unique_ptr<UNIT_BINDER> m_startThickness;
1011 std::unique_ptr<UNIT_BINDER> m_endLength;
1012 std::unique_ptr<UNIT_BINDER> m_endWidth;
1013 std::unique_ptr<UNIT_BINDER> m_endThickness;
1014
1015 std::vector<BOUND_CONTROL> m_boundCtrls;
1016 std::unique_ptr<GEOM_SYNCER> m_geomSync;
1018};
1019
1020
1021static std::map<SHAPE_T, int> s_lastTabForShape;
1022
1023
1025{
1026 wxCHECK_RET( m_upperSizer, wxT( "Shape properties dialog has no upper sizer" ) );
1027
1028 m_endingsSizer = new wxBoxSizer( wxVERTICAL );
1029
1030 wxGridBagSizer* gbSizerEndings = new wxGridBagSizer( 3, 0 );
1031 gbSizerEndings->SetFlexibleDirection( wxBOTH );
1032 gbSizerEndings->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
1033 gbSizerEndings->SetEmptyCellSize( wxSize( 20, -1 ) );
1034
1035 m_startShapeLabel = new wxStaticText( this, wxID_ANY, _( "Start Shape:" ) );
1036 gbSizerEndings->Add( m_startShapeLabel, wxGBPosition( 0, 0 ), wxGBSpan( 1, 1 ),
1037 wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
1038
1039 m_startShapeChoice = new wxBitmapComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
1040 nullptr, wxCB_READONLY );
1041 gbSizerEndings->Add( m_startShapeChoice, wxGBPosition( 0, 1 ), wxGBSpan( 1, 2 ),
1042 wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
1043
1044 m_endShapeLabel = new wxStaticText( this, wxID_ANY, _( "End Shape:" ) );
1045 gbSizerEndings->Add( m_endShapeLabel, wxGBPosition( 0, 4 ), wxGBSpan( 1, 1 ),
1046 wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
1047
1048 m_endShapeChoice = new wxBitmapComboBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0,
1049 nullptr, wxCB_READONLY );
1050 gbSizerEndings->Add( m_endShapeChoice, wxGBPosition( 0, 5 ), wxGBSpan( 1, 2 ),
1051 wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
1052
1053 auto addEndingValue =
1054 [&]( int aRow, int aCol, const wxString& aLabel, wxStaticText*& aLabelCtrl, wxTextCtrl*& aValueCtrl,
1055 wxStaticText*& aUnitsCtrl, int aFlags )
1056 {
1057 aLabelCtrl = new wxStaticText( this, wxID_ANY, aLabel );
1058 gbSizerEndings->Add( aLabelCtrl, wxGBPosition( aRow, aCol ), wxGBSpan( 1, 1 ),
1059 wxALIGN_CENTER_VERTICAL | wxLEFT | aFlags, 15 );
1060
1061 aValueCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1, -1 ), 0 );
1062 gbSizerEndings->Add( aValueCtrl, wxGBPosition( aRow, aCol + 1 ), wxGBSpan( 1, 1 ),
1063 wxALIGN_CENTER_VERTICAL | wxLEFT | wxEXPAND, 15 );
1064
1065 aUnitsCtrl = new wxStaticText( this, wxID_ANY, _( "unit" ) );
1066 gbSizerEndings->Add( aUnitsCtrl, wxGBPosition( aRow, aCol + 2 ), wxGBSpan( 1, 1 ),
1067 wxALIGN_CENTER_VERTICAL | wxLEFT, 3 );
1068 };
1069
1070 addEndingValue( 1, 0, _( "Length:" ), m_startLengthLabel, m_startLengthCtrl, m_startLengthUnits, wxRIGHT );
1071 addEndingValue( 1, 4, _( "Length:" ), m_endLengthLabel, m_endLengthCtrl, m_endLengthUnits, wxLEFT | wxRIGHT );
1072 addEndingValue( 2, 0, _( "Width:" ), m_startWidthLabel, m_startWidthCtrl, m_startWidthUnits, wxRIGHT );
1073 addEndingValue( 2, 4, _( "Width:" ), m_endWidthLabel, m_endWidthCtrl, m_endWidthUnits, wxLEFT | wxRIGHT );
1074 addEndingValue( 3, 0, _( "Thickness:" ), m_startThicknessLabel, m_startThicknessCtrl, m_startThicknessUnits,
1075 wxRIGHT );
1076 addEndingValue( 3, 4, _( "Thickness:" ), m_endThicknessLabel, m_endThicknessCtrl, m_endThicknessUnits,
1077 wxLEFT | wxRIGHT );
1078
1079 gbSizerEndings->AddGrowableCol( 1 );
1080 gbSizerEndings->AddGrowableCol( 5 );
1081
1082 m_endingsSizer->Add( gbSizerEndings, 0, wxEXPAND | wxALL, 5 );
1083
1084 m_endingsHelpLabel = new wxStaticText( this, wxID_ANY, wxEmptyString );
1085 m_endingsSizer->Add( m_endingsHelpLabel, 0, wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, 5 );
1086
1087 m_upperSizer->Insert( 1, m_endingsSizer, 0, wxEXPAND, 5 );
1088
1089 wxColour fg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWTEXT );
1090 wxSize iconSize = GetTextExtent( wxT( "XXXXXX" ) );
1091
1092 struct
1093 {
1094 wxString name;
1095 LINE_ENDING_STYLE style;
1096 } shapeItems[] = {
1097 { _( "None" ), LINE_ENDING_STYLE::NONE },
1098 { _( "Arrow" ), LINE_ENDING_STYLE::ARROW },
1099 { _( "Open Arrow" ), LINE_ENDING_STYLE::ARROW_OPEN },
1100 { _( "Circle" ), LINE_ENDING_STYLE::CIRCLE },
1101 { _( "Square" ), LINE_ENDING_STYLE::SQUARE },
1102 };
1103
1104 for( const auto& item : shapeItems )
1105 {
1106 wxBitmap startBmp = MakeLineEndingBitmap( item.style, iconSize, fg, this, false );
1107 wxBitmap endBmp = MakeLineEndingBitmap( item.style, iconSize, fg, this, true );
1108 m_startShapeChoice->Append( item.name, startBmp );
1109 m_endShapeChoice->Append( item.name, endBmp );
1110 }
1111
1112 m_startShapeChoice->SetSelection( 0 );
1113 m_endShapeChoice->SetSelection( 0 );
1114
1115 m_startLength = std::make_unique<UNIT_BINDER>( aParent, m_startLengthLabel, m_startLengthCtrl, m_startLengthUnits );
1116 m_startWidth = std::make_unique<UNIT_BINDER>( aParent, m_startWidthLabel, m_startWidthCtrl, m_startWidthUnits );
1117 m_startThickness = std::make_unique<UNIT_BINDER>( aParent, m_startThicknessLabel, m_startThicknessCtrl,
1119 m_endLength = std::make_unique<UNIT_BINDER>( aParent, m_endLengthLabel, m_endLengthCtrl, m_endLengthUnits );
1120 m_endWidth = std::make_unique<UNIT_BINDER>( aParent, m_endWidthLabel, m_endWidthCtrl, m_endWidthUnits );
1121 m_endThickness = std::make_unique<UNIT_BINDER>( aParent, m_endThicknessLabel, m_endThicknessCtrl,
1123}
1124
1125
1128 m_parent( aParent ),
1129 m_item( aShape ),
1134{
1135 SetTitle( wxString::Format( GetTitle(), m_item->GetFriendlyName() ) );
1136 m_hash_key = TO_UTF8( GetTitle() );
1137
1138 createLineEndingControls( aParent );
1139
1140 wxFont infoFont = KIUI::GetSmallInfoFont( this );
1141 m_techLayersLabel->SetFont( infoFont );
1142
1143 // All the pages exist in the WxFB template, but we'll scrap the ones we don't
1144 // use. Constructing on-demand would work fine too.
1145 std::set<int> shownPages;
1146
1147 const auto showPage =
1148 [&]( wxSizer& aMainSizer, bool aSelect = false )
1149 {
1150 // Get the parent of the sizer, which is the panel
1151 wxWindow* page = aMainSizer.GetContainingWindow();
1152 wxCHECK( page, /* void */ );
1153 page->Layout();
1154
1155 const int pageIdx = m_notebookShapeDefs->FindPage( page );
1156 shownPages.insert( pageIdx );
1157
1158 if( aSelect )
1159 m_notebookShapeDefs->SetSelection( pageIdx );
1160 };
1161
1162 switch( m_item->GetShape() )
1163 {
1164 case SHAPE_T::RECTANGLE:
1165 // For all these functions, it's very important that the fields are added in the same order
1166 // as the CTRL_IDX enums in the GEOM_SYNCER classes.
1167 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1168 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 3, _( "End Point" ), false, m_boundCtrls );
1169
1170 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1171 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 3, _( "Size" ), true, m_boundCtrls );
1172
1173 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 0, _( "Center" ), false, m_boundCtrls );
1174 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 3, _( "Size" ), true, m_boundCtrls );
1175
1176 m_geomSync = std::make_unique<RECTANGLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1177
1178 showPage( *m_gbsRectangleByCorners, true );
1179 showPage( *m_gbsRectangleByCornerSize );
1180 showPage( *m_gbsRectangleByCenterSize );
1181 break;
1182
1183 case SHAPE_T::SEGMENT:
1184
1185 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1186 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 3, _( "End Point" ), false, m_boundCtrls );
1187
1188 AddXYPointToSizer( *aParent, *m_gbsLineByLengthAngle, 0, 0, _( "Start Point" ), false, m_boundCtrls);
1191
1192 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1193 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
1194
1195 m_geomSync = std::make_unique<LINE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1196
1197 showPage( *m_gbsLineByEnds, true );
1198 showPage( *m_gbsLineByLengthAngle );
1199 showPage( *m_gbsLineByStartMid );
1200 break;
1201
1202 case SHAPE_T::ARC:
1203 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 0, _( "Center" ), false, m_boundCtrls);
1204 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 3, _( "Start Point" ), false, m_boundCtrls);
1205 AddFieldToSizer( *aParent, *m_gbsArcByCSA, 3, 0, _( "Included Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true, m_boundCtrls );
1206
1207 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1208 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
1209 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 3, 0, _( "End Point" ), false, m_boundCtrls );
1210
1211 AddXYPointToSizer( *aParent, *m_gbsArcByCRAA, 0, 0, _( "Center" ), false, m_boundCtrls );
1212 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 0, 3, _( "Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1213 m_boundCtrls );
1214 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 1, 3, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1215 m_boundCtrls );
1216 AddFieldToSizer( *aParent, *m_gbsArcByCRAA, 2, 3, _( "End Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1217 m_boundCtrls );
1218
1219 m_geomSync = std::make_unique<ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1220
1221 showPage( *m_gbsArcByCSA, true );
1222 showPage( *m_gbsArcBySME );
1223 showPage( *m_gbsArcByCRAA );
1224 break;
1225
1226 case SHAPE_T::CIRCLE:
1227 AddXYPointToSizer( *aParent, *m_gbsCircleCenterRadius, 0, 0, _( "Center" ), false, m_boundCtrls);
1229
1230 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 0, _( "Center" ), false, m_boundCtrls );
1231 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 3, _( "Point on Circle" ), false, m_boundCtrls );
1232
1233 m_geomSync = std::make_unique<CIRCLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1234
1235 showPage( *m_gbsCircleCenterRadius, true );
1236 showPage( *m_gbsCircleCenterPoint );
1237 break;
1238
1239 case SHAPE_T::BEZIER:
1240 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 0, _( "Start Point" ), false, m_boundCtrls );
1241 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 3, _( "End Point" ), false, m_boundCtrls );
1242 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 0, _( "Control Point 1" ), false, m_boundCtrls );
1243 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 3, _( "Control Point 2" ), false, m_boundCtrls );
1244
1245 m_geomSync = std::make_unique<BEZIER_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1246
1247 showPage( *m_gbsBezier, TRUE );
1248 break;
1249
1250 case SHAPE_T::POLY:
1251 m_notebookShapeDefs->Hide();
1252 // Nothing to do here...yet
1253 break;
1254
1255 case SHAPE_T::ELLIPSE:
1256 AddXYPointToSizer( *aParent, *m_gbsEllipse, 0, 0, _( "Center" ), false, m_boundCtrls );
1257 AddFieldToSizer( *aParent, *m_gbsEllipse, 3, 0, _( "Major Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1258 m_boundCtrls );
1259 AddFieldToSizer( *aParent, *m_gbsEllipse, 4, 0, _( "Minor Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1260 m_boundCtrls );
1261 AddFieldToSizer( *aParent, *m_gbsEllipse, 5, 0, _( "Rotation" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1262 m_boundCtrls );
1263
1264 m_geomSync = std::make_unique<ELLIPSE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1265
1266 showPage( *m_gbsEllipse, true );
1267 break;
1268
1270 AddXYPointToSizer( *aParent, *m_gbsEllipseArc, 0, 0, _( "Center" ), false, m_boundCtrls );
1271 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 3, 0, _( "Major Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1272 m_boundCtrls );
1273 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 4, 0, _( "Minor Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false,
1274 m_boundCtrls );
1275 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 5, 0, _( "Rotation" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1276 m_boundCtrls );
1277 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 6, 0, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1278 m_boundCtrls );
1279 AddFieldToSizer( *aParent, *m_gbsEllipseArc, 7, 0, _( "End Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true,
1280 m_boundCtrls );
1281
1282 m_geomSync = std::make_unique<ELLIPSE_ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
1283
1284 showPage( *m_gbsEllipseArc, true );
1285 break;
1286
1287 case SHAPE_T::UNDEFINED:
1288 wxFAIL_MSG( "Undefined shape" );
1289 break;
1290 }
1291
1292 // Remove any tabs not used (Hide() doesn't work on Windows)
1293 for( int i = (int) m_notebookShapeDefs->GetPageCount() - 1; i >= 0; --i )
1294 {
1295 if( shownPages.count( i ) == 0 )
1296 m_notebookShapeDefs->RemovePage( i );
1297 }
1298
1299 // Used the last saved tab if any
1300 if( s_lastTabForShape.count( m_item->GetShape() ) > 0
1301 && s_lastTabForShape[m_item->GetShape()] < (int) m_notebookShapeDefs->GetPageCount()
1302 && s_lastTabForShape[m_item->GetShape()] >= 0 )
1303 {
1304 m_notebookShapeDefs->SetSelection( s_lastTabForShape[m_item->GetShape()] );
1305 }
1306
1307 // Find the first control in the shown tab
1308 wxWindow* tabPanel = m_notebookShapeDefs->GetCurrentPage();
1309
1310 for( size_t i = 0; i < m_boundCtrls.size(); ++i )
1311 {
1312 if( m_boundCtrls[i].m_Ctrl->IsDescendant( tabPanel ) )
1313 {
1314 m_boundCtrls[i].m_Ctrl->SetFocus();
1315 break;
1316 }
1317 }
1318
1319 // Do not allow locking items in the footprint editor
1320 m_locked->Show( dynamic_cast<PCB_EDIT_FRAME*>( aParent ) != nullptr );
1321
1322 // Configure the layers list selector
1323 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1324 {
1325 // In the footprint editor, turn off the layers that the footprint doesn't have
1326 const LSET& brdLayers = aParent->GetBoard()->GetEnabledLayers();
1327 LSET forbiddenLayers = LSET::AllLayersMask() & ~brdLayers;
1328
1329 m_LayerSelectionCtrl->SetNotAllowedLayerSet( forbiddenLayers );
1330 }
1331
1332 for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
1333 m_lineStyleCombo->Append( lineStyleDesc.name, KiBitmapBundle( lineStyleDesc.bitmap ) );
1334
1335 m_LayerSelectionCtrl->SetLayersHotkeys( false );
1336 m_LayerSelectionCtrl->SetBoardFrame( m_parent );
1337 m_LayerSelectionCtrl->Resync();
1338
1339 m_netSelector->SetNetInfo( &aParent->GetBoard()->GetNetInfo() );
1340
1341 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1342 {
1343 m_netLabel->Hide();
1344 m_netSelector->Hide();
1345 }
1346
1347 if( m_item->GetShape() == SHAPE_T::ARC || m_item->GetShape() == SHAPE_T::SEGMENT
1348 || m_item->GetShape() == SHAPE_T::BEZIER || m_item->GetShape() == SHAPE_T::ELLIPSE_ARC )
1349 {
1350 m_fillLabel->Show( false );
1351 m_fillCtrl->Show( false );
1352 }
1353
1354 if( m_item->GetShape() != SHAPE_T::RECTANGLE )
1355 {
1356 m_cbRoundRect->Show( false );
1357 m_cornerRadius.Show( false );
1358 }
1359
1360 // Only show line ending controls for open shapes
1361 m_endingsSizer->Show( !m_item->IsClosed() );
1362
1363 m_endingsHelpLabel->SetFont( KIUI::GetSmallInfoFont( this ).Italic() );
1364 m_endingsHelpLabel->SetLabel( wxString::Format( _( "Set line ending sizes to 0 for automatic (%g\u00d7 line width)." ),
1366
1368
1369 // Now all widgets have the size fixed, call FinishDialogSettings
1371}
1372
1373
1375{
1376 wxCHECK_RET( aShape, wxT( "ShowGraphicItemPropertiesDialog() error: NULL item" ) );
1377
1378 DIALOG_SHAPE_PROPERTIES dlg( this, aShape );
1379
1380 if( dlg.ShowQuasiModal() == wxID_OK )
1381 {
1382 if( aShape->IsOnLayer( GetActiveLayer() ) )
1383 {
1384 DRAWING_TOOL* drawingTool = m_toolManager->GetTool<DRAWING_TOOL>();
1385 drawingTool->SetStroke( aShape->GetStroke(), GetActiveLayer() );
1386 }
1387 }
1388}
1389
1390
1392{
1393 if( !m_cbRoundRect->GetValue() )
1394 m_cornerRadius.ChangeValue( wxEmptyString );
1395}
1396
1397
1398void DIALOG_SHAPE_PROPERTIES::onCornerRadius( wxCommandEvent &event )
1399{
1400 m_cbRoundRect->SetValue( true );
1401}
1402
1403
1405{
1406 if( m_LayerSelectionCtrl->GetLayerSelection() >= 0 )
1407 enableNetInfo();
1408
1410}
1411
1412
1414{
1416}
1417
1418
1420{
1421 if( !m_item )
1422 return false;
1423
1424 // Not all shapes have a syncer (e.g. polygons)
1425 if( m_geomSync )
1427
1428 m_fillCtrl->SetSelection( m_item->GetFillModeProp() );
1429 m_locked->SetValue( m_item->IsLocked() );
1430
1432 {
1433 if( m_item->GetCornerRadius() > 0 )
1434 {
1435 m_cbRoundRect->SetValue( true );
1437 }
1438 else
1439 {
1440 m_cbRoundRect->SetValue( false );
1441 m_cornerRadius.ChangeValue( wxEmptyString );
1442 }
1443 }
1444
1445 m_thickness.SetValue( m_item->GetStroke().GetWidth() );
1446
1447 int style = static_cast<int>( m_item->GetStroke().GetLineStyle() );
1448
1449 if( style >= 0 && style < (int) lineTypeNames.size() )
1450 m_lineStyleCombo->SetSelection( style );
1451 else
1452 m_lineStyleCombo->SetSelection( 0 );
1453
1454 m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() );
1455
1456 m_hasSolderMask->SetValue( m_item->HasSolderMask() );
1457
1458 if( m_item->GetLocalSolderMaskMargin().has_value() )
1459 m_solderMaskMargin.SetValue( m_item->GetLocalSolderMaskMargin().value() );
1460 else
1461 m_solderMaskMargin.SetValue( wxEmptyString );
1462
1463 if( m_parent->GetFrameType() == FRAME_PCB_EDITOR )
1464 {
1465 int net = m_item->GetNetCode();
1466
1467 if( net >= 0 )
1468 {
1469 m_netSelector->SetSelectedNetcode( net );
1470 }
1471 else
1472 {
1473 m_netSelector->SetIndeterminateString( INDETERMINATE_STATE );
1474 m_netSelector->SetIndeterminate();
1475 }
1476 }
1477
1478 enableNetInfo();
1480
1481 // Line endings (only for open shapes)
1482 if( !m_item->IsClosed() )
1483 {
1484 m_startShapeChoice->SetSelection( LINE_ENDING::StyleToChoiceIndex( m_item->GetStartEndingStyle() ) );
1485 m_endShapeChoice->SetSelection( LINE_ENDING::StyleToChoiceIndex( m_item->GetEndEndingStyle() ) );
1486 m_startLength->SetValue( m_item->GetStartEndingLength() );
1487 m_startWidth->SetValue( m_item->GetStartEndingWidth() );
1488 m_startThickness->SetValue( m_item->GetStartEndingStrokeWidth() );
1489 m_endLength->SetValue( m_item->GetEndEndingLength() );
1490 m_endWidth->SetValue( m_item->GetEndEndingWidth() );
1491 m_endThickness->SetValue( m_item->GetEndEndingStrokeWidth() );
1492 }
1493
1494 return DIALOG_SHAPE_PROPERTIES_BASE::TransferDataToWindow();
1495}
1496
1497
1499{
1500 if( !DIALOG_SHAPE_PROPERTIES_BASE::TransferDataFromWindow() )
1501 return false;
1502
1503 if( !m_item )
1504 return true;
1505
1506 int layer = m_LayerSelectionCtrl->GetLayerSelection();
1507
1508 BOARD_COMMIT commit( m_parent );
1509 commit.Modify( m_item );
1510
1511 bool pushCommit = ( m_item->GetEditFlags() == 0 );
1512
1513 // Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
1514 // SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
1515 if( !pushCommit )
1516 m_item->SetFlags( IN_EDIT );
1517
1519
1520 bool wasLocked = m_item->IsLocked();
1521
1522 if( m_item->GetShape() == SHAPE_T::RECTANGLE )
1523 m_item->SetCornerRadius( m_cbRoundRect->GetValue() ? m_cornerRadius.GetIntValue() : 0 );
1524
1525 m_item->SetFillModeProp( (UI_FILL_MODE) m_fillCtrl->GetSelection() );
1526 m_item->SetLocked( m_locked->GetValue() );
1527
1528 m_item->SetWidth( m_thickness.GetIntValue() );
1529
1530 auto it = lineTypeNames.begin();
1531 std::advance( it, m_lineStyleCombo->GetSelection() );
1532
1533 if( it == lineTypeNames.end() )
1534 m_item->SetLineStyle( LINE_STYLE::SOLID );
1535 else
1536 m_item->SetLineStyle( it->first );
1537
1538 m_item->SetLayer( ToLAYER_ID( layer ) );
1539
1540 m_item->SetHasSolderMask( m_hasSolderMask->GetValue() );
1541
1542 if( m_solderMaskMargin.IsNull() )
1543 m_item->SetLocalSolderMaskMargin( {} );
1544 else
1545 m_item->SetLocalSolderMaskMargin( m_solderMaskMargin.GetIntValue() );
1546
1547 // Line endings (only for open shapes)
1548 if( !m_item->IsClosed() )
1549 {
1550 int startSel = m_startShapeChoice->GetSelection();
1551
1552 if( startSel >= 0 && startSel < LINE_ENDING::s_defaultChoiceCount )
1553 m_item->SetStartEndingStyle( LINE_ENDING::s_defaultChoiceOrder[startSel] );
1554
1555 int endSel = m_endShapeChoice->GetSelection();
1556
1557 if( endSel >= 0 && endSel < LINE_ENDING::s_defaultChoiceCount )
1558 m_item->SetEndEndingStyle( LINE_ENDING::s_defaultChoiceOrder[endSel] );
1559
1560 if( !m_startLength->IsIndeterminate() )
1561 m_item->SetStartEndingLength( std::max( 0, m_startLength->GetIntValue() ) );
1562
1563 if( !m_startWidth->IsIndeterminate() )
1564 m_item->SetStartEndingWidth( std::max( 0, m_startWidth->GetIntValue() ) );
1565
1566 if( !m_startThickness->IsIndeterminate() )
1567 m_item->SetStartEndingStrokeWidth( std::max( 0, m_startThickness->GetIntValue() ) );
1568
1569 if( !m_endLength->IsIndeterminate() )
1570 m_item->SetEndEndingLength( std::max( 0, m_endLength->GetIntValue() ) );
1571
1572 if( !m_endWidth->IsIndeterminate() )
1573 m_item->SetEndEndingWidth( std::max( 0, m_endWidth->GetIntValue() ) );
1574
1575 if( !m_endThickness->IsIndeterminate() )
1576 m_item->SetEndEndingStrokeWidth( std::max( 0, m_endThickness->GetIntValue() ) );
1577 }
1578
1579 m_item->RebuildBezierToSegmentsPointsList( m_item->GetMaxError() );
1580
1581 if( m_item->IsOnCopperLayer() )
1582 m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
1583 else
1584 m_item->SetNetCode( -1 );
1585
1586 if( pushCommit )
1587 commit.Push( _( "Edit Shape Properties" ) );
1588
1589 // Save the tab
1590 s_lastTabForShape[m_item->GetShape()] = m_notebookShapeDefs->GetSelection();
1591
1592 // Notify clients which treat locked and unlocked items differently (ie: POINT_EDITOR)
1593 if( wasLocked != m_item->IsLocked() )
1594 m_parent->GetToolManager()->PostEvent( EVENTS::SelectedEvent );
1595
1596 return true;
1597}
1598
1599
1601{
1602 wxArrayString errors;
1603
1604 if( !DIALOG_SHAPE_PROPERTIES_BASE::Validate() )
1605 return false;
1606
1607 if( m_geomSync )
1608 m_geomSync->Validate( errors );
1609
1610 // Type specific checks.
1611 switch( m_item->GetShape() )
1612 {
1613 case SHAPE_T::ARC:
1614 if( m_thickness.GetValue() <= 0 )
1615 errors.Add( _( "Line width must be greater than zero." ) );
1616 break;
1617
1618 case SHAPE_T::CIRCLE:
1619 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1620 errors.Add( _( "Line width must be greater than zero for an unfilled circle." ) );
1621
1622 break;
1623
1624 case SHAPE_T::RECTANGLE:
1625 {
1626 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1627 errors.Add( _( "Line width must be greater than zero for an unfilled rectangle." ) );
1628
1629 const RECTANGLE_GEOM_SYNCER* rectGeomSync = static_cast<RECTANGLE_GEOM_SYNCER*>( m_geomSync.get() );
1630 int shortSide = std::min( rectGeomSync->GetRectangleWidth(), rectGeomSync->GetRectangleHeight() );
1631
1632 if( m_cbRoundRect->GetValue() && m_cornerRadius.GetIntValue() * 2 > shortSide )
1633 {
1634 errors.Add( _( "Corner radius must be less than or equal to half the smaller side." ) );
1635 m_cornerRadius.SetValue( KiROUND( shortSide / 2.0 ) );
1636 }
1637
1638 break;
1639 }
1640
1641 case SHAPE_T::POLY:
1642 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1643 errors.Add( _( "Line width must be greater than zero for an unfilled polygon." ) );
1644
1645 break;
1646
1647 case SHAPE_T::SEGMENT:
1648 if( m_thickness.GetValue() <= 0 )
1649 errors.Add( _( "Line width must be greater than zero." ) );
1650
1651 break;
1652
1653 case SHAPE_T::BEZIER:
1654 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1655 errors.Add( _( "Line width must be greater than zero for an unfilled curve." ) );
1656
1657 break;
1658
1659 case SHAPE_T::ELLIPSE:
1660 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1661 errors.Add( _( "Line width must be greater than zero for an unfilled ellipse." ) );
1662
1663 break;
1664
1666 if( m_thickness.GetValue() <= 0 )
1667 errors.Add( _( "Line width must be greater than zero." ) );
1668
1669 break;
1670
1671 default:
1672 UNIMPLEMENTED_FOR( m_item->SHAPE_T_asString() );
1673 break;
1674 }
1675
1676 if( errors.GetCount() )
1677 {
1678 HTML_MESSAGE_BOX dlg( this, _( "Error List" ) );
1679 dlg.ListSet( errors );
1680 dlg.ShowModal();
1681 }
1682
1683 return errors.GetCount() == 0;
1684}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
ARC_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool Validate(wxArrayString &aErrs) const override
BEZIER_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool IsLocked() const override
const NETINFO_LIST & GetNetInfo() const
Definition board.h:1215
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1185
bool Validate(wxArrayString &aErrs) const override
CIRCLE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
DIALOG_SHAPE_PROPERTIES_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("%s Properties"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
std::unique_ptr< UNIT_BINDER > m_endWidth
wxBitmapComboBox * m_startShapeChoice
std::unique_ptr< UNIT_BINDER > m_startWidth
void createLineEndingControls(SCH_BASE_FRAME *aParent)
wxBitmapComboBox * m_endShapeChoice
DIALOG_SHAPE_PROPERTIES(SCH_BASE_FRAME *aParent, SCH_SHAPE *aShape)
std::unique_ptr< UNIT_BINDER > m_startLength
std::unique_ptr< UNIT_BINDER > m_endLength
std::unique_ptr< UNIT_BINDER > m_startThickness
bool TransferDataToWindow() override
std::unique_ptr< UNIT_BINDER > m_endThickness
void onCornerRadius(wxCommandEvent &event) override
~DIALOG_SHAPE_PROPERTIES() override=default
void onRoundedRectChanged(wxCommandEvent &event) override
void onLayerSelection(wxCommandEvent &event) override
void onTechLayersChanged(wxCommandEvent &event) override
bool TransferDataFromWindow() override
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
int ShowModal() override
Tool responsible for drawing graphical elements like lines, arcs, circles, etc.
void SetStroke(const STROKE_PARAMS &aStroke, PCB_LAYER_ID aLayer)
double AsDegrees() const
Definition eda_angle.h:115
UI_FILL_MODE GetFillModeProp() const
int GetEllipseMinorRadius() const
Definition eda_shape.h:395
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:368
const VECTOR2I & GetEllipseCenter() const
Definition eda_shape.h:377
void SetCenter(const VECTOR2I &aCenter)
EDA_ANGLE GetEllipseEndAngle() const
Definition eda_shape.h:423
int GetEllipseMajorRadius() const
Definition eda_shape.h:386
EDA_ANGLE GetEllipseRotation() const
Definition eda_shape.h:404
int GetRadius() const
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
void SetRadius(int aX)
Definition eda_shape.h:350
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
EDA_ANGLE GetEllipseStartAngle() const
Definition eda_shape.h:414
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:365
int GetCornerRadius() const
VECTOR2I GetArcMid() const
ELLIPSE_ARC_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
bool Validate(wxArrayString &aErrs) const override
bool Validate(wxArrayString &aErrs) const override
ELLIPSE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
static const TOOL_EVENT SelectedEvent
Definition actions.h:345
void ChangeValue(size_t aIndex, int aValue)
wxTextCtrl * GetCtrl(size_t aIndex) const
EDA_ANGLE GetAngleValue(size_t aIndex) const
void BindCtrls(size_t aFrom, size_t aTo, std::function< void()> aCb)
virtual void updateAll()=0
int GetIntValue(size_t aIndex) const
virtual bool Validate(wxArrayString &aErrs) const
const PCB_SHAPE & GetShape() const
void ChangeAngleValue(size_t aIndex, const EDA_ANGLE &aValue)
std::vector< BOUND_CONTROL > & m_boundCtrls
GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
void ListSet(const wxString &aList)
Add a list of items.
static constexpr double DEFAULT_RATIO_LENGTH
static const int s_defaultChoiceCount
static const LINE_ENDING_STYLE s_defaultChoiceOrder[]
Dropdown display order for line ending style choosers.
Definition line_ending.h:38
static int StyleToChoiceIndex(LINE_ENDING_STYLE aStyle)
Return the dropdown index for the given style, or 0 (NONE) if not found.
LINE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllLayersMask()
Definition lset.cpp:637
Common, abstract interface for edit frames.
void ShowGraphicItemPropertiesDialog(PCB_SHAPE *aShape)
virtual PCB_LAYER_ID GetActiveLayer() const
BOARD * GetBoard() const
The main frame for Pcbnew.
void SetEllipseCenter(const VECTOR2I &aPt) override
void SetBezierC1(const VECTOR2I &aPt) override
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:78
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Definition pcb_shape.h:107
void SetEllipseStartAngle(const EDA_ANGLE &aA) override
void SetEllipseEndAngle(const EDA_ANGLE &aA) override
void SetEnd(const VECTOR2I &aEnd) override
void SetEllipseRotation(const EDA_ANGLE &aA) override
void SetEllipseMinorRadius(int aR) override
STROKE_PARAMS GetStroke() const override
void SetStart(const VECTOR2I &aStart) override
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
void SetBezierC2(const VECTOR2I &aPt) override
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd) override
Set the three controlling points for an arc.
void SetEllipseMajorRadius(int aR) override
Class that keeps a rectangle's various fields all up to date.
bool Validate(wxArrayString &aErrs) const override
RECTANGLE_GEOM_SYNCER(PCB_SHAPE &aShape, std::vector< BOUND_CONTROL > &aBoundCtrls)
TOOL_MANAGER * m_toolManager
virtual void ChangeValue(int aValue)
Set new value (in Internal Units) for the text field, taking care of units conversion WITHOUT trigger...
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:574
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:448
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:454
UI_FILL_MODE
Definition eda_fill.h:41
@ SOLID
Definition eda_fill.h:43
#define IN_EDIT
Item currently edited.
@ UNDEFINED
Definition eda_shape.h:55
@ ELLIPSE
Definition eda_shape.h:62
@ SEGMENT
Definition eda_shape.h:56
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:57
@ ELLIPSE_ARC
Definition eda_shape.h:63
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:39
void AddXYPointToSizer(EDA_DRAW_FRAME &aFrame, wxGridBagSizer &aSizer, int row, int col, const wxString &aName, bool aRelative, std::vector< BOUND_CONTROL > &aBoundCtrls)
void AddFieldToSizer(EDA_DRAW_FRAME &aFrame, wxGridBagSizer &aSizer, int row, int col, const wxString &aName, ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType, bool aIsAngle, std::vector< BOUND_CONTROL > &aBoundCtrls)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition idf_helpers.h:37
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:714
LINE_ENDING_STYLE
Line ending styles for graphic lines, arcs, and beziers.
Definition line_ending.h:44
wxBitmap MakeLineEndingBitmap(LINE_ENDING_STYLE aStyle, const wxSize &aSize, const wxColour &aForeground, wxWindow *aWindow, bool aShapeOnRight)
Generate a DPI-aware bitmap icon showing a line ending style.
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:750
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:92
KICOMMON_API wxFont GetSmallInfoFont(wxWindow *aWindow)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
static std::map< SHAPE_T, int > s_lastTabForShape
static bool isCopper(const PNS::ITEM *aItem)
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
Conversion map between LINE_STYLE values and style names displayed.
VECTOR2I center
int radius
VECTOR2I end
VECTOR2I GetRotated(const VECTOR2I &aVector, const EDA_ANGLE &aAngle)
Return a new VECTOR2I that is the result of rotating aVector by aAngle.
Definition trigo.h:73
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition trigo.cpp:506
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707