Move HV45 to suggestions when moving

Snap lines shown when snapped
This commit is contained in:
Seth Hillbrand
2025-10-10 14:44:00 -07:00
parent 5865431759
commit e892783109
12 changed files with 531 additions and 176 deletions
+2
View File
@@ -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 );
@@ -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
+18 -2
View File
@@ -24,6 +24,7 @@
#include "preview_items/construction_geom.h"
#include <layer_ids.h>
#include <utility>
#include <gal/graphics_abstraction_layer.h>
#include <geometry/shape_utils.h>
#include <preview_items/item_drawing_utils.h>
@@ -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<SNAP_GUIDE> 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 );
+245 -79
View File
@@ -23,11 +23,18 @@
#include "tool/construction_manager.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <limits>
#include <numeric>
#include <utility>
#include <wx/timer.h>
#include <wx/debug.h>
#include <advanced_config.h>
#include <math/util.h>
#include <hash.h>
@@ -329,9 +336,9 @@ void CONSTRUCTION_MANAGER::acceptConstructionItems( std::unique_ptr<PENDING_BATC
{
for( const CONSTRUCTION_ITEM& item : batch )
{
for( const KIGFX::CONSTRUCTION_GEOM::DRAWABLE& drawable : item.Constructions )
for( const CONSTRUCTION_ITEM::DRAWABLE_ENTRY& drawable : item.Constructions )
{
geom.AddDrawable( drawable, aIsPersistent );
geom.AddDrawable( drawable.Drawable, aIsPersistent, drawable.LineWidth );
}
}
}
@@ -383,16 +390,111 @@ bool CONSTRUCTION_MANAGER::HasActiveConstruction() const
SNAP_LINE_MANAGER::SNAP_LINE_MANAGER( CONSTRUCTION_VIEW_HANDLER& aViewHandler ) :
m_viewHandler( aViewHandler )
m_viewHandler( aViewHandler ), m_snapManager( static_cast<SNAP_MANAGER*>( &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<int> findDirectionIndex( const std::vector<VECTOR2I>& 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<int>( i );
}
return std::nullopt;
}
void SNAP_LINE_MANAGER::SetDirections( const std::vector<VECTOR2I>& aDirections )
{
std::vector<VECTOR2I> 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<int> 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<double>::max();
std::optional<VECTOR2I> 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<KIGFX::CONSTRUCTION_GEOM::SNAP_GUIDE> guides;
const OPT_VECTOR2I& origin = m_snapLineManager.GetSnapLineOrigin();
const std::vector<VECTOR2I>& directions = m_snapLineManager.GetDirections();
if( origin && !directions.empty() )
{
const std::optional<int> 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<int>( 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<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH>
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<VECTOR2I>& directions = m_snapLineManager.GetDirections();
const std::optional<int> 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<int>( ii ) ) ? 2 : 1;
snapPointItem.Constructions.push_back( entry );
}
if( !snapPointItem.Constructions.empty() )
batches.push_back( std::move( batch ) );
}
return batches;
+117 -8
View File
@@ -24,13 +24,13 @@
#include "tool/grid_helper.h"
#include <functional>
#include <cmath>
#include <limits>
#include <advanced_config.h>
#include <gal/graphics_abstraction_layer.h>
#include <gal/painter.h>
#include <math/util.h> // for KiROUND
#include <math/vector2d.h>
#include <render_settings.h>
#include <tool/tool_manager.h>
#include <tool/tools_holder.h>
#include <view/view.h>
@@ -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<VECTOR2I>& 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<VECTOR2I> 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<VECTOR2I>& directions = snapLineManager.GetDirections();
const std::optional<int> activeDirection = snapLineManager.GetActiveDirection();
const VECTOR2D originVec( origin );
const VECTOR2D cursorVec( aPoint );
const VECTOR2D delta = cursorVec - originVec;
std::optional<VECTOR2I> bestPoint;
double bestPerp = std::numeric_limits<double>::max();
double bestDistance = std::numeric_limits<double>::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<int>( 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 )
+25 -54
View File
@@ -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<VECTOR2I> 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<VECTOR2I> 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;
}
+3
View File
@@ -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 )
+11 -1
View File
@@ -46,6 +46,13 @@ public:
// Supported items
using DRAWABLE = std::variant<SEG, LINE, HALF_LINE, CIRCLE, SHAPE_ARC, VECTOR2I>;
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<SNAP_GUIDE> 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<DRAWABLE_INFO> m_drawables;
std::vector<SNAP_GUIDE> m_snapGuides;
// The snap line to draw
std::optional<SEG> m_snapLine;
+31 -3
View File
@@ -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<int> aDistToNearest, int snapRange ) const;
void SetDirections( const std::vector<VECTOR2I>& aDirections );
const std::vector<VECTOR2I>& GetDirections() const { return m_directions; }
std::optional<int> 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<VECTOR2I> m_directions;
std::optional<int> 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<KIGFX::CONSTRUCTION_GEOM::DRAWABLE> Constructions;
SOURCE Source;
EDA_ITEM* Item;
struct DRAWABLE_ENTRY
{
KIGFX::CONSTRUCTION_GEOM::DRAWABLE Drawable;
int LineWidth = 1;
};
std::vector<DRAWABLE_ENTRY> 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<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM_BATCH> 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<VECTOR2I> m_referenceOnlyPoints;
KIGFX::COLOR4D m_snapGuideColor;
KIGFX::COLOR4D m_snapGuideHighlightColor;
};
+7
View File
@@ -124,6 +124,13 @@ public:
bool GetUseGrid() const { return m_enableGrid; }
void SetSnapLine( bool aSnap ) { m_enableSnapLine = aSnap; }
void SetSnapLineDirections( const std::vector<VECTOR2I>& aDirections );
void SetSnapLineOrigin( const VECTOR2I& aOrigin );
void ClearSnapLine();
std::optional<VECTOR2I> 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; }
+46 -19
View File
@@ -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<VECTOR2I> 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 ) )
+24 -10
View File
@@ -304,10 +304,20 @@ void PCB_GRID_HELPER::AddConstructionItems( std::vector<BOARD_ITEM*> 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<CONSTRUCTION_MANAGER::CONSTRUCTION_ITEM::DRAWABLE_ENTRY> 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<VECTOR2I> 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<BOARD_ITEM*>& aItems,
{
BOARD_ITEM* involvedItem = static_cast<BOARD_ITEM*>( 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<BOARD_ITEM*>& aItems,
addAnchor( visited, SNAPPABLE | CONSTRUCTED, involvedItem, POINT_TYPE::PT_NONE );
}
},
drawable );
drawable.Drawable );
}
}
}