From 4a0d6297ab187646fae964d9123200156ef7c33d Mon Sep 17 00:00:00 2001 From: Fabien Corona Date: Sat, 29 Aug 2020 22:59:11 +0000 Subject: [PATCH] pcbnew: Add an alternate edit method for arcs The alternate edit methods keeps the radius constant Fixes https://gitlab.com/kicad/code/kicad/-/issues/5369 --- common/tool/actions.cpp | 3 + include/tool/actions.h | 1 + pcbnew/class_drawsegment.cpp | 1 + pcbnew/tools/point_editor.cpp | 677 ++++++++++++++++++++++------------ pcbnew/tools/point_editor.h | 30 ++ 5 files changed, 469 insertions(+), 243 deletions(-) diff --git a/common/tool/actions.cpp b/common/tool/actions.cpp index d5ea190e8c..1527a818f2 100644 --- a/common/tool/actions.cpp +++ b/common/tool/actions.cpp @@ -194,6 +194,9 @@ TOOL_ACTION ACTIONS::deleteTool( "common.Interactive.deleteTool", TOOL_ACTION ACTIONS::activatePointEditor( "common.Control.activatePointEditor", AS_GLOBAL ); +TOOL_ACTION ACTIONS::changeEditMethod( "common.Interactive.changeEditMethod", AS_GLOBAL, + MD_CTRL + ' ', "", _( "Change Edit Method" ), _( "Change edit method constraints" ) ); + TOOL_ACTION ACTIONS::find( "common.Interactive.find", AS_GLOBAL, MD_CTRL + 'F', LEGACY_HK_NAME( "Find" ), diff --git a/include/tool/actions.h b/include/tool/actions.h index 7440524a42..e83c8e0521 100644 --- a/include/tool/actions.h +++ b/include/tool/actions.h @@ -163,6 +163,7 @@ public: // Internal static TOOL_ACTION updateMenu; static TOOL_ACTION activatePointEditor; + static TOOL_ACTION changeEditMethod; // Suite static TOOL_ACTION configurePaths; diff --git a/pcbnew/class_drawsegment.cpp b/pcbnew/class_drawsegment.cpp index 4b2f3348ff..db49b9f30f 100644 --- a/pcbnew/class_drawsegment.cpp +++ b/pcbnew/class_drawsegment.cpp @@ -1220,6 +1220,7 @@ void DRAWSEGMENT::SwapData( BOARD_ITEM* aImage ) std::swap( m_Width, image->m_Width ); std::swap( m_Start, image->m_Start ); std::swap( m_End, image->m_End ); + std::swap( m_ThirdPoint, image->m_ThirdPoint ); std::swap( m_Shape, image->m_Shape ); std::swap( m_Type, image->m_Type ); std::swap( m_Angle, image->m_Angle ); diff --git a/pcbnew/tools/point_editor.cpp b/pcbnew/tools/point_editor.cpp index fa5d27ace3..9737ed3e68 100644 --- a/pcbnew/tools/point_editor.cpp +++ b/pcbnew/tools/point_editor.cpp @@ -469,6 +469,177 @@ int POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) return 0; } +void POINT_EDITOR::editArcEndpointKeepTangent( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, + const VECTOR2I aCursor ) const +{ + VECTOR2D startLine = aStart - aCenter; + VECTOR2D endLine = aEnd - aCenter; + double newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() ); + + bool clockwise; + bool movingStart; + bool arcValid = true; + + VECTOR2I *p1, *p2, *p3; + // p1 does not move, p2 does. + + if( aStart != aArc->GetArcStart() ) + { + aStart = aCursor; + p1 = &aEnd; + p2 = &aStart; + p3 = &aMid; + movingStart = true; + } + else + { + aEnd = aCursor; + p1 = &aStart; + p2 = &aEnd; + p3 = &aMid; + movingStart = false; + } + + VECTOR2D v1, v2, v3, v4; + + // Move the coordinate system + v1 = *p1 - aCenter; + v2 = *p2 - aCenter; + v3 = *p3 - aCenter; + + VECTOR2D u1, u2, u3; + + // A point cannot be both the center and on the arc. + if( ( v1.EuclideanNorm() == 0 ) || ( v2.EuclideanNorm() == 0 ) ) + return; + + 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; + + // u1 and u2 are unit vectors, and perpendicular. + // det should not be 0. In case it is, do not change the arc. + if( det == 0 ) + return; + + 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; + + /* 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_drawArcCenterMaxAngle ) + { + arcValid = false; + } + // Never recorded a problem, but still checking. + if( !std::isfinite( delta ) ) + { + arcValid = false; + } + // v4 is the new center + v4 = ( !transformCircle ) ? VECTOR2D( -delta, 0 ) : VECTOR2D( 2 * R + delta, 0 ); + + clockwise = aArc->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; + + aCenter = v4 + aCenter; + + startLine = aStart - aCenter; + endLine = aEnd - aCenter; + 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 ) + { + aArc->SetAngle( newAngle ); + aArc->SetCenter( wxPoint( aCenter.x, aCenter.y ) ); + + if( movingStart ) + { + aArc->SetArcStart( wxPoint( aStart.x, aStart.y ) ); + // Set angle computes the end point, so re-force it now. + aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) ); + } + else + { + aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) ); + } + } + } +} + /** * Update the coordinates of 4 corners of a rectangle, according to pad constraints and the @@ -572,6 +743,227 @@ static void pinEditedCorner( int aEditedPointIndex, int aMinWidth, int aMinHeigh } +void POINT_EDITOR::editArcEndpointKeepCenter( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, + const VECTOR2I aCursor ) const +{ + bool clockwise; + bool movingStart; + + VECTOR2I *p1, *p2; + VECTOR2I target; + + // p1 does not move, p2 does. + + if( aStart != aArc->GetArcStart() ) + { + p1 = &aEnd; + p2 = &aStart; + movingStart = true; + } + else + { + p1 = &aStart; + p2 = &aEnd; + movingStart = false; + } + + target = *p2 - aCenter; + + double sqRadius = ( *p1 - aCenter ).SquaredEuclideanNorm(); + + *p1 = *p1 - aCenter; + *p2 = *p2 - aCenter; + + // 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 ) + + if( target.x == 0 ) + { + p2->x = 0; + p2->y = ( target.y > 0 ) ? sqrt( sqRadius ) : -sqrt( sqRadius ); + } + else + { + double tan = target.y / static_cast( target.x ); + // The divider is always greater than 1 ( cannot be 0 ) + double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) ); + // Move to the correct quadrant + tmp = target.x > 0 ? tmp : -tmp; + p2->y = target.y / static_cast( target.x ) * tmp; + p2->x = tmp; + } + + *p1 = *p1 + aCenter; + *p2 = *p2 + aCenter; + + clockwise = aArc->GetAngle() > 0; + + VECTOR2D startLine = aStart - aCenter; + VECTOR2D endLine = aEnd - aCenter; + double newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() ); + + if( clockwise && newAngle < 0.0 ) + newAngle += 3600.0; + else if( !clockwise && newAngle > 0.0 ) + newAngle -= 3600.0; + + aArc->SetAngle( newAngle ); + aArc->SetCenter( wxPoint( aCenter.x, aCenter.y ) ); + + if( movingStart ) + { + aArc->SetArcStart( wxPoint( aStart.x, aStart.y ) ); + // Set angle computes the end point, so re-force it now. + aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) ); + } + else + { + aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) ); + } +} + + +void POINT_EDITOR::editArcMidKeepCenter( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor ) const +{ + // Now, update the edit point position + // Express the point in a cercle-centered coordinate system. + aStart = aStart - aCenter; + aEnd = aEnd - aCenter; + + double sqRadius = ( aCursor - aCenter ).SquaredEuclideanNorm(); + + // Special case, because the tangent would lead to +/- infinity + if( aStart.x == 0 ) + { + aStart.y = aCursor.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius ); + } + else + { + // 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 ) + + double tan = aStart.y / static_cast( aStart.x ); + double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) ); + // Move to the correct quadrant + tmp = aStart.x > 0 ? tmp : -tmp; + aStart.y = aStart.y / static_cast( aStart.x ) * tmp; + aStart.x = tmp; + } + + // Special case, because the tangent would lead to +/- infinity + if( aEnd.x == 0 ) + { + aEnd.y = aMid.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius ); + } + else + { + // 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 ) + + double tan = aEnd.y / static_cast( aEnd.x ); + double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) ); + // Move to the correct quadrant + tmp = aEnd.x > 0 ? tmp : -tmp; + aEnd.y = aEnd.y / static_cast( aEnd.x ) * tmp; + aEnd.x = tmp; + } + + aStart = aStart + aCenter; + aEnd = aEnd + aCenter; + + aArc->SetArcStart( wxPoint( aStart.x, aStart.y ) ); + aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) ); +} + + +void POINT_EDITOR::editArcMidKeepEnpoints( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor ) const +{ + bool clockwise; + VECTOR2I oldCenter = aArc->GetCenter(); + + + // This allows the user to go on the sides of the arc + aMid = aCursor; + // Find the new center + aCenter = GetArcCenter( aStart, aMid, aEnd ); + + aArc->SetCenter( wxPoint( aCenter.x, aCenter.y ) ); + + // Check if the new arc is CW or CCW + VECTOR2D startLine = aStart - aCenter; + VECTOR2D endLine = aEnd - aCenter; + double newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() ); + VECTOR2D v1, v2; + + v1 = aStart - aMid; + v2 = aEnd - aMid; + 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 = aStart; + RotatePoint( &( endTest.x ), &( endTest.y ), aCenter.x, aCenter.y, -newAngle ); + double distance = ( endTest - aEnd ).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* + aArc->SetCenter( wxPoint( oldCenter.x, oldCenter.y ) ); + } + else + { + aArc->SetAngle( newAngle ); + aArc->SetArcEnd( wxPoint( aEnd.x, aEnd.y ) ); + } + + // Now, update the edit point position + // Express the point in a cercle-centered coordinate system. + aMid = aCursor - aCenter; + + double sqRadius = ( aEnd - aCenter ).SquaredEuclideanNorm(); + + // Special case, because the tangent would lead to +/- infinity + if( aMid.x == 0 ) + { + aMid.y = aMid.y > 0 ? sqrt( sqRadius ) : -sqrt( sqRadius ); + } + else + { + // 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 ) + + double tan = aMid.y / static_cast( aMid.x ); + double tmp = sqrt( sqRadius / ( 1.0 + tan * tan ) ); + // Move to the correct quadrant + tmp = aMid.x > 0 ? tmp : -tmp; + aMid.y = aMid.y / static_cast( aMid.x ) * tmp; + aMid.x = tmp; + } +} + + void POINT_EDITOR::updateItem() const { EDA_ITEM* item = m_editPoints->GetParent(); @@ -631,241 +1023,34 @@ void POINT_EDITOR::updateItem() const VECTOR2I start = m_editPoints->Point( ARC_START ).GetPosition(); VECTOR2I end = m_editPoints->Point( ARC_END ).GetPosition(); - if( center != segment->GetCenter() ) + const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition(); + + if( isModified( m_editPoints->Point( ARC_CENTER ) ) ) { wxPoint moveVector = wxPoint( center.x, center.y ) - segment->GetCenter(); segment->Move( moveVector ); - - m_editPoints->Point( ARC_START ).SetPosition( segment->GetArcStart() ); - m_editPoints->Point( ARC_END ).SetPosition( segment->GetArcEnd() ); - m_editPoints->Point( ARC_MID ).SetPosition( segment->GetArcMid() ); } - else + else if( isModified( m_editPoints->Point( ARC_MID ) ) ) { - const VECTOR2I& cursorPos = getViewControls()->GetCursorPosition(); - VECTOR2I oldCenter = segment->GetCenter(); - double newAngle; - bool clockwise; - - if( mid != segment->GetArcMid() ) + if( m_altEditMethod ) { - // 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 ); - - newAngle = GetArcAngle( start, mid, end ); - - // 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 ) ); - } - - // 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 - { - // 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 ) - double tmp = sqrt( sqRadius / - ( ( 1.0 + mid.y / static_cast( mid.x ) * mid.y - / static_cast( mid.x ) ) ) ); - // Move to the correct quadrant - tmp = mid.x > 0 ? tmp : -tmp; - mid.y = mid.y / static_cast( mid.x ) * tmp; - mid.x = tmp; - } - // Go back to the main coordinate system - mid = mid + center; - - m_editPoints->Point( ARC_MID ).SetPosition( mid ); + editArcMidKeepCenter( segment, center, start, mid, end, cursorPos ); } - else if( ( start != segment->GetArcStart() ) || ( end != segment->GetArcEnd() ) ) + else { - - VECTOR2D startLine = start - center; - VECTOR2D endLine = end - center; - newAngle = RAD2DECIDEG( endLine.Angle() - startLine.Angle() ); - - 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 = ∣ - movingStart = true; - } - else - { - end = cursorPos; - p1 = &start; - p2 = &end; - p3 = ∣ - 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; - - /* 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_drawArcCenterMaxAngle ) - { - 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 ); - } - } + editArcMidKeepEnpoints( segment, center, start, mid, end, cursorPos ); + } + } + else if( isModified( m_editPoints->Point( ARC_START ) ) + || isModified( m_editPoints->Point( ARC_END ) ) ) + { + if( m_altEditMethod ) + { + editArcEndpointKeepCenter( segment, center, start, mid, end, cursorPos ); + } + else + { + editArcEndpointKeepTangent( segment, center, start, mid, end, cursorPos ); } } } @@ -982,17 +1167,17 @@ void POINT_EDITOR::updateItem() const if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) ) || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) ) { - dist[0] = center.x - topLeft.x; - dist[1] = center.y - topLeft.y; - dist[2] = botRight.x - center.x; - dist[3] = botRight.y - center.y; + dist[0] = center.x - m_editPoints->Point( RECT_TOP_LEFT ).GetPosition().x; + dist[1] = center.y - m_editPoints->Point( RECT_TOP_LEFT ).GetPosition().y; + dist[2] = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition().x - center.x; + dist[3] = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition().y - center.y; } else { - dist[0] = center.x - botLeft.x; - dist[1] = center.y - topRight.y; - dist[2] = topRight.x - center.x; - dist[3] = botLeft.y - center.y; + dist[0] = center.x - m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().x; + dist[1] = center.y - m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().y; + dist[2] = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().x - center.x; + dist[3] = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().y - center.y; } wxSize padSize( dist[0] + dist[2], dist[1] + dist[3] ); @@ -1015,17 +1200,17 @@ void POINT_EDITOR::updateItem() const if( isModified( m_editPoints->Point( RECT_TOP_LEFT ) ) || isModified( m_editPoints->Point( RECT_BOT_RIGHT ) ) ) { - left = topLeft.x; - top = topLeft.y; - right = botRight.x; - bottom = botRight.y; + left = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition().x; + top = m_editPoints->Point( RECT_TOP_LEFT ).GetPosition().y; + right = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition().x; + bottom = m_editPoints->Point( RECT_BOT_RIGHT ).GetPosition().y; } else { - left = botLeft.x; - top = topRight.y; - right = topRight.x; - bottom = botLeft.y; + left = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().x; + top = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().y; + right = m_editPoints->Point( RECT_TOP_RIGHT ).GetPosition().x; + bottom = m_editPoints->Point( RECT_BOT_LEFT ).GetPosition().y; } wxSize padSize( abs( right - left ), abs( bottom - top ) ); @@ -1754,6 +1939,11 @@ int POINT_EDITOR::modifiedSelection( const TOOL_EVENT& aEvent ) return 0; } +int POINT_EDITOR::changeEditMethod( const TOOL_EVENT& aEvent ) +{ + m_altEditMethod = !m_altEditMethod; + return 0; +} void POINT_EDITOR::setTransitions() { @@ -1763,4 +1953,5 @@ void POINT_EDITOR::setTransitions() Go( &POINT_EDITOR::modifiedSelection, EVENTS::SelectedItemsModified ); Go( &POINT_EDITOR::OnSelectionChange, EVENTS::SelectedEvent ); Go( &POINT_EDITOR::OnSelectionChange, EVENTS::UnselectedEvent ); + Go( &POINT_EDITOR::changeEditMethod, ACTIONS::changeEditMethod.MakeEvent() ); } diff --git a/pcbnew/tools/point_editor.h b/pcbnew/tools/point_editor.h index 220d95474a..d4b83aaf80 100644 --- a/pcbnew/tools/point_editor.h +++ b/pcbnew/tools/point_editor.h @@ -89,6 +89,9 @@ private: // Flag indicating whether the selected zone needs to be refilled bool m_refill; + // Flag indicating whether the alternative edit method is enabled. + bool m_altEditMethod; + std::unique_ptr m_statusPopup; ///> Updates item's points with edit points. @@ -149,6 +152,33 @@ private: int addCorner( const TOOL_EVENT& aEvent ); int removeCorner( const TOOL_EVENT& aEvent ); int modifiedSelection( const TOOL_EVENT& aEvent ); + + /** Move an end point of the arc, while keeping the tangent at the other endpoint. + * + */ + void editArcEndpointKeepTangent( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor ) const; + + /** Move an end point of the arc, while keeping radius, and the other point position. + * + */ + void editArcEndpointKeepCenter( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor ) const; + + /** Move the mid point of the arc, while keeping the two endpoints. + * + */ + void editArcMidKeepEnpoints( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, + VECTOR2I aMid, VECTOR2I aEnd, const VECTOR2I aCursor ) const; + + /** Move the mid point of the arc, while keeping the angle. + * + */ + void editArcMidKeepCenter( DRAWSEGMENT* aArc, VECTOR2I aCenter, VECTOR2I aStart, VECTOR2I aMid, + VECTOR2I aEnd, const VECTOR2I aCursor ) const; + + ///> Change the edit method to an alternative method ( currently, arcs only ) + int changeEditMethod( const TOOL_EVENT& aEvent ); }; #endif