diff --git a/common/gal/cairo/cairo_gal.cpp b/common/gal/cairo/cairo_gal.cpp index 3cdc9186bc..bc65c0e131 100644 --- a/common/gal/cairo/cairo_gal.cpp +++ b/common/gal/cairo/cairo_gal.cpp @@ -1746,6 +1746,8 @@ void CAIRO_GAL_BASE::DrawGrid() return; VECTOR2D gridScreenSize( m_gridSize ); + gridScreenSize = VECTOR2D( std::max( 1.0, gridScreenSize.x ), + std::max( 1.0, gridScreenSize.y ) ); double gridThreshold = KiROUND( computeMinGridSpacing() / m_worldScale ); diff --git a/common/gal/graphics_abstraction_layer.cpp b/common/gal/graphics_abstraction_layer.cpp index ae2538566f..7c514fe04a 100644 --- a/common/gal/graphics_abstraction_layer.cpp +++ b/common/gal/graphics_abstraction_layer.cpp @@ -76,6 +76,8 @@ GAL::GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions ) : SetCoarseGrid( 10 ); m_gridLineWidth = 0.5f; m_gridStyle = GRID_STYLE::LINES; + m_gridSize = VECTOR2D( 1.0, 1.0 ); + m_gridOffset = VECTOR2D( 0.0, 0.0 ); m_gridMinSpacing = 10; // Initialize the cursor shape diff --git a/common/preview_items/construction_geom.cpp b/common/preview_items/construction_geom.cpp index 41f8699537..5604649392 100644 --- a/common/preview_items/construction_geom.cpp +++ b/common/preview_items/construction_geom.cpp @@ -24,6 +24,7 @@ #include "preview_items/construction_geom.h" #include +#include #include #include #include @@ -39,9 +40,15 @@ CONSTRUCTION_GEOM::CONSTRUCTION_GEOM() : } -void CONSTRUCTION_GEOM::AddDrawable( const DRAWABLE& aItem, bool aPersistent ) +void CONSTRUCTION_GEOM::AddDrawable( const DRAWABLE& aItem, bool aPersistent, int aLineWidth ) { - m_drawables.push_back( { aItem, aPersistent } ); + m_drawables.push_back( { aItem, aPersistent, aLineWidth } ); +} + + +void CONSTRUCTION_GEOM::SetSnapGuides( std::vector aGuides ) +{ + m_snapGuides = std::move( aGuides ); } @@ -90,6 +97,7 @@ void CONSTRUCTION_GEOM::ViewDraw( int aLayer, VIEW* aView ) const for( const DRAWABLE_INFO& drawable : m_drawables ) { gal.SetStrokeColor( drawable.IsPersistent ? m_persistentColor : m_color ); + gal.SetLineWidth( drawable.LineWidth ); std::visit( [&]( const auto& visited ) @@ -139,9 +147,17 @@ void CONSTRUCTION_GEOM::ViewDraw( int aLayer, VIEW* aView ) const drawable.Item ); } + for( const SNAP_GUIDE& guide : m_snapGuides ) + { + gal.SetStrokeColor( guide.Color ); + gal.SetLineWidth( guide.LineWidth ); + gal.DrawLine( guide.Segment.A, guide.Segment.B ); + } + if( haveSnapLine ) { gal.SetStrokeColor( m_persistentColor ); + gal.SetLineWidth( 2 ); const int dashSizeBasis = aView->ToWorld( 12 ); const int snapOriginMarkerSize = aView->ToWorld( 16 ); diff --git a/common/tool/construction_manager.cpp b/common/tool/construction_manager.cpp index 952d100ab9..f2b2bfda96 100644 --- a/common/tool/construction_manager.cpp +++ b/common/tool/construction_manager.cpp @@ -23,11 +23,18 @@ #include "tool/construction_manager.h" +#include #include +#include +#include +#include +#include #include +#include #include +#include #include @@ -329,9 +336,9 @@ void CONSTRUCTION_MANAGER::acceptConstructionItems( std::unique_ptr( &aViewHandler ) ) { + wxASSERT( m_snapManager ); + SetDirections( { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ) } ); +} + + +static VECTOR2I normalizeDirection( const VECTOR2I& aDir ) +{ + if( aDir.x == 0 && aDir.y == 0 ) + return VECTOR2I( 0, 0 ); + + int dx = aDir.x; + int dy = aDir.y; + + int gcd = std::gcd( std::abs( dx ), std::abs( dy ) ); + + if( gcd > 0 ) + { + dx /= gcd; + dy /= gcd; + } + + if( dx < 0 || ( dx == 0 && dy < 0 ) ) + { + dx = -dx; + dy = -dy; + } + + return VECTOR2I( dx, dy ); +} + + +static std::optional findDirectionIndex( const std::vector& aDirections, + const VECTOR2I& aDelta ) +{ + VECTOR2I normalized = normalizeDirection( aDelta ); + + if( normalized.x == 0 && normalized.y == 0 ) + return std::nullopt; + + for( size_t i = 0; i < aDirections.size(); ++i ) + { + if( aDirections[i] == normalized ) + return static_cast( i ); + } + + return std::nullopt; +} + + +void SNAP_LINE_MANAGER::SetDirections( const std::vector& aDirections ) +{ + std::vector uniqueDirections; + uniqueDirections.reserve( aDirections.size() ); + + for( const VECTOR2I& direction : aDirections ) + { + VECTOR2I normalized = normalizeDirection( direction ); + + if( normalized.x == 0 && normalized.y == 0 ) + continue; + + if( std::find( uniqueDirections.begin(), uniqueDirections.end(), normalized ) + == uniqueDirections.end() ) + { + uniqueDirections.push_back( normalized ); + } + } + + if( uniqueDirections != m_directions ) + { + m_directions = std::move( uniqueDirections ); + m_activeDirection.reset(); + + if( m_snapLineOrigin && m_snapLineEnd ) + { + if( !findDirectionIndex( m_directions, *m_snapLineEnd - *m_snapLineOrigin ) ) + m_snapLineEnd.reset(); + } + + if( m_directions.empty() ) + { + ClearSnapLine(); + return; + } + + notifyGuideChange(); + } } void SNAP_LINE_MANAGER::SetSnapLineOrigin( const VECTOR2I& aOrigin ) { - // Setting the origin clears the snap line as the end point is no longer valid - ClearSnapLine(); + if( m_snapLineOrigin && *m_snapLineOrigin == aOrigin && !m_snapLineEnd ) + { + notifyGuideChange(); + return; + } + m_snapLineOrigin = aOrigin; + m_snapLineEnd.reset(); + m_activeDirection.reset(); + m_viewHandler.GetViewItem().ClearSnapLine(); + notifyGuideChange(); } @@ -402,12 +504,17 @@ void SNAP_LINE_MANAGER::SetSnapLineEnd( const OPT_VECTOR2I& aSnapEnd ) { m_snapLineEnd = aSnapEnd; + if( m_snapLineEnd ) + m_activeDirection = findDirectionIndex( m_directions, *m_snapLineEnd - *m_snapLineOrigin ); + else + m_activeDirection.reset(); + if( m_snapLineEnd ) m_viewHandler.GetViewItem().SetSnapLine( SEG{ *m_snapLineOrigin, *m_snapLineEnd } ); else m_viewHandler.GetViewItem().ClearSnapLine(); - m_viewHandler.updateView(); + notifyGuideChange(); } } @@ -416,8 +523,9 @@ void SNAP_LINE_MANAGER::ClearSnapLine() { m_snapLineOrigin.reset(); m_snapLineEnd.reset(); + m_activeDirection.reset(); m_viewHandler.GetViewItem().ClearSnapLine(); - m_viewHandler.updateView(); + notifyGuideChange(); } @@ -425,7 +533,7 @@ void SNAP_LINE_MANAGER::SetSnappedAnchor( const VECTOR2I& aAnchorPos ) { if( m_snapLineOrigin.has_value() ) { - if( aAnchorPos.x == m_snapLineOrigin->x || aAnchorPos.y == m_snapLineOrigin->y ) + if( findDirectionIndex( m_directions, aAnchorPos - *m_snapLineOrigin ) ) { SetSnapLineEnd( aAnchorPos ); } @@ -444,93 +552,86 @@ void SNAP_LINE_MANAGER::SetSnappedAnchor( const VECTOR2I& aAnchorPos ) } -/** - * Check if the cursor has moved far enough away from the snap line origin to escape snapping - * in the X direction. - * - * This is defined as within aEscapeRange of the snap line origin, and within aLongRangeEscapeAngle - * of the vertical line passing through the snap line origin. - */ -static bool pointHasEscapedSnapLineX( const VECTOR2I& aCursor, const VECTOR2I& aSnapLineOrigin, - int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle ) -{ - if( std::abs( aCursor.x - aSnapLineOrigin.x ) < aEscapeRange ) - { - return false; - } - EDA_ANGLE angle = EDA_ANGLE( aCursor - aSnapLineOrigin ) + EDA_ANGLE( 90, DEGREES_T ); - return std::abs( angle.Normalize90() ) > aLongRangeEscapeAngle; -} - - -/** - * As above, but for the Y direction. - */ -static bool pointHasEscapedSnapLineY( const VECTOR2I& aCursor, const VECTOR2I& aSnapLineOrigin, - int aEscapeRange, EDA_ANGLE aLongRangeEscapeAngle ) -{ - if( std::abs( aCursor.y - aSnapLineOrigin.y ) < aEscapeRange ) - { - return false; - } - EDA_ANGLE angle = EDA_ANGLE( aCursor - aSnapLineOrigin ); - return std::abs( angle.Normalize90() ) > aLongRangeEscapeAngle; -} - - OPT_VECTOR2I SNAP_LINE_MANAGER::GetNearestSnapLinePoint( const VECTOR2I& aCursor, const VECTOR2I& aNearestGrid, std::optional aDistToNearest, int aSnapRange ) const { - // return std::nullopt; - if( m_snapLineOrigin ) + wxUnusedVar( aNearestGrid ); + + if( !m_snapLineOrigin || m_directions.empty() ) + return std::nullopt; + + const bool gridBetterThanNearest = !aDistToNearest || *aDistToNearest > aSnapRange; + + if( !gridBetterThanNearest ) + return std::nullopt; + + const int escapeRange = 2 * aSnapRange; + const EDA_ANGLE longRangeEscapeAngle( 4, DEGREES_T ); + + const VECTOR2D origin( *m_snapLineOrigin ); + const VECTOR2D cursor( aCursor ); + const VECTOR2D delta = cursor - origin; + + double bestPerpDistance = std::numeric_limits::max(); + std::optional bestSnapPoint; + + for( const VECTOR2I& direction : m_directions ) { - bool snapLine = false; - VECTOR2I bestSnapPoint = aNearestGrid; + VECTOR2D dirVector( direction ); + double dirLength = dirVector.EuclideanNorm(); - // If there's no snap anchor, or it's too far away, prefer the grid - const bool gridBetterThanNearest = !aDistToNearest || *aDistToNearest > aSnapRange; + if( dirLength == 0.0 ) + continue; - // The escape range is how far you go before the snap line is de-activated. - // Make this a bit more forgiving than the snap range, as you can easily cancel - // deliberately with a mouse move. - // These are both a bit arbitrary, and can be adjusted as preferred - const int escapeRange = 2 * aSnapRange; - const EDA_ANGLE longRangeEscapeAngle( 4, DEGREES_T ); + VECTOR2D dirUnit = dirVector / dirLength; - const bool escapedX = pointHasEscapedSnapLineX( aCursor, *m_snapLineOrigin, escapeRange, - longRangeEscapeAngle ); - const bool escapedY = pointHasEscapedSnapLineY( aCursor, *m_snapLineOrigin, escapeRange, - longRangeEscapeAngle ); + double distanceAlong = delta.Dot( dirUnit ); + VECTOR2D projection = origin + dirUnit * distanceAlong; + VECTOR2D offset = delta - dirUnit * distanceAlong; + double perpDistance = offset.EuclideanNorm(); - /// Allows de-snapping from the line if you are closer to another snap point - /// Or if you have moved far enough away from the line - if( !escapedX && gridBetterThanNearest ) + if( perpDistance > aSnapRange ) + continue; + + bool escaped = false; + + if( perpDistance >= escapeRange ) { - bestSnapPoint.x = m_snapLineOrigin->x; - snapLine = true; + EDA_ANGLE deltaAngle( delta ); + EDA_ANGLE directionAngle( dirVector ); + double angleDiff = ( deltaAngle - directionAngle ).Normalize180().AsDegrees(); + + if( std::abs( angleDiff ) > longRangeEscapeAngle.AsDegrees() ) + escaped = true; } - if( !escapedY && gridBetterThanNearest ) + if( !escaped && perpDistance < bestPerpDistance ) { - bestSnapPoint.y = m_snapLineOrigin->y; - snapLine = true; - } - - if( snapLine ) - { - return bestSnapPoint; + bestPerpDistance = perpDistance; + bestSnapPoint = VECTOR2I( KiROUND( projection.x ), KiROUND( projection.y ) ); } } + if( bestSnapPoint ) + return *bestSnapPoint; + return std::nullopt; } +void SNAP_LINE_MANAGER::notifyGuideChange() +{ + if( m_snapManager ) + m_snapManager->UpdateSnapGuides(); +} + + SNAP_MANAGER::SNAP_MANAGER( KIGFX::CONSTRUCTION_GEOM& aHelper ) : CONSTRUCTION_VIEW_HANDLER( aHelper ), m_snapLineManager( *this ), - m_constructionManager( *this ) + m_constructionManager( *this ), m_snapGuideColor( KIGFX::COLOR4D::WHITE ), + m_snapGuideHighlightColor( KIGFX::COLOR4D::WHITE ) { } @@ -540,13 +641,67 @@ void SNAP_MANAGER::updateView() if( m_updateCallback ) { bool showAnything = m_constructionManager.HasActiveConstruction() - || m_snapLineManager.HasCompleteSnapLine(); + || m_snapLineManager.HasCompleteSnapLine() + || ( m_snapLineManager.GetSnapLineOrigin() + && !m_snapLineManager.GetDirections().empty() ); m_updateCallback( showAnything ); } } +void SNAP_MANAGER::SetSnapGuideColors( const KIGFX::COLOR4D& aBase, const KIGFX::COLOR4D& aHighlight ) +{ + m_snapGuideColor = aBase; + m_snapGuideHighlightColor = aHighlight; + UpdateSnapGuides(); +} + + +void SNAP_MANAGER::UpdateSnapGuides() +{ + std::vector guides; + + const OPT_VECTOR2I& origin = m_snapLineManager.GetSnapLineOrigin(); + const std::vector& directions = m_snapLineManager.GetDirections(); + + if( origin && !directions.empty() ) + { + const std::optional activeDirection = m_snapLineManager.GetActiveDirection(); + const int guideLength = 500000; + + for( size_t ii = 0; ii < directions.size(); ++ii ) + { + const VECTOR2I& direction = directions[ii]; + + if( direction.x == 0 && direction.y == 0 ) + continue; + + VECTOR2I scaled = direction * guideLength; + + KIGFX::CONSTRUCTION_GEOM::SNAP_GUIDE guide; + guide.Segment = SEG( *origin - scaled, *origin + scaled ); + + if( activeDirection && *activeDirection == static_cast( ii ) ) + { + guide.LineWidth = 2; + guide.Color = m_snapGuideHighlightColor; + } + else + { + guide.LineWidth = 1; + guide.Color = m_snapGuideColor; + } + + guides.push_back( guide ); + } + } + + GetViewItem().SetSnapGuides( std::move( guides ) ); + updateView(); +} + + std::vector SNAP_MANAGER::GetConstructionItems() const { @@ -566,13 +721,24 @@ SNAP_MANAGER::GetConstructionItems() const {}, } ); - // One horizontal and one vertical infinite line from the snap point - snapPointItem.Constructions.push_back( - LINE{ *snapLineOrigin, *snapLineOrigin + VECTOR2I( 100000, 0 ) } ); - snapPointItem.Constructions.push_back( - LINE{ *snapLineOrigin, *snapLineOrigin + VECTOR2I( 0, 100000 ) } ); + const std::vector& directions = m_snapLineManager.GetDirections(); + const std::optional activeDirection = m_snapLineManager.GetActiveDirection(); - batches.push_back( std::move( batch ) ); + for( size_t ii = 0; ii < directions.size(); ++ii ) + { + const VECTOR2I& direction = directions[ii]; + + VECTOR2I scaledDirection = direction * 100000; + + CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM::DRAWABLE_ENTRY entry; + entry.Drawable = LINE{ *snapLineOrigin, *snapLineOrigin + scaledDirection }; + entry.LineWidth = ( activeDirection && *activeDirection == static_cast( ii ) ) ? 2 : 1; + + snapPointItem.Constructions.push_back( entry ); + } + + if( !snapPointItem.Constructions.empty() ) + batches.push_back( std::move( batch ) ); } return batches; diff --git a/common/tool/grid_helper.cpp b/common/tool/grid_helper.cpp index 0f0c1129cc..42e7897b89 100644 --- a/common/tool/grid_helper.cpp +++ b/common/tool/grid_helper.cpp @@ -24,13 +24,13 @@ #include "tool/grid_helper.h" #include +#include +#include #include #include -#include #include // for KiROUND #include -#include #include #include #include @@ -61,12 +61,8 @@ GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) : if( !m_toolMgr ) return; - KIGFX::VIEW* view = m_toolMgr->GetView(); - KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings(); - - const KIGFX::COLOR4D constructionColour = settings->GetLayerColor( aConstructionLayer ); - m_constructionGeomPreview.SetPersistentColor( constructionColour ); - m_constructionGeomPreview.SetColor( constructionColour.WithAlpha( 0.7 ) ); + KIGFX::VIEW* view = m_toolMgr->GetView(); + wxUnusedVar( aConstructionLayer ); view->Add( &m_constructionGeomPreview ); view->SetVisible( &m_constructionGeomPreview, false ); @@ -135,6 +131,119 @@ void GRID_HELPER::showConstructionGeometry( bool aShow ) } +void GRID_HELPER::SetSnapLineDirections( const std::vector& aDirections ) +{ + m_snapManager.GetSnapLineManager().SetDirections( aDirections ); +} + + +void GRID_HELPER::SetSnapLineOrigin( const VECTOR2I& aOrigin ) +{ + m_snapManager.GetSnapLineManager().SetSnapLineOrigin( aOrigin ); +} + + +void GRID_HELPER::ClearSnapLine() +{ + m_snapManager.GetSnapLineManager().ClearSnapLine(); +} + + +std::optional GRID_HELPER::SnapToConstructionLines( const VECTOR2I& aPoint, + const VECTOR2I& aNearestGrid, + const VECTOR2D& aGrid, + double aSnapRange ) const +{ + const SNAP_LINE_MANAGER& snapLineManager = m_snapManager.GetSnapLineManager(); + const OPT_VECTOR2I& snapOrigin = snapLineManager.GetSnapLineOrigin(); + + if( !snapOrigin || snapLineManager.GetDirections().empty() ) + return std::nullopt; + + const VECTOR2I& origin = *snapOrigin; + + const std::vector& directions = snapLineManager.GetDirections(); + const std::optional activeDirection = snapLineManager.GetActiveDirection(); + + const VECTOR2D originVec( origin ); + const VECTOR2D cursorVec( aPoint ); + const VECTOR2D delta = cursorVec - originVec; + + std::optional bestPoint; + double bestPerp = std::numeric_limits::max(); + double bestDistance = std::numeric_limits::max(); + + for( size_t ii = 0; ii < directions.size(); ++ii ) + { + const VECTOR2I& dir = directions[ii]; + VECTOR2D dirVector( dir ); + double dirLength = dirVector.EuclideanNorm(); + + if( dirLength == 0.0 ) + continue; + + VECTOR2D dirUnit = dirVector / dirLength; + + double distanceAlong = delta.Dot( dirUnit ); + VECTOR2D projection = originVec + dirUnit * distanceAlong; + VECTOR2D offset = delta - dirUnit * distanceAlong; + double perpDistance = offset.EuclideanNorm(); + + double snapThreshold = aSnapRange; + + if( activeDirection && *activeDirection == static_cast( ii ) ) + snapThreshold *= 1.5; + + if( perpDistance > snapThreshold ) + continue; + + VECTOR2D candidate = projection; + + if( canUseGrid() ) + { + if( dir.x == 0 && dir.y != 0 ) + { + candidate.x = origin.x; + candidate.y = aNearestGrid.y; + } + else if( dir.y == 0 && dir.x != 0 ) + { + candidate.x = aNearestGrid.x; + candidate.y = origin.y; + } + else + { + double stepDistance = std::sqrt( aGrid.x * aGrid.x + aGrid.y * aGrid.y ); + + if( stepDistance == 0.0 ) + continue; + + double steps = std::round( distanceAlong / stepDistance ); + candidate = originVec + dirUnit * ( steps * stepDistance ); + } + } + + VECTOR2I candidateInt( KiROUND( candidate.x ), KiROUND( candidate.y ) ); + + if( candidateInt == m_skipPoint ) + continue; + + VECTOR2D candidateDelta( candidateInt.x - aPoint.x, candidateInt.y - aPoint.y ); + double candidateDistance = candidateDelta.EuclideanNorm(); + + if( perpDistance < bestPerp + || ( std::abs( perpDistance - bestPerp ) < 1e-9 && candidateDistance < bestDistance ) ) + { + bestPerp = perpDistance; + bestDistance = candidateDistance; + bestPoint = candidateInt; + } + } + + return bestPoint; +} + + void GRID_HELPER::updateSnapPoint( const TYPED_POINT2I& aPoint ) { if( !m_toolMgr ) diff --git a/eeschema/tools/ee_grid_helper.cpp b/eeschema/tools/ee_grid_helper.cpp index caa1575fad..0ff1e55eec 100644 --- a/eeschema/tools/ee_grid_helper.cpp +++ b/eeschema/tools/ee_grid_helper.cpp @@ -156,10 +156,8 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR VECTOR2I pt = aOrigin; VECTOR2I snapDist( snapRange, snapRange ); - bool snapLineX = false; - bool snapLineY = false; - bool snapPoint = false; bool gridChecked = false; + bool snappedToAnchor = false; BOX2I bb( VECTOR2I( aOrigin.x - snapRange / 2, aOrigin.y - snapRange / 2 ), VECTOR2I( snapRange, snapRange ) ); @@ -185,42 +183,13 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR showConstructionGeometry( m_enableSnap ); - SNAP_LINE_MANAGER& snapLineManager = getSnapManager().GetSnapLineManager(); - std::optional snapLineOrigin = snapLineManager.GetSnapLineOrigin(); + SNAP_LINE_MANAGER& snapLineManager = getSnapManager().GetSnapLineManager(); + const VECTOR2D gridSize = GetGridSize( aGrid ); - if( m_enableSnapLine && m_snapItem && snapLineOrigin.has_value() - && m_skipPoint != *snapLineOrigin ) - { - if( std::abs( snapLineOrigin->x - aOrigin.x ) < snapDist.x ) - { - pt.x = snapLineOrigin->x; - snapDist.x = std::abs( pt.x - aOrigin.x ); - snapLineX = true; - } + std::optional guideSnap; - if( std::abs( snapLineOrigin->y - aOrigin.y ) < snapDist.y ) - { - pt.y = snapLineOrigin->y; - snapDist.y = std::abs( pt.y - aOrigin.y ); - snapLineY = true; - } - - if( canUseGrid() && std::abs( nearestGrid.x - aOrigin.x ) < snapDist.x ) - { - pt.x = nearestGrid.x; - snapDist.x = std::abs( nearestGrid.x - aOrigin.x ); - snapLineX = false; - } - - if( canUseGrid() && std::abs( nearestGrid.y - aOrigin.y ) < snapDist.y ) - { - pt.y = nearestGrid.y; - snapDist.y = std::abs( nearestGrid.y - aOrigin.y ); - snapLineY = false; - } - - gridChecked = true; - } + if( m_enableSnapLine ) + guideSnap = SnapToConstructionLines( aOrigin, nearestGrid, gridSize, snapRange ); if( m_enableSnap && nearest && nearest->Distance( aOrigin ) < snapDist.EuclideanNorm() ) { @@ -230,29 +199,27 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR pt = nearestGrid; snapDist.x = std::abs( nearestGrid.x - aOrigin.x ); snapDist.y = std::abs( nearestGrid.y - aOrigin.y ); - snapPoint = false; + gridChecked = true; } else { pt = nearest->pos; snapDist.x = std::abs( nearest->pos.x - aOrigin.x ); snapDist.y = std::abs( nearest->pos.y - aOrigin.y ); - snapPoint = true; + snappedToAnchor = true; + gridChecked = true; } - - snapLineX = false; - snapLineY = false; - gridChecked = true; } - if( canUseGrid() && !gridChecked ) - pt = nearestGrid; - - if( snapLineX || snapLineY ) + if( guideSnap && m_skipPoint != *guideSnap ) { - snapLineManager.SetSnapLineEnd( pt ); + snapLineManager.SetSnapLineEnd( *guideSnap ); + m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false ); + m_snapItem = std::nullopt; + return *guideSnap; } - else if( snapPoint ) + + if( snappedToAnchor ) { m_snapItem = *nearest; m_viewSnapPoint.SetPosition( pt ); @@ -264,11 +231,15 @@ VECTOR2I EE_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, GRID_HELPER_GR else m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, true ); } - else - { - snapLineManager.ClearSnapLine(); - m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false ); - } + + if( !snappedToAnchor ) + m_snapItem = std::nullopt; + + if( canUseGrid() && !gridChecked ) + pt = nearestGrid; + + snapLineManager.SetSnapLineEnd( std::nullopt ); + m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false ); return pt; } diff --git a/include/gal/graphics_abstraction_layer.h b/include/gal/graphics_abstraction_layer.h index c81d74524b..90bf1d368e 100644 --- a/include/gal/graphics_abstraction_layer.h +++ b/include/gal/graphics_abstraction_layer.h @@ -854,6 +854,9 @@ public: { VECTOR2D gridScreenSize( m_gridSize ); + gridScreenSize = VECTOR2D( std::max( 1.0, gridScreenSize.x ), + std::max( 1.0, gridScreenSize.y ) ); + double gridThreshold = computeMinGridSpacing() / m_worldScale; if( m_gridStyle == GRID_STYLE::SMALL_CROSS ) diff --git a/include/preview_items/construction_geom.h b/include/preview_items/construction_geom.h index 15102f2d2f..43de26e7ea 100644 --- a/include/preview_items/construction_geom.h +++ b/include/preview_items/construction_geom.h @@ -46,6 +46,13 @@ public: // Supported items using DRAWABLE = std::variant; + struct SNAP_GUIDE + { + SEG Segment; + COLOR4D Color; + int LineWidth; + }; + CONSTRUCTION_GEOM(); wxString GetClass() const override { return wxT( "CONSTRUCTION_GEOM" ); } @@ -59,7 +66,8 @@ public: void SetColor( const COLOR4D& aColor ) { m_color = aColor; } void SetPersistentColor( const COLOR4D& aColor ) { m_persistentColor = aColor; } - void AddDrawable( const DRAWABLE& aItem, bool aIsPersistent ); + void AddDrawable( const DRAWABLE& aItem, bool aIsPersistent, int aLineWidth = 1 ); + void SetSnapGuides( std::vector aGuides ); void ClearDrawables(); void SetSnapLine( const SEG& aLine ) { m_snapLine = aLine; } @@ -72,10 +80,12 @@ private: { DRAWABLE Item; bool IsPersistent; + int LineWidth; }; // The items to draw std::vector m_drawables; + std::vector m_snapGuides; // The snap line to draw std::optional m_snapLine; diff --git a/include/tool/construction_manager.h b/include/tool/construction_manager.h index 680843aa72..55fef266ca 100644 --- a/include/tool/construction_manager.h +++ b/include/tool/construction_manager.h @@ -65,6 +65,9 @@ private: * This is a line that has a start point (the "snap origin") and an end point (the "snap end"). * The end can only be set if the origin is set. If the origin is set, the end will be unset. */ +class SNAP_MANAGER; + + class SNAP_LINE_MANAGER { public: @@ -120,14 +123,26 @@ public: OPT_VECTOR2I GetNearestSnapLinePoint( const VECTOR2I& aCursor, const VECTOR2I& aNearestGrid, std::optional aDistToNearest, int snapRange ) const; + void SetDirections( const std::vector& aDirections ); + + const std::vector& GetDirections() const { return m_directions; } + + std::optional GetActiveDirection() const { return m_activeDirection; } + private: // If a snap point is "active", extra construction geometry is added to the helper // extending from the snap point to the cursor. OPT_VECTOR2I m_snapLineOrigin; OPT_VECTOR2I m_snapLineEnd; + std::vector m_directions; + std::optional m_activeDirection; + // The view handler to update when the snap line changes CONSTRUCTION_VIEW_HANDLER& m_viewHandler; + SNAP_MANAGER* m_snapManager; + + void notifyGuideChange(); }; @@ -157,9 +172,16 @@ public: */ struct CONSTRUCTION_ITEM { - SOURCE Source; - EDA_ITEM* Item; - std::vector Constructions; + SOURCE Source; + EDA_ITEM* Item; + + struct DRAWABLE_ENTRY + { + KIGFX::CONSTRUCTION_GEOM::DRAWABLE Drawable; + int LineWidth = 1; + }; + + std::vector Constructions; }; // A single batch of construction items. Once batch contains all the items (and associated @@ -253,6 +275,7 @@ public: void SetUpdateCallback( GFX_UPDATE_CALLBACK aCallback ) { m_updateCallback = aCallback; } SNAP_LINE_MANAGER& GetSnapLineManager() { return m_snapLineManager; } + const SNAP_LINE_MANAGER& GetSnapLineManager() const { return m_snapLineManager; } CONSTRUCTION_MANAGER& GetConstructionManager() { return m_constructionManager; } @@ -275,6 +298,9 @@ public: */ std::vector GetConstructionItems() const; + void SetSnapGuideColors( const KIGFX::COLOR4D& aBase, const KIGFX::COLOR4D& aHighlight ); + void UpdateSnapGuides(); + public: void updateView() override; @@ -284,4 +310,6 @@ public: CONSTRUCTION_MANAGER m_constructionManager; std::vector m_referenceOnlyPoints; + KIGFX::COLOR4D m_snapGuideColor; + KIGFX::COLOR4D m_snapGuideHighlightColor; }; diff --git a/include/tool/grid_helper.h b/include/tool/grid_helper.h index b7ff26f0fd..884372819c 100644 --- a/include/tool/grid_helper.h +++ b/include/tool/grid_helper.h @@ -124,6 +124,13 @@ public: bool GetUseGrid() const { return m_enableGrid; } void SetSnapLine( bool aSnap ) { m_enableSnapLine = aSnap; } + void SetSnapLineDirections( const std::vector& aDirections ); + void SetSnapLineOrigin( const VECTOR2I& aOrigin ); + void ClearSnapLine(); + std::optional SnapToConstructionLines( const VECTOR2I& aPoint, + const VECTOR2I& aNearestGrid, + const VECTOR2D& aGrid, + double aSnapRange ) const; void SetMask( int aMask ) { m_maskTypes = aMask; } void SetMaskFlag( int aFlag ) { m_maskTypes |= aFlag; } diff --git a/pcbnew/tools/edit_tool_move_fct.cpp b/pcbnew/tools/edit_tool_move_fct.cpp index f2bef7fa7a..ee122d71db 100644 --- a/pcbnew/tools/edit_tool_move_fct.cpp +++ b/pcbnew/tools/edit_tool_move_fct.cpp @@ -332,10 +332,16 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit controls->ForceCursorPosition( false ); auto displayConstraintsMessage = - [editFrame]( bool constrained ) + [editFrame]( bool aHV45Enabled ) { - editFrame->DisplayConstraintsMsg( constrained ? _( "Constrain to H, V, 45" ) - : wxString( wxT( "" ) ) ); + wxString msg; + + if( aHV45Enabled ) + msg = _( "Angle snap guides: H, V, 45°" ); + else + msg.clear(); + + editFrame->DisplayConstraintsMsg( msg ); }; auto updateStatusPopup = @@ -441,7 +447,7 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit VECTOR2I prevPos; bool enableLocalRatsnest = true; - bool hv45Mode = Is45Limited() && !evt->Modifier( MD_SHIFT ); + bool hv45Enabled = Is45Limited(); bool eatFirstMouseUp = true; bool allowRedraw3D = cfg->m_Display.m_Live3DRefresh; bool showCourtyardConflicts = !m_isFootprintEditor && cfg->m_ShowCourtyardCollisions; @@ -456,7 +462,29 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit drc_on_move->Init( board ); } - displayConstraintsMessage( hv45Mode ); + auto configureAngleSnap = + [&]( bool aHV45Enabled ) + { + std::vector directions; + + if( aHV45Enabled ) + { + // H, V, and both 45° diagonals + directions = { VECTOR2I( 1, 0 ), VECTOR2I( 0, 1 ), + VECTOR2I( 1, 1 ), VECTOR2I( 1, -1 ) }; + } + // Not enabled = no directions = no guides + + grid.SetSnapLineDirections( directions ); + + if( !directions.empty() ) + grid.SetSnapLineOrigin( originalPos ); + else + grid.ClearSnapLine(); + }; + + configureAngleSnap( hv45Enabled ); + displayConstraintsMessage( hv45Enabled ); // Prime the pump m_toolMgr->PostAction( ACTIONS::refreshPreview ); @@ -506,12 +534,6 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit if( !selection.HasReferencePoint() ) originalPos = m_cursor; - if( hv45Mode ) - { - VECTOR2I moveVector = m_cursor - originalPos; - m_cursor = originalPos + GetVectorSnapped45( moveVector ); - } - if( updateBBox ) { originalBBox = BOX2I(); @@ -611,12 +633,6 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit // start moving with the reference point attached to the cursor grid.SetAuxAxes( false ); - if( hv45Mode ) - { - VECTOR2I moveVector = m_cursor - originalPos; - m_cursor = originalPos + GetVectorSnapped45( moveVector ); - } - movement = m_cursor - selection.GetReferencePoint(); // Drag items to the current cursor position @@ -665,6 +681,10 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit if( moveWithReference ) { selection.SetReferencePoint( pickedReferencePoint ); + + if( hv45Enabled ) + grid.SetSnapLineOrigin( selection.GetReferencePoint() ); + controls->ForceCursorPosition( true, pickedReferencePoint ); m_cursor = pickedReferencePoint; } @@ -685,6 +705,10 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit } selection.SetReferencePoint( snapped ); + + if( hv45Enabled ) + grid.SetSnapLineOrigin( selection.GetReferencePoint() ); + grid.SetAuxAxes( true, snapped ); if( !editFrame->GetMoveWarpsCursor() ) @@ -769,6 +793,8 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit originalPos = nextItem->GetPosition(); m_selectionTool->AddItemToSel( nextItem ); selection.SetReferencePoint( originalPos ); + if( hv45Enabled ) + grid.SetSnapLineOrigin( selection.GetReferencePoint() ); sel_items.clear(); sel_items.push_back( nextItem ); @@ -794,8 +820,9 @@ bool EDIT_TOOL::doMoveSelection( const TOOL_EVENT& aEvent, BOARD_COMMIT* aCommit } else if( evt->IsAction( &PCB_ACTIONS::toggleHV45Mode ) ) { - hv45Mode = Is45Limited(); - displayConstraintsMessage( hv45Mode ); + hv45Enabled = Is45Limited(); + configureAngleSnap( hv45Enabled ); + displayConstraintsMessage( hv45Enabled ); evt->SetPassEvent( false ); } else if( evt->IsAction( &ACTIONS::increment ) ) diff --git a/pcbnew/tools/pcb_grid_helper.cpp b/pcbnew/tools/pcb_grid_helper.cpp index bc8862d890..afcf5be2c7 100644 --- a/pcbnew/tools/pcb_grid_helper.cpp +++ b/pcbnew/tools/pcb_grid_helper.cpp @@ -304,10 +304,20 @@ void PCB_GRID_HELPER::AddConstructionItems( std::vector aItems, boo // At this point, constructionDrawables can be empty, which is fine // (it means there's no additional construction geometry to draw, but // the item is still going to be proposed for activation) + + // Convert the drawables to DRAWABLE_ENTRY format + std::vector drawableEntries; + drawableEntries.reserve( constructionDrawables.size() ); + for( auto& drawable : constructionDrawables ) + { + drawableEntries.emplace_back( + CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM::DRAWABLE_ENTRY{ std::move( drawable ), 1 } ); + } + constructionItemsBatch->emplace_back( CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM{ CONSTRUCTION_MANAGER::SOURCE::FROM_ITEMS, item, - std::move( constructionDrawables ), + std::move( drawableEntries ), } ); } @@ -617,6 +627,15 @@ VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& a OPT_VECTOR2I snapLineSnap = snapLineManager.GetNearestSnapLinePoint( aOrigin, nearestGrid, snapDist, snapRange ); + if( !snapLineSnap ) + { + std::optional constructionSnap = + SnapToConstructionLines( aOrigin, nearestGrid, GetGridSize( aGrid ), snapRange ); + + if( constructionSnap ) + snapLineSnap = *constructionSnap; + } + // We found a better snap point that the nearest one if( snapLineSnap && m_skipPoint != *snapLineSnap ) { @@ -719,14 +738,9 @@ VECTOR2I PCB_GRID_HELPER::BestSnapAnchor( const VECTOR2I& aOrigin, const LSET& a m_snapItem = std::nullopt; if( !snapValid ) - { - snapLineManager.ClearSnapLine(); snapManager.GetConstructionManager().CancelProposal(); - } - else - { - snapLineManager.SetSnapLineEnd( std::nullopt ); - } + + snapLineManager.SetSnapLineEnd( std::nullopt ); m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, false ); @@ -980,7 +994,7 @@ void PCB_GRID_HELPER::computeAnchors( const std::vector& aItems, { BOARD_ITEM* involvedItem = static_cast( constructionItem.Item ); - for( const KIGFX::CONSTRUCTION_GEOM::DRAWABLE& drawable : constructionItem.Constructions ) + for( const CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM::DRAWABLE_ENTRY& drawable : constructionItem.Constructions ) { std::visit( [&]( const auto& visited ) @@ -1000,7 +1014,7 @@ void PCB_GRID_HELPER::computeAnchors( const std::vector& aItems, addAnchor( visited, SNAPPABLE | CONSTRUCTED, involvedItem, POINT_TYPE::PT_NONE ); } }, - drawable ); + drawable.Drawable ); } } }