FreeCAD

Форк
0
/
TaskMeasure.cpp 
538 строк · 15.8 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2023 David Friedli <david[at]friedli-be.ch>             *
3
 *                                                                         *
4
 *   This file is part of FreeCAD.                                         *
5
 *                                                                         *
6
 *   FreeCAD is free software: you can redistribute it and/or modify it    *
7
 *   under the terms of the GNU Lesser General Public License as           *
8
 *   published by the Free Software Foundation, either version 2.1 of the  *
9
 *   License, or (at your option) any later version.                       *
10
 *                                                                         *
11
 *   FreeCAD is distributed in the hope that it will be useful, but        *
12
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
14
 *   Lesser General Public License for more details.                       *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Lesser General Public      *
17
 *   License along with FreeCAD. If not, see                               *
18
 *   <https://www.gnu.org/licenses/>.                                      *
19
 *                                                                         *
20
 **************************************************************************/
21

22

23
#include "PreCompiled.h"
24

25
#ifndef _PreComp_
26
#include <QApplication>
27
#include <QKeyEvent>
28
#endif
29

30

31
#include "TaskMeasure.h"
32

33
#include <App/DocumentObjectGroup.h>
34
#include <App/Link.h>
35
#include <Mod/Measure/App/MeasureDistance.h>
36
#include <App/PropertyStandard.h>
37
#include <Gui/MainWindow.h>
38
#include <Gui/Application.h>
39
#include <Gui/BitmapFactory.h>
40
#include <Gui/Control.h>
41
#include <Gui/ViewProviderDocumentObject.h>
42

43
#include <QFormLayout>
44
#include <QPushButton>
45
#include <QSettings>
46

47
using namespace Gui;
48

49
namespace
50
{
51
constexpr auto taskMeasureSettingsGroup = "TaskMeasure";
52
constexpr auto taskMeasureShowDeltaSettingsName = "ShowDelta";
53
}  // namespace
54

55
TaskMeasure::TaskMeasure()
56
{
57
    qApp->installEventFilter(this);
58

59
    this->setButtonPosition(TaskMeasure::South);
60
    auto taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("umf-measurement"),
61
                                              tr("Measurement"),
62
                                              true,
63
                                              nullptr);
64

65
    QSettings settings;
66
    settings.beginGroup(QLatin1String(taskMeasureSettingsGroup));
67
    delta = settings.value(QLatin1String(taskMeasureShowDeltaSettingsName), true).toBool();
68

69
    showDelta = new QCheckBox();
70
    showDelta->setChecked(delta);
71
    showDeltaLabel = new QLabel(tr("Show Delta:"));
72
    connect(showDelta, &QCheckBox::stateChanged, this, &TaskMeasure::showDeltaChanged);
73

74
    // Create mode dropdown and add all registered measuretypes
75
    modeSwitch = new QComboBox();
76
    modeSwitch->addItem(QString::fromLatin1("Auto"));
77

78
    for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) {
79
        modeSwitch->addItem(QString::fromLatin1(mType->label.c_str()));
80
    }
81

82
    // Connect dropdown's change signal to our onModeChange slot
83
    connect(modeSwitch,
84
            qOverload<int>(&QComboBox::currentIndexChanged),
85
            this,
86
            &TaskMeasure::onModeChanged);
87

88
    // Result widget
89
    valueResult = new QLineEdit();
90
    valueResult->setReadOnly(true);
91

92
    // Main layout
93
    QBoxLayout* layout = taskbox->groupLayout();
94

95
    QFormLayout* formLayout = new QFormLayout();
96
    formLayout->setHorizontalSpacing(10);
97
    // Note: How can the split between columns be kept in the middle?
98
    // formLayout->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow);
99
    formLayout->setFormAlignment(Qt::AlignCenter);
100

101
    formLayout->addRow(tr("Mode:"), modeSwitch);
102
    formLayout->addRow(showDeltaLabel, showDelta);
103
    formLayout->addRow(tr("Result:"), valueResult);
104
    layout->addLayout(formLayout);
105

106
    Content.emplace_back(taskbox);
107

108
    // engage the selectionObserver
109
    attachSelection();
110

111
    // Set selection style
112
    Gui::Selection().setSelectionStyle(Gui::SelectionSingleton::SelectionStyle::GreedySelection);
113

114
    if (!App::GetApplication().getActiveTransaction()) {
115
        App::GetApplication().setActiveTransaction("Add Measurement");
116
    }
117

118

119
    // Call invoke method delayed, otherwise the dialog might not be fully initialized
120
    QTimer::singleShot(0, this, &TaskMeasure::invoke);
121
}
122

123
TaskMeasure::~TaskMeasure()
124
{
125
    Gui::Selection().setSelectionStyle(Gui::SelectionSingleton::SelectionStyle::NormalSelection);
126
    detachSelection();
127
    qApp->removeEventFilter(this);
128
}
129

130

131
void TaskMeasure::modifyStandardButtons(QDialogButtonBox* box)
132
{
133

134
    QPushButton* btn = box->button(QDialogButtonBox::Apply);
135
    btn->setText(tr("Save"));
136
    btn->setToolTip(tr("Save the measurement in the active document."));
137
    connect(btn, &QPushButton::released, this, &TaskMeasure::apply);
138

139
    // Disable button by default
140
    btn->setEnabled(false);
141
    btn = box->button(QDialogButtonBox::Abort);
142
    btn->setText(tr("Close"));
143
    btn->setToolTip(tr("Close the measurement task."));
144

145
    // Connect reset button
146
    btn = box->button(QDialogButtonBox::Reset);
147
    connect(btn, &QPushButton::released, this, &TaskMeasure::reset);
148
}
149

150
bool canAnnotate(Measure::MeasureBase* obj)
151
{
152
    if (obj == nullptr) {
153
        // null object, can't annotate this
154
        return false;
155
    }
156

157
    auto vpName = obj->getViewProviderName();
158
    // if there is not a vp, return false
159
    if ((vpName == nullptr) || (vpName[0] == '\0')) {
160
        return false;
161
    }
162

163
    return true;
164
}
165

166
void TaskMeasure::enableAnnotateButton(bool state)
167
{
168
    // if the task ui is not init yet we don't have a button box.
169
    if (!this->buttonBox) {
170
        return;
171
    }
172
    // Enable/Disable annotate button
173
    auto btn = this->buttonBox->button(QDialogButtonBox::Apply);
174
    btn->setEnabled(state);
175
}
176

177
void TaskMeasure::setMeasureObject(Measure::MeasureBase* obj)
178
{
179
    _mMeasureObject = obj;
180
}
181

182

183
App::DocumentObject* TaskMeasure::createObject(const App::MeasureType* measureType)
184
{
185
    auto measureClass =
186
        measureType->isPython ? "Measure::MeasurePython" : measureType->measureObject;
187
    auto type = Base::Type::getTypeIfDerivedFrom(measureClass.c_str(),
188
                                                 App::DocumentObject::getClassTypeId(),
189
                                                 true);
190

191
    if (type.isBad()) {
192
        return nullptr;
193
    }
194

195
    _mMeasureObject = static_cast<Measure::MeasureBase*>(type.createInstance());
196

197
    // Create an instance of the python measure class, the classe's
198
    // initializer sets the object as proxy
199
    if (measureType->isPython) {
200
        Base::PyGILStateLocker lock;
201
        Py::Tuple args(1);
202
        args.setItem(0, Py::asObject(_mMeasureObject->getPyObject()));
203
        PyObject* result = PyObject_CallObject(measureType->pythonClass, args.ptr());
204
        Py_XDECREF(result);
205
    }
206

207
    return static_cast<App::DocumentObject*>(_mMeasureObject);
208
}
209

210

211
Gui::ViewProviderDocumentObject* TaskMeasure::createViewObject(App::DocumentObject* measureObj)
212
{
213
    // Add view object
214
    auto vpName = measureObj->getViewProviderName();
215
    if ((vpName == nullptr) || (vpName[0] == '\0')) {
216
        return nullptr;
217
    }
218

219
    auto vpType =
220
        Base::Type::getTypeIfDerivedFrom(vpName,
221
                                         Gui::ViewProviderDocumentObject::getClassTypeId(),
222
                                         true);
223
    if (vpType.isBad()) {
224
        return nullptr;
225
    }
226

227
    auto vp = static_cast<Gui::ViewProviderDocumentObject*>(vpType.createInstance());
228

229
    _mGuiDocument = Gui::Application::Instance->activeDocument();
230
    _mGuiDocument->setAnnotationViewProvider(vp->getTypeId().getName(), vp);
231
    vp->attach(measureObj);
232

233
    // Init the position of the annotation
234
    static_cast<MeasureGui::ViewProviderMeasureBase*>(vp)->positionAnno(_mMeasureObject);
235

236
    vp->updateView();
237
    vp->setActiveMode();
238

239
    _mViewObject = vp;
240
    return vp;
241
}
242

243

244
void TaskMeasure::saveObject()
245
{
246
    if (_mViewObject && _mGuiDocument) {
247
        _mGuiDocument->addViewProvider(_mViewObject);
248
        _mGuiDocument->takeAnnotationViewProvider(_mViewObject->getTypeId().getName());
249
        _mViewObject = nullptr;
250
    }
251

252
    _mDocument = App::GetApplication().getActiveDocument();
253
    _mDocument->addObject(_mMeasureObject, _mMeasureType->label.c_str());
254
}
255

256

257
void TaskMeasure::update()
258
{
259
    App::Document* doc = App::GetApplication().getActiveDocument();
260

261
    // Reset selection if the selected object is not valid
262
    for (auto sel : Gui::Selection().getSelection()) {
263
        App::DocumentObject* ob = sel.pObject;
264
        App::DocumentObject* sub = ob->getSubObject(sel.SubName);
265

266
        // Resolve App::Link
267
        if (sub->isDerivedFrom<App::Link>()) {
268
            auto link = static_cast<App::Link*>(sub);
269
            sub = link->getLinkedObject(true);
270
        }
271

272
        std::string mod = Base::Type::getModuleName(sub->getTypeId().getName());
273
        if (!App::MeasureManager::hasMeasureHandler(mod.c_str())) {
274
            Base::Console().Message("No measure handler available for geometry of module: %s\n",
275
                                    mod);
276
            clearSelection();
277
            return;
278
        }
279
    }
280

281
    valueResult->setText(QString::asprintf("-"));
282

283
    // Get valid measure type
284

285
    std::string mode = explicitMode ? modeSwitch->currentText().toStdString() : "";
286

287
    App::MeasureSelection selection;
288
    for (auto s : Gui::Selection().getSelection(doc->getName(), ResolveMode::NoResolve)) {
289
        App::SubObjectT sub(s.pObject, s.SubName);
290

291
        App::MeasureSelectionItem item = {sub, Base::Vector3d(s.x, s.y, s.z)};
292
        selection.push_back(item);
293
    }
294

295
    auto measureTypes = App::MeasureManager::getValidMeasureTypes(selection, mode);
296
    if (measureTypes.size() > 0) {
297
        _mMeasureType = measureTypes.front();
298
    }
299

300

301
    if (!_mMeasureType) {
302

303
        // Note: If there's no valid measure type we might just restart the selection,
304
        // however this requires enough coverage of measuretypes that we can access all of them
305

306
        // std::tuple<std::string, std::string> sel = selection.back();
307
        // clearSelection();
308
        // addElement(measureModule.c_str(), get<0>(sel).c_str(), get<1>(sel).c_str());
309

310
        // Reset measure object
311
        if (!explicitMode) {
312
            setModeSilent(nullptr);
313
        }
314
        removeObject();
315
        enableAnnotateButton(false);
316
        return;
317
    }
318

319
    // Update tool mode display
320
    setModeSilent(_mMeasureType);
321

322
    if (!_mMeasureObject
323
        || _mMeasureType->measureObject != _mMeasureObject->getTypeId().getName()) {
324
        // we don't already have a measureobject or it isn't the same type as the new one
325
        removeObject();
326
        createObject(_mMeasureType);
327
    }
328

329
    // we have a valid measure object so we can enable the annotate button
330
    enableAnnotateButton(true);
331

332
    // Fill measure object's properties from selection
333
    _mMeasureObject->parseSelection(selection);
334

335
    // Get result
336
    valueResult->setText(_mMeasureObject->getResultString());
337

338
    createViewObject(_mMeasureObject);
339

340
    // Must be after createViewObject!
341
    assert(_mViewObject);
342
    auto* prop = dynamic_cast<App::PropertyBool*>(_mViewObject->getPropertyByName("ShowDelta"));
343
    setDeltaPossible(prop != nullptr);
344
    if (prop) {
345
        prop->setValue(showDelta->isChecked());
346
        _mViewObject->update(prop);
347
    }
348
}
349

350
void TaskMeasure::close()
351
{
352
    Control().closeDialog();
353
}
354

355

356
void TaskMeasure::ensureGroup(Measure::MeasureBase* measurement)
357
{
358
    // Ensure measurement object is part of the measurements group
359

360
    const char* measurementGroupName = "Measurements";
361
    if (measurement == nullptr) {
362
        return;
363
    }
364

365
    App::Document* doc = measurement->getDocument();
366
    App::DocumentObject* obj = doc->getObject(measurementGroupName);
367
    if (!obj || !obj->isValid()) {
368
        obj = doc->addObject("App::DocumentObjectGroup",
369
                             measurementGroupName,
370
                             true,
371
                             "MeasureGui::ViewProviderMeasureGroup");
372
    }
373

374
    auto group = static_cast<App::DocumentObjectGroup*>(obj);
375
    group->addObject(measurement);
376
}
377

378

379
// Runs after the dialog is created
380
void TaskMeasure::invoke()
381
{
382
    update();
383
}
384

385
bool TaskMeasure::apply()
386
{
387
    saveObject();
388
    ensureGroup(_mMeasureObject);
389
    reset();
390

391
    // Commit transaction
392
    App::GetApplication().closeActiveTransaction();
393
    App::GetApplication().setActiveTransaction("Add Measurement");
394
    return false;
395
}
396

397
bool TaskMeasure::reject()
398
{
399
    removeObject();
400
    close();
401

402
    // Abort transaction
403
    App::GetApplication().closeActiveTransaction(true);
404
    return false;
405
}
406

407
void TaskMeasure::reset()
408
{
409
    // Reset tool state
410
    _mMeasureType = nullptr;
411
    _mMeasureObject = nullptr;
412
    this->clearSelection();
413

414
    // Should the explicit mode also be reset?
415
    // setModeSilent(nullptr);
416
    // explicitMode = false;
417

418
    this->update();
419
}
420

421

422
void TaskMeasure::removeObject()
423
{
424
    if (_mMeasureObject == nullptr) {
425
        return;
426
    }
427
    if (_mMeasureObject->isRemoving()) {
428
        return;
429
    }
430

431
    if (_mViewObject && _mGuiDocument) {
432
        _mGuiDocument->removeAnnotationViewProvider(_mViewObject->getTypeId().getName());
433
        _mViewObject = nullptr;
434
    }
435

436
    delete _mMeasureObject;
437
    setMeasureObject(nullptr);
438
}
439

440
bool TaskMeasure::hasSelection()
441
{
442
    return !Gui::Selection().getSelection().empty();
443
}
444

445
void TaskMeasure::clearSelection()
446
{
447
    Gui::Selection().clearSelection();
448
}
449

450
void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg)
451
{
452
    // Skip non-relevant events
453
    if (msg.Type != SelectionChanges::AddSelection && msg.Type != SelectionChanges::RmvSelection
454
        && msg.Type != SelectionChanges::SetSelection
455
        && msg.Type != SelectionChanges::ClrSelection) {
456

457
        return;
458
    }
459

460
    update();
461
}
462

463
bool TaskMeasure::eventFilter(QObject* obj, QEvent* event)
464
{
465

466
    if (event->type() == QEvent::KeyPress) {
467
        auto keyEvent = static_cast<QKeyEvent*>(event);
468

469
        if (keyEvent->key() == Qt::Key_Escape) {
470

471
            if (this->hasSelection()) {
472
                this->reset();
473
            }
474
            else {
475
                this->reject();
476
            }
477

478
            return true;
479
        }
480

481
        if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
482
            // Save object. Indirectly dependent on whether the apply button is enabled
483
            // enabled if valid measurement object.
484
            this->buttonBox->button(QDialogButtonBox::Apply)->click();
485
            return true;
486
        }
487
    }
488

489
    return TaskDialog::eventFilter(obj, event);
490
}
491

492
void TaskMeasure::setDeltaPossible(bool possible)
493
{
494
    showDelta->setVisible(possible);
495
    showDeltaLabel->setVisible(possible);
496
}
497

498
void TaskMeasure::onModeChanged(int index)
499
{
500
    explicitMode = (index != 0);
501

502
    this->update();
503
}
504

505
void TaskMeasure::showDeltaChanged(int checkState)
506
{
507
    delta = checkState == Qt::CheckState::Checked;
508

509
    QSettings settings;
510
    settings.beginGroup(QLatin1String(taskMeasureSettingsGroup));
511
    settings.setValue(QLatin1String(taskMeasureShowDeltaSettingsName), delta);
512

513
    this->update();
514
}
515

516
void TaskMeasure::setModeSilent(App::MeasureType* mode)
517
{
518
    modeSwitch->blockSignals(true);
519

520
    if (mode == nullptr) {
521
        modeSwitch->setCurrentIndex(0);
522
    }
523
    else {
524
        modeSwitch->setCurrentText(QString::fromLatin1(mode->label.c_str()));
525
    }
526
    modeSwitch->blockSignals(false);
527
}
528

529
// Get explicitly set measure type from the mode switch
530
App::MeasureType* TaskMeasure::getMeasureType()
531
{
532
    for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) {
533
        if (mType->label.c_str() == modeSwitch->currentText().toLatin1()) {
534
            return mType;
535
        }
536
    }
537
    return nullptr;
538
}
539

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

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

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

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