Less restrictive Arc Track Dragging tool

Use the connecting straight tracks even if not exactly parallel - allow
an error margin configurable in ADVANCED_CFG (default 1 degree). Also
be less strict about end point matching and use the width of the track
as the criteria to determine suitability.

Finally, delete any short lengths of track at the end of the operation
and amend the arc end points to keep connectivity.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/7967
This commit is contained in:
Roberto Fernandez Bautista 2021-03-31 21:13:08 +01:00 committed by Jeff Young
parent 0a2c8575ce
commit 235688e459
7 changed files with 132 additions and 21 deletions

View File

@ -121,6 +121,19 @@ static const wxChar DrawArcAccuracy[] = wxT( "DrawArcAccuracy" );
*/ */
static const wxChar DrawArcCenterStartEndMaxAngle[] = wxT( "DrawArcCenterStartEndMaxAngle" ); static const wxChar DrawArcCenterStartEndMaxAngle[] = wxT( "DrawArcCenterStartEndMaxAngle" );
/**
* For arc track interactive drag-resizing
* Maximum angle between the tangent line of an arc track and a connected straight track
* in order to commence arc dragging. Units are degrees.
*/
static const wxChar MaxTangentTrackAngleDeviation[] = wxT( "MaxTangentTrackAngleDeviation" );
/**
* For arc track interactive drag-resizing
* Maximum track length to keep after doing an arc track resizing operation. Units are mm.
*/
static const wxChar MaxTrackLengthToKeep[] = wxT( "MaxTrackLengthToKeep" );
/** /**
* When true, GAL will stroke the triangulations (only used in OpenGL) with a visible color * When true, GAL will stroke the triangulations (only used in OpenGL) with a visible color
*/ */
@ -237,6 +250,8 @@ ADVANCED_CFG::ADVANCED_CFG()
m_ShowRouterDebugGraphics = false; m_ShowRouterDebugGraphics = false;
m_DrawArcAccuracy = 10.0; m_DrawArcAccuracy = 10.0;
m_DrawArcCenterMaxAngle = 50.0; m_DrawArcCenterMaxAngle = 50.0;
m_MaxTangentAngleDeviation = 1.0;
m_MaxTrackLengthToKeep = 0.0001;
m_DrawTriangulationOutlines = false; m_DrawTriangulationOutlines = false;
m_PluginAltiumSch = false; m_PluginAltiumSch = false;
@ -323,6 +338,12 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::DrawArcCenterStartEndMaxAngle, configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::DrawArcCenterStartEndMaxAngle,
&m_DrawArcCenterMaxAngle, 50.0, 0.0, 100000.0 ) ); &m_DrawArcCenterMaxAngle, 50.0, 0.0, 100000.0 ) );
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::MaxTangentTrackAngleDeviation,
&m_MaxTangentAngleDeviation, 1.0, 0.0, 90.0 ) );
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::MaxTrackLengthToKeep,
&m_MaxTrackLengthToKeep, 0.0005, 0.0, 1.0 ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::StrokeTriangulation, configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::StrokeTriangulation,
&m_DrawTriangulationOutlines, false ) ); &m_DrawTriangulationOutlines, false ) );

View File

@ -78,6 +78,17 @@ public:
*/ */
double m_DrawArcCenterMaxAngle; double m_DrawArcCenterMaxAngle;
/**
* Maximum angle between the tangent line of an arc track and a connected straight track
* in order to commence arc dragging. Units are degrees.
*/
double m_MaxTangentAngleDeviation;
/**
* Maximum track length to keep after doing an arc track resizing operation. Units are mm.
*/
double m_MaxTrackLengthToKeep;
/** /**
* Extra fill clearance for zone fills. Note that for zone tests this is essentially * Extra fill clearance for zone fills. Note that for zone tests this is essentially
* additive with m_DRCEpsilon. Units are mm. * additive with m_DRCEpsilon. Units are mm.

View File

@ -158,6 +158,14 @@ public:
*/ */
int LineDistance( const VECTOR2I& aP, bool aDetermineSide = false ) const; int LineDistance( const VECTOR2I& aP, bool aDetermineSide = false ) const;
/**
* Determine the smallest angle between two segments (result in degrees)
*
* @param aOther point to determine the orientation wrs to self
* @return smallest angle between this and aOther (degrees)
*/
double AngleDegrees( const SEG& aOther ) const;
/** /**
* Compute a point on the segment (this) that is closest to point \a aP. * Compute a point on the segment (this) that is closest to point \a aP.
* *

View File

@ -27,6 +27,7 @@
#include <geometry/seg.h> #include <geometry/seg.h>
#include <math/util.h> // for rescale #include <math/util.h> // for rescale
#include <math/vector2d.h> // for VECTOR2I, VECTOR2 #include <math/vector2d.h> // for VECTOR2I, VECTOR2
#include <trigo.h> // for RAD2DEG
template <typename T> template <typename T>
int sgn( T aVal ) int sgn( T aVal )
@ -60,6 +61,19 @@ SEG::ecoord SEG::SquaredDistance( const SEG& aSeg ) const
} }
double SEG::AngleDegrees( const SEG& aOther ) const
{
VECTOR2I thisVec = A - B;
VECTOR2I otherVec = aOther.A - aOther.B;
double thisVecAngle = NormalizeAngle180( RAD2DECIDEG( thisVec.Angle() ) );
double otherVecAngle = NormalizeAngle180( RAD2DECIDEG( otherVec.Angle() ) );
double angleDegrees = std::abs( NormalizeAngle180( thisVecAngle - otherVecAngle ) ) / 10.0;
return std::min( 180.0 - angleDegrees, angleDegrees );
}
const VECTOR2I SEG::NearestPoint( const SEG& aSeg ) const const VECTOR2I SEG::NearestPoint( const SEG& aSeg ) const
{ {
if( OPT_VECTOR2I p = Intersect( aSeg ) ) if( OPT_VECTOR2I p = Intersect( aSeg ) )

View File

@ -647,10 +647,14 @@ bool CONNECTIVITY_DATA::TestTrackEndpointDangling( TRACK* aTrack, wxPoint* aPos
const std::vector<BOARD_CONNECTED_ITEM*> CONNECTIVITY_DATA::GetConnectedItemsAtAnchor( const std::vector<BOARD_CONNECTED_ITEM*> CONNECTIVITY_DATA::GetConnectedItemsAtAnchor(
const BOARD_CONNECTED_ITEM* aItem, const VECTOR2I& aAnchor, const KICAD_T aTypes[] ) const const BOARD_CONNECTED_ITEM* aItem,
const VECTOR2I& aAnchor,
const KICAD_T aTypes[],
const int& aMaxError ) const
{ {
auto& entry = m_connAlgo->ItemEntry( aItem ); auto& entry = m_connAlgo->ItemEntry( aItem );
std::vector<BOARD_CONNECTED_ITEM* > rv; std::vector<BOARD_CONNECTED_ITEM*> rv;
SEG::ecoord maxErrorSq = (SEG::ecoord) aMaxError * aMaxError;
for( auto cnItem : entry.GetItems() ) for( auto cnItem : entry.GetItems() )
{ {
@ -658,7 +662,7 @@ const std::vector<BOARD_CONNECTED_ITEM*> CONNECTIVITY_DATA::GetConnectedItemsAtA
{ {
for( auto anchor : connected->Anchors() ) for( auto anchor : connected->Anchors() )
{ {
if( anchor->Pos() == aAnchor ) if( ( anchor->Pos() - aAnchor ).SquaredEuclideanNorm() <= maxErrorSq )
{ {
for( int i = 0; aTypes[i] > 0; i++ ) for( int i = 0; aTypes[i] > 0; i++ )
{ {

View File

@ -210,14 +210,18 @@ public:
/** /**
* Function GetConnectedItemsAtAnchor() * Function GetConnectedItemsAtAnchor()
* Returns a list of items connected to a source item aItem at position aAnchor * Returns a list of items connected to a source item aItem at position aAnchor
* with an optional maximum distance from the defined anchor.
* @param aItem is the reference item to find other connected items. * @param aItem is the reference item to find other connected items.
* @param aAnchor is the position to find connected items on. * @param aAnchor is the position to find connected items on.
* @param aTypes allows one to filter by item types. * @param aTypes allows one to filter by item types.
* @param aMaxError Maximum distance of the found items' anchors to aAnchor in IU
* @return * @return
*/ */
const std::vector<BOARD_CONNECTED_ITEM*> GetConnectedItemsAtAnchor( const BOARD_CONNECTED_ITEM* aItem, const std::vector<BOARD_CONNECTED_ITEM*> GetConnectedItemsAtAnchor(
const VECTOR2I& aAnchor, const BOARD_CONNECTED_ITEM* aItem,
const KICAD_T aTypes[] ) const; const VECTOR2I& aAnchor,
const KICAD_T aTypes[],
const int& aMaxError = 0 ) const;
void GetUnconnectedEdges( std::vector<CN_EDGE>& aEdges ) const; void GetUnconnectedEdges( std::vector<CN_EDGE>& aEdges ) const;

View File

@ -24,6 +24,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/ */
#include <advanced_config.h>
#include <limits> #include <limits>
#include <board.h> #include <board.h>
#include <footprint.h> #include <footprint.h>
@ -298,6 +299,15 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
Activate(); Activate();
ARC* theArc = static_cast<ARC*>( selection.Front() ); ARC* theArc = static_cast<ARC*>( selection.Front() );
double arcAngleDegrees = std::abs( theArc->GetAngle() ) / 10.0;
if( arcAngleDegrees + ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation >= 180.0 )
{
frame()->ShowInfoBarError(
wxString::Format( _( "Unable to resize arc tracks %.1f degrees or greater." ),
180.0 - ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation ) );
return 0; // don't bother with > 180 degree arcs
}
KIGFX::VIEW_CONTROLS* controls = getViewControls(); KIGFX::VIEW_CONTROLS* controls = getViewControls();
@ -309,9 +319,6 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() ); SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() );
SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() ); SEG tanEnd = SEG( arcCenter, theArc->GetEnd() ).PerpendicularSeg( theArc->GetEnd() );
if( tanStart.ApproxParallel( tanEnd ) )
return 0; // don't bother with 180 degree arcs
//Ensure the tangent segments are in the correct orientation //Ensure the tangent segments are in the correct orientation
VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd ).get(); VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd ).get();
tanStart.A = tanIntersect; tanStart.A = tanIntersect;
@ -325,7 +332,22 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
[&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> TRACK* [&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> TRACK*
{ {
auto conn = board()->GetConnectivity(); auto conn = board()->GetConnectivity();
auto itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor, track_types );
// Allow items at a distance within the width of the arc track
int allowedDeviation = theArc->GetWidth();
std::vector<BOARD_CONNECTED_ITEM*> itemsOnAnchor;
for( int i = 0; i < 3; i++ )
{
itemsOnAnchor = conn->GetConnectedItemsAtAnchor( theArc, aAnchor, track_types,
allowedDeviation );
allowedDeviation /= 2;
if( itemsOnAnchor.size() == 1 )
break;
}
TRACK* retval = nullptr; TRACK* retval = nullptr;
if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T ) if( itemsOnAnchor.size() == 1 && itemsOnAnchor.front()->Type() == PCB_TRACE_T )
@ -333,9 +355,12 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
retval = static_cast<TRACK*>( itemsOnAnchor.front() ); retval = static_cast<TRACK*>( itemsOnAnchor.front() );
SEG trackSeg( retval->GetStart(), retval->GetEnd() ); SEG trackSeg( retval->GetStart(), retval->GetEnd() );
//Ensure it is collinear // Allow deviations in colinearity as defined in ADVANCED_CFG
if( !trackSeg.ApproxCollinear( aCollinearSeg ) ) if( trackSeg.AngleDegrees( aCollinearSeg )
> ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation )
{
retval = nullptr; retval = nullptr;
}
} }
if( !retval ) if( !retval )
@ -361,18 +386,21 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
TRACK* trackOnStartCopy = new TRACK( *trackOnStart ); TRACK* trackOnStartCopy = new TRACK( *trackOnStart );
TRACK* trackOnEndCopy = new TRACK( *trackOnEnd ); TRACK* trackOnEndCopy = new TRACK( *trackOnEnd );
if( trackOnStart->GetLength() > tanStart.Length() ) if( trackOnStart->GetLength() != 0 )
{ {
tanStart.A = trackOnStart->GetStart(); tanStart.A = trackOnStart->GetStart();
tanStart.B = trackOnStart->GetEnd(); tanStart.B = trackOnStart->GetEnd();
} }
if( trackOnEnd->GetLength() > tanEnd.Length() ) if( trackOnEnd->GetLength() != 0 )
{ {
tanEnd.A = trackOnEnd->GetStart(); tanEnd.A = trackOnEnd->GetStart();
tanEnd.B = trackOnEnd->GetEnd(); tanEnd.B = trackOnEnd->GetEnd();
} }
// Recalculate intersection point
tanIntersect = tanStart.IntersectLines( tanEnd ).get();
auto isTrackStartClosestToArcStart = auto isTrackStartClosestToArcStart =
[&]( TRACK* aPointA ) -> bool [&]( TRACK* aPointA ) -> bool
{ {
@ -530,42 +558,63 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent )
// Ensure we only do one commit operation on each object // Ensure we only do one commit operation on each object
auto processTrack = auto processTrack =
[&]( TRACK* aTrack, TRACK* aTrackCopy ) [&]( TRACK* aTrack, TRACK* aTrackCopy, int aMaxLengthIU ) -> bool
{ {
if( aTrack->IsNew() ) if( aTrack->IsNew() )
{ {
getView()->Remove( aTrack ); getView()->Remove( aTrack );
if( aTrack->GetStart() == aTrack->GetEnd() ) if( aTrack->GetLength() <= aMaxLengthIU )
{ {
delete aTrack; delete aTrack;
delete aTrackCopy; delete aTrackCopy;
aTrack = nullptr; aTrack = nullptr;
aTrackCopy = nullptr; aTrackCopy = nullptr;
return false;
} }
else else
{ {
m_commit->Add( aTrack ); m_commit->Add( aTrack );
delete aTrackCopy; delete aTrackCopy;
aTrackCopy = nullptr; aTrackCopy = nullptr;
return true;
} }
} }
else if( aTrack->GetStart() == aTrack->GetEnd() ) else if( aTrack->GetLength() <= aMaxLengthIU )
{ {
aTrack->SwapData( aTrackCopy ); //restore the original before notifying COMMIT aTrack->SwapData( aTrackCopy ); //restore the original before notifying COMMIT
m_commit->Remove( aTrack ); m_commit->Remove( aTrack );
delete aTrackCopy; delete aTrackCopy;
aTrackCopy = nullptr; aTrackCopy = nullptr;
return false;
} }
else else
{ {
m_commit->Modified( aTrack, aTrackCopy ); m_commit->Modified( aTrack, aTrackCopy );
} }
return true;
}; };
processTrack( trackOnStart, trackOnStartCopy ); // Ammend the end points of the arc if we delete the joining tracks
processTrack( trackOnEnd, trackOnEndCopy ); wxPoint newStart = trackOnStart->GetStart();
processTrack( theArc, theArcCopy ); wxPoint newEnd = trackOnEnd->GetStart();
if( isStartTrackOnStartPt )
newStart = trackOnStart->GetEnd();
if( isEndTrackOnStartPt )
newEnd = trackOnEnd->GetEnd();
int maxLengthIU = KiROUND( ADVANCED_CFG::GetCfg().m_MaxTrackLengthToKeep * IU_PER_MM );
if( !processTrack( trackOnStart, trackOnStartCopy, maxLengthIU ) )
theArc->SetStart( newStart );
if( !processTrack( trackOnEnd, trackOnEndCopy, maxLengthIU ) )
theArc->SetEnd( newEnd );
processTrack( theArc, theArcCopy, 0 ); // only delete the arc if start and end points coincide
// Should we commit? // Should we commit?
if( restore_state ) if( restore_state )