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
This commit is contained in:
kwahoo2
2024-12-13 18:32:37 +01:00
committed by GitHub
parent c8530ae0a8
commit 64b24cd27d
4 changed files with 165 additions and 0 deletions

View File

@@ -47,9 +47,11 @@
# include <Inventor/nodes/SoOrthographicCamera.h>
# include <Inventor/nodes/SoPerspectiveCamera.h>
# include <Inventor/nodes/SoSeparator.h>
# include <Inventor/SoPickedPoint.h>
#endif
#include <App/Document.h>
#include <App/GeoFeature.h>
#include <Base/Builder3D.h>
#include <Base/Console.h>
#include <Base/Interpreter.h>
@@ -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<Base::Vector3d>(Point->getPoint());
ViewProvider* vp = getViewer()->getViewProviderByPath(Point->getPath());
if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) {
if (!vp->isSelectable()) {
return ret;
}
auto vpd = static_cast<ViewProviderDocumentObject*>(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

View File

@@ -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<std::string> parentObject;
std::optional<std::string> component;
std::optional<std::string> 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;

View File

@@ -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<Base::VectorPy*>(vs)->getVectorPtr();
Base::Vector3d* dirvec = static_cast<Base::VectorPy*>(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 {

View File

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