FreeCAD

Форк
0
/
SnapManager.cpp 
392 строки · 13.1 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2023 Pierre-Louis Boyer <pierrelouis.boyer@gmail.com>   *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
#include <QApplication>
26
#endif  // #ifndef _PreComp_
27

28
#include <Mod/Sketcher/App/SketchObject.h>
29

30
#include "SnapManager.h"
31
#include "ViewProviderSketch.h"
32

33

34
using namespace SketcherGui;
35
using namespace Sketcher;
36

37
/************************************ Attorney *******************************************/
38

39
inline int ViewProviderSketchSnapAttorney::getPreselectPoint(const ViewProviderSketch& vp)
40
{
41
    return vp.getPreselectPoint();
42
}
43

44
inline int ViewProviderSketchSnapAttorney::getPreselectCross(const ViewProviderSketch& vp)
45
{
46
    return vp.getPreselectCross();
47
}
48

49
inline int ViewProviderSketchSnapAttorney::getPreselectCurve(const ViewProviderSketch& vp)
50
{
51
    return vp.getPreselectCurve();
52
}
53

54
/**************************** ParameterObserver nested class *****************************/
55
SnapManager::ParameterObserver::ParameterObserver(SnapManager& client)
56
    : client(client)
57
{
58
    initParameters();
59
    subscribeToParameters();
60
}
61

62
SnapManager::ParameterObserver::~ParameterObserver()
63
{
64
    unsubscribeToParameters();
65
}
66

67
void SnapManager::ParameterObserver::initParameters()
68
{
69
    // static map to avoid substantial if/else branching
70
    //
71
    // key->first               => String of parameter,
72
    // key->second              => Update function to be called for the parameter,
73
    str2updatefunction = {
74
        {"Snap",
75
         [this](const std::string& param) {
76
             updateSnapParameter(param);
77
         }},
78
        {"SnapToObjects",
79
         [this](const std::string& param) {
80
             updateSnapToObjectParameter(param);
81
         }},
82
        {"SnapToGrid",
83
         [this](const std::string& param) {
84
             updateSnapToGridParameter(param);
85
         }},
86
        {"SnapAngle",
87
         [this](const std::string& param) {
88
             updateSnapAngleParameter(param);
89
         }},
90
    };
91

92
    for (auto& val : str2updatefunction) {
93
        auto string = val.first;
94
        auto function = val.second;
95

96
        function(string);
97
    }
98
}
99

100
void SnapManager::ParameterObserver::updateSnapParameter(const std::string& parametername)
101
{
102
    ParameterGrp::handle hGrp = getParameterGrpHandle();
103

104
    client.snapRequested = hGrp->GetBool(parametername.c_str(), true);
105
}
106

107
void SnapManager::ParameterObserver::updateSnapToObjectParameter(const std::string& parametername)
108
{
109
    ParameterGrp::handle hGrp = getParameterGrpHandle();
110

111
    client.snapToObjectsRequested = hGrp->GetBool(parametername.c_str(), true);
112
}
113

114
void SnapManager::ParameterObserver::updateSnapToGridParameter(const std::string& parametername)
115
{
116
    ParameterGrp::handle hGrp = getParameterGrpHandle();
117

118
    client.snapToGridRequested = hGrp->GetBool(parametername.c_str(), false);
119
}
120

121
void SnapManager::ParameterObserver::updateSnapAngleParameter(const std::string& parametername)
122
{
123
    ParameterGrp::handle hGrp = getParameterGrpHandle();
124

125
    client.snapAngle = fmod(hGrp->GetFloat(parametername.c_str(), 5.) * M_PI / 180, 2 * M_PI);
126
}
127

128
void SnapManager::ParameterObserver::subscribeToParameters()
129
{
130
    try {
131
        ParameterGrp::handle hGrp = getParameterGrpHandle();
132
        hGrp->Attach(this);
133
    }
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());
137
    }
138
}
139

140
void SnapManager::ParameterObserver::unsubscribeToParameters()
141
{
142
    try {
143
        ParameterGrp::handle hGrp = getParameterGrpHandle();
144
        hGrp->Detach(this);
145
    }
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());
150
    }
151
}
152

153
void SnapManager::ParameterObserver::OnChange(Base::Subject<const char*>& rCaller,
154
                                              const char* sReason)
155
{
156
    (void)rCaller;
157

158
    auto key = str2updatefunction.find(sReason);
159
    if (key != str2updatefunction.end()) {
160
        auto string = key->first;
161
        auto function = key->second;
162

163
        function(string);
164
    }
165
}
166

167
ParameterGrp::handle SnapManager::ParameterObserver::getParameterGrpHandle()
168
{
169
    return App::GetApplication().GetParameterGroupByPath(
170
        "User parameter:BaseApp/Preferences/Mod/Sketcher/Snap");
171
}
172

173
//**************************** SnapManager class ******************************
174

175
SnapManager::SnapManager(ViewProviderSketch& vp)
176
    : viewProvider(vp)
177
    , angleSnapRequested(false)
178
    , referencePoint(Base::Vector2d(0., 0.))
179
    , lastMouseAngle(0.0)
180
{
181
    // Create parameter observer and initialise watched parameters
182
    pObserver = std::make_unique<SnapManager::ParameterObserver>(*this);
183
}
184

185
SnapManager::~SnapManager()
186
{}
187

188
bool SnapManager::snap(double& x, double& y)
189
{
190
    if (!snapRequested) {
191
        return false;
192
    }
193

194
    // In order of priority :
195

196
    // 1 - Snap at an angle
197
    if (angleSnapRequested && QApplication::keyboardModifiers() == Qt::ControlModifier) {
198
        return snapAtAngle(x, y);
199
    }
200
    else {
201
        lastMouseAngle = 0.0;
202
    }
203

204
    // 2 - Snap to objects
205
    if (snapToObjectsRequested && snapToObject(x, y)) {
206
        return true;
207
    }
208

209
    // 3 - Snap to grid
210
    if (snapToGridRequested /*&& viewProvider.ShowGrid.getValue() */) {  // Snap to grid is enabled
211
                                                                         // even if the grid is not
212
                                                                         // visible.
213
        return snapToGrid(x, y);
214
    }
215

216
    return false;
217
}
218

219
bool SnapManager::snapAtAngle(double& x, double& y)
220
{
221
    Base::Vector2d pointToOverride(x, y);
222
    double length = (pointToOverride - referencePoint).Length();
223

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;
227

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;
232

233
    return true;
234
}
235

236
bool SnapManager::snapToObject(double& x, double& y)
237
{
238
    Sketcher::SketchObject* Obj = viewProvider.getSketchObject();
239
    int geoId = GeoEnum::GeoUndef;
240
    Sketcher::PointPos posId = Sketcher::PointPos::none;
241

242
    int VtId = ViewProviderSketchSnapAttorney::getPreselectPoint(viewProvider);
243
    int CrsId = ViewProviderSketchSnapAttorney::getPreselectCross(viewProvider);
244
    int CrvId = ViewProviderSketchSnapAttorney::getPreselectCurve(viewProvider);
245

246
    if (CrsId == 0 || VtId >= 0) {
247
        if (CrsId == 0) {
248
            geoId = Sketcher::GeoEnum::RtPnt;
249
            posId = Sketcher::PointPos::start;
250
        }
251
        else if (VtId >= 0) {
252
            Obj->getGeoVertexIndex(VtId, geoId, posId);
253
        }
254

255
        x = Obj->getPoint(geoId, posId).x;
256
        y = Obj->getPoint(geoId, posId).y;
257
        return true;
258
    }
259
    else if (CrsId == 1) {  // H_Axis
260
        y = 0;
261
        return true;
262
    }
263
    else if (CrsId == 2) {  // V_Axis
264
        x = 0;
265
        return true;
266
    }
267
    else if (CrvId >= 0 || CrvId <= Sketcher::GeoEnum::RefExt) {  // Curves
268

269
        const Part::Geometry* geo = Obj->getGeometry(CrvId);
270

271
        Base::Vector3d pointToOverride(x, y, 0.);
272

273
        double pointParam = 0.0;
274
        auto curve = dynamic_cast<const Part::GeomCurve*>(geo);
275
        if (curve) {
276
            try {
277
                curve->closestParameter(pointToOverride, pointParam);
278
                pointToOverride = curve->pointAtParameter(pointParam);
279
            }
280
            catch (Base::CADKernelError& e) {
281
                e.ReportException();
282
                return false;
283
            }
284

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);
289
            }
290

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);
295
            }
296

297
            x = pointToOverride.x;
298
            y = pointToOverride.y;
299

300
            return true;
301
        }
302
    }
303

304
    return false;
305
}
306

307
bool SnapManager::snapToGrid(double& x, double& y)
308
{
309
    // Snap Tolerance in pixels
310
    const double snapTol = viewProvider.getGridSize() / 5;
311

312
    double tmpX = x, tmpY = y;
313

314
    viewProvider.getClosestGridPoint(tmpX, tmpY);
315

316
    bool snapped = false;
317

318
    // Check if x within snap tolerance
319
    if (x < tmpX + snapTol && x > tmpX - snapTol) {
320
        x = tmpX;  // Snap X Mouse Position
321
        snapped = true;
322
    }
323

324
    // Check if y within snap tolerance
325
    if (y < tmpY + snapTol && y > tmpY - snapTol) {
326
        y = tmpY;  // Snap Y Mouse Position
327
        snapped = true;
328
    }
329

330
    return snapped;
331
}
332

333
bool SnapManager::snapToLineMiddle(Base::Vector3d& pointToOverride,
334
                                   const Part::GeomLineSegment* line)
335
{
336
    Base::Vector3d startPoint = line->getStartPoint();
337
    Base::Vector3d endPoint = line->getEndPoint();
338
    Base::Vector3d midPoint = (startPoint + endPoint) / 2;
339

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;
343
        return true;
344
    }
345

346
    return false;
347
}
348

349
bool SnapManager::snapToArcMiddle(Base::Vector3d& pointToOverride, const Part::GeomArcOfCircle* arc)
350
{
351
    Base::Vector3d centerPoint = arc->getCenter();
352
    Base::Vector3d startVec = (arc->getStartPoint() - centerPoint);
353
    Base::Vector3d middleVec = startVec + (arc->getEndPoint() - centerPoint);
354

355
    /* Handle the case of arc angle = 180 */
356
    if (middleVec.Length() < Precision::Confusion()) {
357
        middleVec.x = startVec.y;
358
        middleVec.y = -startVec.x;
359
    }
360
    else {
361
        middleVec = middleVec / middleVec.Length() * arc->getRadius();
362
    }
363

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);
367

368
    double u, v;
369
    arc->getRange(u, v, true);
370
    if (v < u) {
371
        v += 2 * M_PI;
372
    }
373
    double angle = v - u;
374
    int revert = angle < M_PI ? 1 : -1;
375

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.
379
     */
380
    if (fabs(pVec.Angle() - (revert * mVec).Angle()) < 0.10 * angle) {
381
        pointToOverride = centerPoint + middleVec * revert;
382
        return true;
383
    }
384

385
    return false;
386
}
387

388
void SnapManager::setAngleSnapping(bool enable, Base::Vector2d referencepoint)
389
{
390
    angleSnapRequested = enable;
391
    referencePoint = referencepoint;
392
}
393

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.