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, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25/*
26 * Edit properties of Lines, Circles, Arcs and Polygons for PCBNew and Footprint Editor
27 */
29
30#include <wx/valnum.h>
31
32#include <pcb_base_edit_frame.h>
33#include <pcb_edit_frame.h>
34#include <board_commit.h>
39#include <string_utils.h>
40#include <tool/tool_manager.h>
41#include <tool/actions.h>
42#include <pcb_shape.h>
43#include <macros.h>
44#include <algorithm>
45#include <widgets/unit_binder.h>
46
47#include <tools/drawing_tool.h>
48
49
51{
52 std::unique_ptr<UNIT_BINDER> m_Binder;
53 wxTextCtrl* m_Ctrl;
54};
55
56
66class GEOM_SYNCER : public wxEvtHandler
67{
68public:
69 GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
70 m_shape( aShape ),
71 m_boundCtrls( aBoundCtrls )
72 {
73 }
74
75 void BindCtrls( size_t aFrom, size_t aTo, std::function<void()> aCb )
76 {
77 wxCHECK( aFrom < m_boundCtrls.size(), /* void */ );
78 wxCHECK( aTo < m_boundCtrls.size(), /* void */ );
79
80 for( size_t i = aFrom; i <= aTo; ++i )
81 {
82 m_boundCtrls[i].m_Ctrl->Bind( wxEVT_TEXT,
83 [aCb]( wxCommandEvent& aEvent )
84 {
85 aCb();
86 } );
87 }
88 }
89
90 void SetShape( PCB_SHAPE& aShape )
91 {
92 m_shape = aShape;
93 updateAll();
94 }
95
96 virtual bool Validate( wxArrayString& aErrs ) const { return true; }
97
98protected:
99 virtual void updateAll() = 0;
100
101 wxTextCtrl* GetCtrl( size_t aIndex ) const
102 {
103 wxCHECK( aIndex < m_boundCtrls.size(), nullptr );
104 return m_boundCtrls[aIndex].m_Ctrl;
105 }
106
107 int GetIntValue( size_t aIndex ) const
108 {
109 wxCHECK( aIndex < m_boundCtrls.size(), 0.0 );
110 return static_cast<int>( m_boundCtrls[aIndex].m_Binder->GetValue() );
111 }
112
113 EDA_ANGLE GetAngleValue( size_t aIndex ) const
114 {
115 wxCHECK( aIndex < m_boundCtrls.size(), EDA_ANGLE() );
116 return m_boundCtrls[aIndex].m_Binder->GetAngleValue();
117 }
118
119 void ChangeValue( size_t aIndex, int aValue )
120 {
121 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
122 m_boundCtrls[aIndex].m_Binder->ChangeValue( aValue );
123 }
124
125 void ChangeAngleValue( size_t aIndex, const EDA_ANGLE& aValue )
126 {
127 wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
128 m_boundCtrls[aIndex].m_Binder->ChangeAngleValue( aValue );
129 }
130
132
133 const PCB_SHAPE& GetShape() const { return m_shape; }
134
135private:
137 std::vector<BOUND_CONTROL>& m_boundCtrls;
138};
139
140
145{
146public:
164
165 RECTANGLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
166 GEOM_SYNCER( aShape, aBoundCtrls )
167 {
168 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
169 wxASSERT( GetShape().GetShape() == SHAPE_T::RECTANGLE );
170
172 [this]()
173 {
175 } );
176
178 [this]()
179 {
181 } );
182
184 [this]()
185 {
187 } );
188 }
189
190 bool Validate( wxArrayString& aErrs ) const override
191 {
193 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
194
195 if( p0 == p1 )
196 {
197 aErrs.push_back( _( "Rectangle cannot be zero-sized." ) );
198 return false;
199 }
200
201 return true;
202 }
203
204 void updateAll() override
205 {
209 }
210
212 {
214 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
215
216 GetShape().SetStart( p0 );
217 GetShape().SetEnd( p1 );
218
221 }
222
224 {
225 const VECTOR2I p0 = GetShape().GetStart();
226 const VECTOR2I p1 = GetShape().GetEnd();
227
228 ChangeValue( START_X, p0.x );
229 ChangeValue( START_Y, p0.y );
230 ChangeValue( END_X, p1.x );
231 ChangeValue( END_Y, p1.y );
232 }
233
235 {
237 const VECTOR2I size{ GetIntValue( CORNER_W ), GetIntValue( CORNER_H ) };
238
239 GetShape().SetStart( p0 );
240 GetShape().SetEnd( p0 + size );
241
244 }
245
247 {
248 const VECTOR2I p0 = GetShape().GetStart();
249
250 ChangeValue( CORNER_X, p0.x );
251 ChangeValue( CORNER_Y, p0.y );
252 ChangeValue( CORNER_W, GetShape().GetRectangleWidth() );
253 ChangeValue( CORNER_H, GetShape().GetRectangleHeight() );
254 }
255
257 {
259 const VECTOR2I size = { GetIntValue( CENTER_W ), GetIntValue( CENTER_H ) };
260
261 GetShape().SetStart( center - size / 2 );
262 GetShape().SetEnd( center + size / 2 );
263
266 }
267
269 {
270 const VECTOR2I c = GetShape().GetCenter();
271
272 ChangeValue( CENTER_X, c.x );
273 ChangeValue( CENTER_Y, c.y );
274 ChangeValue( CENTER_W, GetShape().GetRectangleWidth() );
275 ChangeValue( CENTER_H, GetShape().GetRectangleHeight() );
276 }
277};
278
279
281{
282public:
302
303 LINE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
304 GEOM_SYNCER( aShape, aBoundCtrls )
305 {
306 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
307 wxASSERT( GetShape().GetShape() == SHAPE_T::SEGMENT );
308
310 [this]()
311 {
312 OnEndsChange();
313 } );
314
316 [this]()
317 {
319 } );
320
322 [this]()
323 {
325 } );
326 }
327
328 void updateAll() override
329 {
330 updateEnds();
331 updatePolar();
333 }
334
336 {
338 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
339
340 GetShape().SetStart( p0 );
341 GetShape().SetEnd( p1 );
342
343 updatePolar();
345 }
346
348 {
349 const VECTOR2I p0 = GetShape().GetStart();
350 const VECTOR2I p1 = GetShape().GetEnd();
351
352 ChangeValue( START_X, p0.x );
353 ChangeValue( START_Y, p0.y );
354 ChangeValue( END_X, p1.x );
355 ChangeValue( END_Y, p1.y );
356 }
357
359 {
361 const int length = GetIntValue( LENGTH );
362 const EDA_ANGLE angle = GetAngleValue( ANGLE );
363
364 const VECTOR2I polar = GetRotated( VECTOR2I{ length, 0 }, angle );
365
366 GetShape().SetStart( p0 );
367 GetShape().SetEnd( p0 + polar );
368
369 updateEnds();
371 }
372
374 {
375 const VECTOR2I p0 = GetShape().GetStart();
376 const VECTOR2I p1 = GetShape().GetEnd();
377
380 ChangeValue( LENGTH, p0.Distance( p1 ) );
381 ChangeAngleValue( ANGLE, -EDA_ANGLE( p1 - p0 ) );
382 }
383
385 {
387 const VECTOR2I mid{ GetIntValue( MID_X ), GetIntValue( MID_Y ) };
388
389 GetShape().SetStart( start );
390 GetShape().SetEnd( mid - ( start - mid ) );
391
392 updateEnds();
393 updatePolar();
394 }
395
397 {
398 const VECTOR2I s = GetShape().GetStart();
399 const VECTOR2I c = GetShape().GetCenter();
400
401 ChangeValue( MID_X, c.x );
402 ChangeValue( MID_Y, c.y );
405 }
406};
407
408
410{
411public:
430
431 ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
432 GEOM_SYNCER( aShape, aBoundCtrls )
433 {
434 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
435 wxASSERT( GetShape().GetShape() == SHAPE_T::ARC );
436
438 [this]()
439 {
440 OnCSAChange();
441 } );
442
444 [this]()
445 {
446 OnSMEChange();
447 } );
448 }
449
450 bool Validate( wxArrayString& aErrs ) const override
451 {
452 const EDA_ANGLE angle = GetAngleValue( CSA_ANGLE );
453
454 if( angle == ANGLE_0 )
455 {
456 aErrs.push_back( _( "Arc angle must be greater than 0" ) );
457 return false;
458 }
459
463
464 if( start == mid || mid == end || start == end )
465 {
466 aErrs.push_back( _( "Arc must have 3 distinct points" ) );
467 return false;
468 }
469 else
470 {
471 const VECTOR2D center = CalcArcCenter( start, end, angle );
472
473 double radius = ( center - start ).EuclideanNorm();
474 double max_offset = std::max( std::abs( center.x ), std::abs( center.y ) ) + radius;
475 VECTOR2I center_i = VECTOR2I( center.x, center.y );
476
477 if( max_offset >= ( std::numeric_limits<VECTOR2I::coord_type>::max() / 2.0 )
478 || center_i == start || center_i == end )
479 {
480 aErrs.push_back( wxString::Format( _( "Invalid Arc with radius %f and angle %f." ),
481 radius, angle.AsDegrees() ) );
482 return false;
483 }
484 }
485
486 return true;
487 }
488
489 void updateAll() override
490 {
491 updateCSA();
492 updateSME();
493 }
494
496 {
499 const EDA_ANGLE angle{ GetAngleValue( CSA_ANGLE ) };
500
502 GetShape().SetStart( start );
503 GetShape().SetArcAngleAndEnd( angle );
504
505 updateSME();
506 }
507
509 {
510 const VECTOR2I center = GetShape().GetCenter();
511 const VECTOR2I start = GetShape().GetStart();
512
515 ChangeValue( CSA_START_X, start.x );
516 ChangeValue( CSA_START_Y, start.y );
517 ChangeAngleValue( CSA_ANGLE, GetShape().GetArcAngle() );
518 }
519
521 {
525
526 GetShape().SetArcGeometry( p0, p1, p2 );
527
528 updateCSA();
529 }
530
532 {
533 const VECTOR2I p0 = GetShape().GetStart();
534 const VECTOR2I p1 = GetShape().GetArcMid();
535 const VECTOR2I p2 = GetShape().GetEnd();
536
539 ChangeValue( SME_MID_X, p1.x );
540 ChangeValue( SME_MID_Y, p1.y );
541 ChangeValue( SME_END_X, p2.x );
542 ChangeValue( SME_END_Y, p2.y );
543 }
544};
545
546
548{
549public:
562
563 CIRCLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
564 GEOM_SYNCER( aShape, aBoundCtrls )
565 {
566 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
567 wxASSERT( GetShape().GetShape() == SHAPE_T::CIRCLE );
568
570 [this]()
571 {
573 } );
574
576 [this]()
577 {
579 } );
580 }
581
582 void updateAll() override
583 {
586 }
587
588 bool Validate( wxArrayString& aErrs ) const override
589 {
590 if( GetIntValue( RADIUS ) <= 0 )
591 {
592 aErrs.push_back( _( "Radius must be greater than 0" ) );
593 return false;
594 }
595
596 return true;
597 }
598
600 {
602 const int radius = GetIntValue( RADIUS );
603
606
608 }
609
611 {
612 const VECTOR2I center = GetShape().GetCenter();
613
616 ChangeValue( RADIUS, GetShape().GetRadius() );
617 }
618
620 {
623
625 GetShape().SetEnd( pt );
626
628 }
629
631 {
632 const VECTOR2I center = GetShape().GetCenter();
633 const VECTOR2I pt = GetShape().GetEnd();
634
637 ChangeValue( PT_PT_X, pt.x );
638 ChangeValue( PT_PT_Y, pt.y );
639 }
640};
641
642
644{
645public:
659
660 BEZIER_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
661 GEOM_SYNCER( aShape, aBoundCtrls )
662 {
663 wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
664 wxASSERT( GetShape().GetShape() == SHAPE_T::BEZIER );
665
667 [this]()
668 {
670 } );
671 }
672
673 void updateAll() override
674 {
675 updateBezier();
676 }
677
679 {
681 const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
684
685 GetShape().SetStart( p0 );
686 GetShape().SetEnd( p1 );
687 GetShape().SetBezierC1( c1 );
688 GetShape().SetBezierC2( c2 );
689 }
690
692 {
693 const VECTOR2I p0 = GetShape().GetStart();
694 const VECTOR2I p1 = GetShape().GetEnd();
695 const VECTOR2I c1 = GetShape().GetBezierC1();
696 const VECTOR2I c2 = GetShape().GetBezierC2();
697
698 ChangeValue( START_X, p0.x );
699 ChangeValue( START_Y, p0.y );
700 ChangeValue( END_X, p1.x );
701 ChangeValue( END_Y, p1.y );
702 ChangeValue( CTRL1_X, c1.x );
703 ChangeValue( CTRL1_Y, c1.y );
704 ChangeValue( CTRL2_X, c2.x );
705 ChangeValue( CTRL2_Y, c2.y );
706 }
707};
708
709
711{
712public:
713 DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, PCB_SHAPE* aShape );
714 ~DIALOG_SHAPE_PROPERTIES() override = default;
715
716private:
717 bool TransferDataToWindow() override;
718 bool TransferDataFromWindow() override;
719
720 void onRoundedRectChanged( wxCommandEvent& event ) override;
721 void onCornerRadius( wxCommandEvent& event ) override;
722 void onLayerSelection( wxCommandEvent& event ) override;
723 void onTechLayersChanged( wxCommandEvent& event ) override;
724
725 bool Validate() override;
726
728 {
729 bool isCopper = IsCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
730
731 m_netSelector->Enable( isCopper );
732 m_netLabel->Enable( isCopper );
733 }
734
736 {
737 bool isExtCopper = IsExternalCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
738
739 m_techLayersLabel->Enable( isExtCopper );
740 m_hasSolderMask->Enable( isExtCopper );
741
742 bool showMaskMargin = isExtCopper && m_hasSolderMask->GetValue();
743
744 m_solderMaskMarginLabel->Enable( showMaskMargin );
745 m_solderMaskMarginCtrl->Enable( showMaskMargin );
746 m_solderMaskMarginUnit->Enable( showMaskMargin );
747 }
748
749private:
752
756
757 std::vector<BOUND_CONTROL> m_boundCtrls;
758 std::unique_ptr<GEOM_SYNCER> m_geomSync;
760};
761
762
763static void AddXYPointToSizer( EDA_DRAW_FRAME& aFrame, wxGridBagSizer& aSizer, int row, int col,
764 const wxString& aName, bool aRelative, std::vector<BOUND_CONTROL>& aBoundCtrls )
765{
766 // Name
767 // X [Ctrl] mm
768 // Y [Ctrl] mm
769 wxWindow* parent = aSizer.GetContainingWindow();
770
771 wxStaticText* titleLabel = new wxStaticText( parent, wxID_ANY, aName );
772 aSizer.Add( titleLabel, wxGBPosition( row, col ), wxGBSpan( 1, 3 ),
773 wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL | wxALL | wxEXPAND );
774 row++;
775
776 for( size_t coord = 0; coord < 2; ++coord )
777 {
778 wxStaticText* label = new wxStaticText( parent, wxID_ANY, coord == 0 ? _( "X:" ) : _( "Y:" ) );
779 aSizer.Add( label, wxGBPosition( row, col ), wxDefaultSpan,
780 wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, col > 0 ? 20 : 5 );
781
782 wxTextCtrl* ctrl = new wxTextCtrl( parent, wxID_ANY, "" );
783 aSizer.Add( ctrl, wxGBPosition( row, col + 1 ), wxDefaultSpan,
784 wxEXPAND | wxALIGN_CENTER_VERTICAL, 5 );
785
786 wxStaticText* units = new wxStaticText( parent, wxID_ANY, _( "mm" ) );
787 aSizer.Add( units, wxGBPosition( row, col + 2 ), wxDefaultSpan,
788 wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
789
790 auto binder = std::make_unique<UNIT_BINDER>( &aFrame, label, ctrl, units );
791
792 if( aRelative )
793 binder->SetCoordType( coord == 0 ? ORIGIN_TRANSFORMS::REL_X_COORD : ORIGIN_TRANSFORMS::REL_Y_COORD );
794 else
795 binder->SetCoordType( coord == 0 ? ORIGIN_TRANSFORMS::ABS_X_COORD : ORIGIN_TRANSFORMS::ABS_Y_COORD );
796
797 aBoundCtrls.push_back( BOUND_CONTROL{ std::move( binder ), ctrl } );
798 row++;
799 }
800
801 if( !aSizer.IsColGrowable( col + 1 ) )
802 aSizer.AddGrowableCol( col + 1 );
803}
804
805
806void AddFieldToSizer( EDA_DRAW_FRAME& aFrame, wxGridBagSizer& aSizer, int row, int col,
807 const wxString& aName, ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType,
808 bool aIsAngle, std::vector<BOUND_CONTROL>& aBoundCtrls )
809{
810 // Name: [Ctrl] mm
811 wxWindow* parent = aSizer.GetContainingWindow();
812
813 wxStaticText* label = new wxStaticText( parent, wxID_ANY, aName + wxS( ":" ) );
814 aSizer.Add( label, wxGBPosition( row, col ), wxDefaultSpan,
815 wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, col > 0 ? 20 : 5 );
816
817 wxTextCtrl* ctrl = new wxTextCtrl( parent, wxID_ANY );
818 aSizer.Add( ctrl, wxGBPosition( row, col + 1 ), wxDefaultSpan,
819 wxEXPAND | wxALIGN_CENTER_VERTICAL, 5 );
820
821 wxStaticText* units = new wxStaticText( parent, wxID_ANY, _( "mm" ) );
822 aSizer.Add( units, wxGBPosition( row, col + 2 ), wxDefaultSpan,
823 wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
824
825 auto binder = std::make_unique<UNIT_BINDER>( &aFrame, label, ctrl, units );
826 binder->SetCoordType( aCoordType );
827
828 if( aIsAngle )
829 {
830 binder->SetPrecision( 4 );
831 binder->SetUnits( EDA_UNITS::DEGREES );
832 }
833
834 aBoundCtrls.push_back( BOUND_CONTROL{ std::move( binder ), ctrl } );
835
836 if( !aSizer.IsColGrowable( col + 1 ) )
837 aSizer.AddGrowableCol( col + 1 );
838}
839
840
841static std::map<SHAPE_T, int> s_lastTabForShape;
842
843
846 m_parent( aParent ),
847 m_item( aShape ),
852{
853 SetTitle( wxString::Format( GetTitle(), m_item->GetFriendlyName() ) );
854 m_hash_key = TO_UTF8( GetTitle() );
855
856 wxFont infoFont = KIUI::GetSmallInfoFont( this );
857 m_techLayersLabel->SetFont( infoFont );
858
859 // All the pages exist in the WxFB template, but we'll scrap the ones we don't
860 // use. Constructing on-demand would work fine too.
861 std::set<int> shownPages;
862
863 const auto showPage =
864 [&]( wxSizer& aMainSizer, bool aSelect = false )
865 {
866 // Get the parent of the sizer, which is the panel
867 wxWindow* page = aMainSizer.GetContainingWindow();
868 wxCHECK( page, /* void */ );
869 page->Layout();
870
871 const int pageIdx = m_notebookShapeDefs->FindPage( page );
872 shownPages.insert( pageIdx );
873
874 if( aSelect )
875 m_notebookShapeDefs->SetSelection( pageIdx );
876 };
877
878 switch( m_item->GetShape() )
879 {
881 // For all these functions, it's very important that the fields are added in the same order
882 // as the CTRL_IDX enums in the GEOM_SYNCER classes.
883 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 0, _( "Start Point" ), false, m_boundCtrls );
884 AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 3, _( "End Point" ), false, m_boundCtrls );
885
886 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 0, _( "Start Point" ), false, m_boundCtrls );
887 AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 3, _( "Size" ), true, m_boundCtrls );
888
889 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 0, _( "Center" ), false, m_boundCtrls );
890 AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 3, _( "Size" ), true, m_boundCtrls );
891
892 m_geomSync = std::make_unique<RECTANGLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
893
894 showPage( *m_gbsRectangleByCorners, true );
895 showPage( *m_gbsRectangleByCornerSize );
896 showPage( *m_gbsRectangleByCenterSize );
897 break;
898
899 case SHAPE_T::SEGMENT:
900
901 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 0, _( "Start Point" ), false, m_boundCtrls );
902 AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 3, _( "End Point" ), false, m_boundCtrls );
903
904 AddXYPointToSizer( *aParent, *m_gbsLineByLengthAngle, 0, 0, _( "Start Point" ), false, m_boundCtrls);
907
908 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 0, _( "Start Point" ), false, m_boundCtrls );
909 AddXYPointToSizer( *aParent, *m_gbsLineByStartMid, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
910
911 m_geomSync = std::make_unique<LINE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
912
913 showPage( *m_gbsLineByEnds, true );
914 showPage( *m_gbsLineByLengthAngle );
915 showPage( *m_gbsLineByStartMid );
916
917 m_cbRoundRect->Show( false );
918 m_cornerRadius.Show( false );
919 break;
920
921 case SHAPE_T::ARC:
922 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 0, _( "Center" ), false, m_boundCtrls);
923 AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 3, _( "Start Point" ), false, m_boundCtrls);
924 AddFieldToSizer( *aParent, *m_gbsArcByCSA, 3, 0, _( "Included Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true, m_boundCtrls );
925
926 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 0, _( "Start Point" ), false, m_boundCtrls );
927 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 3, _( "Mid Point" ), false, m_boundCtrls );
928 AddXYPointToSizer( *aParent, *m_gbsArcBySME, 3, 0, _( "End Point" ), false, m_boundCtrls );
929
930 m_geomSync = std::make_unique<ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
931
932 showPage( *m_gbsArcByCSA, true );
933 showPage( *m_gbsArcBySME );
934
935 m_cbRoundRect->Show( false );
936 m_cornerRadius.Show( false );
937 break;
938
939 case SHAPE_T::CIRCLE:
940 AddXYPointToSizer( *aParent, *m_gbsCircleCenterRadius, 0, 0, _( "Center" ), false, m_boundCtrls);
942
943 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 0, _( "Center" ), false, m_boundCtrls );
944 AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 3, _( "Point on Circle" ), false, m_boundCtrls );
945
946 m_geomSync = std::make_unique<CIRCLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
947
948 showPage( *m_gbsCircleCenterRadius, true );
949 showPage( *m_gbsCircleCenterPoint );
950
951 m_cbRoundRect->Show( false );
952 m_cornerRadius.Show( false );
953 break;
954
955 case SHAPE_T::BEZIER:
956 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 0, _( "Start Point" ), false, m_boundCtrls );
957 AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 3, _( "End Point" ), false, m_boundCtrls );
958 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 0, _( "Control Point 1" ), false, m_boundCtrls );
959 AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 3, _( "Control Point 2" ), false, m_boundCtrls );
960
961 m_geomSync = std::make_unique<BEZIER_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
962
963 showPage( *m_gbsBezier, TRUE );
964 break;
965
966 case SHAPE_T::POLY:
967 m_notebookShapeDefs->Hide();
968 // Nothing to do here...yet
969
970 m_cbRoundRect->Show( false );
971 m_cornerRadius.Show( false );
972 break;
973
975 wxFAIL_MSG( "Undefined shape" );
976 break;
977 }
978
979 // Remove any tabs not used (Hide() doesn't work on Windows)
980 for( int i = (int) m_notebookShapeDefs->GetPageCount() - 1; i >= 0; --i )
981 {
982 if( shownPages.count( i ) == 0 )
983 m_notebookShapeDefs->RemovePage( i );
984 }
985
986 // Used the last saved tab if any
987 if( s_lastTabForShape.count( m_item->GetShape() ) > 0
988 && s_lastTabForShape[m_item->GetShape()] < (int) m_notebookShapeDefs->GetPageCount()
989 && s_lastTabForShape[m_item->GetShape()] >= 0 )
990 {
991 m_notebookShapeDefs->SetSelection( s_lastTabForShape[m_item->GetShape()] );
992 }
993
994 // Find the first control in the shown tab
995 wxWindow* tabPanel = m_notebookShapeDefs->GetCurrentPage();
996
997 for( size_t i = 0; i < m_boundCtrls.size(); ++i )
998 {
999 if( m_boundCtrls[i].m_Ctrl->IsDescendant( tabPanel ) )
1000 {
1001 m_boundCtrls[i].m_Ctrl->SetFocus();
1002 break;
1003 }
1004 }
1005
1006 // Do not allow locking items in the footprint editor
1007 m_locked->Show( dynamic_cast<PCB_EDIT_FRAME*>( aParent ) != nullptr );
1008
1009 // Configure the layers list selector
1010 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1011 {
1012 // In the footprint editor, turn off the layers that the footprint doesn't have
1013 const LSET& brdLayers = aParent->GetBoard()->GetEnabledLayers();
1014 LSET forbiddenLayers = LSET::AllLayersMask() & ~brdLayers;
1015
1016 m_LayerSelectionCtrl->SetNotAllowedLayerSet( forbiddenLayers );
1017 }
1018
1019 for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
1020 m_lineStyleCombo->Append( lineStyleDesc.name, KiBitmapBundle( lineStyleDesc.bitmap ) );
1021
1022 m_LayerSelectionCtrl->SetLayersHotkeys( false );
1023 m_LayerSelectionCtrl->SetBoardFrame( m_parent );
1024 m_LayerSelectionCtrl->Resync();
1025
1026 m_netSelector->SetNetInfo( &aParent->GetBoard()->GetNetInfo() );
1027
1028 if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
1029 {
1030 m_netLabel->Hide();
1031 m_netSelector->Hide();
1032 }
1033
1034 if( m_item->GetShape() == SHAPE_T::ARC
1035 || m_item->GetShape() == SHAPE_T::SEGMENT
1036 || m_item->GetShape() == SHAPE_T::BEZIER )
1037 {
1038 m_fillLabel->Show( false );
1039 m_fillCtrl->Show( false );
1040 }
1041
1043
1044 // Now all widgets have the size fixed, call FinishDialogSettings
1046}
1047
1048
1050{
1051 wxCHECK_RET( aShape, wxT( "ShowGraphicItemPropertiesDialog() error: NULL item" ) );
1052
1053 DIALOG_SHAPE_PROPERTIES dlg( this, aShape );
1054
1055 if( dlg.ShowQuasiModal() == wxID_OK )
1056 {
1057 if( aShape->IsOnLayer( GetActiveLayer() ) )
1058 {
1059 DRAWING_TOOL* drawingTool = m_toolManager->GetTool<DRAWING_TOOL>();
1060 drawingTool->SetStroke( aShape->GetStroke(), GetActiveLayer() );
1061 }
1062 }
1063}
1064
1065
1067{
1068 if( !m_cbRoundRect->GetValue() )
1069 m_cornerRadius.ChangeValue( wxEmptyString );
1070}
1071
1072
1073void DIALOG_SHAPE_PROPERTIES::onCornerRadius( wxCommandEvent &event )
1074{
1075 m_cbRoundRect->SetValue( true );
1076}
1077
1078
1080{
1081 if( m_LayerSelectionCtrl->GetLayerSelection() >= 0 )
1082 enableNetInfo();
1083
1085}
1086
1087
1089{
1091}
1092
1093
1095{
1096 if( !m_item )
1097 return false;
1098
1099 // Not all shapes have a syncer (e.g. polygons)
1100 if( m_geomSync )
1102
1103 m_fillCtrl->SetSelection( m_item->GetFillModeProp() );
1104 m_locked->SetValue( m_item->IsLocked() );
1105
1107 {
1108 if( m_item->GetCornerRadius() > 0 )
1109 {
1110 m_cbRoundRect->SetValue( true );
1112 }
1113 else
1114 {
1115 m_cbRoundRect->SetValue( false );
1116 m_cornerRadius.ChangeValue( wxEmptyString );
1117 }
1118 }
1119
1120 m_thickness.SetValue( m_item->GetStroke().GetWidth() );
1121
1122 int style = static_cast<int>( m_item->GetStroke().GetLineStyle() );
1123
1124 if( style >= 0 && style < (int) lineTypeNames.size() )
1125 m_lineStyleCombo->SetSelection( style );
1126 else
1127 m_lineStyleCombo->SetSelection( 0 );
1128
1129 m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() );
1130
1131 m_hasSolderMask->SetValue( m_item->HasSolderMask() );
1132
1133 if( m_item->GetLocalSolderMaskMargin().has_value() )
1134 m_solderMaskMargin.SetValue( m_item->GetLocalSolderMaskMargin().value() );
1135 else
1136 m_solderMaskMargin.SetValue( wxEmptyString );
1137
1138 if( m_parent->GetFrameType() == FRAME_PCB_EDITOR )
1139 {
1140 int net = m_item->GetNetCode();
1141
1142 if( net >= 0 )
1143 {
1144 m_netSelector->SetSelectedNetcode( net );
1145 }
1146 else
1147 {
1148 m_netSelector->SetIndeterminateString( INDETERMINATE_STATE );
1149 m_netSelector->SetIndeterminate();
1150 }
1151 }
1152
1153 enableNetInfo();
1155
1156 return DIALOG_SHAPE_PROPERTIES_BASE::TransferDataToWindow();
1157}
1158
1159
1161{
1162 if( !DIALOG_SHAPE_PROPERTIES_BASE::TransferDataFromWindow() )
1163 return false;
1164
1165 if( !m_item )
1166 return true;
1167
1168 int layer = m_LayerSelectionCtrl->GetLayerSelection();
1169
1170 BOARD_COMMIT commit( m_parent );
1171 commit.Modify( m_item );
1172
1173 bool pushCommit = ( m_item->GetEditFlags() == 0 );
1174
1175 // Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
1176 // SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
1177 if( !pushCommit )
1178 m_item->SetFlags( IN_EDIT );
1179
1181
1182 bool wasLocked = m_item->IsLocked();
1183
1184 if( m_item->GetShape() == SHAPE_T::RECTANGLE )
1185 m_item->SetCornerRadius( m_cbRoundRect->GetValue() ? m_cornerRadius.GetIntValue() : 0 );
1186
1187 m_item->SetFillModeProp( (UI_FILL_MODE) m_fillCtrl->GetSelection() );
1188 m_item->SetLocked( m_locked->GetValue() );
1189
1190 m_item->SetWidth( m_thickness.GetIntValue() );
1191
1192 auto it = lineTypeNames.begin();
1193 std::advance( it, m_lineStyleCombo->GetSelection() );
1194
1195 if( it == lineTypeNames.end() )
1196 m_item->SetLineStyle( LINE_STYLE::SOLID );
1197 else
1198 m_item->SetLineStyle( it->first );
1199
1200 m_item->SetLayer( ToLAYER_ID( layer ) );
1201
1202 m_item->SetHasSolderMask( m_hasSolderMask->GetValue() );
1203
1204 if( m_solderMaskMargin.IsNull() )
1205 m_item->SetLocalSolderMaskMargin( {} );
1206 else
1207 m_item->SetLocalSolderMaskMargin( m_solderMaskMargin.GetIntValue() );
1208
1209 m_item->RebuildBezierToSegmentsPointsList( m_item->GetMaxError() );
1210
1211 if( m_item->IsOnCopperLayer() )
1212 m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
1213 else
1214 m_item->SetNetCode( -1 );
1215
1216 if( pushCommit )
1217 commit.Push( _( "Edit Shape Properties" ) );
1218
1219 // Save the tab
1220 s_lastTabForShape[m_item->GetShape()] = m_notebookShapeDefs->GetSelection();
1221
1222 // Notify clients which treat locked and unlocked items differently (ie: POINT_EDITOR)
1223 if( wasLocked != m_item->IsLocked() )
1224 m_parent->GetToolManager()->PostEvent( EVENTS::SelectedEvent );
1225
1226 return true;
1227}
1228
1229
1231{
1232 wxArrayString errors;
1233
1234 if( !DIALOG_SHAPE_PROPERTIES_BASE::Validate() )
1235 return false;
1236
1237 if( m_geomSync )
1238 m_geomSync->Validate( errors );
1239
1240 // Type specific checks.
1241 switch( m_item->GetShape() )
1242 {
1243 case SHAPE_T::ARC:
1244 if( m_thickness.GetValue() <= 0 )
1245 errors.Add( _( "Line width must be greater than zero." ) );
1246 break;
1247
1248 case SHAPE_T::CIRCLE:
1249 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1250 errors.Add( _( "Line width must be greater than zero for an unfilled circle." ) );
1251
1252 break;
1253
1254 case SHAPE_T::RECTANGLE:
1255 {
1256 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1257 errors.Add( _( "Line width must be greater than zero for an unfilled rectangle." ) );
1258
1259 int shortSide = std::min( m_item->GetRectangleWidth(), m_item->GetRectangleHeight() );
1260
1261 if( m_cbRoundRect->GetValue() && m_cornerRadius.GetIntValue() * 2 > shortSide )
1262 {
1263 errors.Add( _( "Corner radius must be less than or equal to half the smaller side." ) );
1264 m_cornerRadius.SetValue( KiROUND( shortSide / 2.0 ) );
1265 }
1266
1267 break;
1268 }
1269
1270 case SHAPE_T::POLY:
1271 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1272 errors.Add( _( "Line width must be greater than zero for an unfilled polygon." ) );
1273
1274 break;
1275
1276 case SHAPE_T::SEGMENT:
1277 if( m_thickness.GetValue() <= 0 )
1278 errors.Add( _( "Line width must be greater than zero." ) );
1279
1280 break;
1281
1282 case SHAPE_T::BEZIER:
1283 if( m_fillCtrl->GetSelection() != UI_FILL_MODE::SOLID && m_thickness.GetValue() <= 0 )
1284 errors.Add( _( "Line width must be greater than zero for an unfilled curve." ) );
1285
1286 break;
1287
1288 default:
1289 UNIMPLEMENTED_FOR( m_item->SHAPE_T_asString() );
1290 break;
1291 }
1292
1293 if( errors.GetCount() )
1294 {
1295 HTML_MESSAGE_BOX dlg( this, _( "Error List" ) );
1296 dlg.ListSet( errors );
1297 dlg.ShowModal();
1298 }
1299
1300 return errors.GetCount() == 0;
1301}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
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:933
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:923
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)
DIALOG_SHAPE_PROPERTIES(SCH_BASE_FRAME *aParent, SCH_SHAPE *aShape)
bool TransferDataToWindow() override
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:116
The base class for create windows for drawing purpose.
UI_FILL_MODE GetFillModeProp() const
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:258
void SetBezierC2(const VECTOR2I &aPt)
Definition eda_shape.h:257
void SetCenter(const VECTOR2I &aCenter)
SHAPE_T GetShape() const
Definition eda_shape.h:168
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:215
void SetRadius(int aX)
Definition eda_shape.h:240
void SetStart(const VECTOR2I &aStart)
Definition eda_shape.h:177
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:173
void SetEnd(const VECTOR2I &aEnd)
Definition eda_shape.h:219
void SetBezierC1(const VECTOR2I &aPt)
Definition eda_shape.h:254
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:255
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
int GetCornerRadius() const
VECTOR2I GetArcMid() const
static const TOOL_EVENT SelectedEvent
Definition actions.h:346
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.
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:624
COORD_TYPES_T
The supported Display Origin Transform types.
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.
VECTOR2I GetCenter() const override
This defaults to the center of the bounding box if not overridden.
Definition pcb_shape.h:81
STROKE_PARAMS GetStroke() const override
Definition pcb_shape.h:91
bool IsOnLayer(PCB_LAYER_ID aLayer) const override
Test to see if this object is on the given layer.
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:561
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
#define IN_EDIT
Item currently edited.
@ UNDEFINED
Definition eda_shape.h:44
@ SEGMENT
Definition eda_shape.h:45
@ RECTANGLE
Use RECTANGLE instead of RECT to avoid collision in a Windows header.
Definition eda_shape.h:46
UI_FILL_MODE
Definition eda_shape.h:68
@ SOLID
Definition eda_shape.h:70
@ FRAME_PCB_EDITOR
Definition frame_type.h:42
@ FRAME_FOOTPRINT_EDITOR
Definition frame_type.h:43
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:674
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:685
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition lset.cpp:737
This file contains miscellaneous commonly used macros and functions.
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:96
KICOMMON_API wxFont GetSmallInfoFont(wxWindow *aWindow)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
static 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)
static std::map< SHAPE_T, int > s_lastTabForShape
static bool isCopper(const PNS::ITEM *aItem)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
Conversion map between LINE_STYLE values and style names displayed.
std::unique_ptr< UNIT_BINDER > m_Binder
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:77
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:521
#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:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694