Gui: Add a context menu to select obstructed items (from RT fork)

As the title says, this is from RT's fork. I only adjusted it a little
bit and removed pie selection which was previously in the
implementation.

To activate context menu - "G, G", then if it can't resolve edges vs
faces, it adds QMenus on top to let user decide which one to select.

Co-authored-by: tetektoza <tetektoza@users.noreply.github.com>
This commit is contained in:
realthunder
2025-06-17 01:47:47 +02:00
committed by tetektoza
parent 04e085cba3
commit c52bfd37de
5 changed files with 340 additions and 2 deletions

View File

@@ -28,6 +28,7 @@
# include <Inventor/events/SoMouseButtonEvent.h>
# include <Inventor/nodes/SoOrthographicCamera.h>
# include <Inventor/nodes/SoPerspectiveCamera.h>
# include <Inventor/SoPickedPoint.h>
# include <QApplication>
# include <QDialog>
# include <QDomDocument>
@@ -47,6 +48,7 @@
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/DocumentObjectGroup.h>
#include <App/DocumentObserver.h>
#include <App/GeoFeature.h>
#include <App/GeoFeatureGroupExtension.h>
#include <App/Part.h>
@@ -73,6 +75,7 @@
#include "OverlayManager.h"
#include "SceneInspector.h"
#include "Selection.h"
#include "Selection/SelectionView.h"
#include "SelectionObject.h"
#include "SoFCOffscreenRenderer.h"
#include "TextureMapping.h"
@@ -3953,6 +3956,114 @@ bool StdCmdAlignToSelection::isActive()
return getGuiApplication()->sendHasMsgToActiveView("AlignToSelection");
}
//===========================================================================
// Std_PickGeometry
//===========================================================================
DEF_STD_CMD_A(StdCmdPickGeometry)
StdCmdPickGeometry::StdCmdPickGeometry()
: Command("Std_PickGeometry")
{
sGroup = "View";
sMenuText = QT_TR_NOOP("Pick geometry");
sToolTipText = QT_TR_NOOP("Pick hidden geometries under the mouse cursor in 3D view.\n"
"This command is supposed to be activated by keyboard shortcut.");
sWhatsThis = "Std_PickGeometry";
sStatusTip = sToolTipText;
sAccel = "G, G";
eType = NoTransaction | AlterSelection;
}
void StdCmdPickGeometry::activated(int iMsg)
{
Q_UNUSED(iMsg);
// Get the active view
auto view = Application::Instance->activeView();
if (!view || !view->isDerivedFrom(View3DInventor::getClassTypeId())) {
return;
}
auto view3d = static_cast<View3DInventor*>(view);
auto viewer = view3d->getViewer();
if (!viewer) {
return;
}
// Get cursor position in the viewer
QPoint pos = QCursor::pos();
QWidget* widget = viewer->getGLWidget();
if (!widget) {
return;
}
QPoint local = widget->mapFromGlobal(pos);
SbVec2s point;
point[0] = local.x();
point[1] = widget->height() - local.y() - 1;
// Use ray picking to get all objects under cursor
SoRayPickAction pickAction(viewer->getSoRenderManager()->getViewportRegion());
pickAction.setPoint(point);
pickAction.setRadius(viewer->getPickRadius());
pickAction.setPickAll(true); // Get all objects under cursor
pickAction.apply(viewer->getSoRenderManager()->getSceneGraph());
const SoPickedPointList& pplist = pickAction.getPickedPointList();
if (pplist.getLength() == 0) {
return;
}
// Convert picked points to PickData list
std::vector<PickData> selections;
for (int i = 0; i < pplist.getLength(); ++i) {
SoPickedPoint* pp = pplist[i];
if (!pp || !pp->getPath())
continue;
ViewProvider* vp = viewer->getViewProviderByPath(pp->getPath());
if (!vp)
continue;
// Cast to ViewProviderDocumentObject to get the object
auto vpDoc = dynamic_cast<Gui::ViewProviderDocumentObject*>(vp);
if (!vpDoc)
continue;
App::DocumentObject* obj = vpDoc->getObject();
if (!obj)
continue;
std::string elementName = vp->getElement(pp->getDetail());
// Create PickData with selection information
PickData pickData;
pickData.obj = obj;
pickData.element = elementName;
pickData.docName = obj->getDocument()->getName();
pickData.objName = obj->getNameInDocument();
pickData.subName = elementName;
selections.push_back(pickData);
}
if (selections.empty()) {
return;
}
// Use SelectionMenu to display and handle the pick menu
SelectionMenu contextMenu(widget);
contextMenu.doPick(selections);
}
bool StdCmdPickGeometry::isActive()
{
auto view = qobject_cast<View3DInventor*>(getMainWindow()->activeWindow());
return view != nullptr;
}
//===========================================================================
// Instantiation
//===========================================================================
@@ -3985,6 +4096,7 @@ void CreateViewStdCommands()
rcCmdMgr.addCommand(new StdRecallWorkingView());
rcCmdMgr.addCommand(new StdCmdViewGroup());
rcCmdMgr.addCommand(new StdCmdAlignToSelection());
rcCmdMgr.addCommand(new StdCmdPickGeometry());
rcCmdMgr.addCommand(new StdCmdViewExample1());
rcCmdMgr.addCommand(new StdCmdViewExample2());

View File

@@ -48,6 +48,8 @@
#include "Navigation/NavigationStyle.h"
#include "Navigation/NavigationStylePy.h"
#include "Application.h"
#include "Command.h"
#include "Action.h"
#include "Inventor/SoMouseWheelEvent.h"
#include "MenuManager.h"
#include "MouseSelection.h"
@@ -1953,6 +1955,8 @@ void NavigationStyle::openPopupMenu(const SbVec2s& position)
QAction *item = navMenuGroup->addAction(name);
navMenu->addAction(item);
item->setCheckable(true);
QByteArray data(style.first.getName());
item->setData(data);
if (const Base::Type item_style = style.first; item_style != this->getTypeId()) {
auto triggeredFun = [this, item_style](){
@@ -1970,7 +1974,64 @@ void NavigationStyle::openPopupMenu(const SbVec2s& position)
item->setChecked(true);
}
contextMenu->popup(QCursor::pos());
// Add Pick Geometry option if there are objects under cursor
bool separator = false;
auto posAction = !contextMenu->actions().empty() ? contextMenu->actions().front() : nullptr;
// Get picked objects at position
SoRayPickAction rp(viewer->getSoRenderManager()->getViewportRegion());
rp.setPoint(position);
rp.setRadius(viewer->getPickRadius());
rp.setPickAll(true);
rp.apply(viewer->getSoRenderManager()->getSceneGraph());
const SoPickedPointList& pplist = rp.getPickedPointList();
QAction *pickAction = nullptr;
if (pplist.getLength() > 0) {
separator = true;
auto cmd = Application::Instance->commandManager().getCommandByName("Std_PickGeometry");
if (cmd) {
pickAction = new QAction(cmd->getAction()->text(), contextMenu);
pickAction->setShortcut(cmd->getAction()->shortcut());
} else {
pickAction = new QAction(QObject::tr("Pick geometry"), contextMenu);
}
if (posAction) {
contextMenu->insertAction(posAction, pickAction);
contextMenu->insertSeparator(posAction);
} else {
contextMenu->addAction(pickAction);
}
}
if (separator && posAction)
contextMenu->insertSeparator(posAction);
QAction* used = contextMenu->exec(QCursor::pos());
if (used && navMenuGroup->actions().indexOf(used) >= 0 && used->isChecked()) {
QByteArray type = used->data().toByteArray();
QWidget* widget = viewer->getWidget();
while (widget && !widget->inherits("Gui::View3DInventor"))
widget = widget->parentWidget();
if (widget) {
// this is the widget where the viewer is embedded
Base::Type style = Base::Type::fromName((const char*)type);
if (style != this->getTypeId()) {
QEvent* event = new NavigationStyleEvent(style);
QApplication::postEvent(widget, event);
}
}
return;
}
if (pickAction && used == pickAction) {
// Execute the Pick Geometry command at this position
auto cmd = Application::Instance->commandManager().getCommandByName("Std_PickGeometry");
if (cmd && cmd->isActive()) {
cmd->invoke(0);
}
}
}
PyObject* NavigationStyle::getPyObject()

View File

@@ -42,6 +42,7 @@
#include "BitmapFactory.h"
#include "Command.h"
#include "Document.h"
#include "ViewProvider.h"
FC_LOG_LEVEL_INIT("Selection", true, true, true)
@@ -704,4 +705,127 @@ void SelectionView::onEnablePickList()
/// @endcond
// SelectionMenu implementation
SelectionMenu::SelectionMenu(QWidget *parent)
: QMenu(parent)
{
}
struct ElementInfo {
QMenu *menu = nullptr;
QIcon icon;
std::vector<int> indices;
};
struct SubMenuInfo {
QMenu *menu = nullptr;
// Map from sub-object label to map from object path to element info
std::map<std::string, std::map<std::string, ElementInfo> > items;
};
PickData SelectionMenu::doPick(const std::vector<PickData> &sels)
{
clear();
std::map<std::string, std::vector<int>> typeGroups;
// Group selections by element type
for (int i = 0; i < (int)sels.size(); ++i) {
const auto &sel = sels[i];
std::string elementType = "Other";
// Extract element type from element name
if (!sel.element.empty()) {
if (sel.element.find("Face") == 0) elementType = "Face";
else if (sel.element.find("Edge") == 0) elementType = "Edge";
else if (sel.element.find("Vertex") == 0) elementType = "Vertex";
else if (sel.element.find("Wire") == 0) elementType = "Wire";
else if (sel.element.find("Shell") == 0) elementType = "Shell";
else if (sel.element.find("Solid") == 0) elementType = "Solid";
}
typeGroups[elementType].push_back(i);
}
// Create menu structure
for (const auto &typeGroup : typeGroups) {
const std::string &typeName = typeGroup.first;
const std::vector<int> &indices = typeGroup.second;
if (indices.empty()) continue;
QMenu *typeMenu = nullptr;
if (typeGroups.size() > 1) {
typeMenu = addMenu(QString::fromUtf8(typeName.c_str()));
}
for (int idx : indices) {
const auto &sel = sels[idx];
QString text = QString::fromUtf8(sel.obj->Label.getValue());
if (!sel.element.empty()) {
text += QString::fromLatin1(" (") + QString::fromUtf8(sel.element.c_str()) + QString::fromLatin1(")");
}
// Get icon from view provider
QIcon icon;
auto vp = Application::Instance->getViewProvider(sel.obj);
if (vp) {
icon = vp->getIcon();
}
QAction *action;
if (typeMenu) {
action = typeMenu->addAction(icon, text);
} else {
action = addAction(icon, text);
}
action->setData(idx);
}
}
QAction* picked = this->exec(QCursor::pos());
return onPicked(picked, sels);
}
PickData SelectionMenu::onPicked(QAction *picked, const std::vector<PickData> &sels)
{
Gui::Selection().rmvPreselect();
if (!picked)
return PickData{};
int index = picked->data().toInt();
if (index >= 0 && index < (int)sels.size()) {
const auto &sel = sels[index];
if (sel.obj) {
Gui::Selection().addSelection(sel.docName.c_str(),
sel.objName.c_str(),
sel.subName.c_str());
}
return sel;
}
return PickData{};
}
void SelectionMenu::onHover(QAction *action)
{
if (!action)
return;
// For now, just clear preselection on hover
// Could be enhanced to preselect the hovered item
Gui::Selection().rmvPreselect();
}
bool SelectionMenu::eventFilter(QObject *obj, QEvent *event)
{
return QMenu::eventFilter(obj, event);
}
void SelectionMenu::leaveEvent(QEvent *e)
{
Gui::Selection().rmvPreselect();
QMenu::leaveEvent(e);
}
#include "moc_SelectionView.cpp"

View File

@@ -25,6 +25,8 @@
#include "DockWindow.h"
#include "Selection.h"
#include <QMenu>
#include <QPointer>
class QListWidget;
@@ -112,6 +114,44 @@ private:
};
} // namespace DockWnd
// Simple selection data structure
struct PickData {
App::DocumentObject* obj;
std::string element;
std::string docName;
std::string objName;
std::string subName;
};
// Add SelectionMenu class outside the DockWnd namespace
class GuiExport SelectionMenu : public QMenu {
Q_OBJECT
public:
SelectionMenu(QWidget *parent=nullptr);
/** Populate and show the menu for picking geometry elements.
*
* @param sels: a list of geometry element references
* @return Return the picked geometry reference
*
* The menu will be divided into submenus that are grouped by element type.
*/
PickData doPick(const std::vector<PickData> &sels);
public Q_SLOTS:
void onHover(QAction *);
protected:
bool eventFilter(QObject *, QEvent *) override;
void leaveEvent(QEvent *e) override;
PickData onPicked(QAction *, const std::vector<PickData> &sels);
private:
QPointer<QMenu> activeMenu;
QPointer<QAction> activeAction;
};
} // namespace Gui
#endif // GUI_DOCKWND_SELECTIONVIEW_H

View File

@@ -737,7 +737,8 @@ MenuItem* StdWorkbench::setupMenuBar() const
<< "Separator"
<< "Std_ProjectUtil"
<< "Std_DlgParameter"
<< "Std_DlgCustomize";
<< "Std_DlgCustomize"
<< "Std_PickGeometry";
// Macro
auto macro = new MenuItem( menuBar );