Pcbnew fix arc edition

This commit is contained in:
Fabien Corona 2020-08-09 23:03:31 +00:00 committed by Jon Evans
parent abc64f228b
commit 133d082cc5
7 changed files with 331 additions and 22 deletions

View File

@ -91,6 +91,23 @@ static const wxChar ShowRouterDebugGraphics[] = wxT( "ShowRouterDebugGraphics" )
*/
static const wxChar CompactFileSave[] = wxT( "CompactSave" );
/**
* For drawsegments - arcs.
* Distance from an arc end point and the estimated end point,
* when rotating from the start point to the end point.
* 0 will not allow any approximate result, and the arc will not show.
* Squared value for performances, in system unit.
*/
static const wxChar DrawArcAccuracy[] = wxT( "DrawArcAccuracy" );
/**
* For drawsegments - arcs.
* When drawing an arc, the angle ( center - start ) - ( start - end ),
* can be limited to avoid extremely high radii.
* The value is the tan( angle )
*/
static const wxChar DrawArcCenterStartEndMaxAngle[] = wxT( "DrawArcCenterStartEndMaxAngle" );
} // namespace KEYS
@ -171,6 +188,8 @@ ADVANCED_CFG::ADVANCED_CFG()
m_realTimeConnectivity = true;
m_coroutineStackSize = AC_STACK::default_stack;
m_ShowRouterDebugGraphics = false;
m_drawArcAccuracy = 10.0;
m_drawArcCenterStartEndMaxAngle = 50.0;
loadFromConfigFile();
}
@ -220,6 +239,12 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::CompactFileSave,
&m_CompactSave, false ) );
configParams.push_back( new PARAM_CFG_DOUBLE(
true, AC_KEYS::DrawArcAccuracy, &m_drawArcAccuracy, 10.0, 0.0, 100000.0 ) );
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::DrawArcCenterStartEndMaxAngle,
&m_drawArcCenterStartEndMaxAngle, 50.0, 0.0, 100000.0 ) );
wxConfigLoadSetups( &aCfg, configParams );
for( auto param : configParams )

View File

@ -68,6 +68,20 @@ public:
*/
static const ADVANCED_CFG& GetCfg();
/**
* For drawsegments - arcs.
* Distance from an arc end point and the estimated end point,
* when rotating from the start point to the end point.
*/
double m_drawArcAccuracy;
/**
* For drawsegments - arcs.
* When drawing an arc, the angle ( center - start ) - ( start - end )
* can be limited to avoid extremely high radii.
*/
double m_drawArcCenterStartEndMaxAngle;
/**
* Extra fill clearance for zone fills
*/

View File

@ -115,6 +115,10 @@ void DRAWSEGMENT::Move( const wxPoint& aMoveVector )
m_Poly.Move( VECTOR2I( aMoveVector ) );
break;
case S_ARC:
m_ThirdPoint += aMoveVector;
break;
case S_CURVE:
m_BezierC1 += aMoveVector;
m_BezierC2 += aMoveVector;
@ -352,11 +356,7 @@ wxPoint DRAWSEGMENT::GetArcEnd() const
switch( m_Shape )
{
case S_ARC:
// rotate the starting point of the arc, given by m_End, through the
// angle m_Angle to get the ending point of the arc.
// m_Start is the arc centre
endPoint = m_End; // m_End = start point of arc
RotatePoint( &endPoint, m_Start, -m_Angle );
endPoint = m_ThirdPoint;
break;
default:
@ -403,11 +403,29 @@ double DRAWSEGMENT::GetArcAngleStart() const
return angleStart;
}
double DRAWSEGMENT::GetArcAngleEnd() const
{
// due to the Y axis orient atan2 needs - y value
double angleStart = ArcTangente( GetArcEnd().y - GetCenter().y,
GetArcEnd().x - GetCenter().x );
// Normalize it to 0 ... 360 deg, to avoid discontinuity for angles near 180 deg
// because 180 deg and -180 are very near angles when ampping betewwen -180 ... 180 deg.
// and this is not easy to handle in calculations
NORMALIZE_ANGLE_POS( angleStart );
return angleStart;
}
void DRAWSEGMENT::SetAngle( double aAngle )
{
// Mark as depreciated.
// m_Angle does not define the arc anymore
// m_Angle must be >= -360 and <= +360 degrees
m_Angle = NormalizeAngle360Max( aAngle );
m_ThirdPoint = m_End;
RotatePoint( &m_ThirdPoint, m_Start, -m_Angle );
}

View File

@ -51,6 +51,7 @@ protected:
int m_Width; ///< thickness of lines ...
wxPoint m_Start; ///< Line start point or Circle and Arc center
wxPoint m_End; ///< Line end point or circle and arc start point
wxPoint m_ThirdPoint; ///< Used only for Arcs: arc end point
STROKE_T m_Shape; ///< Shape: line, Circle, Arc
int m_Type; ///< Used in complex associations ( Dimensions.. )
@ -148,6 +149,17 @@ public:
void SetEndY( int y ) { m_End.y = y; }
void SetEndX( int x ) { m_End.x = x; }
/**
* Function GetThirdPoint
* returns the third point point of the graphic
*/
const wxPoint& GetThirdPoint() const { return m_ThirdPoint; }
int GetThirdPointY() { return m_ThirdPoint.y; }
int GetThirdPointX() { return m_ThirdPoint.x; }
void SetThirdPoint( const wxPoint& aPoint ) { m_ThirdPoint = aPoint; }
void SetThirdPointY( int y ) { m_ThirdPoint.y = y; }
void SetThirdPointX( int x ) { m_ThirdPoint.x = x; }
// Some attributes are read only, since they are "calculated" from
// m_Start, m_End, and m_Angle.
// No Set...() function for these attributes.
@ -164,6 +176,12 @@ public:
*/
double GetArcAngleStart() const;
/**
* function GetArcAngleEnd()
* @return the angle of the ending point of this arc, between 0 and 3600 in 0.1 deg
*/
double GetArcAngleEnd() const;
/**
* Function GetRadius
* returns the radius of this item
@ -184,6 +202,13 @@ public:
void SetArcStart( const wxPoint& aArcStartPoint )
{ m_End = aArcStartPoint; }
/**
* Initialize the start arc point. can be used for circles
* to initialize one point of the cicumference
*/
void SetArcEnd( const wxPoint& aArcEndPoint )
{ m_ThirdPoint = aArcEndPoint; }
/** For arcs and circles:
*/
void SetCenter( const wxPoint& aCenterPoint ) { m_Start = aCenterPoint; }

View File

@ -968,7 +968,7 @@ void PCB_PAINTER::draw( const DRAWSEGMENT* aSegment, int aLayer )
case S_ARC:
m_gal->DrawArcSegment( start, aSegment->GetRadius(),
DECIDEG2RAD( aSegment->GetArcAngleStart() ),
DECIDEG2RAD( aSegment->GetArcAngleStart() + aSegment->GetAngle() ),
DECIDEG2RAD( aSegment->GetArcAngleStart() + aSegment->GetAngle() ), // Change this
thickness );
break;

View File

@ -1258,6 +1258,9 @@ static void updateArcFromConstructionMgr( const KIGFX::PREVIEW::ARC_GEOM_MANAGER
aArc.SetArcStart( { vec.x, vec.y } );
aArc.SetAngle( RAD2DECIDEG( -aMgr.GetSubtended() ) );
vec = aMgr.GetEndRadiusEnd();
aArc.SetArcEnd( { vec.x, vec.y } );
}

View File

@ -25,6 +25,7 @@
#include <functional>
#include <memory>
using namespace std::placeholders;
#include <advanced_config.h>
#include <tool/tool_manager.h>
#include <view/view_controls.h>
#include <gal/graphics_abstraction_layer.h>
@ -541,28 +542,251 @@ void POINT_EDITOR::updateItem() const
}
else
{
const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition();
VECTOR2I oldCenter = segment->GetCenter();
double newAngle;
bool clockwise;
if( mid != segment->GetArcMid() )
{
// This allows the user to go on the sides of the arc
mid = cursorPos;
// Find the new center
center = GetArcCenter( start, mid, end );
segment->SetCenter( wxPoint( center.x, center.y ) );
m_editPoints->Point( ARC_CENTER ).SetPosition( center );
// Check if the new arc is CW or CCW
VECTOR2D startLine = start - center;
VECTOR2D endLine = end - center;
newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
VECTOR2D v1, v2;
v1 = start - mid;
v2 = end - mid;
double theta = RAD2DECIDEG( v1.Angle() );
RotatePoint( &( v1.x ), &( v1.y ), theta );
RotatePoint( &( v2.x ), &( v2.y ), theta );
clockwise = ( ( v1.Angle() - v2.Angle() ) > 0 );
// Normalize the angle
if( clockwise && newAngle < 0.0 )
newAngle += 3600.0;
else if( !clockwise && newAngle > 0.0 )
newAngle -= 3600.0;
// Accuracy test
// First, get the angle
VECTOR2I endTest = start;
RotatePoint( &( endTest.x ), &( endTest.y ), center.x, center.y, -newAngle );
double distance = ( endTest - end ).SquaredEuclideanNorm();
if( distance > ADVANCED_CFG::GetCfg().m_drawArcAccuracy )
{
// Cancel Everything
// If the accuracy is low, we can't draw precisely the arc.
// It may happen when the radius is *high*
segment->SetCenter( wxPoint( oldCenter.x, oldCenter.y ) );
}
else
{
segment->SetAngle( newAngle );
segment->SetArcEnd( wxPoint( end.x, end.y ) );
}
segment->SetArcStart( wxPoint( start.x, start.y ) );
// Now, update the edit point position
// Express the point in a cercle-centered coordinate system.
mid = cursorPos - center;
double sqRadius = ( end - center ).SquaredEuclideanNorm();
// Special case, because the tangent would lead to +/- infinity
if( mid.x == 0 )
{
mid.y = mid.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius );
}
else
{
double tan = mid.y / static_cast<double>( mid.x );
double tmp = mid.x;
// Circle : x^2 + y^2 = R ^ 2
// In this coordinate system, the angular position of the cursor is (r, theta)
// The line coming from the center of the circle is y = start.y / start.x * x
// The intersection fulfills : x^2 = R^2 / ( 1 + ( start.y / start.x ) ^ 2 )
tmp = sqrt( sqRadius
/ ( ( 1.0
+ mid.y / static_cast<double>( mid.x ) * mid.y
/ static_cast<double>( mid.x ) ) ) );
// Move to the correct quadrant
tmp = mid.x > 0 ? tmp : -tmp;
mid.y = mid.y / static_cast<double>( mid.x ) * tmp;
mid.x = tmp;
}
// Go back to the main coordinate system
mid = mid + center;
m_editPoints->Point( ARC_MID ).SetPosition( mid );
}
else if( ( start != segment->GetArcStart() ) || ( end != segment->GetArcEnd() ) )
{
VECTOR2D startLine = start - center;
VECTOR2D endLine = end - center;
double newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
// Adjust the new angle to (counter)clockwise setting
bool clockwise = ( segment->GetAngle() > 0 );
VECTOR2I *p1, *p2, *p3;
// p1 does not move, p2 does.
bool movingStart;
bool arcValid = true;
if( start != segment->GetArcStart() )
{
start = cursorPos;
p1 = &end;
p2 = &start;
p3 = &mid;
movingStart = true;
}
else
{
end = cursorPos;
p1 = &start;
p2 = &end;
p3 = &mid;
movingStart = false;
}
VECTOR2D v1, v2, v3, v4;
// Move the coordinate system
v1 = *p1 - center;
v2 = *p2 - center;
v3 = *p3 - center;
VECTOR2D u1, u2, u3;
u1 = v1 / v1.EuclideanNorm();
u2 = v3 - ( u1.x * v3.x + u1.y * v3.y ) * u1;
u2 = u2 / u2.EuclideanNorm();
// [ u1, u3 ] is a base centered on the circle with:
// u1 : unit vector toward the point that does not move
// u2 : unit vector toward the mid point.
// Get vectors v1, and v2 in that coordinate system.
double det = u1.x * u2.y - u2.x * u1.y;
double tmpx = v1.x * u2.y - v1.y * u2.x;
double tmpy = -v1.x * u1.y + v1.y * u1.x;
v1.x = tmpx;
v1.y = tmpy;
v1 = v1 / det;
tmpx = v2.x * u2.y - v2.y * u2.x;
tmpy = -v2.x * u1.y + v2.y * u1.x;
v2.x = tmpx;
v2.y = tmpy;
v2 = v2 / det;
double R = v1.EuclideanNorm();
bool transformCircle = false;
bool invertY = ( v2.y < 0 );
/* p2
* X***
* ** <---- This is the arc
* y ^ **
* | R *
* | <-----------> *
* x------x------>--------x p1
* C' <----> C x
* delta
*
* p1 does not move, and the tangent at p1 remains the same.
* => The new center, C', will be on the C-p1 axis.
* p2 moves
*
* The radius of the new circle is delta + R
*
* || C' p2 || = || C' P1 ||
* is the same as :
* ( delta + p2.x ) ^ 2 + p2.y ^ 2 = ( R + delta ) ^ 2
*
* delta = ( R^2 - p2.x ^ 2 - p2.y ^2 ) / ( 2 * p2.x - 2 * R )
*
* We can use this equation for any point p2 with p2.x < R
*/
if( v2.x == R )
{
// Straight line, do nothing
}
else
{
if( v2.x > R )
{
// If we need to invert the curvature.
// We modify the input so we can use the same equation
transformCircle = true;
v2.x = 2 * R - v2.x;
}
// We can keep the tangent constraint.
double delta = ( R * R - v2.x * v2.x - v2.y * v2.y ) / ( 2 * v2.x - 2 * R );
// This is just to limit the radius, so nothing overflows later when drawing.
if( abs( v2.y / ( R - v2.x ) )
> ADVANCED_CFG::GetCfg().m_drawArcCenterStartEndMaxAngle )
{
arcValid = false;
}
// v4 is the new center
v4 = ( !transformCircle ) ? VECTOR2D( -delta, 0 ) :
VECTOR2D( 2 * R + delta, 0 );
clockwise = segment->GetAngle() > 0;
if( transformCircle )
clockwise = !clockwise;
tmpx = v4.x * u1.x + v4.y * u2.x;
tmpy = v4.x * u1.y + v4.y * u2.y;
v4.x = tmpx;
v4.y = tmpy;
center = v4 + center;
startLine = start - center;
endLine = end - center;
newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() );
if( clockwise && newAngle < 0.0 )
newAngle += 3600.0;
else if( !clockwise && newAngle > 0.0 )
newAngle -= 3600.0;
if( arcValid )
{
segment->SetAngle( newAngle );
segment->SetCenter( wxPoint( center.x, center.y ) );
if( movingStart )
{
segment->SetArcStart( wxPoint( start.x, start.y ) );
// Set angle computes the end point, so re-force it now.
segment->SetArcEnd( wxPoint( end.x, end.y ) );
m_editPoints->Point( ARC_START ).SetPosition( start );
}
else
{
segment->SetArcEnd( wxPoint( end.x, end.y ) );
m_editPoints->Point( ARC_END ).SetPosition( end );
}
m_editPoints->Point( ARC_CENTER ).SetPosition( center );
}
}
}
}
}
break;