CHANGED: Dragging a curved track starts an interactive resizing of it, keeping start an end points tangent

This commit is contained in:
Roberto Fernandez Bautista 2021-01-03 01:21:20 +00:00 committed by Jon Evans
parent 25956152c6
commit 2b5c1bae97
10 changed files with 817 additions and 23 deletions

View File

@ -4,6 +4,7 @@ set( KIMATH_SRCS
src/md5_hash.cpp src/md5_hash.cpp
src/trigo.cpp src/trigo.cpp
src/geometry/circle.cpp
src/geometry/convex_hull.cpp src/geometry/convex_hull.cpp
src/geometry/direction_45.cpp src/geometry/direction_45.cpp
src/geometry/geometry_utils.cpp src/geometry/geometry_utils.cpp

View File

@ -0,0 +1,105 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __CIRCLE_H
#define __CIRCLE_H
#include <math/vector2d.h> // for VECTOR2I
#include <vector> // 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<VECTOR2I> 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<VECTOR2I> Intersect( const SEG& aLine ) const;
};
#endif // __CIRCLE_H

View File

@ -200,6 +200,24 @@ public:
return Intersect( aSeg, false, true ); 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; bool Collide( const SEG& aSeg, int aClearance, int* aActual = nullptr ) const;
ecoord SquaredDistance( const SEG& aSeg ) const; ecoord SquaredDistance( const SEG& aSeg ) const;

View File

@ -26,6 +26,7 @@
#define __SHAPE_CIRCLE_H #define __SHAPE_CIRCLE_H
#include <geometry/shape.h> #include <geometry/shape.h>
#include <geometry/circle.h>
#include <math/box2.h> #include <math/box2.h>
#include <math/vector2d.h> #include <math/vector2d.h>
@ -36,19 +37,17 @@ class SHAPE_CIRCLE : public SHAPE
public: public:
SHAPE_CIRCLE() : SHAPE_CIRCLE() :
SHAPE( SH_CIRCLE ), SHAPE( SH_CIRCLE ),
m_radius( 0 ) m_circle()
{} {}
SHAPE_CIRCLE( const VECTOR2I& aCenter, int aRadius ) : SHAPE_CIRCLE( const VECTOR2I& aCenter, int aRadius ) :
SHAPE( SH_CIRCLE ), SHAPE( SH_CIRCLE ),
m_radius( aRadius ), m_circle( aCenter, aRadius )
m_center( aCenter )
{} {}
SHAPE_CIRCLE( const SHAPE_CIRCLE& aOther ) : SHAPE_CIRCLE( const SHAPE_CIRCLE& aOther ) :
SHAPE( SH_CIRCLE ), SHAPE( SH_CIRCLE ),
m_radius( aOther.m_radius ), m_circle( aOther.m_circle )
m_center( aOther.m_center )
{}; {};
~SHAPE_CIRCLE() ~SHAPE_CIRCLE()
@ -63,17 +62,17 @@ public:
const BOX2I BBox( int aClearance = 0 ) const override 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, bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr,
VECTOR2I* aLocation = nullptr ) const override VECTOR2I* aLocation = nullptr ) const override
{ {
int minDist = aClearance + m_radius; int minDist = aClearance + m_circle.Radius;
VECTOR2I pn = aSeg.NearestPoint( m_center ); VECTOR2I pn = aSeg.NearestPoint( m_circle.Center );
ecoord dist_sq = ( pn - m_center ).SquaredEuclideanNorm(); ecoord dist_sq = ( pn - m_circle.Center ).SquaredEuclideanNorm();
if( dist_sq == 0 || dist_sq < SEG::Square( minDist ) ) if( dist_sq == 0 || dist_sq < SEG::Square( minDist ) )
{ {
@ -81,7 +80,7 @@ public:
*aLocation = pn; *aLocation = pn;
if( aActual ) 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; return true;
} }
@ -91,34 +90,39 @@ public:
void SetRadius( int aRadius ) void SetRadius( int aRadius )
{ {
m_radius = aRadius; m_circle.Radius = aRadius;
} }
void SetCenter( const VECTOR2I& aCenter ) void SetCenter( const VECTOR2I& aCenter )
{ {
m_center = aCenter; m_circle.Center = aCenter;
} }
int GetRadius() const int GetRadius() const
{ {
return m_radius; return m_circle.Radius;
} }
const VECTOR2I GetCenter() const const VECTOR2I GetCenter() const
{ {
return m_center; return m_circle.Center;
}
const CIRCLE GetCircle() const
{
return m_circle;
} }
void Move( const VECTOR2I& aVector ) override void Move( const VECTOR2I& aVector ) override
{ {
m_center += aVector; m_circle.Center += aVector;
} }
void Rotate( double aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override void Rotate( double aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override
{ {
m_center -= aCenter; m_circle.Center -= aCenter;
m_center = m_center.Rotate( aAngle ); m_circle.Center = m_circle.Center.Rotate( aAngle );
m_center += aCenter; m_circle.Center += aCenter;
} }
bool IsSolid() const override bool IsSolid() const override
@ -127,8 +131,7 @@ public:
} }
private: private:
int m_radius; CIRCLE m_circle;
VECTOR2I m_center;
}; };
#endif #endif

View File

@ -118,6 +118,20 @@ const wxPoint GetArcCenter( VECTOR2I aStart, VECTOR2I aEnd, double aAngle );
*/ */
double GetArcAngle( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd ); 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 /* Return the arc tangent of 0.1 degrees coord vector dx, dy
* between -1800 and 1800 * between -1800 and 1800
* Equivalent to atan2 (but faster for calculations if * Equivalent to atan2 (but faster for calculations if

View File

@ -0,0 +1,320 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Roberto Fernandez Bautista <roberto.fer.bau@gmail.com>
* 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 <http://www.gnu.org/licenses/>.
*/
#include <geometry/circle.h>
#include <geometry/seg.h>
#include <geometry/shape_arc.h> // for MIN_PRECISION_IU
#include <math/util.h> // for KiROUND
#include <math/vector2d.h> // for VECTOR2I
#include <math.h> // for sqrt
#include <trigo.h> // 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<VECTOR2I> 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<VECTOR2I> 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<VECTOR2I> 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<VECTOR2I> 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<VECTOR2I> CIRCLE::Intersect( const SEG& aLine ) const
{
std::vector<VECTOR2I> 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;
}

View File

@ -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 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 ); return (ecoord) ( aC.y - aA.y ) * ( aB.x - aA.x ) > (ecoord) ( aB.y - aA.y ) * ( aC.x - aA.x );

View File

@ -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 ) double ArcTangente( int dy, int dx )
{ {

View File

@ -61,6 +61,7 @@ using namespace std::placeholders;
#include <dialogs/dialog_track_via_properties.h> #include <dialogs/dialog_track_via_properties.h>
#include <dialogs/dialog_unit_entry.h> #include <dialogs/dialog_unit_entry.h>
#include <board_commit.h> #include <board_commit.h>
#include <pcb_target.h>
#include <zone_filler.h> #include <zone_filler.h>
@ -269,7 +270,294 @@ int EDIT_TOOL::Drag( const TOOL_EVENT& aEvent )
if( selection.Empty() ) if( selection.Empty() )
return 0; 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<ARC*>( 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<TRACK*>( 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; return 0;
} }

View File

@ -82,10 +82,17 @@ public:
/** /**
* Function Drag() * 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 ); 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() * Function Properties()
* Displays properties window for the selected object. * Displays properties window for the selected object.