1
/***************************************************************************
2
* Copyright (c) 2023 Pierre-Louis Boyer <pierrelouis.boyer@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library 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 Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
25
#include <QApplication>
26
#endif // #ifndef _PreComp_
28
#include <Mod/Sketcher/App/SketchObject.h>
30
#include "SnapManager.h"
31
#include "ViewProviderSketch.h"
34
using namespace SketcherGui;
35
using namespace Sketcher;
37
/************************************ Attorney *******************************************/
39
inline int ViewProviderSketchSnapAttorney::getPreselectPoint(const ViewProviderSketch& vp)
41
return vp.getPreselectPoint();
44
inline int ViewProviderSketchSnapAttorney::getPreselectCross(const ViewProviderSketch& vp)
46
return vp.getPreselectCross();
49
inline int ViewProviderSketchSnapAttorney::getPreselectCurve(const ViewProviderSketch& vp)
51
return vp.getPreselectCurve();
54
/**************************** ParameterObserver nested class *****************************/
55
SnapManager::ParameterObserver::ParameterObserver(SnapManager& client)
59
subscribeToParameters();
62
SnapManager::ParameterObserver::~ParameterObserver()
64
unsubscribeToParameters();
67
void SnapManager::ParameterObserver::initParameters()
69
// static map to avoid substantial if/else branching
71
// key->first => String of parameter,
72
// key->second => Update function to be called for the parameter,
73
str2updatefunction = {
75
[this](const std::string& param) {
76
updateSnapParameter(param);
79
[this](const std::string& param) {
80
updateSnapToObjectParameter(param);
83
[this](const std::string& param) {
84
updateSnapToGridParameter(param);
87
[this](const std::string& param) {
88
updateSnapAngleParameter(param);
92
for (auto& val : str2updatefunction) {
93
auto string = val.first;
94
auto function = val.second;
100
void SnapManager::ParameterObserver::updateSnapParameter(const std::string& parametername)
102
ParameterGrp::handle hGrp = getParameterGrpHandle();
104
client.snapRequested = hGrp->GetBool(parametername.c_str(), true);
107
void SnapManager::ParameterObserver::updateSnapToObjectParameter(const std::string& parametername)
109
ParameterGrp::handle hGrp = getParameterGrpHandle();
111
client.snapToObjectsRequested = hGrp->GetBool(parametername.c_str(), true);
114
void SnapManager::ParameterObserver::updateSnapToGridParameter(const std::string& parametername)
116
ParameterGrp::handle hGrp = getParameterGrpHandle();
118
client.snapToGridRequested = hGrp->GetBool(parametername.c_str(), false);
121
void SnapManager::ParameterObserver::updateSnapAngleParameter(const std::string& parametername)
123
ParameterGrp::handle hGrp = getParameterGrpHandle();
125
client.snapAngle = fmod(hGrp->GetFloat(parametername.c_str(), 5.) * M_PI / 180, 2 * M_PI);
128
void SnapManager::ParameterObserver::subscribeToParameters()
131
ParameterGrp::handle hGrp = getParameterGrpHandle();
134
catch (const Base::ValueError& e) { // ensure that if parameter strings are not well-formed,
135
// the exception is not propagated
136
Base::Console().DeveloperError("SnapManager", "Malformed parameter string: %s\n", e.what());
140
void SnapManager::ParameterObserver::unsubscribeToParameters()
143
ParameterGrp::handle hGrp = getParameterGrpHandle();
146
catch (const Base::ValueError&
147
e) { // ensure that if parameter strings are not well-formed, the program is not
148
// terminated when calling the noexcept destructor.
149
Base::Console().DeveloperError("SnapManager", "Malformed parameter string: %s\n", e.what());
153
void SnapManager::ParameterObserver::OnChange(Base::Subject<const char*>& rCaller,
158
auto key = str2updatefunction.find(sReason);
159
if (key != str2updatefunction.end()) {
160
auto string = key->first;
161
auto function = key->second;
167
ParameterGrp::handle SnapManager::ParameterObserver::getParameterGrpHandle()
169
return App::GetApplication().GetParameterGroupByPath(
170
"User parameter:BaseApp/Preferences/Mod/Sketcher/Snap");
173
//**************************** SnapManager class ******************************
175
SnapManager::SnapManager(ViewProviderSketch& vp)
177
, angleSnapRequested(false)
178
, referencePoint(Base::Vector2d(0., 0.))
179
, lastMouseAngle(0.0)
181
// Create parameter observer and initialise watched parameters
182
pObserver = std::make_unique<SnapManager::ParameterObserver>(*this);
185
SnapManager::~SnapManager()
188
bool SnapManager::snap(double& x, double& y)
190
if (!snapRequested) {
194
// In order of priority :
196
// 1 - Snap at an angle
197
if (angleSnapRequested && QApplication::keyboardModifiers() == Qt::ControlModifier) {
198
return snapAtAngle(x, y);
201
lastMouseAngle = 0.0;
204
// 2 - Snap to objects
205
if (snapToObjectsRequested && snapToObject(x, y)) {
210
if (snapToGridRequested /*&& viewProvider.ShowGrid.getValue() */) { // Snap to grid is enabled
211
// even if the grid is not
213
return snapToGrid(x, y);
219
bool SnapManager::snapAtAngle(double& x, double& y)
221
Base::Vector2d pointToOverride(x, y);
222
double length = (pointToOverride - referencePoint).Length();
224
double angle1 = (pointToOverride - referencePoint).Angle();
225
double angle2 = angle1 + (angle1 < 0. ? 2 : -2) * M_PI;
226
lastMouseAngle = abs(angle1 - lastMouseAngle) < abs(angle2 - lastMouseAngle) ? angle1 : angle2;
228
double angle = round(lastMouseAngle / snapAngle) * snapAngle;
229
pointToOverride = referencePoint + length * Base::Vector2d(cos(angle), sin(angle));
230
x = pointToOverride.x;
231
y = pointToOverride.y;
236
bool SnapManager::snapToObject(double& x, double& y)
238
Sketcher::SketchObject* Obj = viewProvider.getSketchObject();
239
int geoId = GeoEnum::GeoUndef;
240
Sketcher::PointPos posId = Sketcher::PointPos::none;
242
int VtId = ViewProviderSketchSnapAttorney::getPreselectPoint(viewProvider);
243
int CrsId = ViewProviderSketchSnapAttorney::getPreselectCross(viewProvider);
244
int CrvId = ViewProviderSketchSnapAttorney::getPreselectCurve(viewProvider);
246
if (CrsId == 0 || VtId >= 0) {
248
geoId = Sketcher::GeoEnum::RtPnt;
249
posId = Sketcher::PointPos::start;
251
else if (VtId >= 0) {
252
Obj->getGeoVertexIndex(VtId, geoId, posId);
255
x = Obj->getPoint(geoId, posId).x;
256
y = Obj->getPoint(geoId, posId).y;
259
else if (CrsId == 1) { // H_Axis
263
else if (CrsId == 2) { // V_Axis
267
else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) { // Curves
269
const Part::Geometry* geo = Obj->getGeometry(CrvId);
271
Base::Vector3d pointToOverride(x, y, 0.);
273
double pointParam = 0.0;
274
auto curve = dynamic_cast<const Part::GeomCurve*>(geo);
277
curve->closestParameter(pointToOverride, pointParam);
278
pointToOverride = curve->pointAtParameter(pointParam);
280
catch (Base::CADKernelError& e) {
285
// If it is a line, then we check if we need to snap to the middle.
286
if (geo->is<Part::GeomLineSegment>()) {
287
const Part::GeomLineSegment* line = static_cast<const Part::GeomLineSegment*>(geo);
288
snapToLineMiddle(pointToOverride, line);
291
// If it is an arc, then we check if we need to snap to the middle (not the center).
292
if (geo->is<Part::GeomArcOfCircle>()) {
293
const Part::GeomArcOfCircle* arc = static_cast<const Part::GeomArcOfCircle*>(geo);
294
snapToArcMiddle(pointToOverride, arc);
297
x = pointToOverride.x;
298
y = pointToOverride.y;
307
bool SnapManager::snapToGrid(double& x, double& y)
309
// Snap Tolerance in pixels
310
const double snapTol = viewProvider.getGridSize() / 5;
312
double tmpX = x, tmpY = y;
314
viewProvider.getClosestGridPoint(tmpX, tmpY);
316
bool snapped = false;
318
// Check if x within snap tolerance
319
if (x < tmpX + snapTol && x > tmpX - snapTol) {
320
x = tmpX; // Snap X Mouse Position
324
// Check if y within snap tolerance
325
if (y < tmpY + snapTol && y > tmpY - snapTol) {
326
y = tmpY; // Snap Y Mouse Position
333
bool SnapManager::snapToLineMiddle(Base::Vector3d& pointToOverride,
334
const Part::GeomLineSegment* line)
336
Base::Vector3d startPoint = line->getStartPoint();
337
Base::Vector3d endPoint = line->getEndPoint();
338
Base::Vector3d midPoint = (startPoint + endPoint) / 2;
340
// Check if we are at middle of the line and if so snap to it.
341
if ((pointToOverride - midPoint).Length() < (endPoint - startPoint).Length() * 0.05) {
342
pointToOverride = midPoint;
349
bool SnapManager::snapToArcMiddle(Base::Vector3d& pointToOverride, const Part::GeomArcOfCircle* arc)
351
Base::Vector3d centerPoint = arc->getCenter();
352
Base::Vector3d startVec = (arc->getStartPoint() - centerPoint);
353
Base::Vector3d middleVec = startVec + (arc->getEndPoint() - centerPoint);
355
/* Handle the case of arc angle = 180 */
356
if (middleVec.Length() < Precision::Confusion()) {
357
middleVec.x = startVec.y;
358
middleVec.y = -startVec.x;
361
middleVec = middleVec / middleVec.Length() * arc->getRadius();
364
Base::Vector2d mVec = Base::Vector2d(middleVec.x, middleVec.y);
365
Base::Vector3d pointVec = pointToOverride - centerPoint;
366
Base::Vector2d pVec = Base::Vector2d(pointVec.x, pointVec.y);
369
arc->getRange(u, v, true);
373
double angle = v - u;
374
int revert = angle < M_PI ? 1 : -1;
376
/*To know if we are close to the middle of the arc, we are going to compare the angle of the
377
* (mouse cursor - center) to the angle of the middle of the arc. If it's less than 10% of the
378
* arc angle, then we snap.
380
if (fabs(pVec.Angle() - (revert * mVec).Angle()) < 0.10 * angle) {
381
pointToOverride = centerPoint + middleVec * revert;
388
void SnapManager::setAngleSnapping(bool enable, Base::Vector2d referencepoint)
390
angleSnapRequested = enable;
391
referencePoint = referencepoint;