From 2b2a82d901304b24c060f2887197a6fa7b1f84c2 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 21 Jan 2025 19:38:40 +0100 Subject: [PATCH 1/9] Measure: Fix crash in Measurement::length() when selecting infinite edge (e.g. PD datum line) --- src/Mod/Measure/App/Measurement.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Mod/Measure/App/Measurement.cpp b/src/Mod/Measure/App/Measurement.cpp index bd68cd5d9c..784685b687 100644 --- a/src/Mod/Measure/App/Measurement.cpp +++ b/src/Mod/Measure/App/Measurement.cpp @@ -331,6 +331,9 @@ double Measurement::length() const // Get the length of one edge TopoDS_Shape shape = getShape(*obj, (*subEl).c_str(), TopAbs_EDGE); + if (shape.IsNull()) { + continue; + } const TopoDS_Edge& edge = TopoDS::Edge(shape); BRepAdaptor_Curve curve(edge); @@ -365,8 +368,8 @@ double Measurement::length() const throw Base::RuntimeError( "Measurement - length - Curve type not currently handled"); } - } // end switch - } // end for + } + } } } return result; From 5d3f9cac3f1db12e5e42eae5a8e551c9c5a0ed26 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 21 Jan 2025 23:26:46 +0100 Subject: [PATCH 2/9] Measure: Implement quick measure as command and make it toggable The quick measure can considerably slow down the system when selecting part elements. With this change the user is able to turn off this bottleneck. --- src/Gui/Workbench.cpp | 3 +- src/Mod/Measure/Gui/AppMeasureGui.cpp | 5 -- src/Mod/Measure/Gui/Command.cpp | 66 +++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index e1ce0534ab..c6eefc3ddf 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -723,6 +723,7 @@ MenuItem* StdWorkbench::setupMenuBar() const << "Separator"; #endif *tool << "Std_Measure" + << "Std_QuickMeasure" << "Std_UnitsCalculator" << "Separator" << "Std_ViewLoadImage" @@ -812,7 +813,7 @@ ToolBarItem* StdWorkbench::setupToolBars() const auto view = new ToolBarItem( root ); view->setCommand("View"); *view << "Std_ViewFitAll" << "Std_ViewFitSelection" << "Std_ViewGroup" << "Std_AlignToSelection" - << "Separator" << "Std_DrawStyle" << "Std_TreeViewActions" << "Std_Measure"; + << "Separator" << "Std_DrawStyle" << "Std_TreeViewActions" << "Std_Measure" << "Std_QuickMeasure"; // Individual views auto individualViews = new ToolBarItem(root, ToolBarItem::DefaultVisibility::Hidden); diff --git a/src/Mod/Measure/Gui/AppMeasureGui.cpp b/src/Mod/Measure/Gui/AppMeasureGui.cpp index 7283fc9bcb..e282e00f9c 100644 --- a/src/Mod/Measure/Gui/AppMeasureGui.cpp +++ b/src/Mod/Measure/Gui/AppMeasureGui.cpp @@ -32,7 +32,6 @@ #include #include "DlgPrefsMeasureAppearanceImp.h" -#include "QuickMeasure.h" #include "QuickMeasurePy.h" #include "ViewProviderMeasureAngle.h" #include "ViewProviderMeasureDistance.h" @@ -112,9 +111,5 @@ PyMOD_INIT_FUNC(MeasureGui) Base::Interpreter().addType(&MeasureGui::QuickMeasurePy::Type, mod, "QuickMeasure"); - // Create a QuickMeasure instance - auto measure = new MeasureGui::QuickMeasure(QApplication::instance()); - Q_UNUSED(measure) - PyMOD_Return(mod); } diff --git a/src/Mod/Measure/Gui/Command.cpp b/src/Mod/Measure/Gui/Command.cpp index 2a138080fa..5f78fd85eb 100644 --- a/src/Mod/Measure/Gui/Command.cpp +++ b/src/Mod/Measure/Gui/Command.cpp @@ -20,8 +20,13 @@ **************************************************************************/ #include "PreCompiled.h" +#ifndef _PreComp_ +#include +#endif +#include #include +#include #include #include #include @@ -30,6 +35,7 @@ #include #include +#include "QuickMeasure.h" #include "TaskMeasure.h" @@ -76,6 +82,65 @@ bool StdCmdMeasure::isActive() return false; } + +class StdCmdQuickMeasure: public Gui::Command +{ +public: + StdCmdQuickMeasure() + : Command("Std_QuickMeasure") + { + sGroup = "Measure"; + sMenuText = QT_TR_NOOP("&Quick measure"); + sToolTipText = QT_TR_NOOP("Toggle quick measure"); + sWhatsThis = "Std_QuickMeasure"; + sStatusTip = QT_TR_NOOP("Toggle quick measure"); + accessParameter(); + } + ~StdCmdQuickMeasure() override = default; + StdCmdQuickMeasure(const StdCmdQuickMeasure&) = delete; + StdCmdQuickMeasure(StdCmdQuickMeasure&&) = delete; + StdCmdQuickMeasure& operator=(const StdCmdQuickMeasure&) = delete; + StdCmdQuickMeasure& operator=(StdCmdQuickMeasure&&) = delete; + + const char* className() const override + { + return "StdCmdQuickMeasure"; + } + +protected: + void activated(int iMsg) override + { + if (parameter.isValid()) { + parameter->SetBool("EnableQuickMeasure", iMsg > 0); + } + + if (iMsg == 0) { + quickMeasure.reset(); + } + else { + quickMeasure = std::make_unique(QApplication::instance()); + } + } + Gui::Action* createAction() override + { + Gui::Action* action = Gui::Command::createAction(); + action->setCheckable(true); + action->setChecked(parameter->GetBool("EnableQuickMeasure", false)); + return action; + } + void accessParameter() + { + // clang-format off + parameter = App::GetApplication().GetUserParameter(). + GetGroup("BaseApp/Preferences/Mod/Measure"); + // clang-format on + } + +private: + std::unique_ptr quickMeasure; + ParameterGrp::handle parameter; +}; + void CreateMeasureCommands() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); @@ -83,4 +148,5 @@ void CreateMeasureCommands() auto cmd = new StdCmdMeasure(); cmd->initAction(); rcCmdMgr.addCommand(cmd); + rcCmdMgr.addCommand(new StdCmdQuickMeasure); } From 1acafb240a7f94a4578f06413edf5252160922f4 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 9 Feb 2025 20:33:31 +0100 Subject: [PATCH 3/9] Measure: Fix several crashes in Measurement Measurement::getShape() used to call ShapeFinder::getLocatedShape() that failed to return a valid shape in many cases. And the calling instance didn't check for a valid shape either which caused a segmentation fault. Solution: Replace ShapeFinder::getLocatedShape() with Part::Feature::getShape() that works more reliably and where needed check for a valid shape before accessing it. This fixes issue 19452 and improves the fix for 19349 --- src/Mod/Measure/App/Measurement.cpp | 40 +++++++++++++++++++---------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/Mod/Measure/App/Measurement.cpp b/src/Mod/Measure/App/Measurement.cpp index 784685b687..308890b321 100644 --- a/src/Mod/Measure/App/Measurement.cpp +++ b/src/Mod/Measure/App/Measurement.cpp @@ -287,13 +287,11 @@ MeasureType Measurement::getType() TopoDS_Shape Measurement::getShape(App::DocumentObject* obj, const char* subName, TopAbs_ShapeEnum hint) const { - TopoShape shape = ShapeFinder::getLocatedTopoShape(*obj, subName); - - if (shape.shapeType() == TopAbs_COMPOUND && hint != TopAbs_COMPOUND - && shape.hasSubShape(hint)) { - return shape.getSubTopoShape(hint, true).getShape(); - } - return shape.getShape(); + return Part::Feature::getShape(obj, + Part::ShapeOption::NeedSubElement + | Part::ShapeOption::ResolveLink + | Part::ShapeOption::Transform, + subName); } @@ -331,7 +329,7 @@ double Measurement::length() const // Get the length of one edge TopoDS_Shape shape = getShape(*obj, (*subEl).c_str(), TopAbs_EDGE); - if (shape.IsNull()) { + if (shape.IsNull() || shape.Infinite()) { continue; } const TopoDS_Edge& edge = TopoDS::Edge(shape); @@ -435,8 +433,6 @@ double Measurement::planePlaneDistance() const const auto& objects = References3D.getValues(); const auto& subElements = References3D.getSubValues(); - std::vector planes; - // Get the first plane TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str(), TopAbs_FACE); const TopoDS_Face& face1 = TopoDS::Face(shape1); @@ -687,7 +683,11 @@ double Measurement::volume() const for (size_t i = 0; i < objects.size(); ++i) { GProp_GProps props = GProp_GProps(); - BRepGProp::VolumeProperties(getShape(objects[i], subElements[i].c_str()), props); + TopoDS_Shape shape = getShape(objects[i], subElements[i].c_str()); + if (shape.IsNull() || shape.Infinite()) { + continue; + } + BRepGProp::VolumeProperties(shape, props); result += props.Mass(); } } @@ -710,7 +710,11 @@ double Measurement::area() const for (size_t i = 0; i < objects.size(); ++i) { GProp_GProps props; - BRepGProp::SurfaceProperties(getShape(objects[i], subElements[i].c_str()), props); + TopoDS_Shape shape = getShape(objects[i], subElements[i].c_str()); + if (shape.IsNull() || shape.Infinite()) { + continue; + } + BRepGProp::SurfaceProperties(shape, props); result += props.Mass(); // Area is obtained using Mass method for surface properties } } @@ -745,7 +749,11 @@ Base::Vector3d Measurement::massCenter() const // Compute inertia properties GProp_GProps props = GProp_GProps(); - BRepGProp::VolumeProperties(getShape((*obj), ""), props); + TopoDS_Shape shape = ShapeFinder::getLocatedShape(*(*obj), ""); + if (shape.IsNull()) { + continue; + } + BRepGProp::VolumeProperties(shape, props); gprops.Add(props); // Get inertia properties } @@ -821,11 +829,17 @@ bool Measurement::linesAreParallel() const // Get the first line TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str(), TopAbs_EDGE); + if (shape1.IsNull()) { + return false; + } const TopoDS_Edge& edge1 = TopoDS::Edge(shape1); BRepAdaptor_Curve curve1(edge1); // Get the second line TopoDS_Shape shape2 = getShape(objects[1], subElements[1].c_str(), TopAbs_EDGE); + if (shape2.IsNull()) { + return false; + } const TopoDS_Edge& edge2 = TopoDS::Edge(shape2); BRepAdaptor_Curve curve2(edge2); From 8165d126075f615488defbee2db0b8d19f90e852 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 21 Mar 2025 15:00:12 +0100 Subject: [PATCH 4/9] Measure: Fix TaskMeasure Made several fixes to TaskMeasure: * Move to correct namespace * Handle possible exception in update() method * Add null pointer checks for buttonBox * Cannot use 'Measure::MeasurePython' as template argument in addObject<> because the macro PROPERTY_HEADER_WITH_OVERRIDE determines the invalid string 'App::FeaturePythonT' so that an exception is raised Note: The changes might fix issue 20304 --- src/App/Document.cpp | 2 +- src/Mod/Measure/Gui/Command.cpp | 2 +- src/Mod/Measure/Gui/TaskMeasure.cpp | 38 ++++++++++++++++++++--------- src/Mod/Measure/Gui/TaskMeasure.h | 7 +++--- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 5c01df8069..246a76e388 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -3065,7 +3065,7 @@ DocumentObject* Document::addObject(const char* sType, Base::Type::getTypeIfDerivedFrom(sType, DocumentObject::getClassTypeId(), true); if (type.isBad()) { std::stringstream str; - str << "'" << sType << "' is not a document object type"; + str << "Document::addObject: '" << sType << "' is not a document object type"; throw Base::TypeError(str.str()); } diff --git a/src/Mod/Measure/Gui/Command.cpp b/src/Mod/Measure/Gui/Command.cpp index 5f78fd85eb..873059b9da 100644 --- a/src/Mod/Measure/Gui/Command.cpp +++ b/src/Mod/Measure/Gui/Command.cpp @@ -62,7 +62,7 @@ void StdCmdMeasure::activated(int iMsg) { Q_UNUSED(iMsg); - Gui::TaskMeasure* task = new Gui::TaskMeasure(); + MeasureGui::TaskMeasure* task = new MeasureGui::TaskMeasure(); task->setDocumentName(this->getDocument()->getName()); Gui::Control().showDialog(task); } diff --git a/src/Mod/Measure/Gui/TaskMeasure.cpp b/src/Mod/Measure/Gui/TaskMeasure.cpp index d36454690f..45c784f71d 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.cpp +++ b/src/Mod/Measure/Gui/TaskMeasure.cpp @@ -48,7 +48,7 @@ #include #include -using namespace Gui; +using namespace MeasureGui; namespace { @@ -225,7 +225,9 @@ Measure::MeasureBase* TaskMeasure::createObject(const App::MeasureType* measureT auto pyMeasureClass = measureType->pythonClass; // Create a MeasurePython instance - // Measure::MeasurePython is an alias so we need to use the string based addObject for now. + // Note: writing addObject() is not yet supported because + // getClassName() will determine the string 'App::FeaturePythonT' instead + // of 'Measure::MeasurePython' auto featurePython = doc->addObject("Measure::MeasurePython", measureType->label.c_str()); _mMeasureObject = dynamic_cast(featurePython); @@ -245,8 +247,17 @@ Measure::MeasureBase* TaskMeasure::createObject(const App::MeasureType* measureT return _mMeasureObject; } - void TaskMeasure::update() +{ + try { + tryUpdate(); + } + catch (const Base::Exception& e) { + e.reportException(); + } +} + +void TaskMeasure::tryUpdate() { App::Document* doc = App::GetApplication().getActiveDocument(); @@ -274,7 +285,7 @@ void TaskMeasure::update() std::string mode = explicitMode ? modeSwitch->currentText().toStdString() : ""; App::MeasureSelection selection; - for (auto s : Gui::Selection().getSelection(doc->getName(), ResolveMode::NoResolve)) { + for (auto s : Gui::Selection().getSelection(doc->getName(), Gui::ResolveMode::NoResolve)) { App::SubObjectT sub(s.pObject, s.SubName); App::MeasureSelectionItem item = {sub, Base::Vector3d(s.x, s.y, s.z)}; @@ -358,7 +369,7 @@ void TaskMeasure::initViewObject() void TaskMeasure::close() { - Control().closeDialog(); + Gui::Control().closeDialog(); } @@ -460,9 +471,10 @@ void TaskMeasure::clearSelection() void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg) { // Skip non-relevant events - if (msg.Type != SelectionChanges::AddSelection && msg.Type != SelectionChanges::RmvSelection - && msg.Type != SelectionChanges::SetSelection - && msg.Type != SelectionChanges::ClrSelection) { + if (msg.Type != Gui::SelectionChanges::AddSelection + && msg.Type != Gui::SelectionChanges::RmvSelection + && msg.Type != Gui::SelectionChanges::SetSelection + && msg.Type != Gui::SelectionChanges::ClrSelection) { return; } @@ -476,9 +488,9 @@ void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg) const bool shift = (modifier & Qt::ShiftModifier) > 0; // shift inverts the current state temporarily const auto autosave = (mAutoSave && !shift) || (!mAutoSave && shift); - if ((!ctrl && Selection().getSelectionStyle() == SelectionStyle::NormalSelection) - || (ctrl && Selection().getSelectionStyle() == SelectionStyle::GreedySelection)) { - if (autosave && this->buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) { + if ((!ctrl && Gui::Selection().getSelectionStyle() == SelectionStyle::NormalSelection) + || (ctrl && Gui::Selection().getSelectionStyle() == SelectionStyle::GreedySelection)) { + if (autosave && buttonBox && buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) { apply(false); } } @@ -506,7 +518,9 @@ bool TaskMeasure::eventFilter(QObject* obj, QEvent* event) if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { // Save object. Indirectly dependent on whether the apply button is enabled // enabled if valid measurement object. - this->buttonBox->button(QDialogButtonBox::Apply)->click(); + if (buttonBox) { + buttonBox->button(QDialogButtonBox::Apply)->click(); + } return true; } } diff --git a/src/Mod/Measure/Gui/TaskMeasure.h b/src/Mod/Measure/Gui/TaskMeasure.h index 399641d56c..e5e9694305 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.h +++ b/src/Mod/Measure/Gui/TaskMeasure.h @@ -41,10 +41,10 @@ #include #include -namespace Gui +namespace MeasureGui { -class TaskMeasure: public TaskView::TaskDialog, public Gui::SelectionObserver +class TaskMeasure: public Gui::TaskView::TaskDialog, public Gui::SelectionObserver { public: @@ -70,6 +70,7 @@ public: bool eventFilter(QObject* obj, QEvent* event) override; private: + void tryUpdate(); void onSelectionChanged(const Gui::SelectionChanges& msg) override; Measure::MeasureBase* _mMeasureObject = nullptr; @@ -103,6 +104,6 @@ private: bool mAutoSave = false; }; -} // namespace Gui +} // namespace MeasureGui #endif // MEASURE_TASKMEASURE_H From 75236d4b13d03411ad3d84973b6e0f81dbd6b16f Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 16 Apr 2025 12:23:00 +0200 Subject: [PATCH 5/9] Measure: Allow any surface type for MeasureArea --- src/Mod/Measure/App/MeasureArea.cpp | 12 ++++++++++-- src/Mod/Measure/App/MeasureArea.h | 1 + src/Mod/Part/App/MeasureClient.cpp | 5 ++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Mod/Measure/App/MeasureArea.cpp b/src/Mod/Measure/App/MeasureArea.cpp index e7b5d2f271..474c11a7cd 100644 --- a/src/Mod/Measure/App/MeasureArea.cpp +++ b/src/Mod/Measure/App/MeasureArea.cpp @@ -53,6 +53,15 @@ MeasureArea::MeasureArea() MeasureArea::~MeasureArea() = default; +bool MeasureArea::isSupported(App::MeasureElementType type) +{ + // clang-format off + return (type == App::MeasureElementType::PLANE) || + (type == App::MeasureElementType::CYLINDER) || + (type == App::MeasureElementType::SURFACE) || + (type == App::MeasureElementType::VOLUME); + // clang-format on +} bool MeasureArea::isValidSelection(const App::MeasureSelection& selection) { @@ -68,8 +77,7 @@ bool MeasureArea::isValidSelection(const App::MeasureSelection& selection) return false; } - if ((type != App::MeasureElementType::PLANE && type != App::MeasureElementType::CYLINDER - && type != App::MeasureElementType::SURFACE)) { + if (!isSupported(type)) { return false; } } diff --git a/src/Mod/Measure/App/MeasureArea.h b/src/Mod/Measure/App/MeasureArea.h index ecf7be5efa..c7f98a3897 100644 --- a/src/Mod/Measure/App/MeasureArea.h +++ b/src/Mod/Measure/App/MeasureArea.h @@ -79,6 +79,7 @@ public: private: + static bool isSupported(App::MeasureElementType type); void onChanged(const App::Property* prop) override; }; diff --git a/src/Mod/Part/App/MeasureClient.cpp b/src/Mod/Part/App/MeasureClient.cpp index 42b53d40d8..08c14315fc 100644 --- a/src/Mod/Part/App/MeasureClient.cpp +++ b/src/Mod/Part/App/MeasureClient.cpp @@ -189,6 +189,9 @@ App::MeasureElementType PartMeasureTypeCb(App::DocumentObject* ob, const char* s return App::MeasureElementType::SURFACE; } } } + case TopAbs_SHELL: { + return App::MeasureElementType::SURFACE; + } case TopAbs_SOLID: { return App::MeasureElementType::VOLUME; } @@ -341,7 +344,7 @@ MeasureAreaInfoPtr MeasureAreaHandler(const App::SubObjectT& subject) } TopAbs_ShapeEnum sType = shape.ShapeType(); - if (sType != TopAbs_FACE) { + if (sType != TopAbs_FACE && sType != TopAbs_SHELL && sType != TopAbs_SOLID) { return std::make_shared(false, 0.0, Base::Matrix4D()); } From 9540cfe0ea11e51bfd51b7be3cde031a2dff10d7 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 16 Apr 2025 18:49:19 +0200 Subject: [PATCH 6/9] Measure: Improve TaskMeasure dialog + Use Qt's translation of the Save and Close buttons + Use QShortcut instead of eventFilter() because the latter is too aggressive eating all Enter and ESC events This fixes the remaining issues as mentioned at the PR 17109 --- src/Mod/Measure/Gui/TaskMeasure.cpp | 64 ++++++++++++++--------------- src/Mod/Measure/Gui/TaskMeasure.h | 4 +- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/Mod/Measure/Gui/TaskMeasure.cpp b/src/Mod/Measure/Gui/TaskMeasure.cpp index 45c784f71d..f994d6e9b1 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.cpp +++ b/src/Mod/Measure/Gui/TaskMeasure.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include using namespace MeasureGui; @@ -62,14 +63,14 @@ using SelectionStyle = Gui::SelectionSingleton::SelectionStyle; TaskMeasure::TaskMeasure() { - qApp->installEventFilter(this); - this->setButtonPosition(TaskMeasure::South); auto taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("umf-measurement"), tr("Measurement"), true, nullptr); + setupShortcuts(taskbox); + QSettings settings; settings.beginGroup(QLatin1String(taskMeasureSettingsGroup)); delta = settings.value(QLatin1String(taskMeasureShowDeltaSettingsName), true).toBool(); @@ -153,7 +154,7 @@ TaskMeasure::TaskMeasure() auto* settingsLayout = new QHBoxLayout(); settingsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); settingsLayout->addWidget(mSettings); - formLayout->addRow(QStringLiteral(""), settingsLayout); + formLayout->addRow(QLatin1String(), settingsLayout); formLayout->addRow(tr("Mode:"), modeSwitch); formLayout->addRow(showDeltaLabel, showDelta); formLayout->addRow(tr("Result:"), valueResult); @@ -177,15 +178,13 @@ TaskMeasure::~TaskMeasure() { Gui::Selection().setSelectionStyle(SelectionStyle::NormalSelection); detachSelection(); - qApp->removeEventFilter(this); } - void TaskMeasure::modifyStandardButtons(QDialogButtonBox* box) { QPushButton* btn = box->button(QDialogButtonBox::Apply); - btn->setText(tr("Save")); + btn->setText(QCoreApplication::translate("QPlatformTheme", "Save")); btn->setToolTip(tr("Saves the measurement in the active document")); connect(btn, &QPushButton::released, this, qOverload<>(&TaskMeasure::apply)); @@ -193,7 +192,7 @@ void TaskMeasure::modifyStandardButtons(QDialogButtonBox* box) btn->setEnabled(false); btn = box->button(QDialogButtonBox::Abort); btn->setText(tr("Close")); - btn->setToolTip(tr("Closes the measurement task")); + btn->setToolTip(tr("Close the measurement task.")); // Connect reset button btn = box->button(QDialogButtonBox::Reset); @@ -295,7 +294,7 @@ void TaskMeasure::tryUpdate() // Get valid measure type App::MeasureType* measureType = nullptr; auto measureTypes = App::MeasureManager::getValidMeasureTypes(selection, mode); - if (measureTypes.size() > 0) { + if (!measureTypes.empty()) { measureType = measureTypes.front(); } @@ -497,35 +496,36 @@ void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg) update(); } -bool TaskMeasure::eventFilter(QObject* obj, QEvent* event) +void TaskMeasure::setupShortcuts(QWidget* parent) { + auto shortcutSave = new QShortcut(parent); + shortcutSave->setKey(QKeySequence(QStringLiteral("Return"))); + shortcutSave->setContext(Qt::ApplicationShortcut); + connect(shortcutSave, &QShortcut::activated, this, &TaskMeasure::saveMeasurement); - if (event->type() == QEvent::KeyPress) { - auto keyEvent = static_cast(event); + auto shortcutQuit = new QShortcut(parent); + shortcutQuit->setKey(QKeySequence(QStringLiteral("ESC"))); + shortcutQuit->setContext(Qt::ApplicationShortcut); + connect(shortcutQuit, &QShortcut::activated, this, &TaskMeasure::quitMeasurement); +} - if (keyEvent->key() == Qt::Key_Escape) { - - if (this->hasSelection()) { - this->reset(); - } - else { - this->reject(); - } - - return true; - } - - if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { - // Save object. Indirectly dependent on whether the apply button is enabled - // enabled if valid measurement object. - if (buttonBox) { - buttonBox->button(QDialogButtonBox::Apply)->click(); - } - return true; - } +void TaskMeasure::saveMeasurement() +{ + // Save object. Indirectly dependent on whether the apply button is enabled + // enabled if valid measurement object. + if (buttonBox) { + buttonBox->button(QDialogButtonBox::Apply)->click(); } +} - return TaskDialog::eventFilter(obj, event); +void TaskMeasure::quitMeasurement() +{ + if (this->hasSelection()) { + this->reset(); + } + else { + this->reject(); + } } void TaskMeasure::setDeltaPossible(bool possible) diff --git a/src/Mod/Measure/Gui/TaskMeasure.h b/src/Mod/Measure/Gui/TaskMeasure.h index e5e9694305..97adbdd371 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.h +++ b/src/Mod/Measure/Gui/TaskMeasure.h @@ -67,11 +67,13 @@ public: bool hasSelection(); void clearSelection(); - bool eventFilter(QObject* obj, QEvent* event) override; private: + void setupShortcuts(QWidget* parent); void tryUpdate(); void onSelectionChanged(const Gui::SelectionChanges& msg) override; + void saveMeasurement(); + void quitMeasurement(); Measure::MeasureBase* _mMeasureObject = nullptr; From 4a5217075c40967cf54a28d72e95da39bf826740 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 16 Apr 2025 19:17:40 +0200 Subject: [PATCH 7/9] Measure: Fix some linter warnings --- src/Mod/Measure/Gui/TaskMeasure.cpp | 44 ++++++++++++++--------------- src/Mod/Measure/Gui/TaskMeasure.h | 6 ++-- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Mod/Measure/Gui/TaskMeasure.cpp b/src/Mod/Measure/Gui/TaskMeasure.cpp index f994d6e9b1..53fe55f515 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.cpp +++ b/src/Mod/Measure/Gui/TaskMeasure.cpp @@ -212,11 +212,11 @@ void TaskMeasure::enableAnnotateButton(bool state) } -Measure::MeasureBase* TaskMeasure::createObject(const App::MeasureType* measureType) +void TaskMeasure::createObject(const App::MeasureType* measureType) { App::Document* doc = App::GetApplication().getActiveDocument(); if (!doc) { - return nullptr; + return; } if (measureType->isPython) { @@ -242,8 +242,6 @@ Measure::MeasureBase* TaskMeasure::createObject(const App::MeasureType* measureT _mMeasureObject = dynamic_cast( doc->addObject(measureType->measureObject.c_str(), measureType->label.c_str())); } - - return _mMeasureObject; } void TaskMeasure::update() @@ -330,31 +328,34 @@ void TaskMeasure::tryUpdate() // we have a valid measure object so we can enable the annotate button enableAnnotateButton(true); - // Fill measure object's properties from selection - _mMeasureObject->parseSelection(selection); + if (_mMeasureObject) { + // Fill measure object's properties from selection + _mMeasureObject->parseSelection(selection); - // Get result - valueResult->setText(_mMeasureObject->getResultString()); + // Get result + valueResult->setText(_mMeasureObject->getResultString()); - // Initialite the measurement's viewprovider - initViewObject(); + + // Initialite the measurement's viewprovider + initViewObject(_mMeasureObject); + } } -void TaskMeasure::initViewObject() +void TaskMeasure::initViewObject(Measure::MeasureBase* measure) { Gui::Document* guiDoc = Gui::Application::Instance->activeDocument(); if (!guiDoc) { return; } - Gui::ViewProvider* viewObject = guiDoc->getViewProvider(_mMeasureObject); + Gui::ViewProvider* viewObject = guiDoc->getViewProvider(measure); if (!viewObject) { return; } // Init the position of the annotation - dynamic_cast(viewObject)->positionAnno(_mMeasureObject); + dynamic_cast(viewObject)->positionAnno(measure); // Set the ShowDelta Property if it exists on the measurements view object auto* prop = viewObject->getPropertyByName("ShowDelta"); @@ -366,7 +367,7 @@ void TaskMeasure::initViewObject() } -void TaskMeasure::close() +void TaskMeasure::closeDialog() { Gui::Control().closeDialog(); } @@ -382,16 +383,13 @@ void TaskMeasure::ensureGroup(Measure::MeasureBase* measurement) } App::Document* doc = measurement->getDocument(); - App::DocumentObject* obj = doc->getObject(measurementGroupName); - - - if (!obj || !obj->isValid() || !obj->isDerivedFrom()) { - obj = doc->addObject(measurementGroupName, - true, - "MeasureGui::ViewProviderMeasureGroup"); + auto group = dynamic_cast(doc->getObject(measurementGroupName)); + if (!group || !group->isValid()) { + group = doc->addObject(measurementGroupName, + true, + "MeasureGui::ViewProviderMeasureGroup"); } - auto group = static_cast(obj); group->addObject(measurement); } @@ -424,7 +422,7 @@ bool TaskMeasure::apply(bool reset) bool TaskMeasure::reject() { removeObject(); - close(); + closeDialog(); // Abort transaction App::GetApplication().closeActiveTransaction(true); diff --git a/src/Mod/Measure/Gui/TaskMeasure.h b/src/Mod/Measure/Gui/TaskMeasure.h index 97adbdd371..347bbb85b5 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.h +++ b/src/Mod/Measure/Gui/TaskMeasure.h @@ -59,7 +59,7 @@ public: void invoke(); void update(); - void close(); + void closeDialog(); bool apply(); bool apply(bool reset); bool reject() override; @@ -93,10 +93,10 @@ private: void setModeSilent(App::MeasureType* mode); App::MeasureType* getMeasureType(); void enableAnnotateButton(bool state); - Measure::MeasureBase* createObject(const App::MeasureType* measureType); + void createObject(const App::MeasureType* measureType); void ensureGroup(Measure::MeasureBase* measurement); void setDeltaPossible(bool possible); - void initViewObject(); + void initViewObject(Measure::MeasureBase* measure); // Stores if the mode is explicitly set by the user or implicitly through the selection bool explicitMode = false; From eebb7f78292951672bf0584c828e1b7fa4f62cc0 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 15 May 2025 08:10:48 +0200 Subject: [PATCH 8/9] PD: Increase the tolerance to check for planar surfaces The default tolerance to check for a planar B-spline surface is 1.0e-7 which might be too strict in some cases. Therefore the tolerance is increased to 2.0e-7. This fixes issue 21242 --- src/Mod/Part/App/Attacher.cpp | 8 +++++++- src/Mod/Part/App/Attacher.h | 2 ++ src/Mod/Part/App/Part2DObject.cpp | 4 +++- src/Mod/PartDesign/Gui/SketchWorkflow.cpp | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Mod/Part/App/Attacher.cpp b/src/Mod/Part/App/Attacher.cpp index 9fe2c114a1..f5f5beffae 100644 --- a/src/Mod/Part/App/Attacher.cpp +++ b/src/Mod/Part/App/Attacher.cpp @@ -1377,7 +1377,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vector &objs, const std::vector &subs, const Base::Placement &origPlacement) const override; + static double planarPrecision(); }; //attacher specialized for datum lines diff --git a/src/Mod/Part/App/Part2DObject.cpp b/src/Mod/Part/App/Part2DObject.cpp index 0f363bbe0d..dd818c4227 100644 --- a/src/Mod/Part/App/Part2DObject.cpp +++ b/src/Mod/Part/App/Part2DObject.cpp @@ -60,7 +60,9 @@ PROPERTY_SOURCE_WITH_EXTENSIONS(Part::Part2DObject, Part::Feature) Part2DObject::Part2DObject() { AttachExtension::initExtension(this); - this->setAttacher(new Attacher::AttachEnginePlane); + auto engine = new Attacher::AttachEnginePlane; + engine->precision = Attacher::AttachEnginePlane::planarPrecision(); + this->setAttacher(engine); } diff --git a/src/Mod/PartDesign/Gui/SketchWorkflow.cpp b/src/Mod/PartDesign/Gui/SketchWorkflow.cpp index 19ef91df16..3b10347b58 100644 --- a/src/Mod/PartDesign/Gui/SketchWorkflow.cpp +++ b/src/Mod/PartDesign/Gui/SketchWorkflow.cpp @@ -141,7 +141,7 @@ public: throw WrongSupportException(); } - if (!subshape.isPlanar()) { + if (!subshape.isPlanar(Attacher::AttachEnginePlane::planarPrecision())) { throw SupportNotPlanarException(); } } From 130d5618c23fc428d00dad2cb8dd72e11c30795d Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 15 May 2025 14:09:49 +0200 Subject: [PATCH 9/9] Measure: Extend angle measurements Support to measure the angle using a planar B-spline surface --- src/Mod/Part/App/MeasureClient.cpp | 18 ++- src/Mod/Part/App/VectorAdapter.cpp | 191 ++++++++++++++++++----------- src/Mod/Part/App/VectorAdapter.h | 69 ++++++----- 3 files changed, 169 insertions(+), 109 deletions(-) diff --git a/src/Mod/Part/App/MeasureClient.cpp b/src/Mod/Part/App/MeasureClient.cpp index 08c14315fc..52d6ace3fe 100644 --- a/src/Mod/Part/App/MeasureClient.cpp +++ b/src/Mod/Part/App/MeasureClient.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -56,12 +57,14 @@ #include #include +#include "Attacher.h" #include "VectorAdapter.h" #include "PartFeature.h" #include "MeasureClient.h" using namespace Part; +using Attacher::AttachEnginePlane; // From: https://github.com/Celemation/FreeCAD/blob/joel_selection_summary_demo/src/Gui/Selection/SelectionSummary.cpp @@ -183,10 +186,19 @@ App::MeasureElementType PartMeasureTypeCb(App::DocumentObject* ob, const char* s BRepAdaptor_Surface surface(face); switch (surface.GetType()) { - case GeomAbs_Cylinder: { return App::MeasureElementType::CYLINDER; } - case GeomAbs_Plane: { return App::MeasureElementType::PLANE; } + case GeomAbs_Cylinder: { + return App::MeasureElementType::CYLINDER; + } + case GeomAbs_Plane: { + return App::MeasureElementType::PLANE; + } default: { - return App::MeasureElementType::SURFACE; } + TopLoc_Location loc; + Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc); + GeomLib_IsPlanarSurface check(surf, AttachEnginePlane::planarPrecision()); + return check.IsPlanar() ? App::MeasureElementType::PLANE + : App::MeasureElementType::SURFACE; + } } } case TopAbs_SHELL: { diff --git a/src/Mod/Part/App/VectorAdapter.cpp b/src/Mod/Part/App/VectorAdapter.cpp index a2f9cdec58..7801edac71 100644 --- a/src/Mod/Part/App/VectorAdapter.cpp +++ b/src/Mod/Part/App/VectorAdapter.cpp @@ -31,6 +31,7 @@ #include "PrimitiveFeature.h" #include "PartFeature.h" +#include "Attacher.h" #include #include @@ -40,117 +41,163 @@ #include #include #include +#include +#include +using Attacher::AttachEnginePlane; namespace Part { -VectorAdapter::VectorAdapter() : status(false), vector() +VectorAdapter::VectorAdapter() + : status(false) { } -VectorAdapter::VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) : - status(false), vector(), origin(pickedPointIn) +VectorAdapter::VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) + : status(false) + , origin(pickedPointIn) { - Handle(Geom_Surface) surface = BRep_Tool::Surface(faceIn); - if (surface->IsKind(STANDARD_TYPE(Geom_ElementarySurface))) - { - Handle(Geom_ElementarySurface) eSurface = Handle(Geom_ElementarySurface)::DownCast(surface); - gp_Dir direction = eSurface->Axis().Direction(); - vector = direction; + std::vector> funcs = { + [this](const TopoDS_Face& face, const gp_Vec& vec) { + return this->handleElementarySurface(face, vec); + }, + [this](const TopoDS_Face& face, const gp_Vec& vec) { + return this->handlePlanarSurface(face, vec); + } + }; + for (const auto& it : funcs) { + if (it(faceIn, pickedPointIn)) { + break; + } + } +} + +VectorAdapter::VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn) + : status(false) + , origin(pickedPointIn) +{ + TopoDS_Vertex firstVertex = TopExp::FirstVertex(edgeIn, Standard_True); + TopoDS_Vertex lastVertex = TopExp::LastVertex(edgeIn, Standard_True); + vector = convert(lastVertex) - convert(firstVertex); + if (vector.Magnitude() < Precision::Confusion()) { + return; + } vector.Normalize(); - if (faceIn.Orientation() == TopAbs_REVERSED) { - vector.Reverse(); - } - if (surface->IsKind(STANDARD_TYPE(Geom_CylindricalSurface)) || - surface->IsKind(STANDARD_TYPE(Geom_SphericalSurface)) - ) - { - origin = eSurface->Axis().Location().XYZ(); - projectOriginOntoVector(pickedPointIn); - } - else { - origin = pickedPointIn + vector; - } + status = true; - } + projectOriginOntoVector(pickedPointIn); } -VectorAdapter::VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn) : - status(false), vector(), origin(pickedPointIn) +VectorAdapter::VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In) + : status(false) { - TopoDS_Vertex firstVertex = TopExp::FirstVertex(edgeIn, Standard_True); - TopoDS_Vertex lastVertex = TopExp::LastVertex(edgeIn, Standard_True); - vector = convert(lastVertex) - convert(firstVertex); - if (vector.Magnitude() < Precision::Confusion()) { - return; - } - vector.Normalize(); + vector = convert(vertex2In) - convert(vertex1In); + vector.Normalize(); - status = true; - projectOriginOntoVector(pickedPointIn); + //build origin half way. + gp_Vec tempVector = (convert(vertex2In) - convert(vertex1In)); + double mag = tempVector.Magnitude(); + tempVector.Normalize(); + tempVector *= (mag / 2.0); + origin = tempVector + convert(vertex1In); + + status = true; } -VectorAdapter::VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In) : - status(false), vector(), origin() +VectorAdapter::VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2) + : status(false) { - vector = convert(vertex2In) - convert(vertex1In); - vector.Normalize(); + vector = vector2- vector1; + vector.Normalize(); - //build origin half way. - gp_Vec tempVector = (convert(vertex2In) - convert(vertex1In)); - double mag = tempVector.Magnitude(); - tempVector.Normalize(); - tempVector *= (mag / 2.0); - origin = tempVector + convert(vertex1In); + //build origin half way. + gp_Vec tempVector = vector2 - vector1; + double mag = tempVector.Magnitude(); + tempVector.Normalize(); + tempVector *= (mag / 2.0); + origin = tempVector + vector1; - status = true; + status = true; } -VectorAdapter::VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2) : - status(false), vector(), origin() +bool VectorAdapter::handleElementarySurface(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) { - vector = vector2- vector1; - vector.Normalize(); + Handle(Geom_Surface) surface = BRep_Tool::Surface(faceIn); + if (surface->IsKind(STANDARD_TYPE(Geom_ElementarySurface))) { + Handle(Geom_ElementarySurface) eSurface = Handle(Geom_ElementarySurface)::DownCast(surface); + gp_Dir direction = eSurface->Axis().Direction(); + vector = direction; + vector.Normalize(); + if (faceIn.Orientation() == TopAbs_REVERSED) { + vector.Reverse(); + } + if (surface->IsKind(STANDARD_TYPE(Geom_CylindricalSurface)) || + surface->IsKind(STANDARD_TYPE(Geom_SphericalSurface)) + ) { + origin = eSurface->Axis().Location().XYZ(); + projectOriginOntoVector(pickedPointIn); + } + else { + origin = pickedPointIn + vector; + } + status = true; + return true; + } - //build origin half way. - gp_Vec tempVector = vector2 - vector1; - double mag = tempVector.Magnitude(); - tempVector.Normalize(); - tempVector *= (mag / 2.0); - origin = tempVector + vector1; + return false; +} - status = true; +bool VectorAdapter::handlePlanarSurface(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) +{ + Handle(Geom_Surface) surface = BRep_Tool::Surface(faceIn); + GeomLib_IsPlanarSurface check(surface, AttachEnginePlane::planarPrecision()); + if (check.IsPlanar()) { + double u1 {}; + double u2 {}; + double v1 {}; + double v2 {}; + surface->Bounds(u1, u2, v1, v2); + GeomLProp_SLProps prop(surface, u1, v1, 1, Precision::Confusion()); + if (prop.IsNormalDefined()) { + vector = prop.Normal(); + vector.Normalize(); + if (faceIn.Orientation() == TopAbs_REVERSED) { + vector.Reverse(); + } + origin = pickedPointIn + vector; + status = true; + return true; + } + } + + return false; } void VectorAdapter::projectOriginOntoVector(const gp_Vec &pickedPointIn) { - Handle(Geom_Curve) heapLine = new Geom_Line(origin.XYZ(), vector.XYZ()); - gp_Pnt tempPoint(pickedPointIn.XYZ()); - GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine); - if (projection.NbPoints() < 1) { - return; - } - origin.SetXYZ(projection.Point(1).XYZ()); + Handle(Geom_Curve) heapLine = new Geom_Line(origin.XYZ(), vector.XYZ()); + gp_Pnt tempPoint(pickedPointIn.XYZ()); + GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine); + if (projection.NbPoints() >= 1) { + origin.SetXYZ(projection.Point(1).XYZ()); + } } VectorAdapter::operator gp_Lin() const { - gp_Pnt tempOrigin; - tempOrigin.SetXYZ(origin.XYZ()); - return gp_Lin(tempOrigin, gp_Dir(vector)); + gp_Pnt tempOrigin; + tempOrigin.SetXYZ(origin.XYZ()); + return gp_Lin(tempOrigin, gp_Dir(vector)); } /*convert a vertex to vector*/ gp_Vec VectorAdapter::convert(const TopoDS_Vertex &vertex) { - gp_Pnt point = BRep_Tool::Pnt(vertex); - gp_Vec out(point.X(), point.Y(), point.Z()); - return out; + gp_Pnt point = BRep_Tool::Pnt(vertex); + gp_Vec out(point.X(), point.Y(), point.Z()); + return out; } - - } - diff --git a/src/Mod/Part/App/VectorAdapter.h b/src/Mod/Part/App/VectorAdapter.h index 2196401305..5b6b8a2401 100644 --- a/src/Mod/Part/App/VectorAdapter.h +++ b/src/Mod/Part/App/VectorAdapter.h @@ -43,45 +43,46 @@ namespace Part class VectorAdapter { public: - /*!default construction isValid is set to false*/ - VectorAdapter(); - /*!Build a vector from a faceIn - * @param faceIn vector will be normal to plane and equal to cylindrical axis. - * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ - VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn); - /*!Build a vector from an edgeIn - * @param edgeIn vector will be lastPoint - firstPoint. - * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ - VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn); - /*!Build a vector From 2 vertices. - *vector will be equal to @param vertex2In - @param vertex1In.*/ - VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In); - /*!Build a vector From 2 vectors. - *vector will be equal to @param vector2 - @param vector1.*/ - VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2); + /*!default construction isValid is set to false*/ + VectorAdapter(); + /*!Build a vector from a faceIn + * @param faceIn vector will be normal to plane and equal to cylindrical axis. + * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ + VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn); + /*!Build a vector from an edgeIn + * @param edgeIn vector will be lastPoint - firstPoint. + * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ + VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn); + /*!Build a vector From 2 vertices. + *vector will be equal to @param vertex2In - @param vertex1In.*/ + VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In); + /*!Build a vector From 2 vectors. + *vector will be equal to @param vector2 - @param vector1.*/ + VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2); - /*!make sure no errors in vector construction. - * @return true = vector is good. false = vector is NOT good.*/ - bool isValid() const {return status;} - /*!get the calculated vector. - * @return the vector. use isValid to ensure correct results.*/ - operator gp_Vec() const {return vector;}//explicit bombs - /*!build occ line used for extrema calculation*/ - operator gp_Lin() const;//explicit bombs - gp_Vec getPickPoint() const {return origin;} - - operator Base::Vector3d() const { - return Base::Vector3d(vector.X(), vector.Y(), vector.Z()); - } + /*!make sure no errors in vector construction. + * @return true = vector is good. false = vector is NOT good.*/ + bool isValid() const {return status;} + /*!get the calculated vector. + * @return the vector. use isValid to ensure correct results.*/ + explicit operator gp_Vec() const {return vector;} + /*!build occ line used for extrema calculation*/ + explicit operator gp_Lin() const; + gp_Vec getPickPoint() const {return origin;} + explicit operator Base::Vector3d() const { + return Base::Vector3d(vector.X(), vector.Y(), vector.Z()); + } static gp_Vec convert(const TopoDS_Vertex& vertex); - private: - void projectOriginOntoVector(const gp_Vec &pickedPointIn); - bool status; - gp_Vec vector; - gp_Vec origin; +private: + void projectOriginOntoVector(const gp_Vec &pickedPointIn); + bool handleElementarySurface(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn); + bool handlePlanarSurface(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn); + bool status; + gp_Vec vector; + gp_Vec origin; };