mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2024-11-25 16:15:01 +00:00
1274 lines
38 KiB
C++
1274 lines
38 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2019 Jean-Pierre Charras jp.charras at wanadoo.fr
|
|
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
/*
|
|
* Edit properties of Lines, Circles, Arcs and Polygons for PCBNew and Footprint Editor
|
|
*/
|
|
|
|
#include <pcb_base_edit_frame.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <wx/valnum.h>
|
|
#include <board_commit.h>
|
|
#include <pcb_layer_box_selector.h>
|
|
#include <dialogs/html_message_box.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <tool/actions.h>
|
|
#include <pcb_shape.h>
|
|
#include <macros.h>
|
|
#include <widgets/unit_binder.h>
|
|
|
|
#include <dialog_shape_properties_base.h>
|
|
#include <tools/drawing_tool.h>
|
|
|
|
|
|
struct BOUND_CONTROL
|
|
{
|
|
std::unique_ptr<UNIT_BINDER> m_Binder;
|
|
wxTextCtrl* m_Ctrl;
|
|
};
|
|
|
|
|
|
/**
|
|
* A class that operates over a list of BOUND_CONTROLs
|
|
* and keeps them in sync with a PCB_SHAPE. Exactly how that is done
|
|
* depends on the kind of shape.
|
|
*
|
|
* Inherit from this class and implement the relvant update functions
|
|
* and listen for changes on the right controls for each mode
|
|
* (e.g. edit line segment by endpoints).
|
|
*/
|
|
class GEOM_SYNCER : public wxEvtHandler
|
|
{
|
|
public:
|
|
GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
|
|
m_shape( aShape ), m_boundCtrls( aBoundCtrls )
|
|
{
|
|
}
|
|
|
|
void BindCtrls( size_t aFrom, size_t aTo, std::function<void()> aCb )
|
|
{
|
|
wxCHECK( aFrom < m_boundCtrls.size(), /* void */ );
|
|
wxCHECK( aTo < m_boundCtrls.size(), /* void */ );
|
|
|
|
for( size_t i = aFrom; i <= aTo; ++i )
|
|
{
|
|
m_boundCtrls[i].m_Ctrl->Bind( wxEVT_TEXT,
|
|
[aCb]( wxCommandEvent& aEvent )
|
|
{
|
|
aCb();
|
|
} );
|
|
}
|
|
}
|
|
|
|
void SetShape( PCB_SHAPE& aShape )
|
|
{
|
|
m_shape = aShape;
|
|
updateAll();
|
|
}
|
|
|
|
virtual bool Validate( wxArrayString& aErrs ) const { return true; }
|
|
|
|
protected:
|
|
virtual void updateAll() = 0;
|
|
|
|
wxTextCtrl* GetCtrl( size_t aIndex ) const
|
|
{
|
|
wxCHECK( aIndex < m_boundCtrls.size(), nullptr );
|
|
return m_boundCtrls[aIndex].m_Ctrl;
|
|
}
|
|
|
|
int GetIntValue( size_t aIndex ) const
|
|
{
|
|
wxCHECK( aIndex < m_boundCtrls.size(), 0.0 );
|
|
return static_cast<int>( m_boundCtrls[aIndex].m_Binder->GetValue() );
|
|
}
|
|
|
|
EDA_ANGLE GetAngleValue( size_t aIndex ) const
|
|
{
|
|
wxCHECK( aIndex < m_boundCtrls.size(), EDA_ANGLE() );
|
|
return m_boundCtrls[aIndex].m_Binder->GetAngleValue();
|
|
}
|
|
|
|
void ChangeValue( size_t aIndex, int aValue )
|
|
{
|
|
wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
|
|
m_boundCtrls[aIndex].m_Binder->ChangeValue( aValue );
|
|
}
|
|
|
|
void ChangeAngleValue( size_t aIndex, const EDA_ANGLE& aValue )
|
|
{
|
|
wxCHECK( aIndex < m_boundCtrls.size(), /* void */ );
|
|
m_boundCtrls[aIndex].m_Binder->ChangeAngleValue( aValue );
|
|
}
|
|
|
|
PCB_SHAPE& GetShape() { return m_shape; }
|
|
|
|
const PCB_SHAPE& GetShape() const { return m_shape; }
|
|
|
|
private:
|
|
PCB_SHAPE& m_shape;
|
|
std::vector<BOUND_CONTROL>& m_boundCtrls;
|
|
};
|
|
|
|
|
|
/**
|
|
* Class that keeps a rectangle's various fields all up to date.
|
|
*/
|
|
class RECTANGLE_GEOM_SYNCER : public GEOM_SYNCER
|
|
{
|
|
public:
|
|
enum CTRL_IDX
|
|
{
|
|
START_X = 0,
|
|
START_Y,
|
|
END_X,
|
|
END_Y,
|
|
CORNER_X,
|
|
CORNER_Y,
|
|
CORNER_W,
|
|
CORNER_H,
|
|
CENTER_X,
|
|
CENTER_Y,
|
|
CENTER_W,
|
|
CENTER_H,
|
|
|
|
NUM_CTRLS,
|
|
};
|
|
|
|
RECTANGLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
|
|
GEOM_SYNCER( aShape, aBoundCtrls )
|
|
{
|
|
wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
|
|
wxASSERT( GetShape().GetShape() == SHAPE_T::RECTANGLE );
|
|
|
|
BindCtrls( START_X, END_Y,
|
|
[this]()
|
|
{
|
|
OnCornersChange();
|
|
} );
|
|
|
|
BindCtrls( CORNER_X, CORNER_H,
|
|
[this]()
|
|
{
|
|
OnCornerSizeChange();
|
|
} );
|
|
|
|
BindCtrls( CENTER_X, CENTER_H,
|
|
[this]()
|
|
{
|
|
OnCenterSizeChange();
|
|
} );
|
|
}
|
|
|
|
bool Validate( wxArrayString& aErrs ) const override
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( START_X ), GetIntValue( START_Y ) };
|
|
const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
|
|
|
|
if( p0 == p1 )
|
|
{
|
|
aErrs.push_back( _( "Rectangle cannot be zero-sized." ) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void updateAll() override
|
|
{
|
|
updateCorners();
|
|
updateCornerSize();
|
|
updateCenterSize();
|
|
}
|
|
|
|
void OnCornersChange()
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( START_X ), GetIntValue( START_Y ) };
|
|
const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
|
|
|
|
GetShape().SetStart( p0 );
|
|
GetShape().SetEnd( p1 );
|
|
|
|
updateCenterSize();
|
|
updateCornerSize();
|
|
}
|
|
|
|
void updateCorners()
|
|
{
|
|
const VECTOR2I p0 = GetShape().GetStart();
|
|
const VECTOR2I p1 = GetShape().GetEnd();
|
|
|
|
ChangeValue( START_X, p0.x );
|
|
ChangeValue( START_Y, p0.y );
|
|
ChangeValue( END_X, p1.x );
|
|
ChangeValue( END_Y, p1.y );
|
|
}
|
|
|
|
void OnCornerSizeChange()
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( CORNER_X ), GetIntValue( CORNER_Y ) };
|
|
const VECTOR2I size{ GetIntValue( CORNER_W ), GetIntValue( CORNER_H ) };
|
|
|
|
GetShape().SetStart( p0 );
|
|
GetShape().SetEnd( p0 + size );
|
|
|
|
updateCorners();
|
|
updateCenterSize();
|
|
}
|
|
|
|
void updateCornerSize()
|
|
{
|
|
const VECTOR2I p0 = GetShape().GetStart();
|
|
|
|
ChangeValue( CORNER_X, p0.x );
|
|
ChangeValue( CORNER_Y, p0.y );
|
|
ChangeValue( CORNER_W, GetShape().GetRectangleWidth() );
|
|
ChangeValue( CORNER_H, GetShape().GetRectangleHeight() );
|
|
}
|
|
|
|
void OnCenterSizeChange()
|
|
{
|
|
const VECTOR2I center = { GetIntValue( CENTER_X ), GetIntValue( CENTER_Y ) };
|
|
const VECTOR2I size = { GetIntValue( CENTER_W ), GetIntValue( CENTER_H ) };
|
|
|
|
GetShape().SetStart( center - size / 2 );
|
|
GetShape().SetEnd( center + size / 2 );
|
|
|
|
updateCorners();
|
|
updateCornerSize();
|
|
}
|
|
|
|
void updateCenterSize()
|
|
{
|
|
const VECTOR2I c = GetShape().GetCenter();
|
|
|
|
ChangeValue( CENTER_X, c.x );
|
|
ChangeValue( CENTER_Y, c.y );
|
|
ChangeValue( CENTER_W, GetShape().GetRectangleWidth() );
|
|
ChangeValue( CENTER_H, GetShape().GetRectangleHeight() );
|
|
}
|
|
};
|
|
|
|
|
|
class LINE_GEOM_SYNCER : public GEOM_SYNCER
|
|
{
|
|
public:
|
|
enum CTRL_IDX
|
|
{
|
|
START_X = 0,
|
|
START_Y,
|
|
END_X,
|
|
END_Y,
|
|
|
|
POLAR_START_X,
|
|
POLAR_START_Y,
|
|
LENGTH,
|
|
ANGLE,
|
|
|
|
MID_X,
|
|
MID_Y,
|
|
MID_END_X,
|
|
MID_END_Y,
|
|
|
|
NUM_CTRLS,
|
|
};
|
|
|
|
LINE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
|
|
GEOM_SYNCER( aShape, aBoundCtrls )
|
|
{
|
|
wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
|
|
wxASSERT( GetShape().GetShape() == SHAPE_T::SEGMENT );
|
|
|
|
BindCtrls( START_X, END_Y,
|
|
[this]()
|
|
{
|
|
OnEndsChange();
|
|
} );
|
|
|
|
BindCtrls( POLAR_START_X, ANGLE,
|
|
[this]()
|
|
{
|
|
OnPolarChange();
|
|
} );
|
|
|
|
BindCtrls( MID_X, MID_END_Y,
|
|
[this]()
|
|
{
|
|
OnMidEndpointChange();
|
|
} );
|
|
}
|
|
|
|
void updateAll() override
|
|
{
|
|
updateEnds();
|
|
updatePolar();
|
|
updateMidEndpoint();
|
|
}
|
|
|
|
void OnEndsChange()
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( START_X ), GetIntValue( START_Y ) };
|
|
const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
|
|
|
|
GetShape().SetStart( p0 );
|
|
GetShape().SetEnd( p1 );
|
|
|
|
updatePolar();
|
|
updateMidEndpoint();
|
|
}
|
|
|
|
void updateEnds()
|
|
{
|
|
const VECTOR2I p0 = GetShape().GetStart();
|
|
const VECTOR2I p1 = GetShape().GetEnd();
|
|
|
|
ChangeValue( START_X, p0.x );
|
|
ChangeValue( START_Y, p0.y );
|
|
ChangeValue( END_X, p1.x );
|
|
ChangeValue( END_Y, p1.y );
|
|
}
|
|
|
|
void OnPolarChange()
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( POLAR_START_X ), GetIntValue( POLAR_START_Y ) };
|
|
const int length = GetIntValue( LENGTH );
|
|
const EDA_ANGLE angle = GetAngleValue( ANGLE );
|
|
|
|
VECTOR2I polar = GetRotated( VECTOR2I{ length, 0 }, angle );
|
|
|
|
GetShape().SetStart( p0 );
|
|
GetShape().SetEnd( polar );
|
|
|
|
updateEnds();
|
|
updateMidEndpoint();
|
|
}
|
|
|
|
void updatePolar()
|
|
{
|
|
const VECTOR2I p0 = GetShape().GetStart();
|
|
const VECTOR2I p1 = GetShape().GetEnd();
|
|
|
|
ChangeValue( POLAR_START_X, p0.x );
|
|
ChangeValue( POLAR_START_Y, p0.y );
|
|
ChangeValue( LENGTH, p0.Distance( p1 ) );
|
|
ChangeAngleValue( ANGLE, -EDA_ANGLE( p1 - p0 ) );
|
|
}
|
|
|
|
void OnMidEndpointChange()
|
|
{
|
|
const VECTOR2I mid{ GetIntValue( MID_X ), GetIntValue( MID_Y ) };
|
|
const VECTOR2I end{ GetIntValue( MID_END_X ), GetIntValue( MID_END_Y ) };
|
|
|
|
GetShape().SetStart( mid - ( end - mid ) );
|
|
GetShape().SetEnd( mid + ( end - mid ) );
|
|
|
|
updateEnds();
|
|
updatePolar();
|
|
}
|
|
|
|
void updateMidEndpoint()
|
|
{
|
|
const VECTOR2I c = GetShape().GetCenter();
|
|
const VECTOR2I e = GetShape().GetStart();
|
|
|
|
ChangeValue( MID_X, c.x );
|
|
ChangeValue( MID_Y, c.y );
|
|
ChangeValue( MID_END_X, e.x );
|
|
ChangeValue( MID_END_Y, e.y );
|
|
}
|
|
};
|
|
|
|
|
|
class ARC_GEOM_SYNCER : public GEOM_SYNCER
|
|
{
|
|
public:
|
|
enum CTRL_IDX
|
|
{
|
|
//CSA
|
|
CSA_CENTER_X = 0,
|
|
CSA_CENTER_Y,
|
|
CSA_START_X,
|
|
CSA_START_Y,
|
|
CSA_ANGLE,
|
|
|
|
SME_START_X,
|
|
SME_START_Y,
|
|
SME_MID_X,
|
|
SME_MID_Y,
|
|
SME_END_X,
|
|
SME_END_Y,
|
|
|
|
NUM_CTRLS
|
|
};
|
|
|
|
ARC_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
|
|
GEOM_SYNCER( aShape, aBoundCtrls )
|
|
{
|
|
wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
|
|
wxASSERT( GetShape().GetShape() == SHAPE_T::ARC );
|
|
|
|
BindCtrls( CSA_CENTER_X, CSA_ANGLE,
|
|
[this]()
|
|
{
|
|
OnCSAChange();
|
|
} );
|
|
|
|
BindCtrls( SME_START_X, SME_END_Y,
|
|
[this]()
|
|
{
|
|
OnSMEChange();
|
|
} );
|
|
}
|
|
|
|
bool Validate( wxArrayString& aErrs ) const override
|
|
{
|
|
const EDA_ANGLE angle = GetAngleValue( CSA_ANGLE );
|
|
|
|
if( angle == 0 )
|
|
{
|
|
aErrs.push_back( _( "Arc angle must be greater than 0" ) );
|
|
return false;
|
|
}
|
|
|
|
const VECTOR2I start{ GetIntValue( SME_START_X ), GetIntValue( SME_START_Y ) };
|
|
const VECTOR2I mid{ GetIntValue( SME_MID_X ), GetIntValue( SME_MID_Y ) };
|
|
const VECTOR2I end{ GetIntValue( SME_END_X ), GetIntValue( SME_END_Y ) };
|
|
|
|
if( start == mid || mid == end || start == end )
|
|
{
|
|
aErrs.push_back( _( "Arc must have 3 distinct points" ) );
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
const VECTOR2D center = CalcArcCenter( start, end, angle );
|
|
|
|
double radius = ( center - start ).EuclideanNorm();
|
|
double max_offset = std::max( std::abs( center.x ), std::abs( center.y ) ) + radius;
|
|
VECTOR2I center_i = VECTOR2I( center.x, center.y );
|
|
|
|
if( max_offset >= ( std::numeric_limits<VECTOR2I::coord_type>::max() / 2.0 )
|
|
|| center_i == start || center_i == end )
|
|
{
|
|
aErrs.push_back( wxString::Format( _( "Invalid Arc with radius %f and angle %f." ),
|
|
radius, angle.AsDegrees() ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void updateAll() override
|
|
{
|
|
updateCSA();
|
|
updateSME();
|
|
}
|
|
|
|
void OnCSAChange()
|
|
{
|
|
const VECTOR2I center{ GetIntValue( CSA_CENTER_X ), GetIntValue( CSA_CENTER_Y ) };
|
|
const VECTOR2I start{ GetIntValue( CSA_START_X ), GetIntValue( CSA_START_Y ) };
|
|
const int angle = GetIntValue( CSA_ANGLE );
|
|
|
|
GetShape().SetCenter( center );
|
|
GetShape().SetStart( start );
|
|
GetShape().SetArcAngleAndEnd( angle );
|
|
|
|
updateSME();
|
|
}
|
|
|
|
void updateCSA()
|
|
{
|
|
const VECTOR2I center = GetShape().GetCenter();
|
|
const VECTOR2I start = GetShape().GetStart();
|
|
|
|
ChangeValue( CSA_CENTER_X, center.x );
|
|
ChangeValue( CSA_CENTER_Y, center.y );
|
|
ChangeValue( CSA_START_X, start.x );
|
|
ChangeValue( CSA_START_Y, start.y );
|
|
ChangeAngleValue( CSA_ANGLE, GetShape().GetArcAngle() );
|
|
}
|
|
|
|
void OnSMEChange()
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( SME_START_X ), GetIntValue( SME_START_Y ) };
|
|
const VECTOR2I p1{ GetIntValue( SME_MID_X ), GetIntValue( SME_MID_Y ) };
|
|
const VECTOR2I p2{ GetIntValue( SME_END_X ), GetIntValue( SME_END_Y ) };
|
|
|
|
GetShape().SetArcGeometry( p0, p1, p2 );
|
|
|
|
updateCSA();
|
|
}
|
|
|
|
void updateSME()
|
|
{
|
|
const VECTOR2I p0 = GetShape().GetStart();
|
|
const VECTOR2I p1 = GetShape().GetArcMid();
|
|
const VECTOR2I p2 = GetShape().GetEnd();
|
|
|
|
ChangeValue( SME_START_X, p0.x );
|
|
ChangeValue( SME_START_Y, p0.y );
|
|
ChangeValue( SME_MID_X, p1.x );
|
|
ChangeValue( SME_MID_Y, p1.y );
|
|
ChangeValue( SME_END_X, p2.x );
|
|
ChangeValue( SME_END_Y, p2.y );
|
|
}
|
|
};
|
|
|
|
|
|
class CIRCLE_GEOM_SYNCER : public GEOM_SYNCER
|
|
{
|
|
public:
|
|
enum CTRL_IDX
|
|
{
|
|
CENTER_X = 0,
|
|
CENTER_Y,
|
|
RADIUS,
|
|
CENTER_PT_X,
|
|
CENTER_PT_Y,
|
|
PT_PT_X,
|
|
PT_PT_Y,
|
|
|
|
NUM_CTRLS,
|
|
};
|
|
|
|
CIRCLE_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
|
|
GEOM_SYNCER( aShape, aBoundCtrls )
|
|
{
|
|
wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
|
|
wxASSERT( GetShape().GetShape() == SHAPE_T::CIRCLE );
|
|
|
|
BindCtrls( CENTER_X, RADIUS,
|
|
[this]()
|
|
{
|
|
OnCenterRadiusChange();
|
|
} );
|
|
|
|
BindCtrls( CENTER_PT_X, PT_PT_Y,
|
|
[this]()
|
|
{
|
|
OnCenterPointChange();
|
|
} );
|
|
}
|
|
|
|
void updateAll() override
|
|
{
|
|
updateCenterRadius();
|
|
updateCenterPoint();
|
|
}
|
|
|
|
bool Validate( wxArrayString& aErrs ) const override
|
|
{
|
|
if( GetIntValue( RADIUS ) <= 0 )
|
|
{
|
|
aErrs.push_back( _( "Radius must be greater than 0" ) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OnCenterRadiusChange()
|
|
{
|
|
const VECTOR2I center{ GetIntValue( CENTER_X ), GetIntValue( CENTER_Y ) };
|
|
const int radius = GetIntValue( RADIUS );
|
|
|
|
GetShape().SetCenter( center );
|
|
GetShape().SetRadius( radius );
|
|
|
|
updateCenterPoint();
|
|
}
|
|
|
|
void updateCenterRadius()
|
|
{
|
|
const VECTOR2I center = GetShape().GetCenter();
|
|
|
|
ChangeValue( CENTER_X, center.x );
|
|
ChangeValue( CENTER_Y, center.y );
|
|
ChangeValue( RADIUS, GetShape().GetRadius() );
|
|
}
|
|
|
|
void OnCenterPointChange()
|
|
{
|
|
const VECTOR2I center{ GetIntValue( CENTER_PT_X ), GetIntValue( CENTER_PT_Y ) };
|
|
const VECTOR2I pt{ GetIntValue( PT_PT_X ), GetIntValue( PT_PT_Y ) };
|
|
|
|
GetShape().SetCenter( center );
|
|
GetShape().SetEnd( pt );
|
|
|
|
updateCenterRadius();
|
|
}
|
|
|
|
void updateCenterPoint()
|
|
{
|
|
const VECTOR2I center = GetShape().GetCenter();
|
|
const VECTOR2I pt = GetShape().GetEnd();
|
|
|
|
ChangeValue( CENTER_PT_X, center.x );
|
|
ChangeValue( CENTER_PT_Y, center.y );
|
|
ChangeValue( PT_PT_X, pt.x );
|
|
ChangeValue( PT_PT_Y, pt.y );
|
|
}
|
|
};
|
|
|
|
|
|
class BEZIER_GEOM_SYNCER : public GEOM_SYNCER
|
|
{
|
|
public:
|
|
enum CTRL_IDX
|
|
{
|
|
START_X = 0,
|
|
START_Y,
|
|
END_X,
|
|
END_Y,
|
|
CTRL1_X,
|
|
CTRL1_Y,
|
|
CTRL2_X,
|
|
CTRL2_Y,
|
|
|
|
NUM_CTRLS,
|
|
};
|
|
|
|
BEZIER_GEOM_SYNCER( PCB_SHAPE& aShape, std::vector<BOUND_CONTROL>& aBoundCtrls ) :
|
|
GEOM_SYNCER( aShape, aBoundCtrls )
|
|
{
|
|
wxASSERT( aBoundCtrls.size() == NUM_CTRLS );
|
|
wxASSERT( GetShape().GetShape() == SHAPE_T::BEZIER );
|
|
|
|
BindCtrls( START_X, CTRL2_Y,
|
|
[this]()
|
|
{
|
|
OnBezierChange();
|
|
} );
|
|
}
|
|
|
|
void updateAll() override
|
|
{
|
|
updateBezier();
|
|
}
|
|
|
|
void OnBezierChange()
|
|
{
|
|
const VECTOR2I p0{ GetIntValue( START_X ), GetIntValue( START_Y ) };
|
|
const VECTOR2I p1{ GetIntValue( END_X ), GetIntValue( END_Y ) };
|
|
const VECTOR2I c1{ GetIntValue( CTRL1_X ), GetIntValue( CTRL1_Y ) };
|
|
const VECTOR2I c2{ GetIntValue( CTRL2_X ), GetIntValue( CTRL2_Y ) };
|
|
|
|
GetShape().SetStart( p0 );
|
|
GetShape().SetEnd( p1 );
|
|
GetShape().SetBezierC1( c1 );
|
|
GetShape().SetBezierC2( c2 );
|
|
}
|
|
|
|
void updateBezier()
|
|
{
|
|
const VECTOR2I p0 = GetShape().GetStart();
|
|
const VECTOR2I p1 = GetShape().GetEnd();
|
|
const VECTOR2I c1 = GetShape().GetBezierC1();
|
|
const VECTOR2I c2 = GetShape().GetBezierC2();
|
|
|
|
ChangeValue( START_X, p0.x );
|
|
ChangeValue( START_Y, p0.y );
|
|
ChangeValue( END_X, p1.x );
|
|
ChangeValue( END_Y, p1.y );
|
|
ChangeValue( CTRL1_X, c1.x );
|
|
ChangeValue( CTRL1_Y, c1.y );
|
|
ChangeValue( CTRL2_X, c2.x );
|
|
ChangeValue( CTRL2_Y, c2.y );
|
|
}
|
|
};
|
|
|
|
class DIALOG_SHAPE_PROPERTIES : public DIALOG_SHAPE_PROPERTIES_BASE
|
|
{
|
|
public:
|
|
DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, PCB_SHAPE* aShape );
|
|
~DIALOG_SHAPE_PROPERTIES() {};
|
|
|
|
private:
|
|
bool TransferDataToWindow() override;
|
|
bool TransferDataFromWindow() override;
|
|
|
|
void onFilledCheckbox( wxCommandEvent& event ) override;
|
|
|
|
void onLayerSelection( wxCommandEvent& event ) override;
|
|
|
|
void onTechLayersChanged( wxCommandEvent& event ) override;
|
|
|
|
bool Validate() override;
|
|
|
|
// Show/hide the widgets used in net selection (shown only for copper layers)
|
|
void showHideNetInfo()
|
|
{
|
|
bool isCopper = IsCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
|
|
|
|
m_netSelector->Show( isCopper );
|
|
m_netLabel->Show( isCopper );
|
|
}
|
|
|
|
void showHideTechLayers()
|
|
{
|
|
bool isExtCopper = IsExternalCopperLayer( m_LayerSelectionCtrl->GetLayerSelection() );
|
|
|
|
m_techLayersLabel->Enable( isExtCopper );
|
|
m_hasSolderMask->Enable( isExtCopper );
|
|
|
|
bool showMaskMargin = isExtCopper && m_hasSolderMask->GetValue();
|
|
|
|
m_solderMaskMarginLabel->Enable( showMaskMargin );
|
|
m_solderMaskMarginCtrl->Enable( showMaskMargin );
|
|
m_solderMaskMarginUnit->Enable( showMaskMargin );
|
|
}
|
|
|
|
private:
|
|
PCB_BASE_EDIT_FRAME* m_parent;
|
|
PCB_SHAPE* m_item;
|
|
|
|
UNIT_BINDER m_thickness;
|
|
UNIT_BINDER m_solderMaskMargin;
|
|
|
|
std::vector<BOUND_CONTROL> m_boundCtrls;
|
|
std::unique_ptr<GEOM_SYNCER> m_geomSync;
|
|
PCB_SHAPE m_workingCopy;
|
|
};
|
|
|
|
|
|
static void AddXYPointToSizer( EDA_DRAW_FRAME& aFrame, wxGridBagSizer& aSizer, int row, int col,
|
|
const wxString aName, bool aRelative,
|
|
std::vector<BOUND_CONTROL>& aBoundCtrls )
|
|
{
|
|
// Name
|
|
// X [Ctrl] mm
|
|
// Y [Ctrl] mm
|
|
wxWindow* parent = aSizer.GetContainingWindow();
|
|
|
|
wxStaticText* titleLabel = new wxStaticText( parent, wxID_ANY, aName );
|
|
aSizer.Add( titleLabel, wxGBPosition( row, col ), wxGBSpan( 1, 3 ),
|
|
wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL | wxALL | wxEXPAND );
|
|
row++;
|
|
|
|
for( size_t coord = 0; coord < 2; ++coord )
|
|
{
|
|
wxStaticText* label =
|
|
new wxStaticText( parent, wxID_ANY, coord == 0 ? _( "X" ) : _( "Y" ) );
|
|
aSizer.Add( label, wxGBPosition( row, col ), wxDefaultSpan,
|
|
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 5 );
|
|
|
|
wxTextCtrl* ctrl = new wxTextCtrl( parent, wxID_ANY, "" );
|
|
aSizer.Add( ctrl, wxGBPosition( row, col + 1 ), wxDefaultSpan,
|
|
wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5 );
|
|
|
|
wxStaticText* units = new wxStaticText( parent, wxID_ANY, _( "mm" ) );
|
|
aSizer.Add( units, wxGBPosition( row, col + 2 ), wxDefaultSpan,
|
|
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
|
|
|
|
auto binder = std::make_unique<UNIT_BINDER>( &aFrame, label, ctrl, units );
|
|
|
|
if( aRelative )
|
|
binder->SetCoordType( coord == 0 ? ORIGIN_TRANSFORMS::REL_X_COORD
|
|
: ORIGIN_TRANSFORMS::REL_Y_COORD );
|
|
else
|
|
binder->SetCoordType( coord == 0 ? ORIGIN_TRANSFORMS::ABS_X_COORD
|
|
: ORIGIN_TRANSFORMS::ABS_Y_COORD );
|
|
|
|
aBoundCtrls.push_back( BOUND_CONTROL{ std::move( binder ), ctrl } );
|
|
row++;
|
|
}
|
|
|
|
if( !aSizer.IsColGrowable( col + 1 ) )
|
|
aSizer.AddGrowableCol( col + 1 );
|
|
}
|
|
|
|
|
|
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 )
|
|
{
|
|
// Name [Ctrl] mm
|
|
wxWindow* parent = aSizer.GetContainingWindow();
|
|
|
|
wxStaticText* label = new wxStaticText( parent, wxID_ANY, aName );
|
|
aSizer.Add( label, wxGBPosition( row, col ), wxDefaultSpan,
|
|
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxLEFT, 5 );
|
|
|
|
wxTextCtrl* ctrl = new wxTextCtrl( parent, wxID_ANY );
|
|
aSizer.Add( ctrl, wxGBPosition( row, col + 1 ), wxDefaultSpan,
|
|
wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5 );
|
|
|
|
wxStaticText* units = new wxStaticText( parent, wxID_ANY, _( "mm" ) );
|
|
aSizer.Add( units, wxGBPosition( row, col + 2 ), wxDefaultSpan,
|
|
wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 5 );
|
|
|
|
auto binder = std::make_unique<UNIT_BINDER>( &aFrame, label, ctrl, units );
|
|
binder->SetCoordType( aCoordType );
|
|
|
|
if( aIsAngle )
|
|
{
|
|
binder->SetPrecision( 4 );
|
|
binder->SetUnits( EDA_UNITS::DEGREES );
|
|
}
|
|
|
|
aBoundCtrls.push_back( BOUND_CONTROL{ std::move( binder ), ctrl } );
|
|
|
|
if( !aSizer.IsColGrowable( col + 1 ) )
|
|
aSizer.AddGrowableCol( col + 1 );
|
|
}
|
|
|
|
|
|
static std::map<SHAPE_T, int> s_lastTabForShape;
|
|
|
|
|
|
DIALOG_SHAPE_PROPERTIES::DIALOG_SHAPE_PROPERTIES( PCB_BASE_EDIT_FRAME* aParent, PCB_SHAPE* aShape ):
|
|
DIALOG_SHAPE_PROPERTIES_BASE( aParent ),
|
|
m_parent( aParent ),
|
|
m_item( aShape ),
|
|
m_thickness( aParent, m_thicknessLabel, m_thicknessCtrl, m_thicknessUnits ),
|
|
m_solderMaskMargin( aParent, m_solderMaskMarginLabel, m_solderMaskMarginCtrl, m_solderMaskMarginUnit ),
|
|
m_workingCopy( *m_item )
|
|
{
|
|
SetTitle( wxString::Format( GetTitle(), m_item->GetFriendlyName() ) );
|
|
m_hash_key = TO_UTF8( GetTitle() );
|
|
|
|
wxFont infoFont = KIUI::GetInfoFont( this );
|
|
m_techLayersLabel->SetFont( infoFont );
|
|
|
|
// All the pages exist in the WxFB template, but we'll scrap the ones we don't
|
|
// use. Constructing on-demand would work fine too.
|
|
std::set<int> shownPages;
|
|
|
|
const auto showPage = [&]( wxSizer& aMainSizer, bool aSelect = false )
|
|
{
|
|
// Get the parent of the sizer, which is the panel
|
|
wxWindow* page = aMainSizer.GetContainingWindow();
|
|
wxCHECK( page, /* void */ );
|
|
page->Layout();
|
|
|
|
const int pageIdx = m_notebookShapeDefs->FindPage( page );
|
|
shownPages.insert( pageIdx );
|
|
|
|
if( aSelect )
|
|
m_notebookShapeDefs->SetSelection( pageIdx );
|
|
};
|
|
|
|
switch( m_item->GetShape() )
|
|
{
|
|
case SHAPE_T::RECTANGLE:
|
|
// For all these functions, it's very important that the fields are added in the same order
|
|
// as the CTRL_IDX enums in the GEOM_SYNCER classes.
|
|
AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 0, _( "Start Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsRectangleByCorners, 0, 3, _( "End Point" ), false, m_boundCtrls );
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 0, _( "Start Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsRectangleByCornerSize, 0, 3, _( "Size" ), true, m_boundCtrls );
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 0, _( "Center" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsRectangleByCenterSize, 0, 3, _( "Size" ), true, m_boundCtrls );
|
|
|
|
m_geomSync = std::make_unique<RECTANGLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
|
|
|
|
showPage( *m_gbsRectangleByCorners, true );
|
|
showPage( *m_gbsRectangleByCornerSize );
|
|
showPage( *m_gbsRectangleByCenterSize );
|
|
break;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 0, _( "Start Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsLineByEnds, 0, 3, _( "End Point" ), false, m_boundCtrls);
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsLineByLengthAngle, 0, 0, _( "Start Point" ), false, m_boundCtrls);
|
|
AddFieldToSizer( *aParent, *m_gbsLineByLengthAngle, 1, 3, _( "Length" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false, m_boundCtrls );
|
|
AddFieldToSizer( *aParent, *m_gbsLineByLengthAngle, 2, 3, _( "Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true, m_boundCtrls );
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsLineByMidEnd, 0, 0, _( "Mid Point" ), false, m_boundCtrls );
|
|
AddXYPointToSizer( *aParent, *m_gbsLineByMidEnd, 0, 3, _( "End Point" ), false, m_boundCtrls );
|
|
|
|
m_geomSync = std::make_unique<LINE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
|
|
|
|
showPage( *m_gbsLineByEnds, true );
|
|
showPage( *m_gbsLineByLengthAngle );
|
|
showPage( *m_gbsLineByMidEnd );
|
|
break;
|
|
|
|
case SHAPE_T::ARC:
|
|
AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 0, _( "Center" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsArcByCSA, 0, 3, _( "Start Point" ), false, m_boundCtrls);
|
|
AddFieldToSizer( *aParent, *m_gbsArcByCSA, 3, 0, _( "Start Angle" ), ORIGIN_TRANSFORMS::NOT_A_COORD, true, m_boundCtrls );
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 0, _( "Start Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsArcBySME, 0, 3, _( "Mid Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsArcBySME, 3, 0, _( "End Point" ), false, m_boundCtrls);
|
|
|
|
m_geomSync = std::make_unique<ARC_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
|
|
|
|
showPage( *m_gbsArcByCSA, true );
|
|
showPage( *m_gbsArcBySME );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
AddXYPointToSizer( *aParent, *m_gbsCircleCenterRadius, 0, 0, _( "Center" ), false, m_boundCtrls);
|
|
AddFieldToSizer( *aParent, *m_gbsCircleCenterRadius, 3, 0, _( "Radius" ), ORIGIN_TRANSFORMS::NOT_A_COORD, false, m_boundCtrls );
|
|
|
|
AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 0, _( "Center" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsCircleCenterPoint, 0, 3, _( "Point on Circle" ), false, m_boundCtrls);
|
|
|
|
m_geomSync = std::make_unique<CIRCLE_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
|
|
|
|
showPage( *m_gbsCircleCenterRadius, true );
|
|
showPage( *m_gbsCircleCenterPoint );
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 0, _( "Start Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsBezier, 0, 3, _( "End Point" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 0, _( "Control Point 1" ), false, m_boundCtrls);
|
|
AddXYPointToSizer( *aParent, *m_gbsBezier, 3, 3, _( "Control Point 2" ), false, m_boundCtrls);
|
|
|
|
m_geomSync = std::make_unique<BEZIER_GEOM_SYNCER>( m_workingCopy, m_boundCtrls );
|
|
|
|
showPage( *m_gbsBezier, TRUE );
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
m_notebookShapeDefs->Hide();
|
|
// Nothing to do here...yet
|
|
break;
|
|
|
|
case SHAPE_T::UNDEFINED:
|
|
wxFAIL_MSG( "Undefined shape" );
|
|
break;
|
|
}
|
|
|
|
// Remove any tabs not used (Hide() doesn't work on Windows)
|
|
for( int i = m_notebookShapeDefs->GetPageCount() - 1; i >= 0; --i )
|
|
{
|
|
if( shownPages.count( i ) == 0 )
|
|
m_notebookShapeDefs->RemovePage( i );
|
|
}
|
|
|
|
// Used the last saved tab if any
|
|
if( s_lastTabForShape.count( m_item->GetShape() ) > 0 )
|
|
{
|
|
m_notebookShapeDefs->SetSelection( s_lastTabForShape[m_item->GetShape()] );
|
|
}
|
|
|
|
// Find the first control in the shown tab
|
|
wxWindow* tabPanel = m_notebookShapeDefs->GetCurrentPage();
|
|
for( size_t i = 0; i < m_boundCtrls.size(); ++i )
|
|
{
|
|
if( m_boundCtrls[i].m_Ctrl->IsDescendant( tabPanel ) )
|
|
{
|
|
m_boundCtrls[i].m_Ctrl->SetFocus();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Do not allow locking items in the footprint editor
|
|
m_locked->Show( dynamic_cast<PCB_EDIT_FRAME*>( aParent ) != nullptr );
|
|
|
|
// Configure the layers list selector
|
|
if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
|
|
{
|
|
LSET forbiddenLayers = LSET::ForbiddenFootprintLayers();
|
|
|
|
// If someone went to the trouble of setting the layer in a text editor, then there's
|
|
// very little sense in nagging them about it.
|
|
forbiddenLayers.set( m_item->GetLayer(), false );
|
|
|
|
m_LayerSelectionCtrl->SetNotAllowedLayerSet( forbiddenLayers );
|
|
}
|
|
|
|
for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
|
|
m_lineStyleCombo->Append( lineStyleDesc.name, KiBitmapBundle( lineStyleDesc.bitmap ) );
|
|
|
|
m_lineStyleCombo->Append( DEFAULT_STYLE );
|
|
|
|
m_LayerSelectionCtrl->SetLayersHotkeys( false );
|
|
m_LayerSelectionCtrl->SetBoardFrame( m_parent );
|
|
m_LayerSelectionCtrl->Resync();
|
|
|
|
m_netSelector->SetBoard( aParent->GetBoard() );
|
|
m_netSelector->SetNetInfo( &aParent->GetBoard()->GetNetInfo() );
|
|
|
|
if( m_parent->GetFrameType() == FRAME_FOOTPRINT_EDITOR )
|
|
{
|
|
m_netLabel->Hide();
|
|
m_netSelector->Hide();
|
|
}
|
|
else
|
|
{
|
|
int net = aShape->GetNetCode();
|
|
|
|
if( net >= 0 )
|
|
{
|
|
m_netSelector->SetSelectedNetcode( net );
|
|
}
|
|
else
|
|
{
|
|
m_netSelector->SetIndeterminateString( INDETERMINATE_STATE );
|
|
m_netSelector->SetIndeterminate();
|
|
}
|
|
}
|
|
|
|
if( m_item->GetShape() == SHAPE_T::ARC || m_item->GetShape() == SHAPE_T::SEGMENT )
|
|
m_filledCtrl->Show( false );
|
|
|
|
SetupStandardButtons();
|
|
|
|
// Now all widgets have the size fixed, call FinishDialogSettings
|
|
finishDialogSettings();
|
|
}
|
|
|
|
|
|
void PCB_BASE_EDIT_FRAME::ShowGraphicItemPropertiesDialog( PCB_SHAPE* aShape )
|
|
{
|
|
wxCHECK_RET( aShape, wxT( "ShowGraphicItemPropertiesDialog() error: NULL item" ) );
|
|
|
|
DIALOG_SHAPE_PROPERTIES dlg( this, aShape );
|
|
|
|
if( dlg.ShowQuasiModal() == wxID_OK )
|
|
{
|
|
if( aShape->IsOnLayer( GetActiveLayer() ) )
|
|
{
|
|
DRAWING_TOOL* drawingTool = m_toolManager->GetTool<DRAWING_TOOL>();
|
|
drawingTool->SetStroke( aShape->GetStroke(), GetActiveLayer() );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SHAPE_PROPERTIES::onLayerSelection( wxCommandEvent& event )
|
|
{
|
|
if( m_LayerSelectionCtrl->GetLayerSelection() >= 0 )
|
|
{
|
|
showHideNetInfo();
|
|
}
|
|
|
|
showHideTechLayers();
|
|
}
|
|
|
|
|
|
void DIALOG_SHAPE_PROPERTIES::onFilledCheckbox( wxCommandEvent& event )
|
|
{
|
|
if( m_filledCtrl->GetValue() )
|
|
{
|
|
m_lineStyleCombo->SetSelection( 0 );
|
|
m_lineStyleLabel->Enable( false );
|
|
m_lineStyleCombo->Enable( false );
|
|
}
|
|
else
|
|
{
|
|
LINE_STYLE style = m_item->GetStroke().GetLineStyle();
|
|
|
|
if( style == LINE_STYLE::DEFAULT )
|
|
style = LINE_STYLE::SOLID;
|
|
|
|
if( (int) style < (int) lineTypeNames.size() )
|
|
m_lineStyleCombo->SetSelection( (int) style );
|
|
|
|
m_lineStyleLabel->Enable( true );
|
|
m_lineStyleCombo->Enable( true );
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SHAPE_PROPERTIES::onTechLayersChanged( wxCommandEvent& event )
|
|
{
|
|
showHideTechLayers();
|
|
}
|
|
|
|
|
|
bool DIALOG_SHAPE_PROPERTIES::TransferDataToWindow()
|
|
{
|
|
if( !m_item )
|
|
return false;
|
|
|
|
// Not al shapes have a syncer (e.g. polygons)
|
|
if( m_geomSync )
|
|
m_geomSync->SetShape( *m_item );
|
|
|
|
m_filledCtrl->SetValue( m_item->IsFilled() );
|
|
m_locked->SetValue( m_item->IsLocked() );
|
|
|
|
m_thickness.SetValue( m_item->GetStroke().GetWidth() );
|
|
|
|
int style = static_cast<int>( m_item->GetStroke().GetLineStyle() );
|
|
|
|
if( style == -1 )
|
|
m_lineStyleCombo->SetStringSelection( DEFAULT_STYLE );
|
|
else if( style < (int) lineTypeNames.size() )
|
|
m_lineStyleCombo->SetSelection( style );
|
|
else
|
|
wxFAIL_MSG( "Line type not found in the type lookup map" );
|
|
|
|
m_LayerSelectionCtrl->SetLayerSelection( m_item->GetLayer() );
|
|
|
|
m_hasSolderMask->SetValue( m_item->HasSolderMask() );
|
|
|
|
if( m_item->GetLocalSolderMaskMargin().has_value() )
|
|
m_solderMaskMargin.SetValue( m_item->GetLocalSolderMaskMargin().value() );
|
|
else
|
|
m_solderMaskMargin.SetValue( wxEmptyString );
|
|
|
|
showHideNetInfo();
|
|
showHideTechLayers();
|
|
|
|
return DIALOG_SHAPE_PROPERTIES_BASE::TransferDataToWindow();
|
|
}
|
|
|
|
|
|
bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow()
|
|
{
|
|
if( !DIALOG_SHAPE_PROPERTIES_BASE::TransferDataFromWindow() )
|
|
return false;
|
|
|
|
if( !m_item )
|
|
return true;
|
|
|
|
int layer = m_LayerSelectionCtrl->GetLayerSelection();
|
|
|
|
BOARD_COMMIT commit( m_parent );
|
|
commit.Modify( m_item );
|
|
|
|
bool pushCommit = ( m_item->GetEditFlags() == 0 );
|
|
|
|
// Set IN_EDIT flag to force undo/redo/abort proper operation and avoid new calls to
|
|
// SaveCopyInUndoList for the same text if is moved, and then rotated, edited, etc....
|
|
if( !pushCommit )
|
|
m_item->SetFlags( IN_EDIT );
|
|
|
|
*m_item = m_workingCopy;
|
|
|
|
bool wasLocked = m_item->IsLocked();
|
|
|
|
m_item->SetFilled( m_filledCtrl->GetValue() );
|
|
m_item->SetLocked( m_locked->GetValue() );
|
|
|
|
STROKE_PARAMS stroke = m_item->GetStroke();
|
|
|
|
stroke.SetWidth( m_thickness.GetIntValue() );
|
|
|
|
auto it = lineTypeNames.begin();
|
|
std::advance( it, m_lineStyleCombo->GetSelection() );
|
|
|
|
if( it == lineTypeNames.end() )
|
|
stroke.SetLineStyle( LINE_STYLE::DEFAULT );
|
|
else
|
|
stroke.SetLineStyle( it->first );
|
|
|
|
m_item->SetStroke( stroke );
|
|
|
|
m_item->SetLayer( ToLAYER_ID( layer ) );
|
|
|
|
m_item->SetHasSolderMask( m_hasSolderMask->GetValue() );
|
|
|
|
if( m_solderMaskMargin.IsNull() )
|
|
m_item->SetLocalSolderMaskMargin( {} );
|
|
else
|
|
m_item->SetLocalSolderMaskMargin( m_solderMaskMargin.GetIntValue() );
|
|
|
|
m_item->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
|
|
|
if( m_item->IsOnCopperLayer() )
|
|
m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
|
|
else
|
|
m_item->SetNetCode( -1 );
|
|
|
|
if( pushCommit )
|
|
commit.Push( _( "Edit Shape Properties" ) );
|
|
|
|
// Save the tab
|
|
s_lastTabForShape[m_item->GetShape()] = m_notebookShapeDefs->GetSelection();
|
|
|
|
// Notify clients which treat locked and unlocked items differently (ie: POINT_EDITOR)
|
|
if( wasLocked != m_item->IsLocked() )
|
|
m_parent->GetToolManager()->PostEvent( EVENTS::SelectedEvent );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool DIALOG_SHAPE_PROPERTIES::Validate()
|
|
{
|
|
wxArrayString errors;
|
|
|
|
if( !DIALOG_SHAPE_PROPERTIES_BASE::Validate() )
|
|
return false;
|
|
|
|
if( m_geomSync )
|
|
m_geomSync->Validate( errors );
|
|
|
|
// Type specific checks.
|
|
switch( m_item->GetShape() )
|
|
{
|
|
case SHAPE_T::ARC:
|
|
if( m_thickness.GetValue() <= 0 )
|
|
errors.Add( _( "Line width must be greater than zero." ) );
|
|
break;
|
|
|
|
case SHAPE_T::CIRCLE:
|
|
if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 )
|
|
errors.Add( _( "Line width must be greater than zero for an unfilled circle." ) );
|
|
|
|
break;
|
|
|
|
case SHAPE_T::RECTANGLE:
|
|
if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 )
|
|
errors.Add( _( "Line width must be greater than zero for an unfilled rectangle." ) );
|
|
|
|
break;
|
|
|
|
case SHAPE_T::POLY:
|
|
if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 )
|
|
errors.Add( _( "Line width must be greater than zero for an unfilled polygon." ) );
|
|
|
|
break;
|
|
|
|
case SHAPE_T::SEGMENT:
|
|
if( m_thickness.GetValue() <= 0 )
|
|
errors.Add( _( "Line width must be greater than zero." ) );
|
|
|
|
break;
|
|
|
|
case SHAPE_T::BEZIER:
|
|
if( !m_filledCtrl->GetValue() && m_thickness.GetValue() <= 0 )
|
|
errors.Add( _( "Line width must be greater than zero for an unfilled curve." ) );
|
|
|
|
break;
|
|
|
|
default:
|
|
UNIMPLEMENTED_FOR( m_item->SHAPE_T_asString() );
|
|
break;
|
|
}
|
|
|
|
if( errors.GetCount() )
|
|
{
|
|
HTML_MESSAGE_BOX dlg( this, _( "Error List" ) );
|
|
dlg.ListSet( errors );
|
|
dlg.ShowModal();
|
|
}
|
|
|
|
return errors.GetCount() == 0;
|
|
}
|