/* * This program source code file is part of KICAD, a free EDA CAD application. * * Copyright The 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 2 * 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 */ #include "tool/point_editor_behavior.h" #include #include void POLYGON_POINT_EDIT_BEHAVIOR::BuildForPolyOutline( EDIT_POINTS& aPoints, const SHAPE_POLY_SET& aOutline ) { const int cornersCount = aOutline.TotalVertices(); for( auto iterator = aOutline.CIterateWithHoles(); iterator; iterator++ ) { aPoints.AddPoint( *iterator ); if( iterator.IsEndContour() ) aPoints.AddBreak(); } // Lines have to be added after creating edit points, as they use EDIT_POINT references for( int i = 0; i < cornersCount - 1; ++i ) { if( aPoints.IsContourEnd( i ) ) aPoints.AddLine( aPoints.Point( i ), aPoints.Point( aPoints.GetContourStartIdx( i ) ) ); else aPoints.AddLine( aPoints.Point( i ), aPoints.Point( i + 1 ) ); aPoints.Line( i ).SetConstraint( new EC_PERPLINE( aPoints.Line( i ) ) ); } // The last missing line, connecting the last and the first polygon point aPoints.AddLine( aPoints.Point( cornersCount - 1 ), aPoints.Point( aPoints.GetContourStartIdx( cornersCount - 1 ) ) ); aPoints.Line( aPoints.LinesSize() - 1 ) .SetConstraint( new EC_PERPLINE( aPoints.Line( aPoints.LinesSize() - 1 ) ) ); } void POLYGON_POINT_EDIT_BEHAVIOR::UpdatePointsFromOutline( const SHAPE_POLY_SET& aOutline, EDIT_POINTS& aPoints ) { // No size check here, as we can and will rebuild if that fails if( aPoints.PointsSize() != (unsigned) aOutline.TotalVertices() ) { // Rebuild the points list aPoints.Clear(); BuildForPolyOutline( aPoints, aOutline ); } else { for( int i = 0; i < aOutline.TotalVertices(); ++i ) { aPoints.Point( i ).SetPosition( aOutline.CVertex( i ) ); } } } void POLYGON_POINT_EDIT_BEHAVIOR::UpdateOutlineFromPoints( SHAPE_POLY_SET& aOutline, const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) { CHECK_POINT_COUNT_GE( aPoints, (unsigned) aOutline.TotalVertices() ); for( int i = 0; i < aOutline.TotalVertices(); ++i ) { aOutline.SetVertex( i, aPoints.Point( i ).GetPosition() ); } for( unsigned i = 0; i < aPoints.LinesSize(); ++i ) { if( !isModified( aEditedPoint, aPoints.Line( i ) ) ) { aPoints.Line( i ).SetConstraint( new EC_PERPLINE( aPoints.Line( i ) ) ); } } } void EDA_SEGMENT_POINT_EDIT_BEHAVIOR::MakePoints( EDIT_POINTS& aPoints ) { aPoints.AddPoint( m_segment.GetStart() ); aPoints.AddPoint( m_segment.GetEnd() ); } void EDA_SEGMENT_POINT_EDIT_BEHAVIOR::UpdatePoints( EDIT_POINTS& aPoints ) { CHECK_POINT_COUNT( aPoints, SEGMENT_MAX_POINTS ); aPoints.Point( SEGMENT_START ) = m_segment.GetStart(); aPoints.Point( SEGMENT_END ) = m_segment.GetEnd(); } void EDA_SEGMENT_POINT_EDIT_BEHAVIOR::UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit, std::vector& aUpdatedItems ) { CHECK_POINT_COUNT( aPoints, SEGMENT_MAX_POINTS ); if( isModified( aEditedPoint, aPoints.Point( SEGMENT_START ) ) ) m_segment.SetStart( aPoints.Point( SEGMENT_START ).GetPosition() ); else if( isModified( aEditedPoint, aPoints.Point( SEGMENT_END ) ) ) m_segment.SetEnd( aPoints.Point( SEGMENT_END ).GetPosition() ); } OPT_VECTOR2I EDA_SEGMENT_POINT_EDIT_BEHAVIOR::Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const { // Select the other end of line return aPoints.Next( aEditedPoint )->GetPosition(); } void EDA_CIRCLE_POINT_EDIT_BEHAVIOR::MakePoints( EDIT_POINTS& aPoints ) { aPoints.AddPoint( m_circle.getCenter() ); aPoints.AddPoint( m_circle.GetEnd() ); } void EDA_CIRCLE_POINT_EDIT_BEHAVIOR::UpdatePoints( EDIT_POINTS& aPoints ) { CHECK_POINT_COUNT( aPoints, CIRC_MAX_POINTS ); aPoints.Point( CIRC_CENTER ).SetPosition( m_circle.getCenter() ); aPoints.Point( CIRC_END ).SetPosition( m_circle.GetEnd() ); } void EDA_CIRCLE_POINT_EDIT_BEHAVIOR::UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit, std::vector& aUpdatedItems ) { CHECK_POINT_COUNT( aPoints, CIRC_MAX_POINTS ); const VECTOR2I& center = aPoints.Point( CIRC_CENTER ).GetPosition(); const VECTOR2I& end = aPoints.Point( CIRC_END ).GetPosition(); if( isModified( aEditedPoint, aPoints.Point( CIRC_CENTER ) ) ) { m_circle.SetCenter( center ); } else { m_circle.SetEnd( VECTOR2I( end.x, end.y ) ); } } OPT_VECTOR2I EDA_CIRCLE_POINT_EDIT_BEHAVIOR::Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const { return aPoints.Point( CIRC_CENTER ).GetPosition(); } void EDA_BEZIER_POINT_EDIT_BEHAVIOR::MakePoints( EDIT_POINTS& aPoints ) { aPoints.AddPoint( m_bezier.GetStart() ); aPoints.AddPoint( m_bezier.GetBezierC1() ); aPoints.AddPoint( m_bezier.GetBezierC2() ); aPoints.AddPoint( m_bezier.GetEnd() ); aPoints.AddIndicatorLine( aPoints.Point( BEZIER_START ), aPoints.Point( BEZIER_CTRL_PT1 ) ); aPoints.AddIndicatorLine( aPoints.Point( BEZIER_CTRL_PT2 ), aPoints.Point( BEZIER_END ) ); } void EDA_BEZIER_POINT_EDIT_BEHAVIOR::UpdatePoints( EDIT_POINTS& aPoints ) { CHECK_POINT_COUNT( aPoints, BEZIER_MAX_POINTS ); aPoints.Point( BEZIER_START ).SetPosition( m_bezier.GetStart() ); aPoints.Point( BEZIER_CTRL_PT1 ).SetPosition( m_bezier.GetBezierC1() ); aPoints.Point( BEZIER_CTRL_PT2 ).SetPosition( m_bezier.GetBezierC2() ); aPoints.Point( BEZIER_END ).SetPosition( m_bezier.GetEnd() ); } void EDA_BEZIER_POINT_EDIT_BEHAVIOR::UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit, std::vector& aUpdatedItems ) { CHECK_POINT_COUNT( aPoints, BEZIER_MAX_POINTS ); if( isModified( aEditedPoint, aPoints.Point( BEZIER_START ) ) ) { m_bezier.SetStart( aPoints.Point( BEZIER_START ).GetPosition() ); } else if( isModified( aEditedPoint, aPoints.Point( BEZIER_CTRL_PT1 ) ) ) { m_bezier.SetBezierC1( aPoints.Point( BEZIER_CTRL_PT1 ).GetPosition() ); } else if( isModified( aEditedPoint, aPoints.Point( BEZIER_CTRL_PT2 ) ) ) { m_bezier.SetBezierC2( aPoints.Point( BEZIER_CTRL_PT2 ).GetPosition() ); } else if( isModified( aEditedPoint, aPoints.Point( BEZIER_END ) ) ) { m_bezier.SetEnd( aPoints.Point( BEZIER_END ).GetPosition() ); } m_bezier.RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); } // Note: these static arc functions don't have to be in here - we could ship them out // to a utils area for use by other code (e.g. polygon fillet editing). /** * Move an end point of the arc, while keeping the tangent at the other endpoint. */ static void editArcEndpointKeepTangent( EDA_SHAPE& aArc, const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd, const VECTOR2I& aCursor ) { VECTOR2I center = aCenter; bool movingStart; bool arcValid = true; VECTOR2I p1, p2, p3; // p1 does not move, p2 does. if( aStart != aArc.GetStart() ) { p1 = aEnd; p2 = aStart; p3 = aMid; movingStart = true; } else if( aEnd != aArc.GetEnd() ) { p1 = aStart; p2 = aEnd; p3 = aMid; movingStart = false; } else { return; } VECTOR2D v1, v2, v3, v4; // Move the coordinate system v1 = p1 - aCenter; v2 = p2 - aCenter; v3 = p3 - aCenter; VECTOR2D u1, u2; // 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 ); 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 + aCenter; if( arcValid ) { aArc.SetCenter( center ); if( movingStart ) aArc.SetStart( aStart ); else aArc.SetEnd( aEnd ); } } } /** * Move the arc center but keep endpoint locations. */ static void editArcCenterKeepEndpoints( EDA_SHAPE& aArc, const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ) { const int c_snapEpsilon_sq = 4; VECTOR2I m = ( aStart / 2 + aEnd / 2 ); VECTOR2I perp = ( aEnd - aStart ).Perpendicular().Resize( INT_MAX / 2 ); SEG legal( m - perp, m + perp ); const SEG testSegments[] = { SEG( aCenter, aCenter + VECTOR2( 1, 0 ) ), SEG( aCenter, aCenter + VECTOR2( 0, 1 ) ), }; std::vector points = { legal.A, legal.B }; for( const SEG& seg : testSegments ) { OPT_VECTOR2I vec = legal.IntersectLines( seg ); if( vec && legal.SquaredDistance( *vec ) <= c_snapEpsilon_sq ) points.push_back( *vec ); } OPT_VECTOR2I nearest; SEG::ecoord min_d_sq = VECTOR2I::ECOORD_MAX; // Snap by distance between cursor and intersections for( const VECTOR2I& pt : points ) { SEG::ecoord d_sq = ( pt - aCenter ).SquaredEuclideanNorm(); if( d_sq < min_d_sq - c_snapEpsilon_sq ) { min_d_sq = d_sq; nearest = pt; } } if( nearest ) aArc.SetCenter( *nearest ); } /** * Move an end point of the arc around the circumference. */ static void editArcEndpointKeepCenter( EDA_SHAPE& aArc, const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd, const VECTOR2I& aCursor ) { int minRadius = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ); bool movingStart; VECTOR2I p1, p2, prev_p1; // user is moving p1, we want to move p2 to the new radius. if( aStart != aArc.GetStart() ) { prev_p1 = aArc.GetStart(); p1 = aStart; p2 = aEnd; movingStart = true; } else { prev_p1 = aArc.GetEnd(); p1 = aEnd; p2 = aStart; movingStart = false; } p1 = p1 - aCenter; p2 = p2 - aCenter; if( p1.x == 0 && p1.y == 0 ) p1 = prev_p1 - aCenter; if( p2.x == 0 && p2.y == 0 ) p2 = { 1, 0 }; double radius = p1.EuclideanNorm(); if( radius < minRadius ) radius = minRadius; p1 = aCenter + p1.Resize( radius ); p2 = aCenter + p2.Resize( radius ); aArc.SetCenter( aCenter ); if( movingStart ) { aArc.SetStart( p1 ); aArc.SetEnd( p2 ); } else { aArc.SetStart( p2 ); aArc.SetEnd( p1 ); } } static void editArcEndpointKeepCenterAndRadius( EDA_SHAPE& aArc, const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ) { VECTOR2I p1; bool movingStart = false; // User is moving p1, we need to update whichever end that is // The other end won't move. if( aStart != aArc.GetStart() ) { p1 = aStart; movingStart = true; } else { p1 = aEnd; movingStart = false; } // Do not change the radius p1 = p1 - aCenter; p1 = aCenter + p1.Resize( aArc.GetRadius() ); if( movingStart ) { aArc.SetStart( p1 ); } else { aArc.SetEnd( p1 ); } } /** * Move the mid point of the arc, while keeping the two endpoints. */ static void editArcMidKeepCenter( EDA_SHAPE& aArc, const VECTOR2I& aCenter, const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd, const VECTOR2I& aCursor ) { int minRadius = EDA_UNIT_UTILS::Mils2IU( pcbIUScale, 1 ); SEG chord( aStart, aEnd ); // Now, update the edit point position // Express the point in a circle-centered coordinate system. VECTOR2I start = aStart - aCenter; VECTOR2I end = aEnd - aCenter; double radius = ( aCursor - aCenter ).EuclideanNorm(); if( radius < minRadius ) radius = minRadius; start = start.Resize( radius ); end = end.Resize( radius ); start = start + aCenter; end = end + aCenter; aArc.SetStart( start ); aArc.SetEnd( end ); } /** * Move the mid point of the arc, while keeping the angle. */ static void editArcMidKeepEndpoints( EDA_SHAPE& aArc, const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCursor ) { // Let 'm' be the middle point of the chord between the start and end points VECTOR2I m = ( aStart + aEnd ) / 2; // Legal midpoints lie on a vector starting just off the chord midpoint and extending out // past the existing midpoint. We do not allow arc inflection while point editing. const int JUST_OFF = ( aStart - aEnd ).EuclideanNorm() / 100; VECTOR2I v = (VECTOR2I) aArc.GetArcMid() - m; SEG legal( m + v.Resize( JUST_OFF ), m + v.Resize( INT_MAX / 2 ) ); VECTOR2I mid = legal.NearestPoint( aCursor ); aArc.SetArcGeometry( aStart, mid, aEnd ); } EDA_ARC_POINT_EDIT_BEHAVIOR::EDA_ARC_POINT_EDIT_BEHAVIOR( EDA_SHAPE& aArc, const ARC_EDIT_MODE& aArcEditMode, KIGFX::VIEW_CONTROLS& aViewContols ) : m_arc( aArc ), m_arcEditMode( aArcEditMode ), m_viewControls( aViewContols ) { wxASSERT( m_arc.GetShape() == SHAPE_T::ARC ); } void EDA_ARC_POINT_EDIT_BEHAVIOR::MakePoints( EDIT_POINTS& aPoints ) { aPoints.AddPoint( m_arc.GetStart() ); aPoints.AddPoint( m_arc.GetArcMid() ); aPoints.AddPoint( m_arc.GetEnd() ); aPoints.AddPoint( m_arc.getCenter() ); aPoints.AddIndicatorLine( aPoints.Point( ARC_CENTER ), aPoints.Point( ARC_START ) ); aPoints.AddIndicatorLine( aPoints.Point( ARC_CENTER ), aPoints.Point( ARC_END ) ); } void EDA_ARC_POINT_EDIT_BEHAVIOR::UpdatePoints( EDIT_POINTS& aPoints ) { CHECK_POINT_COUNT( aPoints, 4 ); aPoints.Point( ARC_START ).SetPosition( m_arc.GetStart() ); aPoints.Point( ARC_MID ).SetPosition( m_arc.GetArcMid() ); aPoints.Point( ARC_END ).SetPosition( m_arc.GetEnd() ); aPoints.Point( ARC_CENTER ).SetPosition( m_arc.getCenter() ); } void EDA_ARC_POINT_EDIT_BEHAVIOR::UpdateItem( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints, COMMIT& aCommit, std::vector& aUpdatedItems ) { CHECK_POINT_COUNT( aPoints, 4 ); VECTOR2I center = aPoints.Point( ARC_CENTER ).GetPosition(); VECTOR2I mid = aPoints.Point( ARC_MID ).GetPosition(); VECTOR2I start = aPoints.Point( ARC_START ).GetPosition(); VECTOR2I end = aPoints.Point( ARC_END ).GetPosition(); if( isModified( aEditedPoint, aPoints.Point( ARC_CENTER ) ) ) { switch( m_arcEditMode ) { case ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION: editArcCenterKeepEndpoints( m_arc, center, start, mid, end ); break; case ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS: case ARC_EDIT_MODE::KEEP_CENTER_ENDS_ADJUST_ANGLE: { // Both these modes just move the arc VECTOR2I moveVector = VECTOR2I( center.x, center.y ) - m_arc.getCenter(); m_arc.SetArcGeometry( m_arc.GetStart() + moveVector, m_arc.GetArcMid() + moveVector, m_arc.GetEnd() + moveVector ); break; } } } else if( isModified( aEditedPoint, aPoints.Point( ARC_MID ) ) ) { const VECTOR2I& cursorPos = m_viewControls.GetCursorPosition( false ); switch( m_arcEditMode ) { case ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION: editArcMidKeepEndpoints( m_arc, start, end, cursorPos ); break; case ARC_EDIT_MODE::KEEP_CENTER_ENDS_ADJUST_ANGLE: case ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS: editArcMidKeepCenter( m_arc, center, start, mid, end, cursorPos ); break; } } else if( isModified( aEditedPoint, aPoints.Point( ARC_START ) ) || isModified( aEditedPoint, aPoints.Point( ARC_END ) ) ) { const VECTOR2I& cursorPos = m_viewControls.GetCursorPosition(); switch( m_arcEditMode ) { case ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS: editArcEndpointKeepCenter( m_arc, center, start, mid, end, cursorPos ); break; case ARC_EDIT_MODE::KEEP_CENTER_ENDS_ADJUST_ANGLE: editArcEndpointKeepCenterAndRadius( m_arc, center, start, mid, end ); break; case ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION: editArcEndpointKeepTangent( m_arc, center, start, mid, end, cursorPos ); break; } } } OPT_VECTOR2I EDA_ARC_POINT_EDIT_BEHAVIOR::Get45DegreeConstrainer( const EDIT_POINT& aEditedPoint, EDIT_POINTS& aPoints ) const { return aPoints.Point( ARC_CENTER ).GetPosition(); } void EDA_TABLECELL_POINT_EDIT_BEHAVIOR::MakePoints( EDIT_POINTS& aPoints ) { aPoints.AddPoint( m_cell.GetEnd() - VECTOR2I( 0, m_cell.GetRectangleHeight() / 2 ) ); aPoints.AddPoint( m_cell.GetEnd() - VECTOR2I( m_cell.GetRectangleWidth() / 2, 0 ) ); } void EDA_TABLECELL_POINT_EDIT_BEHAVIOR::UpdatePoints( EDIT_POINTS& aPoints ) { aPoints.Point( COL_WIDTH ) .SetPosition( m_cell.GetEnd() - VECTOR2I( 0, m_cell.GetRectangleHeight() / 2 ) ); aPoints.Point( ROW_HEIGHT ) .SetPosition( m_cell.GetEnd() - VECTOR2I( m_cell.GetRectangleWidth() / 2, 0 ) ); } ARC_EDIT_MODE IncrementArcEditMode( ARC_EDIT_MODE aMode ) { switch( aMode ) { case ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS: return ARC_EDIT_MODE::KEEP_CENTER_ENDS_ADJUST_ANGLE; case ARC_EDIT_MODE::KEEP_CENTER_ENDS_ADJUST_ANGLE: return ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION; case ARC_EDIT_MODE::KEEP_ENDPOINTS_OR_START_DIRECTION: return ARC_EDIT_MODE::KEEP_CENTER_ADJUST_ANGLE_RADIUS; default: wxFAIL_MSG( "Invalid arc edit mode" ); return aMode; } }