diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 38b6027bf8..cbcbf75f11 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -121,6 +121,19 @@ static const wxChar DrawArcAccuracy[] = wxT( "DrawArcAccuracy" ); */ 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 */ @@ -237,6 +250,8 @@ ADVANCED_CFG::ADVANCED_CFG() m_ShowRouterDebugGraphics = false; m_DrawArcAccuracy = 10.0; m_DrawArcCenterMaxAngle = 50.0; + m_MaxTangentAngleDeviation = 1.0; + m_MaxTrackLengthToKeep = 0.0001; m_DrawTriangulationOutlines = 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, &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, &m_DrawTriangulationOutlines, false ) ); diff --git a/include/advanced_config.h b/include/advanced_config.h index f32d2e31f0..e6105aace0 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -78,6 +78,17 @@ public: */ 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 * additive with m_DRCEpsilon. Units are mm. diff --git a/libs/kimath/include/geometry/seg.h b/libs/kimath/include/geometry/seg.h index cf0a291041..a8f10288c9 100644 --- a/libs/kimath/include/geometry/seg.h +++ b/libs/kimath/include/geometry/seg.h @@ -158,6 +158,14 @@ public: */ 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. * diff --git a/libs/kimath/src/geometry/seg.cpp b/libs/kimath/src/geometry/seg.cpp index 9858977c65..5524b930b6 100644 --- a/libs/kimath/src/geometry/seg.cpp +++ b/libs/kimath/src/geometry/seg.cpp @@ -27,6 +27,7 @@ #include #include // for rescale #include // for VECTOR2I, VECTOR2 +#include // for RAD2DEG template 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 { if( OPT_VECTOR2I p = Intersect( aSeg ) ) diff --git a/pcbnew/connectivity/connectivity_data.cpp b/pcbnew/connectivity/connectivity_data.cpp index 5d9286dc07..42cc46ca71 100644 --- a/pcbnew/connectivity/connectivity_data.cpp +++ b/pcbnew/connectivity/connectivity_data.cpp @@ -647,10 +647,14 @@ bool CONNECTIVITY_DATA::TestTrackEndpointDangling( TRACK* aTrack, wxPoint* aPos const std::vector 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 ); - std::vector rv; + auto& entry = m_connAlgo->ItemEntry( aItem ); + std::vector rv; + SEG::ecoord maxErrorSq = (SEG::ecoord) aMaxError * aMaxError; for( auto cnItem : entry.GetItems() ) { @@ -658,7 +662,7 @@ const std::vector CONNECTIVITY_DATA::GetConnectedItemsAtA { for( auto anchor : connected->Anchors() ) { - if( anchor->Pos() == aAnchor ) + if( ( anchor->Pos() - aAnchor ).SquaredEuclideanNorm() <= maxErrorSq ) { for( int i = 0; aTypes[i] > 0; i++ ) { diff --git a/pcbnew/connectivity/connectivity_data.h b/pcbnew/connectivity/connectivity_data.h index e291642668..5a2a67a613 100644 --- a/pcbnew/connectivity/connectivity_data.h +++ b/pcbnew/connectivity/connectivity_data.h @@ -210,14 +210,18 @@ public: /** * Function GetConnectedItemsAtAnchor() * 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 aAnchor is the position to find connected items on. * @param aTypes allows one to filter by item types. + * @param aMaxError Maximum distance of the found items' anchors to aAnchor in IU * @return */ - const std::vector GetConnectedItemsAtAnchor( const BOARD_CONNECTED_ITEM* aItem, - const VECTOR2I& aAnchor, - const KICAD_T aTypes[] ) const; + const std::vector GetConnectedItemsAtAnchor( + const BOARD_CONNECTED_ITEM* aItem, + const VECTOR2I& aAnchor, + const KICAD_T aTypes[], + const int& aMaxError = 0 ) const; void GetUnconnectedEdges( std::vector& aEdges ) const; diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index 2a528e9ac7..970ff7da7c 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -24,6 +24,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ +#include #include #include #include @@ -298,6 +299,15 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent ) Activate(); ARC* theArc = static_cast( 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(); @@ -309,9 +319,6 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent ) SEG tanStart = SEG( arcCenter, theArc->GetStart() ).PerpendicularSeg( theArc->GetStart() ); 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 VECTOR2I tanIntersect = tanStart.IntersectLines( tanEnd ).get(); tanStart.A = tanIntersect; @@ -325,7 +332,22 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent ) [&]( const VECTOR2I& aAnchor, const SEG& aCollinearSeg ) -> TRACK* { 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 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; 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( itemsOnAnchor.front() ); SEG trackSeg( retval->GetStart(), retval->GetEnd() ); - //Ensure it is collinear - if( !trackSeg.ApproxCollinear( aCollinearSeg ) ) + // Allow deviations in colinearity as defined in ADVANCED_CFG + if( trackSeg.AngleDegrees( aCollinearSeg ) + > ADVANCED_CFG::GetCfg().m_MaxTangentAngleDeviation ) + { retval = nullptr; + } } if( !retval ) @@ -361,18 +386,21 @@ int EDIT_TOOL::DragArcTrack( const TOOL_EVENT& aEvent ) TRACK* trackOnStartCopy = new TRACK( *trackOnStart ); TRACK* trackOnEndCopy = new TRACK( *trackOnEnd ); - if( trackOnStart->GetLength() > tanStart.Length() ) + if( trackOnStart->GetLength() != 0 ) { tanStart.A = trackOnStart->GetStart(); tanStart.B = trackOnStart->GetEnd(); } - if( trackOnEnd->GetLength() > tanEnd.Length() ) + if( trackOnEnd->GetLength() != 0 ) { tanEnd.A = trackOnEnd->GetStart(); tanEnd.B = trackOnEnd->GetEnd(); } + // Recalculate intersection point + tanIntersect = tanStart.IntersectLines( tanEnd ).get(); + auto isTrackStartClosestToArcStart = [&]( 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 auto processTrack = - [&]( TRACK* aTrack, TRACK* aTrackCopy ) + [&]( TRACK* aTrack, TRACK* aTrackCopy, int aMaxLengthIU ) -> bool { if( aTrack->IsNew() ) { getView()->Remove( aTrack ); - if( aTrack->GetStart() == aTrack->GetEnd() ) + if( aTrack->GetLength() <= aMaxLengthIU ) { delete aTrack; delete aTrackCopy; aTrack = nullptr; aTrackCopy = nullptr; + return false; } else { m_commit->Add( aTrack ); delete aTrackCopy; aTrackCopy = nullptr; + return true; } } - else if( aTrack->GetStart() == aTrack->GetEnd() ) + else if( aTrack->GetLength() <= aMaxLengthIU ) { aTrack->SwapData( aTrackCopy ); //restore the original before notifying COMMIT m_commit->Remove( aTrack ); delete aTrackCopy; aTrackCopy = nullptr; + return false; } else { m_commit->Modified( aTrack, aTrackCopy ); } + + return true; }; - processTrack( trackOnStart, trackOnStartCopy ); - processTrack( trackOnEnd, trackOnEndCopy ); - processTrack( theArc, theArcCopy ); + // Ammend the end points of the arc if we delete the joining tracks + wxPoint newStart = trackOnStart->GetStart(); + 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? if( restore_state )