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/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/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/Measure/App/Measurement.cpp b/src/Mod/Measure/App/Measurement.cpp index bd68cd5d9c..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,6 +329,9 @@ double Measurement::length() const // Get the length of one edge TopoDS_Shape shape = getShape(*obj, (*subEl).c_str(), TopAbs_EDGE); + if (shape.IsNull() || shape.Infinite()) { + continue; + } const TopoDS_Edge& edge = TopoDS::Edge(shape); BRepAdaptor_Curve curve(edge); @@ -365,8 +366,8 @@ double Measurement::length() const throw Base::RuntimeError( "Measurement - length - Curve type not currently handled"); } - } // end switch - } // end for + } + } } } return result; @@ -432,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); @@ -684,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(); } } @@ -707,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 } } @@ -742,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 } @@ -818,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); 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..873059b9da 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" @@ -56,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); } @@ -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); } diff --git a/src/Mod/Measure/Gui/TaskMeasure.cpp b/src/Mod/Measure/Gui/TaskMeasure.cpp index d36454690f..53fe55f515 100644 --- a/src/Mod/Measure/Gui/TaskMeasure.cpp +++ b/src/Mod/Measure/Gui/TaskMeasure.cpp @@ -46,9 +46,10 @@ #include #include #include +#include #include -using namespace Gui; +using namespace MeasureGui; namespace { @@ -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); @@ -213,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) { @@ -225,7 +224,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); @@ -241,12 +242,19 @@ 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() +{ + try { + tryUpdate(); + } + catch (const Base::Exception& e) { + e.reportException(); + } +} + +void TaskMeasure::tryUpdate() { App::Document* doc = App::GetApplication().getActiveDocument(); @@ -274,7 +282,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)}; @@ -284,7 +292,7 @@ void TaskMeasure::update() // 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(); } @@ -320,31 +328,34 @@ void TaskMeasure::update() // 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"); @@ -356,9 +367,9 @@ void TaskMeasure::initViewObject() } -void TaskMeasure::close() +void TaskMeasure::closeDialog() { - Control().closeDialog(); + Gui::Control().closeDialog(); } @@ -372,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); } @@ -414,7 +422,7 @@ bool TaskMeasure::apply(bool reset) bool TaskMeasure::reject() { removeObject(); - close(); + closeDialog(); // Abort transaction App::GetApplication().closeActiveTransaction(true); @@ -460,9 +468,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,42 +485,45 @@ 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); } } 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. - this->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 399641d56c..347bbb85b5 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: @@ -59,7 +59,7 @@ public: void invoke(); void update(); - void close(); + void closeDialog(); bool apply(); bool apply(bool reset); bool reject() override; @@ -67,10 +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; @@ -90,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; @@ -103,6 +106,6 @@ private: bool mAutoSave = false; }; -} // namespace Gui +} // namespace MeasureGui #endif // MEASURE_TASKMEASURE_H 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/MeasureClient.cpp b/src/Mod/Part/App/MeasureClient.cpp index 42b53d40d8..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,12 +186,24 @@ 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: { + return App::MeasureElementType::SURFACE; + } case TopAbs_SOLID: { return App::MeasureElementType::VOLUME; } @@ -341,7 +356,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()); } 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/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; }; 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(); } }