Bug Summary

File:librecad/src/lib/actions/rs_snapper.cpp
Warning:line 1210, column 30
Although the value stored to 'showSnapIndicator' is used in the enclosing expression, the value is never actually read from 'showSnapIndicator'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name rs_snapper.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-18/lib/clang/18 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D QC_APPDIR="librecad" -D LC_VERSION=2.2.2_alpha1-670-g0ffb38d79 -D LC_PRERELEASE=true; -D QT_NO_DEBUG -D QT_SVG_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D QT_GUI_LIB -D QT_NETWORK_LIB -D QT_CORE_LIB -I . -I /usr/include -I ../../libraries/lciconengine -I ../../libraries/libdxfrw/src -I ../../libraries/libdxfrw/src/intern -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-09-15-234850-5000-1 -x c++ lib/actions/rs_snapper.cpp
1/****************************************************************************
2**
3** This file is part of the LibreCAD project, a 2D CAD program
4**
5** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
6** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
7**
8**
9** This file may be distributed and/or modified under the terms of the
10** GNU General Public License version 2 as published by the Free Software
11** Foundation and appearing in the file gpl-2.0.txt included in the
12** packaging of this file.
13**
14** This program is distributed in the hope that it will be useful,
15** but WITHOUT ANY WARRANTY; without even the implied warranty of
16** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17** GNU General Public License for more details.
18**
19** You should have received a copy of the GNU General Public License
20** along with this program; if not, write to the Free Software
21** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22**
23** This copyright notice MUST APPEAR in all copies of the script!
24**
25**********************************************************************/
26
27#include "rs_snapper.h"
28
29#include <algorithm>
30#include <cmath>
31#include <vector>
32
33#include <QMouseEvent>
34
35#include "lc_actioncontext.h"
36#include "lc_containertraverser.h"
37#include "lc_crosshair.h"
38#include "lc_cursoroverlayinfo.h"
39#include "lc_defaults.h"
40#include "lc_graphicviewport.h"
41#include "lc_linemath.h"
42#include "lc_overlayentitiescontainer.h"
43#include "rs_debug.h"
44#include "rs_graphic.h"
45#include "rs_graphicview.h"
46#include "rs_grid.h"
47#include "rs_line.h"
48#include "rs_pen.h"
49#include "rs_settings.h"
50#include "rs_vector.h"
51#include "lc_visual_snap_manager.h"
52
53namespace {
54 // whether a floating point is positive by tolerance
55 bool isPositive(const double x) {
56 return x > RS_TOLERANCE1.0e-10;
57 }
58
59 // A size vector is valid with a positive size
60 bool isSizeValid(const RS_Vector& sizeVector) {
61 return isPositive(sizeVector.x) || isPositive(sizeVector.y);
62 }
63
64 // The valid size magnitude
65 double getValidSize(const RS_Vector& sizeVector) {
66 return std::hypot(std::max(sizeVector.x, RS_TOLERANCE1.0e-10), std::max(sizeVector.y, RS_TOLERANCE1.0e-10));
67 }
68
69 constexpr int DEFAULT_CATCH_ENTITY_RANGE_PX = 32;
70
71 bool isContainerTarget(const RS2::EntityType type) {
72 switch (type) {
73 case RS2::EntityPolyline:
74 case RS2::EntityContainer:
75 case RS2::EntitySpline:
76 return true;
77 default:
78 return false;
79 }
80 }
81
82 bool matchesRequestedType(const RS_Entity& entity, const RS2::EntityType type) {
83 if (entity.rtti() == type) {
84 return true;
85 }
86 if (!isContainerTarget(type)) {
87 return false;
88 }
89 for (const RS_EntityContainer* parent = entity.getParent(); parent != nullptr; parent = parent->getParent()) {
90 if (parent->rtti() == type) {
91 return true;
92 }
93 }
94 return false;
95 }
96}
97
98/**
99 * Disable all snapping.
100 *
101 * This effectively puts the object into free snap mode.
102 *
103 * @returns A reference to itself.
104 */
105const RS_SnapMode& RS_SnapMode::clear() {
106 *this = RS_SnapMode{};
107
108 return *this;
109}
110
111bool RS_SnapMode::operator ==(const RS_SnapMode& rhs) const {
112 return snapIntersection == rhs.snapIntersection && snapOnEntity == rhs.snapOnEntity && snapCenter == rhs.snapCenter && snapDistance ==
113 rhs.snapDistance && snapMiddle == rhs.snapMiddle && snapEndpoint == rhs.snapEndpoint && snapGrid == rhs.snapGrid && snapFree == rhs.
114 snapFree && restriction == rhs.restriction && snapAngle == rhs.snapAngle;
115}
116
117bool RS_SnapMode::operator !=(const RS_SnapMode& rhs) const {
118 return !this->operator ==(rhs);
119}
120
121/**
122 * snap mode to a flag integer
123 */
124uint RS_SnapMode::toInt(const RS_SnapMode& s) {
125 uint ret{0};
126
127 if (s.snapIntersection) {
128 ret |= RS_SnapMode::SnapIntersection;
129 }
130 if (s.snapOnEntity) {
131 ret |= RS_SnapMode::SnapOnEntity;
132 }
133 if (s.snapCenter) {
134 ret |= RS_SnapMode::SnapCenter;
135 }
136 if (s.snapDistance) {
137 ret |= RS_SnapMode::SnapDistance;
138 }
139 if (s.snapMiddle) {
140 ret |= RS_SnapMode::SnapMiddle;
141 }
142 if (s.snapEndpoint) {
143 ret |= RS_SnapMode::SnapEndpoint;
144 }
145 if (s.snapGrid) {
146 ret |= RS_SnapMode::SnapGrid;
147 }
148 if (s.snapFree) {
149 ret |= RS_SnapMode::SnapFree;
150 }
151 if (s.snapAngle) {
152 ret |= RS_SnapMode::SnapAngle;
153 }
154
155 switch (s.restriction) {
156 case RS2::RestrictHorizontal:
157 ret |= RS_SnapMode::RestrictHorizontal;
158 break;
159 case RS2::RestrictVertical:
160 ret |= RS_SnapMode::RestrictVertical;
161 break;
162 case RS2::RestrictOrthogonal:
163 ret |= RS_SnapMode::RestrictOrthogonal;
164 break;
165 default:
166 break;
167 }
168
169 if (s.snapVisual) {
170 ret |= RS_SnapMode::SnapVisual;
171 }
172 if (s.snapVisualSurvive) {
173 ret |= RS_SnapMode::SnapVisualSurvive;
174 }
175
176 return ret;
177}
178
179/**
180 * integer flag to snapMode
181 */
182RS_SnapMode RS_SnapMode::fromInt(const unsigned int ret) {
183 RS_SnapMode s;
184
185 if (RS_SnapMode::SnapIntersection & ret) {
186 s.snapIntersection = true;
187 }
188 if (RS_SnapMode::SnapOnEntity & ret) {
189 s.snapOnEntity = true;
190 }
191 if (RS_SnapMode::SnapCenter & ret) {
192 s.snapCenter = true;
193 }
194 if (RS_SnapMode::SnapDistance & ret) {
195 s.snapDistance = true;
196 }
197 if (RS_SnapMode::SnapMiddle & ret) {
198 s.snapMiddle = true;
199 }
200 if (RS_SnapMode::SnapEndpoint & ret) {
201 s.snapEndpoint = true;
202 }
203 if (RS_SnapMode::SnapGrid & ret) {
204 s.snapGrid = true;
205 }
206 if (RS_SnapMode::SnapFree & ret) {
207 s.snapFree = true;
208 }
209 if (RS_SnapMode::SnapAngle & ret) {
210 s.snapAngle = true;
211 }
212
213 switch (RS_SnapMode::RestrictOrthogonal & ret) {
214 case RS_SnapMode::RestrictHorizontal:
215 s.restriction = RS2::RestrictHorizontal;
216 break;
217 case RS_SnapMode::RestrictVertical:
218 s.restriction = RS2::RestrictVertical;
219 break;
220 case RS_SnapMode::RestrictOrthogonal:
221 s.restriction = RS2::RestrictOrthogonal;
222 break;
223 default:
224 s.restriction = RS2::RestrictNothing;
225 break;
226 }
227
228 if (RS_SnapMode::SnapVisual & ret) {
229 s.snapVisual = true;
230 }
231
232 if (RS_SnapMode::SnapVisualSurvive & ret) {
233 s.snapVisualSurvive = true;
234 }
235 return s;
236}
237
238/**
239 * Methods and structs for class RS_Snapper
240 */
241struct RS_Snapper::Indicator {
242 bool drawLines = false;
243 int lines_Type = 0;
244 RS_Pen lines_Pen;
245
246 bool drawShape = false;
247 int shape_Type = 0;
248 RS_Pen shape_Pen;
249
250 int pointType = LC_DEFAULTS_PDMode(0|96);
251 int pointSize = LC_DEFAULTS_PDSize(- 2.0);
252};
253
254namespace {
255 constexpr double DEFAULT_SNAP_ANGLE_STEP = RS_Math::deg2rad(15.0);
256 constexpr double DEFAULT_SOFT_SNAP_SENSITIVITY = RS_Math::deg2rad(3.0);
257 constexpr double MAX_SOFT_SNAP_FRACTION_OF_STEP = 0.45;
258}
259
260/**
261 * Constructor.
262 */
263RS_Snapper::RS_Snapper(LC_ActionContext* actionContext, QObject* parent) :
264 QObject(parent), m_document(actionContext->getDocument()), m_graphicView(actionContext->getGraphicView()),
265 m_actionContext(actionContext), m_infoCursorOverlayData{std::make_unique<LC_InfoCursorData>()},
266 m_visualSnapManager{std::make_unique<LC_VisualSnapManager>(this)}, m_impData{std::make_unique<ImpData>()},
267 m_snapIndicator{std::make_unique<Indicator>()}, m_snapToAngleStep{DEFAULT_SNAP_ANGLE_STEP},
268 m_softSnapSensitivityRad{DEFAULT_SOFT_SNAP_SENSITIVITY} {
269 Q_ASSERT(m_document != nullptr)static_cast<void>(false && (m_document != nullptr
))
;
270 Q_ASSERT(m_graphicView != nullptr)static_cast<void>(false && (m_graphicView != nullptr
))
;
271 m_viewport = m_graphicView->getViewPort();
272 m_visualSnapManager->setViewport(m_viewport);
273 m_formatter = m_viewport->getFormatter();
274 m_infoCursorOverlayPrefs = m_graphicView->getInfoCursorOverlayPreferences();
275}
276
277RS_Snapper::~RS_Snapper() = default;
278
279/**
280 * Initialize (called by all constructors)
281 */
282void RS_Snapper::init() {
283 m_snapMode = m_graphicView->getDefaultSnapMode();
284 m_keyEntity = nullptr;
285 m_impData->snapSpot = RS_Vector{false};
286 m_impData->snapCoord = RS_Vector{false};
287 m_snapDistance = 1.0;
288 initSettings();
289}
290
291void RS_Snapper::initSettings() {
292 initFromSettings();
293
294 RS_Graphic* graphic = m_graphicView->getGraphic();
295 if (graphic != nullptr) {
296 initFromGraphic(graphic);
297 }
298}
299
300void RS_Snapper::initFromSettings() {
301 LC_GROUPRS_Settings::instance()->beginGroup("Appearance");
302 {
303 const int snapIndicatorLineWidth = static_cast<RS2::LineType>(LC_GET_INTRS_Settings::instance()->readInt("indicator_lines_line_width", 1));
304 m_snapIndicator->drawLines = LC_GET_BOOLRS_Settings::instance()->readBool("indicator_lines_state", true);
305 if (m_snapIndicator->drawLines) {
306 m_snapIndicator->lines_Type = LC_GET_INTRS_Settings::instance()->readInt("indicator_lines_type", 0);
307 const auto snapIndicatorLineType = static_cast<RS2::LineType>(LC_GET_INTRS_Settings::instance()->readInt("indicator_lines_line_type", RS2::DashLine));
308 const QString snapColorLines = LC_GET_ONE_STRRS_Settings::instance()->readStrSingle("Colors", "snap_indicator_lines", RS_Settings::SNAP_INDICATOR_LINES);
309 m_snapIndicator->lines_Pen = RS_Pen(RS_Color(snapColorLines), RS2::Width00, snapIndicatorLineType);
310 m_snapIndicator->lines_Pen.setScreenWidth(snapIndicatorLineWidth);
311 }
312 else {
313 m_snapIndicator->lines_Type = LC_Crosshair::NoLines;
314 }
315
316 m_snapIndicator->drawShape = LC_GET_BOOLRS_Settings::instance()->readBool("indicator_shape_state", true);
317 if (m_snapIndicator->drawShape) {
318 m_snapIndicator->shape_Type = LC_GET_INTRS_Settings::instance()->readInt("indicator_shape_type", 0);
319 const QString snapColor = LC_GET_ONE_STRRS_Settings::instance()->readStrSingle("Colors", "snap_indicator", RS_Settings::SNAP_INDICATOR);
320 m_snapIndicator->shape_Pen = RS_Pen(RS_Color(snapColor), RS2::Width00, RS2::SolidLine);
321 m_snapIndicator->shape_Pen.setScreenWidth(snapIndicatorLineWidth);
322 }
323 else {
324 m_snapIndicator->shape_Type = LC_Crosshair::NoShape;
325 }
326
327 m_ignoreSnapToGridIfNoGrid = LC_GET_BOOLRS_Settings::instance()->readBool("SnapGridIgnoreIfNoGrid", false);
328 }
329 LC_GROUP_ENDRS_Settings::instance()->endGroup();
330
331 LC_GROUPRS_Settings::instance()->beginGroup("Snap");
332 {
333 m_distanceBeforeSwitchToFreeSnap = LC_GET_INTRS_Settings::instance()->readInt("AdvSnapOnEntitySwitchToFreeDistance", 500) / 100.0;
334 m_catchEntityGuiRange = LC_GET_INTRS_Settings::instance()->readInt("AdvSnapEntityCatchRange", DEFAULT_CATCH_ENTITY_RANGE_PX);
335 m_minGridCellSnapFactor = LC_GET_INTRS_Settings::instance()->readInt("AdvSnapGridCellSnapFactor", 25) / 100.0;
336 m_angleSnapSnapToGridLinesIfGrid = LC_GET_BOOLRS_Settings::instance()->readBool("AngleSnapToLinesIfGrid", true);
337 }
338 LC_GROUP_ENDRS_Settings::instance()->endGroup();
339
340 m_visualSnapManager->updateOptions();
341}
342
343void RS_Snapper::updateSnapAngleStep() {
344 const int stepType = LC_GET_ONE_INTRS_Settings::instance()->readIntSingle("Defaults", "AngleSnapStep", 3);
345 double snapStepDegrees;
346 switch (stepType) {
347 case 0:
348 snapStepDegrees = 1.0;
349 break;
350 case 1:
351 snapStepDegrees = 3.0;
352 break;
353 case 2:
354 snapStepDegrees = 5.0;
355 break;
356 case 3:
357 snapStepDegrees = 10.0;
358 break;
359 case 4:
360 snapStepDegrees = 15.0;
361 break;
362 case 5:
363 snapStepDegrees = 18.0;
364 break;
365 case 6:
366 snapStepDegrees = 22.5;
367 break;
368 case 7:
369 snapStepDegrees = 30.0;
370 break;
371 case 8:
372 snapStepDegrees = 45.0;
373 break;
374 case 9:
375 snapStepDegrees = 90.0;
376 break;
377 default:
378 snapStepDegrees = 15.0;
379 break;
380 }
381 m_snapToAngleStep = RS_Math::deg2rad(snapStepDegrees);
382
383 bool ok = false;
384 const double softSnapSensitivityDegrees = LC_GET_ONE_STRRS_Settings::instance()->readStrSingle("Defaults", "SoftSnapSensitivityAngle", "3.0").toDouble(&ok);
385 m_softSnapEnabled = LC_GET_ONE_BOOLRS_Settings::instance()->readBoolSingle("Defaults", "SoftSnapEnabled", false);
386 const double maximumSoftSnapSensitivity = RS_Math::rad2deg(m_snapToAngleStep)
387 * MAX_SOFT_SNAP_FRACTION_OF_STEP;
388 const double requestedSoftSnapSensitivity = ok && softSnapSensitivityDegrees >= 0.1
389 ? softSnapSensitivityDegrees
390 : RS_Math::rad2deg(DEFAULT_SOFT_SNAP_SENSITIVITY);
391 m_softSnapSensitivityRad = RS_Math::deg2rad(std::min(std::clamp(requestedSoftSnapSensitivity, 0.1, 45.0),
392 maximumSoftSnapSensitivity));
393}
394
395void RS_Snapper::initFromGraphic(RS_Graphic* graphic) {
396 if (graphic != nullptr) {
397 m_snapIndicator->pointType = graphic->getVariableInt("$PDMODE", LC_DEFAULTS_PDMode(0|96));
398 m_snapIndicator->pointSize = graphic->getVariableInt("$PDSIZE", LC_DEFAULTS_PDSize(- 2.0));
399 }
400}
401
402bool RS_Snapper::isInVisualSnapStatus([[maybe_unused]] int status) {
403 return false;
404}
405
406bool RS_Snapper::isClearVisualSnapByRMB() const {
407 return m_visualSnapManager->isClearVisualSnapByRMB();
408}
409
410void RS_Snapper::onVisualSnapEntityRegistered([[maybe_unused]] RS_Entity* point) {
411}
412
413bool RS_Snapper::isVisualSnapApplicable() {
414 return m_snapMode.snapVisual;
415}
416
417void RS_Snapper::finish() {
418 m_finished = true;
419 deleteSnapper();
420 deleteInfoCursor();
421 clearVisualSnap();
422 m_graphicView->hideRelativeInputWidget();
423}
424
425void RS_Snapper::setSnapMode(const RS_SnapMode& snapMode) {
426 this->m_snapMode = snapMode;
427 m_actionContext->requestSnapDistOptions(&m_snapDistance, snapMode.snapDistance);
428 m_actionContext->requestSnapMiddleOptions(&m_middlePoints, snapMode.snapMiddle);
429}
430
431const RS_SnapMode* RS_Snapper::getSnapMode() const {
432 return &m_snapMode;
433}
434
435RS_SnapMode* RS_Snapper::getSnapMode() {
436 return &m_snapMode;
437}
438
439//get current mouse coordinates
440RS_Vector RS_Snapper::snapFree(const QMouseEvent* e) const {
441 if (e == nullptr) {
442 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "RS_Snapper::snapFree: event is nullptr");
443 return RS_Vector(false);
444 }
445 m_impData->update(toGraph(e), RS2::SnapType::FREE);
446 m_impData->snapCoord = m_impData->snapSpot;
447 m_snapIndicator->drawLines = true;
448 return m_impData->snapCoord;
449}
450
451void RS_Snapper::snapEndpoint(const RS_Vector& mouseCoord, double& ds2Min) const {
452 if (m_snapMode.snapEndpoint) {
453 RS_Entity* endpointEntity = nullptr;
454 const RS_Vector snap = snapEndpoint(mouseCoord, &endpointEntity);
455 const double ds2 = mouseCoord.squaredTo(snap);
456
457 if (snap.valid && ds2 < ds2Min) {
458 ds2Min = ds2;
459 m_impData->update(snap, RS2::SnapType::ENDPOINT, endpointEntity);
460 }
461 }
462}
463
464void RS_Snapper::snapCenter(const RS_Vector& mouseCoord, double& ds2Min) const {
465 if (m_snapMode.snapCenter) {
466 RS_Entity* centerEntity = nullptr;
467 const RS_Vector snap = snapCenter(mouseCoord, &centerEntity);
468 const double ds2 = mouseCoord.squaredTo(snap);
469 if (ds2 < ds2Min) {
470 ds2Min = ds2;
471 m_impData->update(snap, RS2::SnapType::CENTER, centerEntity);
472 }
473 }
474}
475
476void RS_Snapper::snapMiddle(const RS_Vector& mouseCoord, double& ds2Min) {
477 if (m_snapMode.snapMiddle) {
478 //this is still brutal force
479 //todo: accept value from widget QG_SnapMiddleOptions
480
481 m_actionContext->requestSnapMiddleOptions(&m_middlePoints, m_snapMode.snapMiddle);
482 const RS_Vector snap = snapMiddle(mouseCoord);
483 const double ds2 = mouseCoord.squaredTo(snap);
484 if (ds2 < ds2Min) {
485 ds2Min = ds2;
486 m_impData->update(snap, RS2::SnapType::MIDDLE);
487 }
488 }
489}
490
491void RS_Snapper::snapDistance(const RS_Vector& mouseCoord, double& ds2Min) {
492 if (m_snapMode.snapDistance) {
493 //this is still brutal force
494 //todo: accept value from widget QG_SnapDistOptions
495 m_actionContext->requestSnapDistOptions(&m_snapDistance, m_snapMode.snapDistance);
496 const RS_Vector snap = snapDist(mouseCoord);
497 const double ds2 = mouseCoord.squaredTo(snap);
498 if (ds2 < ds2Min) {
499 ds2Min = ds2;
500 m_impData->update(snap, RS2::SnapType::DISTANCE);
501 }
502 }
503}
504
505void RS_Snapper::snapIntersection(const RS_Vector& mouseCoord, double& ds2Min) const {
506 m_impData->entityOther = nullptr;
507 if (m_snapMode.snapIntersection) {
508 RS_Entity* entity1 = nullptr;
509 RS_Entity* entity2 = nullptr;
510 const RS_Vector snap = snapIntersection(mouseCoord, &entity1, &entity2);
511 const double ds2 = mouseCoord.squaredTo(snap);
512 if (ds2 < ds2Min) {
513 ds2Min = ds2;
514 m_impData->update(snap, RS2::SnapType::INTERSECTION, entity1, entity2);
515 }
516 }
517}
518
519void RS_Snapper::snapOnEntity(const RS_Vector& mouseCoord, double& ds2Min) {
520 if (m_snapMode.snapOnEntity && m_impData->snapSpot.distanceTo(mouseCoord) > m_distanceBeforeSwitchToFreeSnap) {
521 RS_Entity* entity = nullptr;
522 const RS_Vector snap = snapOnEntity(mouseCoord, &entity);
523 const double ds2 = mouseCoord.squaredTo(snap);
524 if (ds2 < ds2Min) {
525 ds2Min = ds2;
526 m_impData->update(snap, RS2::SnapType::ENTITY, entity);
527 }
528 }
529}
530
531void RS_Snapper::snapGrid(const RS_Vector& mouseCoord, double ds2Min) const {
532 if (isSnapToGrid()) {
533 const RS_Vector snap = snapGrid(mouseCoord);
534 const double ds2 = mouseCoord.squaredTo(snap);
535 if (ds2 < ds2Min) {
536 m_impData->update(snap, RS2::SnapType::GRID);
537 }
538 }
539}
540
541RS_Vector RS_Snapper::snapVisualRayOrLine(const RS_Vector& mouseCoord, const RS_Entity* entity, double& dist) const {
542 RS_Vector snap = entity->getNearestPointOnEntity(mouseCoord, true, &dist);
543 if (isSnapToGrid()) {
544 const RS_Vector startPoint = entity->getStartpoint();
545 const RS_Vector endPoint = entity->getEndpoint();
546 snap = snapGrid(snap, startPoint, endPoint);
547 m_impData->restrictedSnapType = RS2::SnapType::GRID;
548 }
549 return snap;
550}
551
552bool RS_Snapper::snapVisual(const RS_Vector& mouseCoord, RS_Entity** restrictingEntity, RS2::VisualSnapGuideEntityType& restrictingType) {
553 bool visualSnapFound = false;
554
555 // double range = getCatchDistance(getSnapRange(), m_catchEntityGuiRange);
556 const double range = getSnapRange();
557 m_visualSnapManager->setSnapRange(range);
558 if (isVisualSnapApplicable() && hasVisualSnap()) {
559 LC_VisualSnapSolution* visualSnapSolution = m_visualSnapManager->solveVisualSnap(mouseCoord);
560 visualSnapSolution->restrictedPoint.valid = false;
561
562 if (visualSnapSolution != nullptr) {
563 if (visualSnapSolution->hasFoundSnapPoint()) {
564 // exact snap found, it's either intersection or vertex point
565 const RS_Vector snap = visualSnapSolution->foundSnapPoint;
566 m_impData->update(snap, RS2::SnapType::VISUAL_SNAP);
567 m_impData->snapCoord = snap;
568 m_impData->visualSnapType = visualSnapSolution->foundSnapPointInfo;
569 const RS2::SnapType originalSnapType = visualSnapSolution->restrictedOriginalSnapType;
570 if (originalSnapType != RS2::SnapType::NO_SNAP) {
571 m_impData->restrictedSnapType = originalSnapType;
572 }
573 visualSnapFound = true;
574 }
575 else {
576 if (!visualSnapSolution->guidingEntities.empty()) {
577 const int size = visualSnapSolution->guidingEntities.size();
578 // LC_ERR << "VSnap entities size: " << size;
579 LC_VisualSnapEntityHolder holder;
580 if (size == 1) {
581 holder = visualSnapSolution->guidingEntities.front();
582 }
583 else {
584 // find closest entity
585 double minDist = RS_MAXDOUBLE1.0E+10;
586 for (const auto& h : visualSnapSolution->guidingEntities) {
587 const auto e = h.entity;
588 double dist;
589 e->getNearestPointOnEntity(mouseCoord, true, &dist);
590 // LC_ERR << "VSNAP_nearest_dist: " << dist;
591 // LC_ERR << "holder.snapEntityType: " << h.snapEntityType;
592 if (dist < minDist) {
593 minDist = dist;
594 holder = h;
595 }
596 }
597 if (minDist > range) {
598 return false; // otherwise, it will be more than strict snap to nearest line...
599 }
600 }
601 const auto snapEntityType = holder.snapEntityType;
602 // LC_ERR << "VSnap - snapped type : " << snapEntityType;
603 double distance = RS_MAXDOUBLE1.0E+10;
604 const RS_Entity* guideEntity = holder.entity;
605 switch (snapEntityType) {
606 case RS2::VSNAP_LINE_VERTEX_VERTICAL:
607 case RS2::VSNAP_LINE_VERTEX_HORIZONTAL:
608 case RS2::VSNAP_LINE_RESTR_VERTICAL:
609 case RS2::VSNAP_LINE_RESTR_HORIZONTAL:
610 case RS2::VSNAP_POINT_RELATIVE_VERTICAL_DX:
611 case RS2::VSNAP_POINT_RELATIVE_HORIZONTAL_DY: {
612 restrictingType = snapEntityType;
613 *restrictingEntity = holder.entity;
614 break;
615 }
616 case RS2::VSNAP_LINE_ENDPOINT_TANGENT:
617 case RS2::VSNAP_LINE_ENDPOINT_NORMAL:
618 case RS2::VSNAP_LINE_TANGENT1:
619 case RS2::VSNAP_LINE_TANGENT2:
620 case RS2::VSNAP_LINE_RAY:
621 case RS2::VSNAP_LINE_ENDPOINT_ANGLE_STEP:
622 case RS2::VSNAP_LINE_VERTEX_ANGLE_STEP:
623 case RS2::VSNAP_LINE_VERTEX_VERTEX:
624 case RS2::VSNAP_LINE_VERTEX_ORTHO:
625 case RS2::VSNAP_LINE_DISTANCE_TANGENT2:
626 case RS2::VSNAP_LINE_VERTEX_VERTEX_ORTHO:
627 case RS2::VSNAP_POINT_RELATIVE_ANGLE_RAY:
628 case RS2::VSNAP_POINT_RELATIVE_NORMAL: {
629 double distanceToRay;
630 RS_Vector snap = guideEntity->getNearestPointOnEntity(mouseCoord, true, &distanceToRay);
631 snapEndpoint(mouseCoord, distance);
632 snapIntersection(mouseCoord, distance);
633 snapCenter(mouseCoord, distance);
634
635 if (distance > range) {
636 if (distanceToRay < range) {
637 if (isSnapToGrid()) {
638 const RS_Vector startPoint = guideEntity->getStartpoint();
639 const RS_Vector endPoint = guideEntity->getEndpoint();
640 snap = snapGrid(snap, startPoint, endPoint);
641 m_impData->restrictedSnapType = RS2::SnapType::GRID;
642 }
643 else {
644 // LC_ERR << "VS RAY: ";
645 }
646
647 m_impData->update(snap, RS2::SnapType::VISUAL_SNAP);
648 m_impData->snapCoord = snap;
649 RS2::LC_VisualSnapIntersectionInfo snapInfo;
650 snapInfo.entity1 = RS2::VisualSnapGuideEntityType::VSNAP_NONE;
651 snapInfo.entity2 = snapEntityType;
652 if (snapEntityType == RS2::VSNAP_LINE_VERTEX_ANGLE_STEP || snapEntityType ==
653 RS2::VSNAP_LINE_ENDPOINT_ANGLE_STEP) {
654 snapInfo.rayAngle2 = holder.wcsRayAngleRad;
655 }
656 m_impData->visualSnapType = snapInfo;
657 visualSnapFound = true;
658 // LC_ERR << "VS POINT ON RAY: " << snap;
659 }
660 }
661 else {
662 // visualSnapFound = true;
663 // m_impData->snapCoord = m_impData->snapSpot;
664 visualSnapFound = false;
665 // LC_ERR << "VS POINT FOUND: " << m_impData->snapCoord;
666 }
667 break;
668 }
669 default: {
670 const RS_Entity* ent = holder.entity;
671 RS_Vector snap(false);
672 snap = ent->getNearestPointOnEntity(mouseCoord, true, &distance);
673 if (distance < range) {
674 m_impData->update(snap, RS2::SnapType::VISUAL_SNAP);
675 m_impData->snapCoord = snap;
676 RS2::LC_VisualSnapIntersectionInfo snapInfo;
677 snapInfo.entity1 = RS2::VisualSnapGuideEntityType::VSNAP_NONE;
678 snapInfo.entity2 = snapEntityType;
679 if (snapEntityType == RS2::VSNAP_LINE_VERTEX_ANGLE_STEP || snapEntityType ==
680 RS2::VSNAP_LINE_ENDPOINT_ANGLE_STEP) {
681 snapInfo.rayAngle2 = holder.wcsRayAngleRad;
682 }
683 m_impData->visualSnapType = snapInfo;
684 visualSnapFound = true;
685 }
686 }
687 }
688 }
689 }
690 }
691 }
692 return visualSnapFound;
693}
694
695/**
696 * Snap to a coordinate in the drawing using the current snap mode.
697 *
698 * @param e A mouse event.
699 * @return The coordinates of the point or an invalid vector.
700 */
701RS_Vector RS_Snapper::snapPoint(const QMouseEvent* e) {
702 m_impData->restrictedSnapType = RS2::NO_SNAP;
703 m_impData->update(RS_Vector(false), RS2::SnapType::NO_SNAP);
704 m_impData->restriction = RS2::RestrictNothing;
705 m_impData->snapCoord = RS_Vector(false);
706 m_impData->visualSnapType.entity1 = RS2::VSNAP_NONE;
707 m_impData->visualSnapType.entity2 = RS2::VSNAP_NONE;
708
709 if (e == nullptr) {
710 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "RS_Snapper::snapPoint: event is nullptr");
711 return m_impData->snapSpot;
712 }
713
714 const RS_Vector mouseCoord = toGraph(e);
715 double ds2Min = RS_MAXDOUBLE1.0E+10 * RS_MAXDOUBLE1.0E+10;
716
717 RS_Entity* restrictingEntity{nullptr};
718 RS2::VisualSnapGuideEntityType restrictingEntityType;
719
720 const bool visualSnapFound = snapVisual(mouseCoord, &restrictingEntity, restrictingEntityType);
721
722 if (!visualSnapFound) {
723 snapEndpoint(mouseCoord, ds2Min);
724 snapCenter(mouseCoord, ds2Min);
725 snapMiddle(mouseCoord, ds2Min);
726 snapDistance(mouseCoord, ds2Min);
727 snapIntersection(mouseCoord, ds2Min);
728 snapOnEntity(mouseCoord, ds2Min);
729 snapGrid(mouseCoord, ds2Min);
730
731 if (!m_impData->snapSpot.valid) {
732 m_impData->snapSpot = mouseCoord; //default to snapFree
733 m_impData->snapType = RS2::SnapType::FREE;
734 }
735 else {
736 //retreat to snapFree when distance is more than quarter grid
737 // issue #1631: snapFree issues: defines getSnapFree as the minimum graph distance to allow SnapFree
738 if (m_snapMode.snapFree) {
739 const double snapRange = getSnapRange();
740 // compare the current graph distance to the closest snap point to the minimum snapping free distance
741 if ((mouseCoord - m_impData->snapSpot).magnitude() >= snapRange) {
742 m_impData->snapSpot = mouseCoord;
743 m_impData->snapType = RS2::SnapType::FREE;
744 }
745 }
746 }
747
748 // handle snap restrictions that can be activated in addition
749 // to the ones above:
750 //apply restriction
751 RS_Vector vpv;
752 RS_Vector vph;
753 if (m_snapMode.restriction != RS2::RestrictNothing) {
754 const RS_Vector rz = m_viewport->getRelativeZero();
755 if (m_viewport->hasUCS()) {
756 const RS_Vector ucsRZ = m_viewport->toUCS(rz);
757 const RS_Vector ucsSnap = m_viewport->toUCS(m_impData->snapSpot);
758 vpv = m_viewport->toWorld(RS_Vector(ucsRZ.x, ucsSnap.y));
759 vph = m_viewport->toWorld(RS_Vector(ucsSnap.x, ucsRZ.y));
760 }
761 else {
762 vpv = RS_Vector(rz.x, m_impData->snapSpot.y);
763 vph = RS_Vector(m_impData->snapSpot.x, rz.y);
764 }
765 }
766
767 switch (m_snapMode.restriction) {
768 case RS2::RestrictOrthogonal: {
769 m_impData->snapCoord = (mouseCoord.distanceTo(vpv) < mouseCoord.distanceTo(vph)) ? vpv : vph;
770 m_impData->restriction = RS2::RestrictOrthogonal;
771 break;
772 }
773 case RS2::RestrictHorizontal: {
774 m_impData->snapCoord = vph;
775 m_impData->restriction = RS2::RestrictHorizontal;
776 break;
777 }
778 case RS2::RestrictVertical: {
779 m_impData->snapCoord = vpv;
780 m_impData->restriction = RS2::RestrictVertical;
781 break;
782 }
783 //case RS2::RestrictNothing:
784 default: {
785 m_impData->snapCoord = m_impData->snapSpot;
786 m_impData->restriction = RS2::RestrictNothing;
787 break;
788 }
789 }
790 }
791 if (restrictingEntity != nullptr) {
792 LC_VisualSnapSolution* visualSnapSolution = m_visualSnapManager->getCurrentSolution();
793 visualSnapSolution->restrictedPoint = m_impData->snapSpot;
794 visualSnapSolution->restrictedOriginalSnapType = m_impData->snapType;
795 m_impData->restrictedSnapType = m_impData->snapType;
796 m_impData->snapType = RS2::SnapType::VISUAL_SNAP;
797 RS2::LC_VisualSnapIntersectionInfo snapInfo;
798 snapInfo.entity1 = RS2::VisualSnapGuideEntityType::VSNAP_NONE;
799 snapInfo.entity2 = restrictingEntityType;
800 m_impData->visualSnapType = snapInfo;
801
802 if (restrictingEntityType == RS2::VSNAP_LINE_VERTEX_VERTICAL || restrictingEntityType == RS2::VSNAP_POINT_RELATIVE_VERTICAL_DX || restrictingEntityType == RS2::VSNAP_LINE_RESTR_VERTICAL) {
803 m_impData->snapSpot = restrictVertical(restrictingEntity->getStartpoint(), m_impData->snapSpot);
804 m_impData->snapCoord = m_impData->snapSpot;
805 }
806 else if (restrictingEntityType == RS2::VSNAP_LINE_VERTEX_HORIZONTAL || restrictingEntityType ==
807 RS2::VSNAP_POINT_RELATIVE_HORIZONTAL_DY || restrictingEntityType == RS2::VSNAP_LINE_RESTR_HORIZONTAL) {
808 m_impData->snapSpot = restrictHorizontal(restrictingEntity->getStartpoint(), m_impData->snapSpot);
809 m_impData->snapCoord = m_impData->snapSpot;
810 }
811 visualSnapSolution->foundSnapPoint = m_impData->snapSpot;
812 }
813
814 setSnapPoint(m_impData->snapSpot, false);
815
816 return m_impData->snapCoord;
817}
818
819/**manually set snapPoint*/
820RS_Vector RS_Snapper::setSnapPoint(const RS_Vector& coord, const bool setSpot) {
821 if (coord.valid) {
822 m_impData->snapSpot = coord;
823 if (setSpot) {
824 m_impData->snapCoord = coord;
825 }
826 // fixme - sand - it seems that the code below is meaning for preview only?
827 updateCoordinateWidgetByRelZero(m_impData->snapCoord);
828 drawSnapper();
829 drawInfoCursor();
830 }
831 return coord;
832}
833
834double RS_Snapper::getSnapRange() const {
835 // issue #1631: redefine this method to the minimum graph distance to allow "Snap Free"
836 // When the closest of any other snapping point is beyond this distance, free snapping is used.
837
838 double minGraph = RS_MAXDOUBLE1.0E+10;
839 double minGrid = RS_MAXDOUBLE1.0E+10;
840 double minSize = RS_MAXDOUBLE1.0E+10;
841 if (m_graphicView != nullptr) {
842 minGraph = toGraphDX(DEFAULT_CATCH_ENTITY_RANGE_PX);
843 // if grid is on, less than one quarter of the cell vector
844 // if (viewport->isGridOn()) {
845 // todo - sand - check whether it's correct apply this check only if "Snap to Grid" is enabled
846 if (m_viewport->isGridOn() && m_snapMode.snapGrid) {
847 const RS_Grid* grid = m_viewport->getGrid();
848 const RS_Vector& cellVector = grid->getCellVector();
849 minGrid = cellVector.magnitude() * m_minGridCellSnapFactor;
850 }
851 }
852 if (isSizeValid(m_document->getSize())) {
853 // The size bounding box
854 minSize = getValidSize(m_document->getSize());
855 }
856 if (std::min(minGraph, minGrid) < 0.99 * RS_MAXDOUBLE1.0E+10) {
857 return std::min(minGraph, minGrid);
858 }
859 if (minSize < 0.99 * RS_MAXDOUBLE1.0E+10) {
860 return minSize;
861 }
862 // shouldn't happen: no graphicview or a valid size
863 // Allow free snapping by returning the floating point tolerance
864 return RS_TOLERANCE1.0e-10;
865}
866
867/**
868 * Snaps to a free coordinate.
869 *
870 * @param coord The mouse coordinate.
871 * @return The coordinates of the point or an invalid vector.
872 */
873RS_Vector RS_Snapper::snapFree(const RS_Vector& coord) {
874 m_keyEntity = nullptr;
875 return coord;
876}
877
878/**
879 * Snaps to the closest endpoint.
880 *
881 * @param coord The mouse coordinate.
882 * @return The coordinates of the point or an invalid vector.
883 */
884RS_Vector RS_Snapper::snapEndpoint(const RS_Vector& coord, RS_Entity** entity) const {
885 const RS_Vector vec = m_document->getNearestEndpoint(coord, entity, nullptr);
886 return vec;
887}
888
889/**
890 * Snaps to a grid point.
891 *
892 * @param coord The mouse coordinate.
893 * @return The coordinates of the point or an invalid vector.
894 */
895RS_Vector RS_Snapper::snapGrid(const RS_Vector& coord) const {
896 // RS_DEBUG->print("RS_Snapper::snapGrid begin");
897
898 // std::cout<<__FILE__<<" : "<<__func__<<" : line "<<__LINE__<<std::endl;
899 // std::cout<<" mouse: = "<<coord<<std::endl;
900 // std::cout<<" snapGrid: = "<<graphicView->getGrid()->snapGrid(coord)<<std::endl;
901 return m_viewport->snapGrid(coord);
902}
903
904RS_Vector RS_Snapper::snapGrid(const RS_Vector& coord, const RS_Vector& rayStart, const RS_Vector& rayEnd) const {
905 return m_viewport->snapGrid(coord, rayStart, rayEnd);
906}
907
908// fixme - note 1) similarly to ray, actually any entity may be used with snap to grid if it will be possisble to find
909// intersection point points of constuction lines with this grid. However, for UCS - it's necessary to transform the entity
910// accordingly (or, alternativealy, transform a grid cell.. ).
911// Fixme - note 2) it might be practical add method that returns grid cell - and do transformation in viewport
912RS_Vector RS_Snapper::snapGrid(const RS_Vector& coord, RS_Entity* entity) const {
913 return m_viewport->snapGrid(coord, entity);
914}
915
916/**
917 * Snaps to a point on an entity.
918 *
919 * @param coord The mouse coordinate.
920 * @return The coordinates of the point or an invalid vector.
921 */
922RS_Vector RS_Snapper::snapOnEntity(const RS_Vector& coord, RS_Entity** entity) {
923 // Text is skipped at every depth: points on glyphs are never snap targets, and measuring
924 // them would walk every glyph (issue #2804)
925 RS_Entity* nearest = nullptr;
926 if (m_snapMode.snapFree) {
927 // A point beyond the snap range gives way to free snapping anyway, and must not leave a key
928 // entity behind: Delete Free acts on the key entity.
929 RS_EntityContainer nearby(nullptr, false);
930 m_document->appendNearby(coord, getSnapRange(), nearby);
931 nearby.getDistanceToPoint(coord, &nearest, RS2::ResolveAllButTexts);
932 }
933 else {
934 // the drawing skips every entity that lies farther away than the nearest one found so far
935 m_document->getDistanceToPoint(coord, &nearest, RS2::ResolveAllButTexts);
936 }
937
938 RS_Vector point(false);
939 m_keyEntity = nullptr;
940 // what RS_EntityContainer::getNearestPointOnEntity() refuses to snap to
941 const RS_EntityContainer* parent = nearest != nullptr ? nearest->getParent() : nullptr;
942 if (nearest != nullptr && (parent == nullptr || !parent->ignoredSnap())) {
943 RS_Entity* snapEntity = nullptr;
944 point = nearest->getNearestPointOnEntity(coord, true, nullptr, &snapEntity);
945 if (point.valid) {
946 m_keyEntity = snapEntity != nullptr ? snapEntity : nearest;
947 }
948 }
949 *entity = m_keyEntity;
950 return point;
951}
952
953/**
954 * Snaps to the closest center.
955 *
956 * @param coord The mouse coordinate.
957 * @return The coordinates of the point or an invalid vector.
958 */
959RS_Vector RS_Snapper::snapCenter(const RS_Vector& coord, RS_Entity** centerEntity) const {
960 const RS_Vector vec = m_document->getNearestCenter(coord, nullptr, centerEntity);
961 return vec;
962}
963
964/**
965 * Snaps to the closest middle.
966 *
967 * @param coord The mouse coordinate.
968 * @return The coordinates of the point or an invalid vector.
969 */
970RS_Vector RS_Snapper::snapMiddle(const RS_Vector& coord) const {
971 //std::cout<<"RS_Snapper::snapMiddle(): middlePoints="<<middlePoints<<std::endl;
972 return m_document->getNearestMiddle(coord, nullptr, m_middlePoints);
973}
974
975int RS_Snapper::getSnapMiddlePoints() const {
976 return m_middlePoints;
977}
978
979double RS_Snapper::getSnapDistance() const {
980 return m_snapDistance;
981}
982
983/**
984 * Snaps to the closest point with a given distance to the endpoint.
985 *
986 * @param coord The mouse coordinate.
987 * @return The coordinates of the point or an invalid vector.
988 */
989RS_Vector RS_Snapper::snapDist(const RS_Vector& coord) const {
990 //std::cout<<" RS_Snapper::snapDist(RS_Vector coord): distance="<<distance<<std::endl;
991 const RS_Vector vec = m_document->getNearestDist(m_snapDistance, coord, nullptr);
992 return vec;
993}
994
995/**
996 * Snaps to the closest intersection point.
997 *
998 * @param coord The mouse coordinate.
999 * @return The coordinates of the point or an invalid vector.
1000 */
1001RS_Vector RS_Snapper::snapIntersection(const RS_Vector& coord, [[maybe_unused]] RS_Entity** entity,
1002 [[maybe_unused]] RS_Entity** otherEntity) const {
1003 const RS_Vector vec = m_document->getNearestIntersection(coord, nullptr);
1004 return vec;
1005}
1006
1007/**
1008 * 'Corrects' the given coordinates to 0, 90, 180, 270 degrees relative to
1009 * the current relative zero point.
1010 *
1011 * @param coord The uncorrected coordinates.
1012 * @return The corrected coordinates.
1013 */
1014RS_Vector RS_Snapper::restrictOrthogonal(const RS_Vector& coord) const {
1015 const RS_Vector rz = m_viewport->getRelativeZero();
1016 RS_Vector ret(coord);
1017
1018 const auto retx = RS_Vector(rz.x, ret.y);
1019 const auto rety = RS_Vector(ret.x, rz.y);
1020
1021 if (retx.distanceTo(ret) > rety.distanceTo(ret)) {
1022 ret = rety;
1023 }
1024 else {
1025 ret = retx;
1026 }
1027
1028 return ret;
1029}
1030
1031/**
1032 * 'Corrects' the given coordinates to 0, 180 degrees relative to
1033 * the current relative zero point.
1034 *
1035 * @param coord The uncorrected coordinates.
1036 * @return The corrected coordinates.
1037 */
1038
1039RS_Vector RS_Snapper::restrictHorizontal(const RS_Vector& coord) const {
1040 return m_viewport->restrictHorizontal(m_viewport->getRelativeZero(), coord);
1041}
1042
1043/**
1044 * 'Corrects' the given coordinates to 90, 270 degrees relative to
1045 * the current relative zero point.
1046 *
1047 * @param coord The uncorrected coordinates.
1048 * @return The corrected coordinates.
1049 */
1050RS_Vector RS_Snapper::restrictVertical(const RS_Vector& coord) const {
1051 return m_viewport->restrictVertical(m_viewport->getRelativeZero(), coord);
1052}
1053
1054RS_Vector RS_Snapper::restrictVertical(const RS_Vector& base, const RS_Vector& coord) const {
1055 return m_viewport->restrictVertical(base, coord);
1056}
1057
1058RS_Vector RS_Snapper::restrictHorizontal(const RS_Vector& base, const RS_Vector& coord) const {
1059 return m_viewport->restrictHorizontal(base, coord);
1060}
1061
1062RS_Vector RS_Snapper::restrictAngle(const RS_Vector& basePoint, const RS_Vector& snap, const double angle) const {
1063 const double realAngle = toWorldAngle(angle);
1064 const RS_Vector infiniteTickEndPoint = basePoint.relative(10.0, realAngle);
1065 const RS_Vector pointOnInfiniteTick = LC_LineMath::getNearestPointOnInfiniteLine(snap, basePoint, infiniteTickEndPoint);
1066
1067 const RS_Vector possibleEndPoint = pointOnInfiniteTick;
1068 return possibleEndPoint;
1069}
1070
1071/**
1072 * Catches an entity which is close to the given position 'pos'.
1073 *
1074 * @param pos A graphic coordinate.
1075 * @param level The level of resolving for iterating through the entity
1076 * container
1077 * @return Pointer to the entity or nullptr.
1078 */
1079RS_Entity* RS_Snapper::catchEntity(const RS_Vector& pos, const RS2::ResolveLevel level) const {
1080 // set default distance for points inside solids
1081 double dist(0.);
1082 RS_Entity* entity = m_document->getNearestEntity(pos, &dist, level);
1083 if (entity != nullptr && dist <= getCatchDistance(getSnapRange(), m_catchEntityGuiRange)) {
1084 RS_DEBUGRS_Debug::instance()->print("RS_Snapper::catchEntity: found");
1085 return entity;
1086 }
1087 RS_DEBUGRS_Debug::instance()->print("RS_Snapper::catchEntity: not found");
1088 return nullptr;
1089}
1090
1091/**
1092 * Catches an entity which is close to the given position 'pos'.
1093 *
1094 * @param pos A graphic coordinate.
1095 * @param enType
1096 * @param level The level of resolving for iterating through the entity
1097 * container
1098 * @enType, only search for a particular entity type
1099 * @return Pointer to the entity or nullptr.
1100 */
1101RS_Entity* RS_Snapper::catchEntity(const RS_Vector& pos, const RS2::EntityType enType, const RS2::ResolveLevel level) const {
1102 return catchEntity(pos, EntityTypeList{enType}, level);
1103}
1104
1105/**
1106 * Catches an entity which is close to the mouse cursor.
1107 *
1108 * @param e A mouse event.
1109 * @param level The level of resolving for iterating through the entity
1110 * container
1111 * @return Pointer to the entity or nullptr.
1112 */
1113RS_Entity* RS_Snapper::catchEntity(const QMouseEvent* e, const RS2::ResolveLevel level) const {
1114 RS_Entity* entity = catchEntity(toGraph(e), level);
1115 return entity;
1116}
1117
1118/**
1119 * Catches an entity which is close to the mouse cursor.
1120 *
1121 * @param e A mouse event.
1122 * @param enType
1123 * @param level The level of resolving for iterating through the entity
1124 * container
1125 * @enType, only search for a particular entity type
1126 * @return Pointer to the entity or nullptr.
1127 */
1128RS_Entity* RS_Snapper::catchEntity(const QMouseEvent* e, const RS2::EntityType enType, const RS2::ResolveLevel level) const {
1129 return catchEntity(toGraph(e), enType, level);
1130}
1131
1132RS_Entity* RS_Snapper::catchEntity(const QMouseEvent* e, const EntityTypeList& enTypeList, const RS2::ResolveLevel level) const {
1133 const RS_Vector coord = toGraph(e);
1134 return catchEntity(coord, enTypeList, level);
1135}
1136
1137RS_Entity* RS_Snapper::catchEntity(const RS_Vector& pos, const EntityTypeList& enTypeList, const RS2::ResolveLevel level) const {
1138 if (enTypeList.empty()) {
1139 return catchEntity(pos, level);
1140 }
1141
1142 const double catchDistance = getCatchDistance(getSnapRange(), m_catchEntityGuiRange);
1143 RS_EntityContainer nearby(nullptr, false);
1144 m_document->appendNearby(pos, catchDistance, nearby);
1145 // Text and MText stay whole: their glyphs are generated geometry that no action may pick or
1146 // edit, and walking them on every mouse move froze Fillet (issue #2804)
1147 const RS2::ResolveLevel candidateLevel = level == RS2::ResolveAll ? RS2::ResolveAllButTexts : level;
1148 const std::vector<RS_Entity*> candidates = lc::LC_ContainerTraverser{nearby, candidateLevel}.entities();
1149
1150 // The matches of each type follow those of the types listed before it, so an exact tie goes to
1151 // the type listed last and, within a type, to the entity drawn last, as when each type was
1152 // caught on its own.
1153 RS_EntityContainer matches(nullptr, false);
1154 for (const RS2::EntityType type : enTypeList) {
1155 for (RS_Entity* candidate : candidates) {
1156 if (candidate->isVisible() && matchesRequestedType(*candidate, type)) {
1157 matches.push_back(candidate);
1158 }
1159 }
1160 }
1161 double dist = 0.;
1162 RS_Entity* entity = matches.getNearestEntity(pos, &dist, RS2::ResolveNone);
1163 return entity != nullptr && dist <= catchDistance ? entity : nullptr;
1164}
1165
1166void RS_Snapper::suspend() {
1167 m_impData->snapSpot = RS_Vector{false};
1168 m_impData->snapCoord = RS_Vector{false};
1169 suspendRelativeInputWidget();
1170}
1171
1172void RS_Snapper::resume() {
1173 drawSnapper();
1174 initSettings();
1175 // fixme - review/rework this, load from settings as other overlays action(??)
1176 m_infoCursorOverlayPrefs = m_graphicView->getInfoCursorOverlayPreferences();
1177 resumeRelativeInputWidget();
1178}
1179
1180/**
1181 * Hides the snapper options. Default implementation does nothing.
1182 */
1183void RS_Snapper::hideSnapOptions() const {
1184 m_actionContext->hideSnapOptions();
1185}
1186
1187/**
1188 * Deletes the snapper from the screen.
1189 */
1190void RS_Snapper::deleteSnapper() const {
1191 if (m_graphicView != nullptr && !m_graphicView->isCleanUp()) {
1192 m_viewport->clearOverlayDrawablesContainer(RS2::Snapper);
1193 m_graphicView->redraw(RS2::RedrawOverlay); // redraw will happen in the mouse movement event
1194 }
1195}
1196
1197void RS_Snapper::deleteInfoCursor() const {
1198 if (m_graphicView != nullptr && !m_graphicView->isCleanUp()) {
1199 m_viewport->clearOverlayDrawablesContainer(RS2::InfoCursor);
1200 m_graphicView->redraw(RS2::RedrawOverlay); // redraw will happen in the mouse movement event
1201 }
1202}
1203
1204/**
1205 * creates the snap indicator
1206 */
1207void RS_Snapper::drawSnapper() {
1208 LC_OverlayDrawablesContainer* snapperOverlay = m_viewport->getOverlaysDrawablesContainer(RS2::Snapper);
1209 snapperOverlay->clear();
1210 bool showSnapIndicator = showSnapIndicator = isSnapExpected(); // LibreCAD#2520
Although the value stored to 'showSnapIndicator' is used in the enclosing expression, the value is never actually read from 'showSnapIndicator'
1211 if (!m_finished && m_impData->snapSpot.valid && showSnapIndicator) {
1212 if (m_snapIndicator->drawLines || m_snapIndicator->drawShape) {
1213 auto* crosshair = new LC_Crosshair(m_impData->snapCoord, m_snapIndicator->shape_Type, m_snapIndicator->lines_Type,
1214 m_snapIndicator->lines_Pen, m_snapIndicator->pointSize, m_snapIndicator->pointType);
1215 crosshair->setShapesPen(m_snapIndicator->shape_Pen);
1216 snapperOverlay->add(crosshair);
1217 }
1218 }
1219 m_graphicView->redraw(RS2::RedrawOverlay); // redraw will happen in the mouse movement event
1220}
1221
1222LC_OverlayInfoCursor* RS_Snapper::obtainInfoCursor() const {
1223 const auto overlayContainer = m_viewport->getOverlaysDrawablesContainer(RS2::InfoCursor);
1224 LC_OverlayInfoCursor* result = nullptr;
1225 // fixme - this is not absolutely safe if someone put another cursor to overlay container! Rework later!!
1226 const auto entity = overlayContainer->first(); // note - this is not absolutely safe if someone put another cursor to overlay container!
1227 result = dynamic_cast<LC_OverlayInfoCursor*>(entity);
1228 if (result == nullptr && m_infoCursorOverlayPrefs != nullptr) {
1229 result = new LC_OverlayInfoCursor(m_impData->snapCoord, &m_infoCursorOverlayPrefs->options);
1230 overlayContainer->add(result);
1231 }
1232 return result;
1233}
1234
1235void RS_Snapper::drawInfoCursor() {
1236 const auto overlayContainer = m_viewport->getOverlaysDrawablesContainer(RS2::InfoCursor);
1237 if (m_infoCursorOverlayPrefs != nullptr && m_infoCursorOverlayPrefs->enabled) {
1238 // fixme - this is not absolutely safe if someone put another cursor to overlay container! Rework later!!
1239 const auto entity = overlayContainer->first();
1240 auto* infoCursor = dynamic_cast<LC_OverlayInfoCursor*>(entity);
1241 if (infoCursor == nullptr) {
1242 infoCursor = new LC_OverlayInfoCursor(m_impData->snapCoord, &m_infoCursorOverlayPrefs->options);
1243 overlayContainer->add(infoCursor);
1244 }
1245 else {
1246 infoCursor->setOptions(&m_infoCursorOverlayPrefs->options);
1247 infoCursor->setPos(m_impData->snapCoord);
1248 }
1249 const auto prefs = getInfoCursorOverlayPrefs();
1250 if (prefs->showSnapType) {
1251 if (m_graphicView->isInRelativePointInput()) {
1252 m_infoCursorOverlayData->setZone2("");
1253 }
1254 else {
1255 QString snapName = getCurrentSnapName();
1256 QString restrictionName;
1257 if (m_impData->snapType == RS2::ANGLE || m_impData->snapType == RS2::ANGLE_REL || m_impData->snapType == RS2::ANGLE_ON_ENTITY) {
1258 const double ucsAbsSnapAngle = m_impData->angle;
1259 const double ucsBasisAngle = m_formatter->toUCSBasisAngleFromUCS(ucsAbsSnapAngle);
1260 restrictionName = formatAngleRaw(ucsBasisAngle);
1261 }
1262 else {
1263 restrictionName = getCurrentRestrictionName();
1264 }
1265 if (!restrictionName.isEmpty()) {
1266 snapName = snapName + (prefs->multiLine ? "\n" : " | ") + restrictionName;
1267 }
1268 m_infoCursorOverlayData->setZone2(snapName);
1269 }
1270 }
1271 else {
1272 m_infoCursorOverlayData->setZone2("");
1273 }
1274 infoCursor->setZonesData(m_infoCursorOverlayData.get());
1275 }
1276 m_graphicView->redraw(RS2::RedrawOverlay);
1277}
1278
1279QString RS_Snapper::getRestrictionName(const int restriction) {
1280 switch (restriction) {
1281 case RS2::RestrictVertical:
1282 return tr("Vertical");
1283 case RS2::RestrictHorizontal:
1284 return tr("Horizontal");
1285 case RS2::RestrictOrthogonal:
1286 return tr("Orthogonal");
1287 default:
1288 return "";
1289 }
1290}
1291
1292QString RS_Snapper::getCurrentRestrictionName() {
1293 if (m_impData->snapType == RS2::VISUAL_SNAP) {
1294 if (m_impData->visualSnapType.entity1 == RS2::VSNAP_NONE) {
1295 const auto secondEntityType = m_impData->visualSnapType.entity2;
1296 if (secondEntityType == RS2::VSNAP_LINE_VERTEX_VERTICAL) {
1297 return getSnapName(m_impData->restrictedSnapType);
1298 }
1299 if (secondEntityType == RS2::VSNAP_LINE_VERTEX_HORIZONTAL) {
1300 return getSnapName(m_impData->restrictedSnapType);
1301 }
1302 if (secondEntityType == RS2::VSNAP_NONE) {
1303 return getSnapName(m_impData->restrictedSnapType);
1304 }
1305 if (secondEntityType == RS2::VSNAP_POINT_RELATIVE_VERTICAL_DX) {
1306 return getSnapName(m_impData->restrictedSnapType);
1307 }
1308 if (secondEntityType == RS2::VSNAP_POINT_RELATIVE_HORIZONTAL_DY) {
1309 return getSnapName(m_impData->restrictedSnapType);
1310 }
1311 }
1312 return "";
1313 }
1314 else {
1315 return getRestrictionName(m_impData->restriction);
1316 }
1317}
1318
1319QString RS_Snapper::getCurrentSnapName() const {
1320 const RS2::SnapType snapType = m_impData->snapType;
1321 QString msg = getSnapName(snapType);
1322 if (snapType == RS2::SnapType::VISUAL_SNAP) {
1323 const auto pointType = m_impData->visualSnapType;
1324 if (pointType.entity1 == RS2::VSNAP_NONE) {
1325 // this is point
1326 msg = msg + " (" + getVisualSnapName(pointType.entity2, pointType.rayAngle2) + ")";
1327 }
1328 else {
1329 // this is intersection
1330 msg = msg + " (" + getVisualSnapName(pointType.entity1, pointType.rayAngle1) + " x " + getVisualSnapName(
1331 pointType.entity2, pointType.rayAngle2) + ")";
1332 }
1333 }
1334 return msg;
1335}
1336
1337QString RS_Snapper::getSnapName(RS2::SnapType snapType) const {
1338 switch (snapType) {
1339 case RS2::GRID:
1340 return tr("Grid");
1341 case RS2::ENTITY:
1342 return tr("Entity");
1343 case RS2::ENDPOINT:
1344 return tr("Endpoint");
1345 case RS2::INTERSECTION:
1346 return tr("Intersection");
1347 case RS2::MIDDLE:
1348 return tr("Middle");
1349 case RS2::DISTANCE:
1350 return tr("Distance");
1351 case RS2::CENTER:
1352 return tr("Center");
1353 case RS2::ANGLE:
1354 return tr("Angle");
1355 case RS2::ANGLE_REL:
1356 return tr("Angle Relative");
1357 case RS2::ANGLE_ON_ENTITY:
1358 return tr("Angle (on Entity)");
1359 case RS2::VISUAL_SNAP: {
1360 QString msg = tr("Visual");
1361 return msg;
1362 }
1363 case RS2::FREE:
1364 return tr("Free");
1365 default:
1366 return "";
1367 }
1368}
1369
1370QString RS_Snapper::getVisualSnapName(const RS2::VisualSnapGuideEntityType entityType, double rayAngle) const {
1371 switch (entityType) {
1372 case RS2::VSNAP_NONE: {
1373 return tr("None", "visual snap");
1374 }
1375 case RS2::VSNAP_LINE_VERTEX_HORIZONTAL: {
1376 return tr("Horizontal", "visual snap");
1377 }
1378 case RS2::VSNAP_LINE_VERTEX_ANGLE_STEP: {
1379 QString name = tr("Angle Ray");
1380 const double wcsRayAngle = rayAngle;
1381 if (wcsRayAngle > 0) {
1382 const double ucsAbsRayAngle = toUCSBasisAngle(wcsRayAngle);
1383 const double ucsBasisAngle = m_formatter->toUCSBasisAngleFromUCS(ucsAbsRayAngle);
1384 QString angleName = formatAngleRaw(ucsBasisAngle);
1385 name = name + " " + angleName;
1386 }
1387 return name;
1388 }
1389 case RS2::VSNAP_LINE_ENDPOINT_ANGLE_STEP: {
1390 QString name = tr("Relative Angle Ray");
1391 const double wcsRayAngle = rayAngle;
1392 if (wcsRayAngle > 0) {
1393 const double ucsAbsRayAngle = toUCSBasisAngle(wcsRayAngle);
1394 const double ucsBasisAngle = m_formatter->toUCSBasisAngleFromUCS(ucsAbsRayAngle);
1395 QString angleName = formatAngleRaw(ucsBasisAngle);
1396 name = name + " " + angleName;
1397 }
1398 return name;
1399 }
1400 case RS2::VSNAP_LINE_VERTEX_VERTICAL: {
1401 return tr("Vertical", "visual snap");
1402 }
1403 case RS2::VSNAP_LINE_RAY: {
1404 return tr("Line ray");
1405 }
1406 case RS2::VSNAP_LINE_VERTEX_VERTEX: {
1407 return tr("Point-Point");
1408 }
1409 case RS2::VSNAP_LINE_VERTEX_VERTEX_ORTHO: {
1410 return tr("Point-Point Orthogonal");
1411 }
1412 case RS2::VSNAP_LINE_ENDPOINT_TANGENT: {
1413 return tr("Endpoint Tangential");
1414 }
1415 case RS2::VSNAP_LINE_ENDPOINT_NORMAL: {
1416 return tr("Endpoint Normal");
1417 }
1418 case RS2::VSNAP_LINE_TANGENT1: {
1419 return tr("Tangential One");
1420 }
1421 case RS2::VSNAP_LINE_TANGENT2: {
1422 return tr("Tangential Two");
1423 }
1424 case RS2::VSNAP_POINT_MIDDLE: {
1425 return tr("Middle", "visual snap");
1426 }
1427 case RS2::VSNAP_LINE_VERTEX_ORTHO: {
1428 return tr("Orthogonal", "visual snap");
1429 }
1430 case RS2::VSNAP_POINT_DISTANCE_EXPLICIT: {
1431 return tr("Distance (Explicit)");
1432 }
1433 case RS2::VSNAP_LINE_DISTANCE_TANGENT2: {
1434 return tr("Tangential Distance (Explicit)");
1435 }
1436 case RS2::VSNAP_POINT_DISTANCE_VERTEX: {
1437 return tr("Distance (Point)");
1438 }
1439 case RS2::VSNAP_DOC_ENTITY: {
1440 return tr("Entity", "visual snap");
1441 }
1442 case RS2::VSNAP_POINT_RELATIVE_NORMAL: {
1443 return tr("Relative Normal", "visual snap");
1444 }
1445 case RS2::VSNAP_POINT_RELATIVE_DISTANCE: {
1446 return tr("Relative Distance", "visual snap");
1447 }
1448 case RS2::VSNAP_POINT_RELATIVE_VERTICAL_DX: {
1449 return tr("Relative X", "visual snap");
1450 }
1451 case RS2::VSNAP_POINT_RELATIVE_HORIZONTAL_DY: {
1452 return tr("Relative Y", "visual snap");
1453 }
1454 case RS2::VSNAP_POINT_RELATIVE_ANGLE_RAY: {
1455 return tr("Relative Angle", "visual snap");
1456 }
1457 case RS2::VSNAP_LINE_RESTR_HORIZONTAL: {
1458 return tr("Restriction Horizontal", "visual snap");
1459 }
1460 case RS2::VSNAP_LINE_RESTR_VERTICAL: {
1461 return tr("Restriction Vertical", "visual snap");
1462 }
1463 default:
1464 return "";
1465 }
1466}
1467
1468bool RS_Snapper::isSnapToGrid() const {
1469 bool result = m_snapMode.snapGrid;
1470 if (result) {
1471 if (m_ignoreSnapToGridIfNoGrid) {
1472 result = m_viewport->isGridOn();
1473 }
1474 }
1475 return result;
1476}
1477
1478bool RS_Snapper::isLastSnapFree() const {
1479 return m_impData->snapType == RS2::SnapType::FREE;
1480}
1481
1482RS_Vector RS_Snapper::snapToRelativeAngle(const double baseAngle, const RS_Vector& currentCoord, const RS_Vector& referenceCoord,
1483 const double angularResolution) {
1484 if (m_snapMode.restriction != RS2::RestrictNothing || isSnapToGrid()) {
1485 return currentCoord;
1486 }
1487
1488 const double wcsAngleRaw = referenceCoord.angleTo(currentCoord);
1489 const double ucsAngleRaw = toUCSAngle(wcsAngleRaw);
1490 const double ucsAngleSnapped = ucsAngleRaw - std::remainder(ucsAngleRaw, angularResolution);
1491 const double wcsAngleSnappedAbsolute = toWorldAngle(ucsAngleSnapped);
1492
1493 const double wcsAngleSnapped = wcsAngleSnappedAbsolute + baseAngle; // add base angle, so snap is relative
1494
1495 RS_Vector res = RS_Vector::polar(referenceCoord.distanceTo(currentCoord), wcsAngleSnapped);
1496 res += referenceCoord;
1497
1498 if (m_snapMode.snapOnEntity) {
1499 const RS_Vector t = m_document->getNearestVirtualIntersection(res, wcsAngleSnapped, nullptr);
1500 m_impData->snapSpot = t;
1501 m_impData->snapType = (t == res) ? RS2::SnapType::ANGLE_REL : RS2::SnapType::ANGLE_ON_ENTITY;
1502 m_impData->angle = ucsAngleSnapped;
1503 setSnapPoint(m_impData->snapSpot, true);
1504 return t;
1505 }
1506 m_impData->snapType = RS2::SnapType::ANGLE_REL;
1507 m_impData->angle = ucsAngleSnapped;
1508 setSnapPoint(res, true);
1509 return res;
1510}
1511
1512// fixme - snap to angle - rwork
1513RS_Vector RS_Snapper::snapToAngle(const RS_Vector& currentCoord, const RS_Vector& referenceCoord, const double angularResolution) {
1514 if (m_snapMode.restriction != RS2::RestrictNothing || isSnapToGrid()) {
1515 return currentCoord;
1516 }
1517 return doSnapToAngle(currentCoord, referenceCoord, angularResolution);
1518}
1519
1520RS_Vector RS_Snapper::obtainEndPointForAngleSnap(const RS_Vector& currentCoord, const RS_Vector& referenceCoord,
1521 const double angularResolution, double& wcsAngleSnapped, double& ucsAngleSnapped) const {
1522 const double wcsAngleRaw = referenceCoord.angleTo(currentCoord);
1523 const double ucsAngleAbs = toUCSAngle(wcsAngleRaw);
1524
1525 // double ucsAngle = ucsAngleAbs - this->m_anglesBase;
1526 // fixme - fmt - review
1527 const double ucsAngle = m_formatter->toUCSBasisAngleFromUCS(ucsAngleAbs);
1528
1529 ucsAngleSnapped = ucsAngleAbs - remainder(ucsAngle, angularResolution);
1530 wcsAngleSnapped = toWorldAngle(ucsAngleSnapped);
1531
1532 RS_Vector res = RS_Vector::polar(referenceCoord.distanceTo(currentCoord), wcsAngleSnapped);
1533 res += referenceCoord;
1534 return res;
1535}
1536
1537RS_Vector RS_Snapper::doSnapToAngle(const RS_Vector& currentCoord, const RS_Vector& referenceCoord, const double angularResolution) {
1538 double wcsAngleSnapped;
1539 double ucsAngleSnapped;
1540 const RS_Vector res = obtainEndPointForAngleSnap(currentCoord, referenceCoord, angularResolution, wcsAngleSnapped, ucsAngleSnapped);
1541
1542 if (m_snapMode.snapOnEntity) {
1543 const RS_Vector t = m_document->getNearestVirtualIntersection(res, wcsAngleSnapped, nullptr);
1544 m_impData->snapSpot = t;
1545 m_impData->snapType = (t == res) ? RS2::ANGLE : RS2::ANGLE_ON_ENTITY;
1546 m_impData->angle = ucsAngleSnapped;
1547 setSnapPoint(m_impData->snapSpot, true);
1548 return t;
1549 }
1550 m_impData->snapType = RS2::ANGLE;
1551 m_impData->angle = ucsAngleSnapped;
1552 setSnapPoint(res, true);
1553 return res;
1554}
1555
1556RS_Vector RS_Snapper::toGraph(const QMouseEvent* e) const {
1557 const QPointF& pointF = e->position();
1558 const RS_Vector result = m_viewport->toWorldFromUi(pointF.x(), pointF.y());
1559 return result;
1560}
1561
1562double RS_Snapper::toGuiDX(const double wcsDX) const {
1563 return m_viewport->toGuiDX(wcsDX);
1564}
1565
1566double RS_Snapper::toGraphDX(const int wcsDX) const {
1567 return m_viewport->toUcsDX(wcsDX);
1568}
1569
1570const RS_Vector& RS_Snapper::getRelativeZero() const {
1571 return m_viewport->getRelativeZero();
1572}
1573
1574void RS_Snapper::refreshBySettings() {
1575 initFromSettings();
1576 updateSnapAngleStep();
1577 m_visualSnapManager->invalidateSolution();
1578}
1579
1580void RS_Snapper::lockVisualSnap(bool performLock) const {
1581 m_visualSnapManager->lockData(performLock);
1582}
1583
1584void RS_Snapper::updateCoordinateWidgetFormat() const {
1585 m_actionContext->updateCoordinateWidget(toWorld(RS_Vector(0.0, 0.0)), toWorld(RS_Vector(0.0, 0.0)), true);
1586}
1587
1588void RS_Snapper::updateCoordinateWidget(const RS_Vector& abs, const RS_Vector& rel) const {
1589 if (m_infoCursorOverlayPrefs->enabled) {
1590 preparePositionsInfoCursorOverlay(abs, rel);
1591 }
1592 m_actionContext->updateCoordinateWidget(abs, rel, false);
1593}
1594
1595void RS_Snapper::updateCoordinateWidgetByRelZero(const RS_Vector& abs) const {
1596 const RS_Vector& relative = abs - m_viewport->getRelativeZero();
1597 if (m_infoCursorOverlayPrefs->enabled) {
1598 preparePositionsInfoCursorOverlay(abs, relative);
1599 }
1600 m_actionContext->updateCoordinateWidget(abs, relative, false);
1601}
1602
1603LC_InfoCursorOverlayPrefs* RS_Snapper::getInfoCursorOverlayPrefs() const {
1604 return m_infoCursorOverlayPrefs;
1605}
1606
1607bool RS_Snapper::isInfoCursorForModificationEnabled() const {
1608 return m_infoCursorOverlayPrefs->enabled && m_infoCursorOverlayPrefs->showEntityInfoOnModification;
1609}
1610
1611void RS_Snapper::preparePositionsInfoCursorOverlay(const RS_Vector& abs, const RS_Vector& relative) const {
1612 const LC_InfoCursorOverlayPrefs* prefs = getInfoCursorOverlayPrefs();
1613
1614 QString coordAbs = "";
1615 QString coordPolar = "";
1616 if (prefs != nullptr && (prefs->showAbsolutePosition || prefs->showRelativePositionDistAngle || prefs->showRelativePositionDeltas)) {
1617 const RS_Graphic* graphic = m_graphicView->getGraphic();
1618 if (graphic != nullptr) {
1619 const bool showLabels = prefs->showLabels;
1620 if (prefs->showAbsolutePosition) {
1621 const RS_Vector ucs = toUCS(abs);
1622 const QString absX = (showLabels ? "X: " : "") + formatLinear(ucs.x);
1623 const QString absY = (showLabels ? "Y: " : "") + formatLinear(ucs.y);
1624 coordAbs = absX + (prefs->multiLine ? "\n" : showLabels ? " " : " , ") + absY;
1625 }
1626
1627 const bool hasUCS = m_viewport->hasUCS();
1628 if (prefs->showAbsolutePositionWCS && hasUCS) {
1629 const QString absX = (showLabels ? "WX: " : "W") + formatLinear(abs.x);
1630 const QString absY = (showLabels ? "WY: " : "") + formatLinear(abs.y);
1631
1632 const QString coordAbsWCS = absX + (prefs->multiLine ? "\n" : showLabels ? " " : " , ") + absY;
1633
1634 if (coordAbs.isEmpty()) {
1635 coordAbs = coordAbsWCS;
1636 }
1637 else {
1638 coordAbs = coordAbs + "\n" + coordAbsWCS;
1639 }
1640 }
1641
1642 RS_Vector relativeToUse;
1643 if (hasUCS) {
1644 relativeToUse = m_viewport->toUCSDelta(relative);
1645 }
1646 else {
1647 relativeToUse = relative;
1648 }
1649
1650 if (prefs->showRelativePositionDistAngle) {
1651 const QString lenStr = (showLabels ? tr("Dist: ") : "@ ") + formatLinear(relativeToUse.magnitude());
1652 // as we're in ucs coordinates there, use raw formatAngle instead of method
1653
1654 const double relativeAngle = relativeToUse.angle();
1655 const double ucsBasisAngle = ucsAbsToBasisAngle(relativeAngle);
1656 const QString angleStr = (showLabels ? tr("Angle: ") : "< ") + formatAngleRaw(ucsBasisAngle);
1657
1658 coordPolar = lenStr + (prefs->multiLine ? "\n" : showLabels ? " " : " ") + angleStr;
1659 }
1660 if (prefs->showRelativePositionDeltas) {
1661 const QString lenStr = (showLabels ? tr("dX: ") : "@ ") + formatLinear(relativeToUse.x);
1662 const QString angleStr = (showLabels ? tr("dY: ") : "") + formatLinear(relativeToUse.y);
1663
1664 const QString coordDeltas = lenStr + (prefs->multiLine ? "\n" : showLabels ? " " : " , ") + angleStr;
1665 if (coordPolar.isEmpty()) {
1666 coordPolar = coordDeltas;
1667 }
1668 else {
1669 coordPolar = coordPolar + "\n" + coordDeltas;
1670 }
1671 }
1672 }
1673 }
1674
1675 m_infoCursorOverlayData->setZone1(coordAbs);
1676 m_infoCursorOverlayData->setZone3(coordPolar);
1677}
1678
1679void RS_Snapper::invalidateSnapSpot() const {
1680 m_impData->snapSpot.valid = false;
1681}
1682
1683QString RS_Snapper::formatLinear(const double value) const {
1684 return m_formatter->formatLinear(value);
1685}
1686
1687QString RS_Snapper::formatWCSAngle(const double wcsAngle) const {
1688 return m_formatter->formatWCSAngle(wcsAngle);
1689}
1690
1691QString RS_Snapper::formatAngleRaw(const double angle) const {
1692 return m_formatter->formatRawAngle(angle);
1693}
1694
1695// fixme - ucs- move to coordinate mapper?
1696QString RS_Snapper::formatVector(const RS_Vector& value) const {
1697 double x, y;
1698 if (m_viewport->hasUCS()) {
1699 const RS_Vector ucsValue = m_viewport->toUCS(value);
1700 x = ucsValue.x;
1701 y = ucsValue.y;
1702 }
1703 else {
1704 x = value.x;
1705 y = value.y;
1706 }
1707 return formatLinear(x).append(" , ").append(formatLinear(y));
1708}
1709
1710QString RS_Snapper::formatVectorWCS(const RS_Vector& value) const {
1711 return QString("W ").append(formatLinear(value.x)).append(" , ").append(formatLinear(value.y));
1712}
1713
1714QString RS_Snapper::formatRelative(const RS_Vector& value) const {
1715 double x, y;
1716 m_viewport->toUCSDelta(value, x, y);
1717 return QString("@ ").append(formatLinear(x)).append(" , ").append(formatLinear(y));
1718}
1719
1720QString RS_Snapper::formatPolar(const RS_Vector& value) const {
1721 return formatLinear(value.magnitude()).append(" < ").append(formatWCSAngle(value.angle()));
1722}
1723
1724QString RS_Snapper::formatRelativePolar(const RS_Vector& wcsAngle) const {
1725 return QString("@ ").append(formatLinear(wcsAngle.magnitude())).append(" < ").append(formatWCSAngle(wcsAngle.angle()));
1726}
1727
1728void RS_Snapper::forceUpdateInfoCursor(const RS_Vector& pos) const {
1729 LC_OverlayInfoCursor* infoCursor = obtainInfoCursor();
1730 if (infoCursor != nullptr) {
1731 infoCursor->setPos(pos);
1732 infoCursor->setZonesData(m_infoCursorOverlayData.get());
1733 }
1734}
1735
1736double RS_Snapper::toWorldAngle(const double ucsAbsAngle) const {
1737 return m_viewport->toWorldAngle(ucsAbsAngle);
1738}
1739
1740double RS_Snapper::toWorldAngleDegrees(const double ucsAbsAngleDegrees) const {
1741 return m_viewport->toWorldAngleDegrees(ucsAbsAngleDegrees);
1742}
1743
1744double RS_Snapper::toUCSAngle(const double wcsAngle) const {
1745 return m_viewport->toUCSAngle(wcsAngle);
1746}
1747
1748double RS_Snapper::ucsAbsToBasisAngle(const double ucsAbsAngle) const {
1749 return m_viewport->toBasisUCSAngle(ucsAbsAngle);
1750}
1751
1752double RS_Snapper::ucsBasisToAbsAngle(const double ucsRelAngle) const {
1753 return m_viewport->toAbsUCSAngle(ucsRelAngle);
1754}
1755
1756double RS_Snapper::adjustRelativeAngleSignByBasis(const double relativeAngle) const {
1757 return m_formatter->adjustRelativeAngleSignByBasis(relativeAngle);
1758}
1759
1760double RS_Snapper::toUCSBasisAngleDegrees(const double wcsAngle) const {
1761 return m_formatter->toUCSBasisAngleDegrees(wcsAngle);
1762}
1763
1764double RS_Snapper::toWorldAngleFromUCSBasisDegrees(const double ucsBasisAngleDegrees) const {
1765 return m_formatter->toWorldAngleFromUCSBasisDegrees(ucsBasisAngleDegrees);
1766}
1767
1768double RS_Snapper::toWorldAngleFromUCSBasis(const double ucsBasisAngle) const {
1769 return m_formatter->toWorldAngleFromUCSBasis(ucsBasisAngle);
1770}
1771
1772double RS_Snapper::toUCSBasisAngle(const double wcsAngle) const {
1773 return m_formatter->toUCSBasisAngle(wcsAngle);
1774}
1775
1776RS_Vector RS_Snapper::toWorld(const RS_Vector& ucsPos) const {
1777 return m_viewport->toWorld(ucsPos);
1778}
1779
1780RS_Vector RS_Snapper::toUCS(const RS_Vector& worldPos) const {
1781 return m_viewport->toUCS(worldPos);
1782}
1783
1784RS_Vector RS_Snapper::toWorldDelta(const RS_Vector& ucsDelta) const {
1785 return m_viewport->toWorldDelta(ucsDelta);
1786}
1787
1788RS_Vector RS_Snapper::toUCSDelta(const RS_Vector& worldDelta) const {
1789 return m_viewport->toUCSDelta(worldDelta);
1790}
1791
1792// todo - sand - ucs - move to coordinates mapper?
1793void RS_Snapper::calcRectCorners(const RS_Vector& worldCorner1, const RS_Vector& worldCorner3, RS_Vector& worldCorner2,
1794 RS_Vector& worldCorner4) const {
1795 const RS_Vector ucsCorner1 = toUCS(worldCorner1);
1796 const RS_Vector ucsCorner3 = toUCS(worldCorner3);
1797 const auto ucsCorner2 = RS_Vector(ucsCorner1.x, ucsCorner3.y);
1798 const auto ucsCorner4 = RS_Vector(ucsCorner3.x, ucsCorner1.y);
1799 worldCorner2 = toWorld(ucsCorner2);
1800 worldCorner4 = toWorld(ucsCorner4);
1801}
1802
1803bool RS_Snapper::hasNonDefaultAnglesBasis() const {
1804 return m_formatter->hasNonDefaultAnglesBasis();
1805}
1806
1807void RS_Snapper::clearVisualSnap() const {
1808 m_visualSnapManager->clear();
1809}
1810
1811void RS_Snapper::stopVisualSnap() const {
1812 clearVisualSnap();
1813}
1814
1815void RS_Snapper::removePreviousVisualSnapAddition() {
1816 m_visualSnapManager->removeLastAddition();
1817 onVisualSnapSolutionRefresh();
1818}
1819
1820bool RS_Snapper::hasVisualSnap(bool ignoreLastSnapData) const {
1821 return m_visualSnapManager->hasVisualSnap(ignoreLastSnapData);
1822}
1823
1824// get catching entity distance in graph distance
1825double RS_Snapper::getCatchDistance(const double catchDistance, const int catchEntityGuiRange) const {
1826 return (m_graphicView != nullptr) ? std::min(catchDistance, toGraphDX(catchEntityGuiRange)) : catchDistance;
1827}
1828
1829// fixme - sand - review
1830void RS_Snapper::enableCoordinateInput() const {
1831 m_graphicView->enableCoordinateInput();
1832}
1833
1834void RS_Snapper::disableCoordinateInput() const {
1835 m_graphicView->disableCoordinateInput();
1836}
1837
1838void RS_Snapper::redraw(const RS2::RedrawMethod method) const {
1839 // fixme - sand - ucs - decide how it's better to invoke redraw
1840 // viewport->requestRedraw(method);
1841 m_graphicView->redraw(method);
1842}
1843
1844void RS_Snapper::redrawImmediately(const RS2::RedrawMethod method) const {
1845 m_graphicView->redraw(method, true);
1846}
1847
1848void RS_Snapper::redrawDrawing() const {
1849 redraw(RS2::RedrawMethod::RedrawDrawing);
1850}
1851
1852void RS_Snapper::redrawAll() const {
1853 redraw(RS2::RedrawMethod::RedrawAll);
1854}
1855
1856// fixme **********************************************************************
1857// fixme - snap angle and grid enabled!!!!
1858// fixme **********************************************************************
1859
1860// Fixme
1861// options for visual snap
1862// 1) which rays should be created
1863// 2) creating not only normal/tangental, but also interval rays (as in angle snap?)