From 64b24cd27ddec3bf8ea0af948def230ff1f8029c Mon Sep 17 00:00:00 2001 From: kwahoo2 Date: Fri, 13 Dec 2024 18:32:37 +0100 Subject: [PATCH] Gui: add method to select objects with a 3D ray (#16789) * Implementation of ray picking method for 3d picking * Ray picking logic moved to C++ (cherry picked from commit ed23214c0bce7b70fd1003a7c4612e2d0d7da4cb) * formatting, do not return unecessary dict keys, near plane clipping --- src/Gui/View3DInventor.cpp | 95 ++++++++++++++++++++++++++++++++++++++ src/Gui/View3DInventor.h | 13 ++++++ src/Gui/View3DPy.cpp | 56 ++++++++++++++++++++++ src/Gui/View3DPy.h | 1 + 4 files changed, 165 insertions(+) diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index 0b13430634..1c542629c5 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -47,9 +47,11 @@ # include # include # include +# include #endif #include +#include #include #include #include @@ -70,8 +72,10 @@ #include "View3DInventorViewer.h" #include "View3DPy.h" #include "ViewProvider.h" +#include "ViewProviderDocumentObject.h" #include "WaitCursor.h" +#include "Utilities.h" using namespace Gui; @@ -773,6 +777,97 @@ void View3DInventor::setCurrentViewMode(ViewMode newmode) } } +RayPickInfo View3DInventor::getObjInfoRay(Base::Vector3d* startvec, Base::Vector3d* dirvec) +{ + double vsx, vsy, vsz; + double vdx, vdy, vdz; + vsx = startvec->x; + vsy = startvec->y; + vsz = startvec->z; + vdx = dirvec->x; + vdy = dirvec->y; + vdz = dirvec->z; + // near plane clipping is required to avoid false intersections + float near = 0.1; + + RayPickInfo ret = {.isValid = false, + .point = Base::Vector3d(), + .document = "", + .object = "", + .parentObject = std::nullopt, + .component = std::nullopt, + .subName = std::nullopt}; + SoRayPickAction action(getViewer()->getSoRenderManager()->getViewportRegion()); + action.setRay(SbVec3f(vsx, vsy, vsz), SbVec3f(vdx, vdy, vdz), near); + action.apply(getViewer()->getSoRenderManager()->getSceneGraph()); + SoPickedPoint* Point = action.getPickedPoint(); + + if (!Point) { + return ret; + } + + ret.point = Base::convertTo(Point->getPoint()); + ViewProvider* vp = getViewer()->getViewProviderByPath(Point->getPath()); + if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + if (!vp->isSelectable()) { + return ret; + } + auto vpd = static_cast(vp); + if (vp->useNewSelectionModel()) { + std::string subname; + if (!vp->getElementPicked(Point, subname)) { + return ret; + } + auto obj = vpd->getObject(); + if (!obj) { + return ret; + } + if (!subname.empty()) { + App::ElementNamePair elementName; + auto sobj = App::GeoFeature::resolveElement(obj, subname.c_str(), elementName); + if (!sobj) { + return ret; + } + if (sobj != obj) { + ret.parentObject = obj->getExportName(); + ret.subName = subname; + obj = sobj; + } + subname = !elementName.oldName.empty() ? elementName.oldName : elementName.newName; + } + ret.document = obj->getDocument()->getName(); + ret.object = obj->getNameInDocument(); + ret.component = subname; + ret.isValid = true; + } + else { + ret.document = vpd->getObject()->getDocument()->getName(); + ret.object = vpd->getObject()->getNameInDocument(); + // search for a SoFCSelection node + SoFCDocumentObjectAction objaction; + objaction.apply(Point->getPath()); + if (objaction.isHandled()) { + ret.component = objaction.componentName.getString(); + } + } + // ok, found the node of interest + ret.isValid = true; + } + else { + // custom nodes not in a VP: search for a SoFCSelection node + SoFCDocumentObjectAction objaction; + objaction.apply(Point->getPath()); + if (objaction.isHandled()) { + ret.document = objaction.documentName.getString(); + ret.object = objaction.objectName.getString(); + ret.component = objaction.componentName.getString(); + // ok, found the node of interest + ret.isValid = true; + } + } + return ret; +} + bool View3DInventor::eventFilter(QObject* watched, QEvent* e) { // As long as this widget is a top-level window (either in 'TopLevel' or 'FullScreen' mode) we diff --git a/src/Gui/View3DInventor.h b/src/Gui/View3DInventor.h index 3bfe2b237a..882265183a 100644 --- a/src/Gui/View3DInventor.h +++ b/src/Gui/View3DInventor.h @@ -31,6 +31,7 @@ #include "MDIView.h" +#include "Base/Vector3D.h" class QPrinter; class QStackedWidget; @@ -43,6 +44,16 @@ class View3DPy; class View3DSettings; class NaviCubeSettings; +struct RayPickInfo +{ + bool isValid; + Base::Vector3d point; + std::string document; + std::string object; + std::optional parentObject; + std::optional component; + std::optional subName; +}; class GuiExport GLOverlayWidget : public QWidget { Q_OBJECT @@ -98,6 +109,8 @@ public: * GL widget to get all key events in \a TopLevel or \a Fullscreen mode. */ void setCurrentViewMode(ViewMode b) override; + RayPickInfo getObjInfoRay(Base::Vector3d* startvec, + Base::Vector3d* dirvec); bool setCamera(const char* pCamera); void toggleClippingPlane(); bool hasClippingPlane() const; diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index e2e73772a5..41eae0c32e 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -158,6 +158,14 @@ void View3DInventorPy::init_type() "\n" "Does the same as getObjectInfo() but returns a list of dictionaries or None.\n"); add_noargs_method("getSize",&View3DInventorPy::getSize,"getSize()"); + add_varargs_method("getObjectInfoRay",&View3DInventorPy::getObjectInfoRay, + "getObjectInfoRay(tuple(3D vector,3D vector) or tuple of 6 floats) -> dictionary or None\n" + "\n" + "Vectors represent start point and direction of intersection ray\n" + "Return a dictionary with the name of document, object and component. The\n" + "dictionary also contains the coordinates of the appropriate 3d point of\n" + "the underlying geometry in the scenegraph.\n" + "If no geometry was found 'None' is returned, instead.\n"); add_varargs_method("getPoint",&View3DInventorPy::getPointOnFocalPlane, "Same as getPointOnFocalPlane"); add_varargs_method("getPointOnFocalPlane",&View3DInventorPy::getPointOnFocalPlane, @@ -1501,6 +1509,54 @@ Py::Object View3DInventorPy::getObjectsInfo(const Py::Tuple& args) } } +Py::Object View3DInventorPy::getObjectInfoRay(const Py::Tuple& args) +{ + PyObject* vs; + PyObject* vd; + double vsx, vsy, vsz; + double vdx, vdy, vdz; + Py::Object ret = Py::None(); + if (PyArg_ParseTuple(args.ptr(), + "O!O!", + &Base::VectorPy::Type, + &vs, + &Base::VectorPy::Type, + &vd)) { + Base::Vector3d* startvec = static_cast(vs)->getVectorPtr(); + Base::Vector3d* dirvec = static_cast(vd)->getVectorPtr(); + try { + RayPickInfo pinfo = getView3DIventorPtr()->getObjInfoRay(startvec, dirvec); + if (!pinfo.isValid) { + return ret; + } + Py::Dict dict; + dict.setItem("PickedPoint", Py::asObject(new Base::VectorPy(pinfo.point))); + dict.setItem("Document", Py::String(pinfo.document)); + dict.setItem("Object", Py::String(pinfo.object)); + if (pinfo.parentObject) { + dict.setItem("ParentObject", Py::String(pinfo.parentObject.value())); + } + if (pinfo.component) { + dict.setItem("Component", Py::String(pinfo.component.value())); + } + if (pinfo.subName) { + dict.setItem("SubName", Py::String(pinfo.subName.value())); + } + ret = dict; + } + catch (const Py::Exception&) { + throw; + } + } + else { + PyErr_Clear(); + if (!PyArg_ParseTuple(args.ptr(), "dddddd", &vsx, &vsy, &vsz, &vdx, &vdy, &vdz)) { + throw Py::TypeError("Wrong arguments, two Vectors or six floats expected"); + } + } + return ret; +} + Py::Object View3DInventorPy::getSize() { try { diff --git a/src/Gui/View3DPy.h b/src/Gui/View3DPy.h index 2a63dc7d04..08ad8aa07e 100644 --- a/src/Gui/View3DPy.h +++ b/src/Gui/View3DPy.h @@ -95,6 +95,7 @@ public: Py::Object getObjectInfo(const Py::Tuple&); Py::Object getObjectsInfo(const Py::Tuple&); Py::Object getSize(); + Py::Object getObjectInfoRay(const Py::Tuple&); Py::Object getPointOnFocalPlane(const Py::Tuple&); Py::Object projectPointToLine(const Py::Tuple&); Py::Object getPointOnViewport(const Py::Tuple&);