Merge pull request #23291 from 3x380V/measurement

Measure: Useability improvements
This commit is contained in:
Benjamin Nauck
2025-08-26 06:51:50 +02:00
committed by GitHub
16 changed files with 384 additions and 208 deletions

View File

@@ -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());
}

View File

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

View File

@@ -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;
}
}

View File

@@ -79,6 +79,7 @@ public:
private:
static bool isSupported(App::MeasureElementType type);
void onChanged(const App::Property* prop) override;
};

View File

@@ -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<gp_Pln> 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);

View File

@@ -32,7 +32,6 @@
#include <Gui/WidgetFactory.h>
#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);
}

View File

@@ -20,8 +20,13 @@
**************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QApplication>
#endif
#include <App/Application.h>
#include <App/Document.h>
#include <Gui/Action.h>
#include <Gui/Application.h>
#include <Gui/Command.h>
#include <Gui/Control.h>
@@ -30,6 +35,7 @@
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#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<MeasureGui::QuickMeasure>(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<MeasureGui::QuickMeasure> 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);
}

View File

@@ -46,9 +46,10 @@
#include <QSettings>
#include <QAction>
#include <QMenu>
#include <QShortcut>
#include <QToolTip>
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<Measure::MeasurePython>() is not yet supported because
// getClassName() will determine the string 'App::FeaturePythonT<FeatureT>' instead
// of 'Measure::MeasurePython'
auto featurePython = doc->addObject("Measure::MeasurePython", measureType->label.c_str());
_mMeasureObject = dynamic_cast<Measure::MeasureBase*>(featurePython);
@@ -241,12 +242,19 @@ Measure::MeasureBase* TaskMeasure::createObject(const App::MeasureType* measureT
_mMeasureObject = dynamic_cast<Measure::MeasureBase*>(
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<MeasureGui::ViewProviderMeasureBase*>(viewObject)->positionAnno(_mMeasureObject);
dynamic_cast<MeasureGui::ViewProviderMeasureBase*>(viewObject)->positionAnno(measure);
// Set the ShowDelta Property if it exists on the measurements view object
auto* prop = viewObject->getPropertyByName<App::PropertyBool>("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<App::DocumentObjectGroup>()) {
obj = doc->addObject<App::DocumentObjectGroup>(measurementGroupName,
true,
"MeasureGui::ViewProviderMeasureGroup");
auto group = dynamic_cast<App::DocumentObjectGroup*>(doc->getObject(measurementGroupName));
if (!group || !group->isValid()) {
group = doc->addObject<App::DocumentObjectGroup>(measurementGroupName,
true,
"MeasureGui::ViewProviderMeasureGroup");
}
auto group = static_cast<App::DocumentObjectGroup*>(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<QKeyEvent*>(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)

View File

@@ -41,10 +41,10 @@
#include <Gui/TaskView/TaskView.h>
#include <Gui/Selection/Selection.h>
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

View File

@@ -1377,7 +1377,7 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vector<App::DocumentObjec
else {
TopLoc_Location loc;
Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc);
GeomLib_IsPlanarSurface check(surf);
GeomLib_IsPlanarSurface check(surf, precision);
if (check.IsPlanar()) {
plane = check.Plan();
}
@@ -2095,11 +2095,17 @@ Base::Placement AttachEnginePlane::_calculateAttachedPlacement(
//reuse Attacher3d
Base::Placement plm;
AttachEngine3D attacher3D;
attacher3D.precision = precision;
attacher3D.setUp(*this);
plm = attacher3D._calculateAttachedPlacement(objs,subs,origPlacement);
return plm;
}
double AttachEnginePlane::planarPrecision()
{
return 2.0e-7; // NOLINT
}
//=================================================================================
TYPESYSTEM_SOURCE(Attacher::AttachEngineLine, Attacher::AttachEngine)

View File

@@ -391,6 +391,7 @@ public: //members
bool mapReverse = false;
double attachParameter = 0.0;
double surfU = 0.0, surfV = 0.0;
double precision = 1.0e-7;
Base::Placement attachmentOffset;
/**
@@ -489,6 +490,7 @@ public:
const std::vector<App::DocumentObject*> &objs,
const std::vector<std::string> &subs,
const Base::Placement &origPlacement) const override;
static double planarPrecision();
};
//attacher specialized for datum lines

View File

@@ -43,6 +43,7 @@
#include <ShapeAnalysis_Edge.hxx>
#include <gp_Circ.hxx>
#include <BRepBuilderAPI_Copy.hxx>
#include <GeomLib_IsPlanarSurface.hxx>
#include <DatumFeature.h>
#include <App/Application.h>
@@ -56,12 +57,14 @@
#include <Base/Rotation.h>
#include <Base/Vector3D.h>
#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<MeasureAreaInfo>(false, 0.0, Base::Matrix4D());
}

View File

@@ -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);
}

View File

@@ -31,6 +31,7 @@
#include "PrimitiveFeature.h"
#include "PartFeature.h"
#include "Attacher.h"
#include <Standard_Type.hxx>
#include <Geom_CylindricalSurface.hxx>
@@ -40,117 +41,163 @@
#include <Geom_Line.hxx>
#include <GeomAPI_ProjectPointOnCurve.hxx>
#include <BRep_Tool.hxx>
#include <GeomLib_IsPlanarSurface.hxx>
#include <GeomLProp_SLProps.hxx>
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<std::function<bool(const TopoDS_Face &, const gp_Vec &)>> 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;
}
}

View File

@@ -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;
};

View File

@@ -141,7 +141,7 @@ public:
throw WrongSupportException();
}
if (!subshape.isPlanar()) {
if (!subshape.isPlanar(Attacher::AttachEnginePlane::planarPrecision())) {
throw SupportNotPlanarException();
}
}