KiCad PCB EDA Suite
Loading...
Searching...
No Matches
grid_helper.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "tool/grid_helper.h"
25
26#include <functional>
27#include <cmath>
28#include <limits>
29
30#include <advanced_config.h>
31#include <trace_helpers.h>
32#include <wx/log.h>
34#include <math/util.h> // for KiROUND
35#include <math/vector2d.h>
36#include <tool/tool_manager.h>
37#include <tool/tools_holder.h>
38#include <view/view.h>
40#include <gal/painter.h>
41
42
45{
47 m_enableSnap = true;
48 m_enableSnapLine = true;
49 m_enableGrid = true;
50 m_snapItem = std::nullopt;
51
52 m_manualGrid = VECTOR2D( 1, 1 );
54 m_manualOrigin = VECTOR2I( 0, 0 );
56}
57
58
59GRID_HELPER::GRID_HELPER( TOOL_MANAGER* aToolMgr, int aConstructionLayer ) :
61{
62 m_toolMgr = aToolMgr;
64 if( !m_toolMgr )
65 return;
66
67 KIGFX::VIEW* view = m_toolMgr->GetView();
68 KIGFX::RENDER_SETTINGS* settings = view->GetPainter()->GetSettings();
69 KIGFX::COLOR4D constructionColor = settings->GetLayerColor( aConstructionLayer );
70
71 m_constructionGeomPreview.SetColor( constructionColor );
72 m_constructionGeomPreview.SetPersistentColor( constructionColor );
73
76
77 m_snapManager.SetUpdateCallback(
78 [view, this]( bool aAnythingShown )
79 {
80 const bool currentlyVisible = view->IsVisible( &m_constructionGeomPreview );
81
82 if( currentlyVisible && aAnythingShown )
83 {
85 }
86 else
87 {
88 view->SetVisible( &m_constructionGeomPreview, aAnythingShown );
89 }
90
91 m_toolMgr->GetToolHolder()->RefreshCanvas();
92 } );
93
94 // Initialise manual values from view for compatibility
95 m_manualGrid = view->GetGAL()->GetGridSize();
96 m_manualVisibleGrid = view->GetGAL()->GetVisibleGridSize();
99}
103{
104 if( !m_toolMgr )
105 return;
106
107 KIGFX::VIEW& view = *m_toolMgr->GetView();
109
110 if( m_anchorDebug )
111 view.Remove( m_anchorDebug.get() );
112}
113
114
116{
117 static bool permitted = ADVANCED_CFG::GetCfg().m_EnableSnapAnchorsDebug;
118
119 if( !m_toolMgr )
120 return nullptr;
121
122 if( permitted && !m_anchorDebug )
123 {
124 KIGFX::VIEW& view = *m_toolMgr->GetView();
125 m_anchorDebug = std::make_unique<KIGFX::ANCHOR_DEBUG>();
126 view.Add( m_anchorDebug.get() );
127 view.SetVisible( m_anchorDebug.get(), true );
128 }
129
130 return m_anchorDebug.get();
131}
132
133
135{
136 if( m_toolMgr )
137 m_toolMgr->GetView()->SetVisible( &m_constructionGeomPreview, aShow );
138}
139
140
141void GRID_HELPER::SetSnapLineDirections( const std::vector<VECTOR2I>& aDirections )
142{
143 m_snapManager.GetSnapLineManager().SetDirections( aDirections );
144}
145
146
148{
149 m_snapManager.GetSnapLineManager().SetSnapLineOrigin( aOrigin );
150}
151
152void GRID_HELPER::SetSnapLineEnd( const std::optional<VECTOR2I>& aEnd )
153{
154 m_snapManager.GetSnapLineManager().SetSnapLineEnd( aEnd );
155}
156
158{
159 m_snapManager.GetSnapLineManager().ClearSnapLine();
160}
161
162
163std::optional<VECTOR2I> GRID_HELPER::SnapToConstructionLines( const VECTOR2I& aPoint,
164 const VECTOR2I& aNearestGrid,
165 const VECTOR2D& aGrid,
166 double aSnapRange ) const
167{
168 const SNAP_LINE_MANAGER& snapLineManager = m_snapManager.GetSnapLineManager();
169 const OPT_VECTOR2I& snapOrigin = snapLineManager.GetSnapLineOrigin();
170
171 wxLogTrace( traceSnap, "SnapToConstructionLines: aPoint=(%d, %d), nearestGrid=(%d, %d), snapRange=%.1f",
172 aPoint.x, aPoint.y, aNearestGrid.x, aNearestGrid.y, aSnapRange );
173
174 if( !snapOrigin || snapLineManager.GetDirections().empty() )
175 {
176 wxLogTrace( traceSnap, " No snap origin or no directions, returning nullopt" );
177 return std::nullopt;
178 }
179
180 const VECTOR2I& origin = *snapOrigin;
181
182 wxLogTrace( traceSnap, " snapOrigin=(%d, %d), directions count=%zu",
183 origin.x, origin.y, snapLineManager.GetDirections().size() );
184
185 const std::vector<VECTOR2I>& directions = snapLineManager.GetDirections();
186 const std::optional<int> activeDirection = snapLineManager.GetActiveDirection();
187
188 if( activeDirection )
189 wxLogTrace( traceSnap, " activeDirection=%d", *activeDirection );
190
191 const VECTOR2D originVec( origin );
192 const VECTOR2D cursorVec( aPoint );
193 const VECTOR2D delta = cursorVec - originVec;
194
195 std::optional<VECTOR2I> bestPoint;
196 double bestPerp = std::numeric_limits<double>::max();
197 double bestDistance = std::numeric_limits<double>::max();
198
199 for( size_t ii = 0; ii < directions.size(); ++ii )
200 {
201 const VECTOR2I& dir = directions[ii];
202 VECTOR2D dirVector( dir );
203 double dirLength = dirVector.EuclideanNorm();
204
205 if( dirLength == 0.0 )
206 {
207 wxLogTrace( traceSnap, " Direction %zu: zero length, skipping", ii );
208 continue;
209 }
210
211 VECTOR2D dirUnit = dirVector / dirLength;
212
213 double distanceAlong = delta.Dot( dirUnit );
214 VECTOR2D projection = originVec + dirUnit * distanceAlong;
215 VECTOR2D offset = delta - dirUnit * distanceAlong;
216 double perpDistance = offset.EuclideanNorm();
217
218 double snapThreshold = aSnapRange;
219
220 if( activeDirection && *activeDirection == static_cast<int>( ii ) )
221 {
222 snapThreshold *= 1.5;
223 wxLogTrace( traceSnap, " Direction %zu: ACTIVE, increased snapThreshold=%.1f", ii, snapThreshold );
224 }
225
226 wxLogTrace( traceSnap, " Direction %zu: dir=(%d, %d), perpDist=%.1f, threshold=%.1f",
227 ii, dir.x, dir.y, perpDistance, snapThreshold );
228
229 if( perpDistance > snapThreshold )
230 {
231 wxLogTrace( traceSnap, " perpDistance > threshold, skipping" );
232 continue;
233 }
234
235 VECTOR2D candidate = projection;
236
237 if( canUseGrid() )
238 {
239 if( dir.x == 0 && dir.y != 0 )
240 {
241 // Vertical construction line: snap to grid intersection
242 candidate.x = origin.x;
243 candidate.y = aNearestGrid.y;
244 wxLogTrace( traceSnap, " Vertical snap: candidate=(%d, %d)",
245 (int)candidate.x, (int)candidate.y );
246 }
247 else if( dir.y == 0 && dir.x != 0 )
248 {
249 // Horizontal construction line: snap to grid intersection
250 candidate.x = aNearestGrid.x;
251 candidate.y = origin.y;
252 wxLogTrace( traceSnap, " Horizontal snap: candidate=(%d, %d)",
253 (int)candidate.x, (int)candidate.y );
254 }
255 else
256 {
257 // Diagonal construction line: find nearest grid intersection along the line
258 // We need to find grid points near the projection point and pick the closest
259 // one that lies on the construction line
260
261 // Get the grid origin for proper alignment
262 VECTOR2D gridOrigin( GetOrigin() );
263
264 // Calculate the projection point relative to grid
265 VECTOR2D relProjection = projection - gridOrigin;
266
267 // Find nearby grid points (check 9 points in a 3x3 grid around the projection)
268 std::vector<VECTOR2D> gridPoints;
269 for( int dx = -1; dx <= 1; ++dx )
270 {
271 for( int dy = -1; dy <= 1; ++dy )
272 {
273 double gridX = std::round( relProjection.x / aGrid.x ) * aGrid.x + dx * aGrid.x;
274 double gridY = std::round( relProjection.y / aGrid.y ) * aGrid.y + dy * aGrid.y;
275 gridPoints.push_back( VECTOR2D( gridX + gridOrigin.x, gridY + gridOrigin.y ) );
276 }
277 }
278
279 // Find the grid point closest to the construction line
280 double bestGridDist = std::numeric_limits<double>::max();
281 VECTOR2D bestGridPt = projection;
282
283 for( const VECTOR2D& gridPt : gridPoints )
284 {
285 // Calculate perpendicular distance from grid point to construction line
286 VECTOR2D gridDelta = gridPt - originVec;
287 double gridDistAlong = gridDelta.Dot( dirUnit );
288 VECTOR2D gridProjection = originVec + dirUnit * gridDistAlong;
289 double gridPerpDist = ( gridPt - gridProjection ).EuclideanNorm();
290
291 // Also consider distance from cursor
292 double distFromCursor = ( gridPt - cursorVec ).EuclideanNorm();
293
294 // Prefer grid points that are close to the line and close to cursor
295 double score = gridPerpDist + distFromCursor * 0.1;
296
297 if( score < bestGridDist )
298 {
299 bestGridDist = score;
300 bestGridPt = gridPt;
301 }
302 }
303
304 candidate = bestGridPt;
305 wxLogTrace( traceSnap, " Diagonal snap: candidate=(%.1f, %.1f), perpDist=%.1f",
306 candidate.x, candidate.y, bestGridDist );
307 }
308 }
309 else
310 {
311 wxLogTrace( traceSnap, " Grid disabled, using projection candidate=(%.1f, %.1f)",
312 candidate.x, candidate.y );
313 }
314
315 VECTOR2I candidateInt = KiROUND( candidate );
316
317 if( candidateInt == m_skipPoint )
318 {
319 wxLogTrace( traceSnap, " candidateInt matches m_skipPoint, skipping" );
320 continue;
321 }
322
323 VECTOR2D candidateDelta( candidateInt.x - aPoint.x, candidateInt.y - aPoint.y );
324 double candidateDistance = candidateDelta.EuclideanNorm();
325
326 wxLogTrace( traceSnap, " candidateInt=(%d, %d), candidateDist=%.1f",
327 candidateInt.x, candidateInt.y, candidateDistance );
328
329 if( perpDistance < bestPerp
330 || ( std::abs( perpDistance - bestPerp ) < 1e-9 && candidateDistance < bestDistance ) )
331 {
332 wxLogTrace( traceSnap, " NEW BEST: perpDist=%.1f, candDist=%.1f", perpDistance, candidateDistance );
333 bestPerp = perpDistance;
334 bestDistance = candidateDistance;
335 bestPoint = candidateInt;
336 }
337 }
338
339 if( bestPoint )
340 {
341 wxLogTrace( traceSnap, " RETURNING bestPoint=(%d, %d)", bestPoint->x, bestPoint->y );
342 }
343 else
344 {
345 wxLogTrace( traceSnap, " RETURNING nullopt (no valid snap found)" );
346 }
347
348 return bestPoint;
349}
350
351
353{
354 if( !m_toolMgr )
355 return;
356
357 m_viewSnapPoint.SetPosition( aPoint.m_point );
358 m_viewSnapPoint.SetSnapTypes( aPoint.m_types );
359
360 if( m_toolMgr->GetView()->IsVisible( &m_viewSnapPoint ) )
361 m_toolMgr->GetView()->Update( &m_viewSnapPoint, KIGFX::GEOMETRY );
362 else
363 m_toolMgr->GetView()->SetVisible( &m_viewSnapPoint, true );
364}
365
366
368{
369 VECTOR2D size = m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetGridSize() : m_manualGrid;
370 return VECTOR2I( KiROUND( size.x ), KiROUND( size.y ) );
371}
372
373
375{
376 return m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetVisibleGridSize() : m_manualVisibleGrid;
377}
378
379
381{
382 if( m_toolMgr )
383 {
384 VECTOR2D origin = m_toolMgr->GetView()->GetGAL()->GetGridOrigin();
385 return VECTOR2I( origin );
386 }
387
388 return m_manualOrigin;
389}
390
391
393{
394 GRID_HELPER_GRIDS grid = GetItemGrid( aSelection.Front() );
395
396 // Find the largest grid of all the items and use that
397 for( EDA_ITEM* item : aSelection )
398 {
399 GRID_HELPER_GRIDS itemGrid = GetItemGrid( item );
400
401 if( GetGridSize( itemGrid ) > GetGridSize( grid ) )
402 grid = itemGrid;
403 }
404
405 return grid;
406}
407
408
410{
411 return m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetGridSize() : m_manualGrid;
412}
413
414
415void GRID_HELPER::SetAuxAxes( bool aEnable, const VECTOR2I& aOrigin )
416{
417 if( aEnable )
418 {
419 m_auxAxis = aOrigin;
420 m_viewAxis.SetPosition( aOrigin );
421 if( m_toolMgr )
422 m_toolMgr->GetView()->SetVisible( &m_viewAxis, true );
423 }
424 else
425 {
426 m_auxAxis = std::optional<VECTOR2I>();
427 if( m_toolMgr )
428 m_toolMgr->GetView()->SetVisible( &m_viewAxis, false );
429 }
430}
431
432
434{
435 return computeNearest( aPoint, GetGrid(), GetOrigin() );
436}
437
438
440 const VECTOR2D& aOffset ) const
441{
442 // Round the grid size and offset rather than relying on the implicit VECTOR2D->VECTOR2I
443 // truncation in computeNearest. Grid sizes that aren't exact in IEEE 754 (e.g., 0.254mm =
444 // 10 mil) would otherwise truncate to the wrong integer (253999 instead of 254000),
445 // producing positions that aren't true grid multiples.
446 return computeNearest( aPoint, KiROUND( aGrid ), KiROUND( aOffset ) );
447}
448
449
451 const VECTOR2I& aOffset ) const
452{
453 return VECTOR2I( KiROUND( (double) ( aPoint.x - aOffset.x ) / aGrid.x ) * aGrid.x + aOffset.x,
454 KiROUND( (double) ( aPoint.y - aOffset.y ) / aGrid.y ) * aGrid.y + aOffset.y );
455}
456
457
459{
460 return Align( aPoint, GetGrid(), GetOrigin() );
461}
462
463
464VECTOR2I GRID_HELPER::Align( const VECTOR2I& aPoint, const VECTOR2D& aGrid,
465 const VECTOR2D& aOffset ) const
466{
467 if( !canUseGrid() )
468 return aPoint;
469
470 VECTOR2I nearest = AlignGrid( aPoint, aGrid, aOffset );
471
472 if( !m_auxAxis )
473 return nearest;
474
475 if( std::abs( m_auxAxis->x - aPoint.x ) < std::abs( nearest.x - aPoint.x ) )
476 nearest.x = m_auxAxis->x;
477
478 if( std::abs( m_auxAxis->y - aPoint.y ) < std::abs( nearest.y - aPoint.y ) )
479 nearest.y = m_auxAxis->y;
480
481 return nearest;
482}
483
484
486{
487 return m_enableGrid && ( m_toolMgr ? m_toolMgr->GetView()->GetGAL()->GetGridSnapping()
489}
490
491
492std::optional<VECTOR2I> GRID_HELPER::GetSnappedPoint() const
493{
494 if( m_snapItem )
495 return m_snapItem->pos;
496
497 return std::nullopt;
498}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:100
KIGFX::CONSTRUCTION_GEOM m_constructionGeomPreview
Show construction geometry (if any) on the canvas.
std::optional< VECTOR2I > m_auxAxis
VECTOR2I computeNearest(const VECTOR2I &aPoint, const VECTOR2I &aGrid, const VECTOR2I &aOffset) const
std::optional< VECTOR2I > SnapToConstructionLines(const VECTOR2I &aPoint, const VECTOR2I &aNearestGrid, const VECTOR2D &aGrid, double aSnapRange) const
void SetSnapLineDirections(const std::vector< VECTOR2I > &aDirections)
VECTOR2I m_skipPoint
bool m_enableGrid
virtual GRID_HELPER_GRIDS GetItemGrid(const EDA_ITEM *aItem) const
Get the coarsest grid that applies to an item.
void showConstructionGeometry(bool aShow)
SNAP_MANAGER m_snapManager
Manage the construction geometry, snap lines, reference points, etc.
virtual ~GRID_HELPER()
VECTOR2D m_manualVisibleGrid
void SetSnapLineOrigin(const VECTOR2I &aOrigin)
bool m_manualGridSnapping
VECTOR2I m_manualOrigin
virtual GRID_HELPER_GRIDS GetSelectionGrid(const SELECTION &aSelection) const
Gets the coarsest grid that applies to a selecion of items.
TOOL_MANAGER * m_toolMgr
std::optional< VECTOR2I > GetSnappedPoint() const
void SetAuxAxes(bool aEnable, const VECTOR2I &aOrigin=VECTOR2I(0, 0))
VECTOR2D GetVisibleGrid() const
std::unique_ptr< KIGFX::ANCHOR_DEBUG > m_anchorDebug
#VIEW_ITEM for visualising anchor points, if enabled.
virtual VECTOR2D GetGridSize(GRID_HELPER_GRIDS aGrid) const
Return the size of the specified grid.
VECTOR2I GetGrid() const
bool m_enableSnapLine
bool m_enableSnap
VECTOR2I GetOrigin() const
bool canUseGrid() const
Check whether it is possible to use the grid – this depends both on local grid helper settings and gl...
void ClearSnapLine()
std::optional< ANCHOR > m_snapItem
KIGFX::ANCHOR_DEBUG * enableAndGetAnchorDebug()
Enable the anchor debug if permitted and return it.
void SetSnapLineEnd(const std::optional< VECTOR2I > &aEnd)
KIGFX::SNAP_INDICATOR m_viewSnapPoint
virtual VECTOR2I Align(const VECTOR2I &aPoint, GRID_HELPER_GRIDS aGrid) const
Definition grid_helper.h:86
void updateSnapPoint(const TYPED_POINT2I &aPoint)
KIGFX::ORIGIN_VIEWITEM m_viewAxis
VECTOR2D m_manualGrid
virtual VECTOR2I AlignGrid(const VECTOR2I &aPoint, GRID_HELPER_GRIDS aGrid) const
Definition grid_helper.h:91
View item to draw debug items for anchors.
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:105
const VECTOR2D & GetGridOrigin() const
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
const COLOR4D & GetLayerColor(int aLayer) const
Return the color used to draw a layer.
Hold a (potentially large) number of VIEW_ITEMs and renders them on a graphics device provided by the...
Definition view.h:67
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
Definition view.cpp:301
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
Definition view.cpp:405
virtual void Update(const VIEW_ITEM *aItem, int aUpdateFlags) const
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
Definition view.cpp:1809
GAL * GetGAL() const
Return the GAL this view is using to draw graphical primitives.
Definition view.h:211
PAINTER * GetPainter() const
Return the painter object used by the view for drawing #VIEW_ITEMS.
Definition view.h:229
bool IsVisible(const VIEW_ITEM *aItem) const
Return information if the item is visible (or not).
Definition view.cpp:1779
void SetVisible(VIEW_ITEM *aItem, bool aIsVisible=true)
Set the item visibility.
Definition view.cpp:1736
EDA_ITEM * Front() const
Definition selection.h:177
const std::vector< VECTOR2I > & GetDirections() const
std::optional< int > GetActiveDirection() const
const OPT_VECTOR2I & GetSnapLineOrigin() const
Master controller class:
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:546
GRID_HELPER_GRIDS
Definition grid_helper.h:44
bool m_EnableSnapAnchorsDebug
Enable snap anchors debug visualization.
const wxChar *const traceSnap
Flag to enable snap/grid helper debug tracing.
@ GEOMETRY
Position or shape has changed.
Definition view_item.h:55
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
VECTOR2I m_point
Definition point_types.h:77
int delta
wxLogTrace helper definitions.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:687
VECTOR2< double > VECTOR2D
Definition vector2d.h:686