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:
parent
0a2c8575ce
commit
235688e459
|
@ -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 ) );
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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 ) )
|
||||||
|
|
|
@ -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++ )
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 )
|
||||||
|
|
Loading…
Reference in New Issue