From c52bfd37de934a935a77e5add2a4d1b0350b109e Mon Sep 17 00:00:00 2001 From: realthunder Date: Tue, 17 Jun 2025 01:47:47 +0200 Subject: [PATCH] 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 --- src/Gui/CommandView.cpp | 112 ++++++++++++++++++++++ src/Gui/Navigation/NavigationStyle.cpp | 63 ++++++++++++- src/Gui/Selection/SelectionView.cpp | 124 +++++++++++++++++++++++++ src/Gui/Selection/SelectionView.h | 40 ++++++++ src/Gui/Workbench.cpp | 3 +- 5 files changed, 340 insertions(+), 2 deletions(-) diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 65e1502aa0..e54dc65c74 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -28,6 +28,7 @@ # include # include # include +# include # include # include # include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -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(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 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(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(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()); diff --git a/src/Gui/Navigation/NavigationStyle.cpp b/src/Gui/Navigation/NavigationStyle.cpp index 9a5761bb82..d70b3ceec1 100644 --- a/src/Gui/Navigation/NavigationStyle.cpp +++ b/src/Gui/Navigation/NavigationStyle.cpp @@ -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() diff --git a/src/Gui/Selection/SelectionView.cpp b/src/Gui/Selection/SelectionView.cpp index c40a17fa1b..a28d420d95 100644 --- a/src/Gui/Selection/SelectionView.cpp +++ b/src/Gui/Selection/SelectionView.cpp @@ -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 indices; +}; + +struct SubMenuInfo { + QMenu *menu = nullptr; + // Map from sub-object label to map from object path to element info + std::map > items; +}; + +PickData SelectionMenu::doPick(const std::vector &sels) +{ + clear(); + + std::map> 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 &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 &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" diff --git a/src/Gui/Selection/SelectionView.h b/src/Gui/Selection/SelectionView.h index 318d67d6bc..28389691b7 100644 --- a/src/Gui/Selection/SelectionView.h +++ b/src/Gui/Selection/SelectionView.h @@ -25,6 +25,8 @@ #include "DockWindow.h" #include "Selection.h" +#include +#include 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 &sels); + +public Q_SLOTS: + void onHover(QAction *); + +protected: + bool eventFilter(QObject *, QEvent *) override; + void leaveEvent(QEvent *e) override; + PickData onPicked(QAction *, const std::vector &sels); + +private: + QPointer activeMenu; + QPointer activeAction; +}; + } // namespace Gui #endif // GUI_DOCKWND_SELECTIONVIEW_H diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index bf44bf0523..c244079487 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -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 );