diff --git a/libs/kimath/CMakeLists.txt b/libs/kimath/CMakeLists.txt index e61158f210..73950452e2 100644 --- a/libs/kimath/CMakeLists.txt +++ b/libs/kimath/CMakeLists.txt @@ -4,6 +4,7 @@ set( KIMATH_SRCS src/md5_hash.cpp src/trigo.cpp + src/geometry/circle.cpp src/geometry/convex_hull.cpp src/geometry/direction_45.cpp src/geometry/geometry_utils.cpp diff --git a/libs/kimath/include/geometry/circle.h b/libs/kimath/include/geometry/circle.h new file mode 100644 index 0000000000..df1d2aec1f --- /dev/null +++ b/libs/kimath/include/geometry/circle.h @@ -0,0 +1,105 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021 Roberto Fernandez Bautista + * Copyright (C) 2021 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, see . + */ + +#ifndef __CIRCLE_H +#define __CIRCLE_H + +#include // for VECTOR2I +#include // for std::vector + +class SEG; + +/** + * Class Circle + * Represents basic circle geometry with utility geometry functions. + */ +class CIRCLE +{ +public: + int Radius; ///< Public to make access simpler + VECTOR2I Center; ///< Public to make access simpler + + CIRCLE(); + + CIRCLE( const VECTOR2I& aCenter, int aRadius ); + + CIRCLE( const CIRCLE& aOther ); + + /** + * Constructs this circle such that it is tangent to the given lines and passes through the + * given point. There are two possible solutions, controlled by aAlternateSolution. + * + * When aAlternateSolution is false, find the best solution that can be used to fillet both + * lines (i.e. choose the most likely quadrant and find the solution with smallest arc angle + * between the tangent points on the lines) + * + * @param aLineA is the first tangent line. Treated as an infinite line except for the purpose + * of selecting the solution to return. + * @param aLineB is the second tangent line. Treated as an infinite line except for the purpose + * of selecting the solution to return. + * @param aP is the point to pass through + * @param aAlternateSolution If true, returns the other solution. + * @return *this + */ + CIRCLE& ConstructFromTanTanPt( const SEG& aLineA, const SEG& aLineB, const VECTOR2I& aP, + bool aAlternateSolution = false ); + + /** + * Function NearestPoint() + * + * Computes the point on the circumference of the circle that is the closest to aP. + * + * In other words: finds the intersection point of this circle and a line that passes through + * both this circle's center and aP. + * + * @param aP + * @return nearest point to aP + */ + VECTOR2I NearestPoint( const VECTOR2I& aP ) const; + + /** + * Function Intersect() + * + * Computes the intersection points between this circle and aCircle. + * + * @param aCircle The other circle to intersect with this. + * @return std::vector containing: + * - 0 elements if the circles do not intersect + * - 1 element if the circles are tangent + * - 2 elements if the circles intersect + */ + std::vector Intersect( const CIRCLE& aCircle ) const; + + /** + * Function Intersect() + * + * Computes the intersection points between this circle and aLine. + * + * @param aLine The line to intersect with this circle (end points ignored) + * @return std::vector containing: + * - 0 elements if there is no intersection + * - 1 element if the line is tangent to the circle + * - 2 elements if the line intersects the circle + */ + std::vector Intersect( const SEG& aLine ) const; +}; + +#endif // __CIRCLE_H + diff --git a/libs/kimath/include/geometry/seg.h b/libs/kimath/include/geometry/seg.h index 11bf7e300c..e1d2550f4d 100644 --- a/libs/kimath/include/geometry/seg.h +++ b/libs/kimath/include/geometry/seg.h @@ -200,6 +200,24 @@ public: return Intersect( aSeg, false, true ); } + /** + * Function PerpendicularSeg() + * + * Computes a segment perpendicular to this one, passing through point aP + * @param aP Point through which the new segment will pass + * @return SEG perpendicular to this passing through point aP + */ + SEG PerpendicularSeg( const VECTOR2I& aP ) const; + + /** + * Function ParallelSeg() + * + * Computes a segment parallel to this one, passing through point aP + * @param aP Point through which the new segment will pass + * @return SEG parallel to this passing through point aP + */ + SEG ParallelSeg( const VECTOR2I& aP ) const; + bool Collide( const SEG& aSeg, int aClearance, int* aActual = nullptr ) const; ecoord SquaredDistance( const SEG& aSeg ) const; diff --git a/libs/kimath/include/geometry/shape_circle.h b/libs/kimath/include/geometry/shape_circle.h index 41b4202cd1..bce96b8c18 100644 --- a/libs/kimath/include/geometry/shape_circle.h +++ b/libs/kimath/include/geometry/shape_circle.h @@ -26,6 +26,7 @@ #define __SHAPE_CIRCLE_H #include +#include #include #include @@ -36,19 +37,17 @@ class SHAPE_CIRCLE : public SHAPE public: SHAPE_CIRCLE() : SHAPE( SH_CIRCLE ), - m_radius( 0 ) + m_circle() {} SHAPE_CIRCLE( const VECTOR2I& aCenter, int aRadius ) : SHAPE( SH_CIRCLE ), - m_radius( aRadius ), - m_center( aCenter ) + m_circle( aCenter, aRadius ) {} SHAPE_CIRCLE( const SHAPE_CIRCLE& aOther ) : SHAPE( SH_CIRCLE ), - m_radius( aOther.m_radius ), - m_center( aOther.m_center ) + m_circle( aOther.m_circle ) {}; ~SHAPE_CIRCLE() @@ -63,17 +62,17 @@ public: const BOX2I BBox( int aClearance = 0 ) const override { - const VECTOR2I rc( m_radius + aClearance, m_radius + aClearance ); + const VECTOR2I rc( m_circle.Radius + aClearance, m_circle.Radius + aClearance ); - return BOX2I( m_center - rc, rc * 2 ); + return BOX2I( m_circle.Center - rc, rc * 2 ); } bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr, VECTOR2I* aLocation = nullptr ) const override { - int minDist = aClearance + m_radius; - VECTOR2I pn = aSeg.NearestPoint( m_center ); - ecoord dist_sq = ( pn - m_center ).SquaredEuclideanNorm(); + int minDist = aClearance + m_circle.Radius; + VECTOR2I pn = aSeg.NearestPoint( m_circle.Center ); + ecoord dist_sq = ( pn - m_circle.Center ).SquaredEuclideanNorm(); if( dist_sq == 0 || dist_sq < SEG::Square( minDist ) ) { @@ -81,7 +80,7 @@ public: *aLocation = pn; if( aActual ) - *aActual = std::max( 0, (int) sqrt( dist_sq ) - m_radius ); + *aActual = std::max( 0, (int) sqrt( dist_sq ) - m_circle.Radius ); return true; } @@ -91,34 +90,39 @@ public: void SetRadius( int aRadius ) { - m_radius = aRadius; + m_circle.Radius = aRadius; } void SetCenter( const VECTOR2I& aCenter ) { - m_center = aCenter; + m_circle.Center = aCenter; } int GetRadius() const { - return m_radius; + return m_circle.Radius; } const VECTOR2I GetCenter() const { - return m_center; + return m_circle.Center; + } + + const CIRCLE GetCircle() const + { + return m_circle; } void Move( const VECTOR2I& aVector ) override { - m_center += aVector; + m_circle.Center += aVector; } void Rotate( double aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override { - m_center -= aCenter; - m_center = m_center.Rotate( aAngle ); - m_center += aCenter; + m_circle.Center -= aCenter; + m_circle.Center = m_circle.Center.Rotate( aAngle ); + m_circle.Center += aCenter; } bool IsSolid() const override @@ -127,8 +131,7 @@ public: } private: - int m_radius; - VECTOR2I m_center; + CIRCLE m_circle; }; #endif diff --git a/libs/kimath/include/trigo.h b/libs/kimath/include/trigo.h index bd67de1825..75c3f74e72 100644 --- a/libs/kimath/include/trigo.h +++ b/libs/kimath/include/trigo.h @@ -118,6 +118,20 @@ const wxPoint GetArcCenter( VECTOR2I aStart, VECTOR2I aEnd, double aAngle ); */ double GetArcAngle( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ); +/** + * Returns the middle point of an arc, half-way between aStart and aEnd. There are two possible + * solutions which can be found by toggling aMinArcAngle. The behaviour is undefined for + * semicircles (i.e. 180 degree arcs). + * + * @param aStart The starting point of the arc (for calculating the radius) + * @param aEnd The end point of the arc (for determining the arc angle) + * @param aCenter The center point of the arc + * @param aMinArcAngle If true, returns the point that results in the smallest arc angle. + * @return The middle point of the arc +*/ +const VECTOR2I GetArcMid( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter, + bool aMinArcAngle = true ); + /* Return the arc tangent of 0.1 degrees coord vector dx, dy * between -1800 and 1800 * Equivalent to atan2 (but faster for calculations if diff --git a/libs/kimath/src/geometry/circle.cpp b/libs/kimath/src/geometry/circle.cpp new file mode 100644 index 0000000000..3cc81e0b6f --- /dev/null +++ b/libs/kimath/src/geometry/circle.cpp @@ -0,0 +1,320 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021 Roberto Fernandez Bautista + * Copyright (C) 2021 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, see . + */ + +#include +#include +#include // for MIN_PRECISION_IU +#include // for KiROUND +#include // for VECTOR2I +#include // for sqrt +#include // for GetArcMid + + +CIRCLE::CIRCLE() +{ + Radius = 0; +} + + +CIRCLE::CIRCLE( const VECTOR2I& aCenter, int aRadius ) +{ + Center = aCenter; + Radius = aRadius; +} + + +CIRCLE::CIRCLE( const CIRCLE& aOther ) +{ + Center = aOther.Center; + Radius = aOther.Radius; +} + + +CIRCLE& CIRCLE::ConstructFromTanTanPt( const SEG& aLineA, const SEG& aLineB, const VECTOR2I& aP, + bool aAlternateSolution ) +{ + //fixme: There might be more efficient / accurate solution than using geometrical constructs + + SEG anglebisector; + VECTOR2I intersectPoint; + + auto furthestFromIntersect = + [&]( VECTOR2I aPt1, VECTOR2I aPt2 ) -> VECTOR2I& + { + if( ( aPt1 - intersectPoint ).EuclideanNorm() + > ( aPt2 - intersectPoint ).EuclideanNorm() ) + { + return aPt1; + } + else + { + return aPt2; + } + }; + + auto closestToIntersect = + [&]( VECTOR2I aPt1, VECTOR2I aPt2 ) -> VECTOR2I& + { + if( ( aPt1 - intersectPoint ).EuclideanNorm() + <= ( aPt2 - intersectPoint ).EuclideanNorm() ) + { + return aPt1; + } + else + { + return aPt2; + } + }; + + if( aLineA.ApproxParallel( aLineB ) ) + { + // Special case, no intersection point between the two lines + // The center will be in the line equidistant between the two given lines + // The radius will be half the distance between the two lines + // The possible centers can be found by intersection + + SEG perpendicularAtoB( aLineA.A, aLineB.LineProject( aLineA.A ) ); + VECTOR2I midPt = perpendicularAtoB.Center(); + Radius = ( midPt - aLineA.A ).EuclideanNorm(); + + anglebisector = aLineA.ParallelSeg( midPt ); + + Center = aP; // use this circle as a construction to find the actual centers + std::vector possibleCenters = Intersect( anglebisector ); + + wxCHECK_MSG( possibleCenters.size() > 0, *this, "No solutions exist!" ); + intersectPoint = aLineA.A; // just for the purpose of deciding which solution to return + + if( aAlternateSolution ) + Center = closestToIntersect( possibleCenters.front(), possibleCenters.back() ); + else + Center = furthestFromIntersect( possibleCenters.front(), possibleCenters.back() ); + } + else + { + // General case, using homothety. + // All circles inscribed in the same angle are homothetic with center at the intersection + // In this code, the prefix "h" denotes "the homothetic image" + OPT_VECTOR2I intersectCalc = aLineA.IntersectLines( aLineB ); + wxCHECK_MSG( intersectCalc, *this, "Lines do not intersect but are not parallel?" ); + intersectPoint = intersectCalc.get(); + + if( aP == intersectPoint ) + { + //Special case: The point is at the intersection of the two lines + Center = aP; + Radius = 0; + return *this; + } + + // Calculate bisector + VECTOR2I& lineApt = furthestFromIntersect( aLineA.A, aLineA.B ); + VECTOR2I& lineBpt = furthestFromIntersect( aLineB.A, aLineB.B ); + VECTOR2I bisectorPt = GetArcMid( lineApt, lineBpt, intersectPoint, true ); + + anglebisector.A = intersectPoint; + anglebisector.B = bisectorPt; + + if( aAlternateSolution && ( aLineA.Contains( aP ) || aLineB.Contains( aP ) ) ) + anglebisector.PerpendicularSeg( intersectPoint ); + + // Create an arbitrary circle that is tangent to both lines + CIRCLE hSolution; + hSolution.Center = anglebisector.LineProject( aP ); + hSolution.Radius = aLineA.LineDistance( hSolution.Center ); + + // Find the homothetic image of aP in the construction circle (hSolution) + SEG throughaP( intersectPoint, aP ); + std::vector hProjections = hSolution.Intersect( throughaP ); + wxCHECK_MSG( hProjections.size() > 0, *this, "No solutions exist!" ); + + VECTOR2I hSelected; + + if( aAlternateSolution ) + hSelected = furthestFromIntersect( hProjections.front(), hProjections.back() ); + else + hSelected = closestToIntersect( hProjections.front(), hProjections.back() ); + + VECTOR2I hTanLineA = aLineA.LineProject( hSolution.Center ); + VECTOR2I hTanLineB = aLineB.LineProject( hSolution.Center ); + + // To minimise errors, use the furthest away tangent point from aP + if( ( hTanLineA - aP ).EuclideanNorm() > ( hTanLineB - aP ).EuclideanNorm() ) + { + // Find the tangent at line A by homothetic inversion + SEG hT( hTanLineA, hSelected ); + OPT_VECTOR2I actTanA = hT.ParallelSeg( aP ).IntersectLines( aLineA ); + wxCHECK_MSG( actTanA, *this, "No solutions exist!" ); + + // Find circle center by perpendicular intersection with the angle bisector + SEG perpendicularToTanA = aLineA.PerpendicularSeg( actTanA.get() ); + OPT_VECTOR2I actCenter = perpendicularToTanA.IntersectLines( anglebisector ); + wxCHECK_MSG( actCenter, *this, "No solutions exist!" ); + + Center = actCenter.get(); + Radius = aLineA.LineDistance( Center ); + } + else + { + // Find the tangent at line B by inversion + SEG hT( hTanLineB, hSelected ); + OPT_VECTOR2I actTanB = hT.ParallelSeg( aP ).IntersectLines( aLineB ); + wxCHECK_MSG( actTanB, *this, "No solutions exist!" ); + + // Find circle center by perpendicular intersection with the angle bisector + SEG perpendicularToTanB = aLineB.PerpendicularSeg( actTanB.get() ); + OPT_VECTOR2I actCenter = perpendicularToTanB.IntersectLines( anglebisector ); + wxCHECK_MSG( actCenter, *this, "No solutions exist!" ); + + Center = actCenter.get(); + Radius = aLineB.LineDistance( Center ); + } + } + + return *this; +} + + +VECTOR2I CIRCLE::NearestPoint( const VECTOR2I& aP ) const +{ + VECTOR2I vec = aP - Center; + + // Handle special case where aP is equal to this circle's center + if( vec.x == 0 && vec.y == 0 ) + vec.x = 1; // Arbitrary, to ensure the return value is always on the circumference + + return vec.Resize( Radius ) + Center; +} + + +std::vector CIRCLE::Intersect( const CIRCLE& aCircle ) const +{ + // Simplify the problem: + // Let this circle be centered at (0,0), with radius r1 + // Let aCircle be centered at (d, 0), with radius r2 + // (i.e. d is the distance between the two circle centers) + // + // The equations of the two circles are + // (1) x^2 + y^2 = r1^2 + // (2) (x - d)^2 + y^2 = r2^2 + // + // Combining (1) into (2): + // (x - d)^2 + r1^2 - x^2 = r2^2 + // Expanding: + // x^2 - 2*d*x + d^2 + r1^2 - x^2 = r2^2 + // Rearranging for x: + // (3) x = (d^2 + r1^2 - r2^2) / (2 * d) + // + // Rearranging (1) gives: + // (4) y = sqrt(r1^2 - x^2) + + std::vector retval; + + VECTOR2I vecCtoC = aCircle.Center - Center; + int64_t d = vecCtoC.EuclideanNorm(); + int64_t r1 = Radius; + int64_t r2 = aCircle.Radius; + + if( d > ( r1 + r2 ) || d == 0 ) + return retval; //circles do not intersect + + // Equation (3) + int64_t x = ( ( d * d ) + ( r1 * r1 ) - ( r2 * r2 ) ) / ( int64_t( 2 ) * d ); + int64_t r1sqMinusXsq = ( r1 * r1 ) - ( x * x ); + + if( r1sqMinusXsq < 0 ) + return retval; //circles do not intersect + + // Equation (4) + int64_t y = KiROUND( sqrt( r1sqMinusXsq ) ); + + // Now correct back to original coordinates + double rotAngle = vecCtoC.Angle(); + VECTOR2I solution1( x, y ); + solution1 = solution1.Rotate( rotAngle ); + solution1 += Center; + retval.push_back( solution1 ); + + if( y != 0 ) + { + VECTOR2I solution2( x, -y ); + solution2 = solution2.Rotate( rotAngle ); + solution2 += Center; + retval.push_back( solution2 ); + } + + return retval; +} + + +std::vector CIRCLE::Intersect( const SEG& aLine ) const +{ + std::vector retval; + + // + // . * . + // * * + // -----1-------m-------2---- + // * * + // * O * + // * * + // * * + // * * + // * * + // ' * ' + // Let O be the center of this circle, 1 and 2 the intersection points of the line + // and M be the center of the chord connecting points 1 and 2 + // + // M will be O projected perpendicularly to the line since a chord is always perpendicular + // to the radius. + // + // The distance M1 = M2 can be computed by pythagoras since O1 = O2 = Radius + // + // O1= O2 = sqrt( Radius^2 - OM^2) + // + + VECTOR2I m = aLine.LineProject( Center ); + int64_t om = ( m - Center ).EuclideanNorm(); + + if( om > ( (int64_t) Radius + SHAPE_ARC::MIN_PRECISION_IU ) ) + { + return retval; // does not intersect + } + else if( om <= ( (int64_t) Radius + SHAPE_ARC::MIN_PRECISION_IU ) + && om >= ( (int64_t) Radius - SHAPE_ARC::MIN_PRECISION_IU ) ) + { + retval.push_back( m ); + return retval; //tangent + } + + int64_t radiusSquared = (int64_t) Radius * (int64_t) Radius; + int64_t omSquared = om * om; + + int mTo1 = sqrt( radiusSquared - omSquared ); + + VECTOR2I mTo1vec = ( aLine.B - aLine.A ).Resize( mTo1 ); + VECTOR2I mTo2vec = -mTo1vec; + + retval.push_back( mTo1vec + m ); + retval.push_back( mTo2vec + m ); + + return retval; +} + diff --git a/libs/kimath/src/geometry/seg.cpp b/libs/kimath/src/geometry/seg.cpp index cd6181f651..975eadf8e6 100644 --- a/libs/kimath/src/geometry/seg.cpp +++ b/libs/kimath/src/geometry/seg.cpp @@ -130,6 +130,24 @@ OPT_VECTOR2I SEG::Intersect( const SEG& aSeg, bool aIgnoreEndpoints, bool aLines } +SEG SEG::PerpendicularSeg( const VECTOR2I& aP ) const +{ + VECTOR2I slope( B - A ); + VECTOR2I endPoint = slope.Perpendicular() + aP; + + return SEG( aP, endPoint ); +} + + +SEG SEG::ParallelSeg( const VECTOR2I& aP ) const +{ + VECTOR2I slope( B - A ); + VECTOR2I endPoint = slope + aP; + + return SEG( aP, endPoint ); +} + + bool SEG::ccw( const VECTOR2I& aA, const VECTOR2I& aB, const VECTOR2I& aC ) const { return (ecoord) ( aC.y - aA.y ) * ( aB.x - aA.x ) > (ecoord) ( aB.y - aA.y ) * ( aC.x - aA.x ); diff --git a/libs/kimath/src/trigo.cpp b/libs/kimath/src/trigo.cpp index 61eac49f0e..2c59c7469f 100644 --- a/libs/kimath/src/trigo.cpp +++ b/libs/kimath/src/trigo.cpp @@ -159,6 +159,26 @@ bool TestSegmentHit( const wxPoint &aRefPoint, wxPoint aStart, wxPoint aEnd, int } +const VECTOR2I GetArcMid( const VECTOR2I& aStart, const VECTOR2I& aEnd, const VECTOR2I& aCenter, + bool aMinArcAngle ) +{ + VECTOR2I startVector = aStart - aCenter; + VECTOR2I endVector = aEnd - aCenter; + + double startAngle = ArcTangente( startVector.y, startVector.x ); + double endAngle = ArcTangente( endVector.y, endVector.x ); + double midPointRotAngleDeciDeg = NormalizeAngle180( startAngle - endAngle ) / 2; + + if( !aMinArcAngle ) + midPointRotAngleDeciDeg += 1800.0; + + VECTOR2I newMid = aStart; + RotatePoint( newMid, aCenter, midPointRotAngleDeciDeg ); + + return newMid; +} + + double ArcTangente( int dy, int dx ) { diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index f4f2060919..ae5896b07d 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -61,6 +61,7 @@ using namespace std::placeholders; #include #include #include +#include #include @@ -269,7 +270,294 @@ int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent ) if( selection.Empty() ) return 0; - invokeInlineRouter( mode ); + if( selection.Size() == 1 && selection.Front()->Type() == PCB_ARC_T ) + { + // TODO: This really should be done in PNS to ensure DRC is maintained, but for now + // it allows interactive editing of an arc track + return DragArcTrack( aEvent ); + } + else + { + invokeInlineRouter( mode ); + } + + return 0; +} + + +int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent ) +{ + PCB_SELECTION& selection = m_selectionTool->GetSelection(); + + if( selection.Size() != 1 || selection.Front()->Type() != PCB_ARC_T ) + return 0; + + Activate(); + + ARC* theArc = static_cast( selection.Front() ); + m_commit->Modify( theArc ); + + KIGFX::VIEW_CONTROLS* controls = getViewControls(); + + controls->ShowCursor( true ); + controls->SetAutoPan( true ); + bool restore_state = false; + + VECTOR2I arcCenter = theArc->GetCenter(); + SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() ); + SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() ); + + if( !tanStart.ApproxParallel( tanEnd ) ) + { + //Ensure the tangent segments are in the correct orientation + OPT_VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd ); + tanStart.A = tanIntersect.get(); + tanStart.B = theArc->GetStart(); + tanEnd.A = tanIntersect.get(); + tanEnd.B = theArc->GetEnd(); + } + + KICAD_T track_types[] = { PCB_PAD_T, PCB_VIA_T, PCB_TRACE_T, PCB_ARC_T, EOT }; + + auto getUniqueConnectedTrack = + [&]( const VECTOR2I& aAnchor ) -> TRACK* + { + auto conn = board()->GetConnectivity(); + auto itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor, track_types ); + TRACK* retval = nullptr; + + if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T ) + { + retval = static_cast( itemsOnAnchor.front() ); + m_commit->Modify( retval ); + } + else + { + retval = new TRACK( theArc->GetParent() ); + retval->SetStart( (wxPoint) aAnchor ); + retval->SetEnd( (wxPoint) aAnchor ); + retval->SetNet( theArc->GetNet() ); + retval->SetLayer( theArc->GetLayer() ); + retval->SetWidth( theArc->GetWidth() ); + retval->SetFlags( IS_NEW ); + getView()->Add( retval ); + } + + return retval; + }; + + TRACK* trackOnStart = getUniqueConnectedTrack( theArc->GetStart() ); + TRACK* trackOnEnd = getUniqueConnectedTrack( theArc->GetEnd() ); + + if( !trackOnStart->IsNew() ) + { + tanStart.A = trackOnStart->GetStart(); + tanStart.B = trackOnStart->GetEnd(); + } + + if( !trackOnEnd->IsNew() ) + { + tanEnd.A = trackOnEnd->GetStart(); + tanEnd.B = trackOnEnd->GetEnd(); + } + + OPT_VECTOR2I optInterectTan = tanStart.IntersectLines( tanEnd ); + int tanStartSide = tanStart.Side( theArc->GetMid() ); + int tanEndSide = tanEnd.Side( theArc->GetMid() ); + + bool isStartTrackOnStartPt = ( VECTOR2I( theArc->GetStart() ) - tanStart.A ).EuclideanNorm() + < ( VECTOR2I( theArc->GetStart() ) - tanStart.B ).EuclideanNorm(); + + bool isEndTrackOnStartPt = ( VECTOR2I( theArc->GetStart() ) - tanEnd.A ).EuclideanNorm() + < ( VECTOR2I( theArc->GetStart() ) - tanEnd.B ).EuclideanNorm(); + + // Calculate constraints + CIRCLE maxTangentCircle; + VECTOR2I startOther = ( isStartTrackOnStartPt ) ? tanStart.B : tanStart.A; + maxTangentCircle.ConstructFromTanTanPt( tanStart, tanEnd, startOther ); + + VECTOR2I maxTanPtStart = tanStart.LineProject( maxTangentCircle.Center ); + VECTOR2I maxTanPtEnd = tanEnd.LineProject( maxTangentCircle.Center ); + + if( !tanEnd.Contains( maxTanPtEnd ) ) + { + startOther = ( isEndTrackOnStartPt ) ? tanEnd.B : tanEnd.A; + maxTangentCircle.ConstructFromTanTanPt( tanStart, tanEnd, startOther ); + maxTanPtStart = tanStart.LineProject( maxTangentCircle.Center ); + maxTanPtEnd = tanEnd.LineProject( maxTangentCircle.Center ); + } + + SEG constraintSeg( maxTanPtStart, maxTanPtEnd ); + int constraintSegSide = constraintSeg.Side( theArc->GetMid() ); + + while( TOOL_EVENT* evt = Wait() ) + { + m_cursor = controls->GetMousePosition(); + + // Constrain cursor + // Fix-me: this is a bit ugly but it works + if( tanStartSide != tanStart.Side( m_cursor ) ) + { + VECTOR2I projected = tanStart.LineProject( m_cursor ); + + if( tanEndSide != tanEnd.Side( projected ) ) + { + m_cursor = tanEnd.LineProject( m_cursor ); + + if( tanStartSide != tanStart.Side( m_cursor ) ) + m_cursor = optInterectTan.get(); + } + else if( constraintSegSide != constraintSeg.Side( projected ) ) + { + m_cursor = constraintSeg.LineProject( m_cursor ); + + if( tanStartSide != tanStart.Side( m_cursor ) ) + m_cursor = maxTanPtStart; + } + else + { + m_cursor = projected; + } + } + else if( tanEndSide != tanEnd.Side( m_cursor ) ) + { + VECTOR2I projected = tanEnd.LineProject( m_cursor ); + + if( tanStartSide != tanStart.Side( projected ) ) + { + m_cursor = tanStart.LineProject( m_cursor ); + + if( tanEndSide != tanEnd.Side( m_cursor ) ) + m_cursor = optInterectTan.get(); + } + else if( constraintSegSide != constraintSeg.Side( projected ) ) + { + m_cursor = constraintSeg.LineProject( m_cursor ); + + if( tanEndSide != tanEnd.Side( m_cursor ) ) + m_cursor = maxTanPtEnd; + } + else + { + m_cursor = projected; + } + } + else if( constraintSegSide != constraintSeg.Side( m_cursor ) ) + { + VECTOR2I projected = constraintSeg.LineProject( m_cursor ); + + if( tanStartSide != tanStart.Side( projected ) ) + { + m_cursor = tanStart.LineProject( m_cursor ); + + if( constraintSegSide != constraintSeg.Side( m_cursor ) ) + m_cursor = maxTanPtStart; + } + else if( tanEndSide != tanEnd.Side( projected ) ) + { + m_cursor = tanEnd.LineProject( m_cursor ); + + if( constraintSegSide != constraintSeg.Side( m_cursor ) ) + m_cursor = maxTanPtEnd; + } + else + { + m_cursor = projected; + } + } + + if( ( m_cursor - maxTangentCircle.Center ).EuclideanNorm() < maxTangentCircle.Radius ) + m_cursor = maxTangentCircle.NearestPoint( m_cursor ); + + controls->ForceCursorPosition( true, m_cursor ); + + // Calculate resulting object coordinates + CIRCLE circlehelper; + circlehelper.ConstructFromTanTanPt( tanStart, tanEnd, m_cursor ); + + VECTOR2I newCenter = circlehelper.Center; + VECTOR2I newStart = tanStart.LineProject( newCenter ); + VECTOR2I newEnd = tanEnd.LineProject( newCenter ); + VECTOR2I newMid = GetArcMid( newStart, newEnd, newCenter ); + + //Update objects + theArc->SetStart( (wxPoint) newStart ); + theArc->SetEnd( (wxPoint) newEnd ); + theArc->SetMid( (wxPoint) newMid ); + + if( isStartTrackOnStartPt ) + trackOnStart->SetStart( (wxPoint) newStart ); + else + trackOnStart->SetEnd( (wxPoint) newStart ); + + if( isEndTrackOnStartPt ) + trackOnEnd->SetStart( (wxPoint) newEnd ); + else + trackOnEnd->SetEnd( (wxPoint) newEnd ); + + //Update view + getView()->Update( trackOnStart ); + getView()->Update( trackOnEnd ); + getView()->Update( theArc ); + + //Handle events + if( evt->IsCancelInteractive() || evt->IsActivate() ) + { + restore_state = true; // Canceling the tool means that items have to be restored + break; // Finish + } + else if( evt->IsAction( &ACTIONS::undo ) ) + { + restore_state = true; // Perform undo locally + break; // Finish + } + else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) + || evt->IsDblClick( BUT_LEFT ) ) + { + break; // finish + } + } + + // Remove zero length tracks + if( theArc->GetStart() == theArc->GetEnd() ) + m_commit->Remove( theArc ); + + auto cleanupTrack = + [&]( TRACK* aTrack ) + { + if( aTrack->IsNew() ) + { + getView()->Remove( aTrack ); + + if( aTrack->GetStart() == aTrack->GetEnd() ) + delete aTrack; + else + m_commit->Add( aTrack ); + } + else if( aTrack->GetStart() == aTrack->GetEnd() ) + { + m_commit->Remove( aTrack ); + } + }; + + cleanupTrack( trackOnStart ); + cleanupTrack( trackOnEnd ); + + // Should we commit? + if( restore_state ) + { + m_commit->Revert(); + + if( trackOnStart->IsNew() ) + delete trackOnStart; + + if( trackOnEnd->IsNew() ) + delete trackOnEnd; + } + else + { + m_commit->Push( _( "Drag Arc Track" ) ); + } return 0; } diff --git a/pcbnew/tools/edit_tool.h b/pcbnew/tools/edit_tool.h index 9509f346a7..9b0de447e7 100644 --- a/pcbnew/tools/edit_tool.h +++ b/pcbnew/tools/edit_tool.h @@ -82,10 +82,17 @@ public: /** * Function Drag() - * Invoke the PNS router to drag tracks. + * Invoke the PNS router to drag tracks or do an offline resizing of an arc track + * if a single arc track is selected */ int Drag( const TOOL_EVENT& aEvent ); + /** + * Function DragArcTrack() + * Drag-resize an arc (and change end points of connected straight segments) + */ + int DragArcTrack( const TOOL_EVENT& aEvent ); + /** * Function Properties() * Displays properties window for the selected object.