diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index e54dc65c74..6764e67c83 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -4036,7 +4036,15 @@ void StdCmdPickGeometry::activated(int iMsg) if (!obj) continue; + // Get element information - handle sub-objects like Assembly parts std::string elementName = vp->getElement(pp->getDetail()); + std::string subName; + + // Try to get more detailed sub-object information + bool hasSubObject = false; + if (vp->getElementPicked(pp, subName)) { + hasSubObject = true; + } // Create PickData with selection information PickData pickData; @@ -4044,7 +4052,7 @@ void StdCmdPickGeometry::activated(int iMsg) pickData.element = elementName; pickData.docName = obj->getDocument()->getName(); pickData.objName = obj->getNameInDocument(); - pickData.subName = elementName; + pickData.subName = hasSubObject ? subName : elementName; selections.push_back(pickData); } diff --git a/src/Gui/Inventor/So3DAnnotation.cpp b/src/Gui/Inventor/So3DAnnotation.cpp index 50ba2b10ad..2a2fb91811 100644 --- a/src/Gui/Inventor/So3DAnnotation.cpp +++ b/src/Gui/Inventor/So3DAnnotation.cpp @@ -29,17 +29,23 @@ #include #endif #include +#include #endif #include "So3DAnnotation.h" +#include using namespace Gui; SO_ELEMENT_SOURCE(SoDelayedAnnotationsElement); +bool SoDelayedAnnotationsElement::isProcessingDelayedPaths = false; + void SoDelayedAnnotationsElement::init(SoState* state) { SoElement::init(state); + priorityPaths.clear(); + paths.truncate(0); } void SoDelayedAnnotationsElement::initClass() @@ -49,21 +55,72 @@ void SoDelayedAnnotationsElement::initClass() SO_ENABLE(SoGLRenderAction, SoDelayedAnnotationsElement); } -void SoDelayedAnnotationsElement::addDelayedPath(SoState* state, SoPath* path) +void SoDelayedAnnotationsElement::addDelayedPath(SoState* state, SoPath* path, int priority) { auto elt = static_cast(state->getElementNoPush(classStackIndex)); + // add to priority-aware storage if priority has been specified + if (priority > 0) { + elt->priorityPaths.emplace_back(path, priority); + return; + } + elt->paths.append(path); } SoPathList SoDelayedAnnotationsElement::getDelayedPaths(SoState* state) { auto elt = static_cast(state->getElementNoPush(classStackIndex)); - auto copy = elt->paths; - + + // if we don't have priority paths, just return normal delayed paths + if (elt->priorityPaths.empty()) { + auto copy = elt->paths; + elt->paths.truncate(0); + return copy; + } + + // sort by priority (lower numbers render first) + std::stable_sort(elt->priorityPaths.begin(), elt->priorityPaths.end(), + [](const PriorityPath& a, const PriorityPath& b) { + return a.priority < b.priority; + }); + + SoPathList sortedPaths; + for (const auto& priorityPath : elt->priorityPaths) { + sortedPaths.append(priorityPath.path); + } + + // Clear storage + elt->priorityPaths.clear(); elt->paths.truncate(0); + + return sortedPaths; +} - return copy; +void SoDelayedAnnotationsElement::processDelayedPathsWithPriority(SoState* state, SoGLRenderAction* action) +{ + auto elt = static_cast(state->getElementNoPush(classStackIndex)); + + if (elt->priorityPaths.empty()) return; + + std::stable_sort(elt->priorityPaths.begin(), elt->priorityPaths.end(), + [](const PriorityPath& a, const PriorityPath& b) { + return a.priority < b.priority; + }); + + isProcessingDelayedPaths = true; + + for (const auto& priorityPath : elt->priorityPaths) { + SoPathList singlePath; + singlePath.append(priorityPath.path); + + action->apply(singlePath, TRUE); + } + + isProcessingDelayedPaths = false; + + elt->priorityPaths.clear(); + elt->paths.truncate(0); } SO_NODE_SOURCE(So3DAnnotation); diff --git a/src/Gui/Inventor/So3DAnnotation.h b/src/Gui/Inventor/So3DAnnotation.h index 0e2a62c42a..dbd29e5e4d 100644 --- a/src/Gui/Inventor/So3DAnnotation.h +++ b/src/Gui/Inventor/So3DAnnotation.h @@ -27,6 +27,7 @@ #include #include #include +#include namespace Gui { @@ -43,6 +44,15 @@ protected: SoDelayedAnnotationsElement& operator=(const SoDelayedAnnotationsElement& other) = default; SoDelayedAnnotationsElement& operator=(SoDelayedAnnotationsElement&& other) noexcept = default; + // internal structure to hold path with it's rendering + // priority (lower renders first) + struct PriorityPath { + SoPath* path; + int priority; + + PriorityPath(SoPath* p, int pr = 0) : path(p), priority(pr) {} + }; + public: SoDelayedAnnotationsElement(const SoDelayedAnnotationsElement& other) = delete; SoDelayedAnnotationsElement(SoDelayedAnnotationsElement&& other) noexcept = delete; @@ -51,8 +61,13 @@ public: static void initClass(); - static void addDelayedPath(SoState* state, SoPath* path); + static void addDelayedPath(SoState* state, SoPath* path, int priority = 0); + static SoPathList getDelayedPaths(SoState* state); + + static void processDelayedPathsWithPriority(SoState* state, SoGLRenderAction* action); + + static bool isProcessingDelayedPaths; SbBool matches([[maybe_unused]] const SoElement* element) const override { @@ -64,6 +79,10 @@ public: return nullptr; } +private: + // priority-aware paths + std::vector priorityPaths; + SoPathList paths; }; diff --git a/src/Gui/Selection/Selection.cpp b/src/Gui/Selection/Selection.cpp index baa7b87669..11d0c14082 100644 --- a/src/Gui/Selection/Selection.cpp +++ b/src/Gui/Selection/Selection.cpp @@ -2604,3 +2604,13 @@ PyObject *SelectionSingleton::sGetSelectionFromStack(PyObject * /*self*/, PyObje } PY_CATCH; } + +bool SelectionSingleton::pickGeometryActive = false; + +bool SelectionSingleton::isPickGeometryActive() { + return pickGeometryActive; +} + +void SelectionSingleton::setPickGeometryActive(bool active) { + pickGeometryActive = active; +} diff --git a/src/Gui/Selection/Selection.h b/src/Gui/Selection/Selection.h index f207474f26..2faf778350 100644 --- a/src/Gui/Selection/Selection.h +++ b/src/Gui/Selection/Selection.h @@ -291,6 +291,8 @@ private: class GuiExport SelectionSingleton : public Base::Subject { public: + static bool pickGeometryActive; + struct SelObj { const char* DocName; const char* FeatName; @@ -409,6 +411,9 @@ public: */ void setVisible(VisibleState visible); + static bool isPickGeometryActive(); + static void setPickGeometryActive(bool active); + /// signal on new object boost::signals2::signal signalSelectionChanged; diff --git a/src/Gui/Selection/SelectionView.cpp b/src/Gui/Selection/SelectionView.cpp index 46ef5a4493..ad000310e3 100644 --- a/src/Gui/Selection/SelectionView.cpp +++ b/src/Gui/Selection/SelectionView.cpp @@ -31,11 +31,15 @@ #include #include #include +#include #endif #include #include +#include #include +#include +#include #include "SelectionView.h" #include "Application.h" @@ -727,75 +731,100 @@ struct SubMenuInfo { PickData SelectionMenu::doPick(const std::vector &sels) { clear(); + Gui::Selection().setPickGeometryActive(true); - // store reference to selections for use in onHover - currentSelections = &sels; + std::vector selsCopy = sels; + currentSelections = &selsCopy; - 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); - // Connect submenu hovered signals as well - connect(typeMenu, &QMenu::hovered, this, &SelectionMenu::onHover); - } else { - action = addAction(icon, text); - } - action->setData(idx); - } - } + std::map menus; + processSelections(selsCopy, menus); + buildMenuStructure(menus, selsCopy); QAction* picked = this->exec(QCursor::pos()); - return onPicked(picked, sels); + return onPicked(picked, selsCopy); +} + +void SelectionMenu::processSelections(std::vector &selections, std::map &menus) +{ + std::map icons; + std::set createdElementTypes; + std::set processedItems; + + for (int i = 0; i < (int)selections.size(); ++i) { + const auto &sel = selections[i]; + + App::DocumentObject* sobj = getSubObject(sel); + std::string elementType = extractElementType(sel); + std::string objKey = createObjectKey(sel); + std::string itemId = elementType + "|" + std::string(sobj->Label.getValue()) + "|" + sel.subName; + + if (processedItems.find(itemId) != processedItems.end()) { + continue; + } + processedItems.insert(itemId); + + QIcon icon = getOrCreateIcon(sobj, icons); + + auto &elementInfo = menus[elementType].items[sobj->Label.getValue()][objKey]; + elementInfo.icon = icon; + elementInfo.indices.push_back(i); + + addGeoFeatureTypes(sobj, menus, createdElementTypes); + addWholeObjectSelection(sel, sobj, selections, menus, icon); + } +} + +void SelectionMenu::buildMenuStructure(std::map &menus, const std::vector &selections) +{ + std::vector preferredOrder = {"Object", "Solid", "Face", "Edge", "Vertex", "Wire", "Shell", "Compound", "CompSolid"}; + std::vector::iterator> menuArray; + menuArray.reserve(menus.size()); + + for (const auto& category : preferredOrder) { + auto it = menus.find(category); + if (it != menus.end()) { + menuArray.push_back(it); + } + } + + for (auto it = menus.begin(); it != menus.end(); ++it) { + if (std::find(preferredOrder.begin(), preferredOrder.end(), it->first) == preferredOrder.end()) { + menuArray.push_back(it); + } + } + + for (auto it : menuArray) { + auto &v = *it; + auto &info = v.second; + + if (info.items.empty()) { + continue; + } + + info.menu = addMenu(QString::fromUtf8(v.first.c_str())); + bool groupMenu = shouldGroupMenu(info); + + for (auto &vv : info.items) { + const std::string &label = vv.first; + + for (auto &vvv : vv.second) { + auto &elementInfo = vvv.second; + + if (!groupMenu) { + createFlatMenu(elementInfo, info.menu, label, v.first, selections); + } else { + createGroupedMenu(elementInfo, info.menu, label, v.first, selections); + } + } + } + } } PickData SelectionMenu::onPicked(QAction *picked, const std::vector &sels) { + // Clear the PickGeometry active flag when menu is done + Gui::Selection().setPickGeometryActive(false); + Gui::Selection().rmvPreselect(); if (!picked) return PickData{}; @@ -831,13 +860,14 @@ void SelectionMenu::onHover(QAction *action) if (!sel.obj) return; - // extract just the element name (e.g., "Face1") from subName for preselection - std::string elementName = sel.element; - if (!elementName.empty()) { - // use TreeView as message source for menu hover + // For hover preselection, use the whole object path like solids do + // This provides consistent behavior and better highlighting for assembly elements + if (!sel.subName.empty()) { + // Always use the full original path for consistent behavior + // This matches how solids behave and provides better highlighting Gui::Selection().setPreselect(sel.docName.c_str(), sel.objName.c_str(), - elementName.c_str(), + sel.subName.c_str(), 0, 0, 0, SelectionChanges::MsgSource::TreeView); } @@ -854,4 +884,205 @@ void SelectionMenu::leaveEvent(QEvent *e) QMenu::leaveEvent(e); } +App::DocumentObject* SelectionMenu::getSubObject(const PickData &sel) +{ + App::DocumentObject* sobj = sel.obj; + if (!sel.subName.empty()) { + sobj = sel.obj->getSubObject(sel.subName.c_str()); + if (!sobj) { + sobj = sel.obj; + } + } + return sobj; +} + +std::string SelectionMenu::extractElementType(const PickData &sel) +{ + std::string actualElement; + + if (!sel.element.empty()) { + actualElement = sel.element; + } else if (!sel.subName.empty()) { + const char *elementName = Data::findElementName(sel.subName.c_str()); + if (elementName && elementName[0]) { + actualElement = elementName; + } + } + + if (!actualElement.empty()) { + std::size_t pos = actualElement.find_first_of("0123456789"); + if (pos != std::string::npos) { + return actualElement.substr(0, pos); + } + return actualElement; + } + + return "Other"; +} + +std::string SelectionMenu::createObjectKey(const PickData &sel) +{ + std::string objKey = std::string(sel.objName); + if (!sel.subName.empty()) { + std::string subNameNoElement = sel.subName; + const char *elementName = Data::findElementName(sel.subName.c_str()); + if (elementName && elementName[0]) { + std::string elementStr = elementName; + std::size_t elementPos = subNameNoElement.rfind(elementStr); + if (elementPos != std::string::npos) { + subNameNoElement = subNameNoElement.substr(0, elementPos); + } + } + objKey += "." + subNameNoElement; + } + return objKey; +} + +QIcon SelectionMenu::getOrCreateIcon(App::DocumentObject* sobj, std::map &icons) +{ + auto &icon = icons[sobj]; + if (icon.isNull()) { + auto vp = Application::Instance->getViewProvider(sobj); + if (vp) + icon = vp->getIcon(); + } + return icon; +} + +void SelectionMenu::addGeoFeatureTypes(App::DocumentObject* sobj, std::map &menus, std::set &createdTypes) +{ + auto geoFeature = dynamic_cast(sobj->getLinkedObject(true)); + if (geoFeature) { + std::vector types = geoFeature->getElementTypes(true); + for (const char* type : types) { + if (type && type[0] && createdTypes.find(type) == createdTypes.end()) { + menus[type]; + createdTypes.insert(type); + } + } + } +} + +void SelectionMenu::addWholeObjectSelection(const PickData &sel, App::DocumentObject* sobj, + std::vector &selections, std::map &menus, const QIcon &icon) +{ + if (sel.subName.empty()) return; + + std::string actualElement = extractElementType(sel) != "Other" ? sel.element : ""; + if (actualElement.empty() && !sel.subName.empty()) { + const char *elementName = Data::findElementName(sel.subName.c_str()); + if (elementName) actualElement = elementName; + } + if (actualElement.empty()) return; + + bool shouldAdd = false; + if (sobj && sobj != sel.obj) { + std::string typeName = sobj->getTypeId().getName(); + if (typeName == "App::Part" || typeName == "PartDesign::Body") { + shouldAdd = true; + } else { + auto geoFeature = dynamic_cast(sobj->getLinkedObject(true)); + if (geoFeature) { + std::vector types = geoFeature->getElementTypes(true); + if (types.size() > 1) { + shouldAdd = true; + } + } + } + } + + if (shouldAdd) { + std::string subNameStr = sel.subName; + std::size_t lastDot = subNameStr.find_last_of('.'); + if (lastDot != std::string::npos && lastDot > 0) { + std::size_t prevDot = subNameStr.find_last_of('.', lastDot - 1); + std::string subObjName; + if (prevDot != std::string::npos) { + subObjName = subNameStr.substr(prevDot + 1, lastDot - prevDot - 1); + } else { + subObjName = subNameStr.substr(0, lastDot); + } + + if (!subObjName.empty()) { + std::string wholeObjKey = std::string(sel.objName) + "." + subObjName + "."; + auto &objItems = menus["Object"].items[sobj->Label.getValue()]; + if (objItems.find(wholeObjKey) == objItems.end()) { + PickData wholeObjSel = sel; + wholeObjSel.subName = subObjName + "."; + wholeObjSel.element = ""; + + selections.push_back(wholeObjSel); + + auto &wholeObjInfo = objItems[wholeObjKey]; + wholeObjInfo.icon = icon; + wholeObjInfo.indices.push_back(selections.size() - 1); + } + } + } + } +} + +bool SelectionMenu::shouldGroupMenu(const SubMenuInfo &info) +{ + if (info.items.size() > 20) { + return true; + } + + std::size_t objCount = 0; + std::size_t count = 0; + for (auto &vv : info.items) { + objCount += vv.second.size(); + for (auto &vvv : vv.second) + count += vvv.second.indices.size(); + if (count > 5 && objCount > 1) { + return true; + } + } + return false; +} + +void SelectionMenu::createFlatMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label, + const std::string &elementType, const std::vector &selections) +{ + for (int idx : elementInfo.indices) { + const auto &sel = selections[idx]; + QString text = QString::fromUtf8(label.c_str()); + if (!sel.element.empty()) { + text += QString::fromLatin1(" (") + QString::fromUtf8(sel.element.c_str()) + QString::fromLatin1(")"); + } else if (elementType == "Object" && !sel.subName.empty() && sel.subName.back() == '.') { + text += QString::fromLatin1(" (Whole Object)"); + } + QAction *action = parentMenu->addAction(elementInfo.icon, text); + action->setData(idx); + connect(action, &QAction::hovered, this, [this, action]() { + onHover(action); + }); + } +} + +void SelectionMenu::createGroupedMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label, + const std::string &elementType, const std::vector &selections) +{ + if (!elementInfo.menu) { + elementInfo.menu = parentMenu->addMenu(elementInfo.icon, QString::fromUtf8(label.c_str())); + } + + for (int idx : elementInfo.indices) { + const auto &sel = selections[idx]; + QString text; + if (!sel.element.empty()) { + text = QString::fromUtf8(sel.element.c_str()); + } else if (elementType == "Object" && !sel.subName.empty() && sel.subName.back() == '.') { + text = QString::fromLatin1("Whole Object"); + } else { + text = QString::fromUtf8(sel.subName.c_str()); + } + QAction *action = elementInfo.menu->addAction(text); + action->setData(idx); + connect(action, &QAction::hovered, this, [this, action]() { + onHover(action); + }); + } +} + #include "moc_SelectionView.cpp" diff --git a/src/Gui/Selection/SelectionView.h b/src/Gui/Selection/SelectionView.h index a5584a0cce..e08c700027 100644 --- a/src/Gui/Selection/SelectionView.h +++ b/src/Gui/Selection/SelectionView.h @@ -27,6 +27,10 @@ #include "Selection.h" #include #include +#include +#include +#include +#include class QListWidget; @@ -38,6 +42,9 @@ namespace App { class DocumentObject; } +struct ElementInfo; +struct SubMenuInfo; + namespace Gui { namespace DockWnd { @@ -148,6 +155,22 @@ protected: PickData onPicked(QAction *, const std::vector &sels); private: + void processSelections(std::vector &selections, std::map &menus); + void buildMenuStructure(std::map &menus, const std::vector &selections); + + App::DocumentObject* getSubObject(const PickData &sel); + std::string extractElementType(const PickData &sel); + std::string createObjectKey(const PickData &sel); + QIcon getOrCreateIcon(App::DocumentObject* sobj, std::map &icons); + void addGeoFeatureTypes(App::DocumentObject* sobj, std::map &menus, std::set &createdTypes); + void addWholeObjectSelection(const PickData &sel, App::DocumentObject* sobj, std::vector &selections, + std::map &menus, const QIcon &icon); + bool shouldGroupMenu(const SubMenuInfo &info); + void createFlatMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label, + const std::string &elementType, const std::vector &selections); + void createGroupedMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label, + const std::string &elementType, const std::vector &selections); + QPointer activeMenu; QPointer activeAction; const std::vector* currentSelections; diff --git a/src/Gui/Selection/SoFCUnifiedSelection.cpp b/src/Gui/Selection/SoFCUnifiedSelection.cpp index 88c6aa619d..def60c048e 100644 --- a/src/Gui/Selection/SoFCUnifiedSelection.cpp +++ b/src/Gui/Selection/SoFCUnifiedSelection.cpp @@ -367,20 +367,51 @@ void SoFCUnifiedSelection::doAction(SoAction *action) App::Document* doc = App::GetApplication().getDocument(preselectAction->SelChange.pDocName); App::DocumentObject* obj = doc->getObject(preselectAction->SelChange.pObjectName); ViewProvider*vp = Application::Instance->getViewProvider(obj); - SoDetail* detail = vp->getDetail(preselectAction->SelChange.pSubName); + + // use getDetailPath() like selection does, instead of just getDetail() + SoDetail* detail = nullptr; + detailPath->truncate(0); + auto subName = preselectAction->SelChange.pSubName; + + SoFullPath* pathToHighlight = nullptr; + if (vp && vp->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId()) && + (useNewSelection.getValue() || vp->useNewSelectionModel()) && vp->isSelectable()) { + + // get proper detail path for sub-objects (like Assembly parts) + if (!subName || !subName[0] || vp->getDetailPath(subName, detailPath, true, detail)) { + if (detailPath->getLength()) { + pathToHighlight = detailPath; + } else { + // fallback to ViewProvider root if no specific path + pathToHighlight = static_cast(new SoPath(2)); + pathToHighlight->ref(); + pathToHighlight->append(vp->getRoot()); + } + } + } else { + detail = vp->getDetail(subName); + pathToHighlight = static_cast(new SoPath(2)); + pathToHighlight->ref(); + pathToHighlight->append(vp->getRoot()); + } - SoHighlightElementAction highlightAction; - highlightAction.setHighlighted(true); - highlightAction.setColor(this->colorHighlight.getValue()); - highlightAction.setElement(detail); - highlightAction.apply(vp->getRoot()); + if (pathToHighlight) { + SoHighlightElementAction highlightAction; + highlightAction.setHighlighted(true); + highlightAction.setColor(this->colorHighlight.getValue()); + highlightAction.setElement(detail); + highlightAction.apply(pathToHighlight); + + currentHighlightPath = static_cast(pathToHighlight->copy()); + currentHighlightPath->ref(); + + // clean up temporary path if we created one + if (pathToHighlight != detailPath) { + pathToHighlight->unref(); + } + } + delete detail; - - SoSearchAction sa; - sa.setNode(vp->getRoot()); - sa.apply(vp->getRoot()); - currentHighlightPath = static_cast(sa.getPath()->copy()); - currentHighlightPath->ref(); } if (useNewSelection.getValue()) diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index d1542de411..aaf6c5c66d 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -2473,7 +2473,15 @@ void View3DInventorViewer::renderScene() So3DAnnotation::render = true; glClear(GL_DEPTH_BUFFER_BIT); - glra->apply(SoDelayedAnnotationsElement::getDelayedPaths(state)); + + // process delayed paths with priority support + if (Gui::Selection().isPickGeometryActive()) { + Gui::SoDelayedAnnotationsElement::processDelayedPathsWithPriority(state, glra); + } else { + // standard processing for normal delayed annotations + glra->apply(Gui::SoDelayedAnnotationsElement::getDelayedPaths(state)); + } + So3DAnnotation::render = false; } catch (const Base::MemoryException&) { diff --git a/src/Mod/Part/Gui/SoBrepEdgeSet.cpp b/src/Mod/Part/Gui/SoBrepEdgeSet.cpp index 934ae99343..7a1a6f4d3b 100644 --- a/src/Mod/Part/Gui/SoBrepEdgeSet.cpp +++ b/src/Mod/Part/Gui/SoBrepEdgeSet.cpp @@ -44,10 +44,18 @@ # include # include # include +# include +# include #endif #include +#include +#include #include "SoBrepEdgeSet.h" +#include "SoBrepFaceSet.h" +#include "ViewProviderExt.h" + +#include using namespace PartGui; @@ -79,6 +87,20 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction *action) SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext(this,selContext,ctx2); if(ctx2 && ctx2->selectionIndex.empty()) return; + + + if (Gui::Selection() + .isPickGeometryActive() && !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths + && ((ctx && !ctx->hl.empty()) || viewProvider->isFaceHighlightActive())) { + // if we are using pickgeometry - add this to delayed paths with priority + // as we want to get this rendered on top of everything + viewProvider->setFaceHighlightActive(true); + Gui::SoDelayedAnnotationsElement::addDelayedPath(action->getState(), + action->getCurPath()->copy(), + 200); + return; + } + if(selContext2->checkGlobal(ctx)) { if(selContext2->isSelectAll()) { selContext2->sl.clear(); @@ -132,9 +154,23 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction *action) } if(ctx2 && !ctx2->selectionIndex.empty()) renderSelection(action,ctx2,false); - else + else if (Gui::Selection().isPickGeometryActive() + && ((ctx && !ctx->hl.empty()) || viewProvider->isFaceHighlightActive()) + && Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) { + state->push(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(false); + glDisable(GL_DEPTH_TEST); + inherited::GLRender(action); + state->pop(); + } + else { + inherited::GLRender(action); + } + // Workaround for #0000433 //#if !defined(FC_OS_WIN32) if(!action->isRenderingDelayedPaths()) diff --git a/src/Mod/Part/Gui/SoBrepEdgeSet.h b/src/Mod/Part/Gui/SoBrepEdgeSet.h index c63674af0d..a341c027dd 100644 --- a/src/Mod/Part/Gui/SoBrepEdgeSet.h +++ b/src/Mod/Part/Gui/SoBrepEdgeSet.h @@ -36,6 +36,8 @@ class SoTextureCoordinateBundle; namespace PartGui { +class ViewProviderPartExt; + class PartGuiExport SoBrepEdgeSet : public SoIndexedLineSet { using inherited = SoIndexedLineSet; @@ -44,6 +46,8 @@ class PartGuiExport SoBrepEdgeSet : public SoIndexedLineSet { public: static void initClass(); SoBrepEdgeSet(); + + void setViewProvider(ViewProviderPartExt* vp) { viewProvider = vp; } protected: ~SoBrepEdgeSet() override = default; @@ -68,11 +72,15 @@ private: void renderSelection(SoGLRenderAction *action, SelContextPtr, bool push=true); bool validIndexes(const SoCoordinateElement*, const std::vector&) const; + private: SelContextPtr selContext; SelContextPtr selContext2; Gui::SoFCSelectionCounter selCounter; uint32_t packedColor{0}; + + // backreference to viewprovider that owns this node + ViewProviderPartExt* viewProvider = nullptr; }; } // namespace PartGui diff --git a/src/Mod/Part/Gui/SoBrepFaceSet.cpp b/src/Mod/Part/Gui/SoBrepFaceSet.cpp index e8ada70029..b1a006e03c 100644 --- a/src/Mod/Part/Gui/SoBrepFaceSet.cpp +++ b/src/Mod/Part/Gui/SoBrepFaceSet.cpp @@ -73,8 +73,12 @@ #include #include #include +#include #include "SoBrepFaceSet.h" +#include "ViewProviderExt.h" +#include "SoBrepEdgeSet.h" + using namespace PartGui; @@ -188,6 +192,9 @@ void SoBrepFaceSet::doAction(SoAction* action) ctx->highlightIndex = -1; touch(); } + if (viewProvider) { + viewProvider->setFaceHighlightActive(false); + } return; } @@ -204,6 +211,9 @@ void SoBrepFaceSet::doAction(SoAction* action) ctx->highlightIndex = -1; touch(); } + if (viewProvider) { + viewProvider->setFaceHighlightActive(false); + } }else { int index = static_cast(detail)->getPartIndex(); SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action,this,selContext); @@ -521,6 +531,40 @@ void SoBrepFaceSet::GLRender(SoGLRenderAction *action) auto state = action->getState(); selCounter.checkRenderCache(state); + + // for the tool add this node to delayed paths as we want to render it on top of the scene + if (Gui::Selection().isPickGeometryActive() && ctx && ctx->isHighlighted() + && !ctx->isHighlightAll() && ctx->highlightIndex >= 0 + && ctx->highlightIndex < partIndex.getNum()) { + + if (!Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) { + if (viewProvider) { + viewProvider->setFaceHighlightActive(true); + } + + const SoPath* currentPath = action->getCurPath(); + Gui::SoDelayedAnnotationsElement::addDelayedPath(action->getState(), + currentPath->copy(), + 100); + return; + } else { + // during priority delayed paths processing: + // render base faces normally first, then render highlight on top + + inherited::GLRender(action); + + state->push(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(false); + glDisable(GL_DEPTH_TEST); + + renderHighlight(action, ctx); + + state->pop(); + return; + } + } // override material binding to PER_PART_INDEX to achieve // preselection/selection with transparency @@ -730,7 +774,7 @@ bool SoBrepFaceSet::overrideMaterialBinding(SoGLRenderAction *action, SelContext singleColor = ctx?-1:1; } - bool partialRender = ctx2 && !ctx2->isSelectAll(); + bool partialRender = (ctx2 && !ctx2->isSelectAll()); if(singleColor>0 && !partialRender) { //optimization for single color non-partial rendering @@ -772,7 +816,8 @@ bool SoBrepFaceSet::overrideMaterialBinding(SoGLRenderAction *action, SelContext packedColors.push_back(ctx->highlightColor.getPackedValue(trans0)); matIndex[ctx->highlightIndex] = packedColors.size()-1; } - }else{ + } + else{ if(partialRender) { packedColors.push_back(SbColor(1.0,1.0,1.0).getPackedValue(1.0)); matIndex.resize(partIndex.getNum(),0); @@ -848,7 +893,7 @@ bool SoBrepFaceSet::overrideMaterialBinding(SoGLRenderAction *action, SelContext SoLazyElement::setPacked(state, this, packedColors.size(), packedColors.data(), hasTransparency); SoTextureEnabledElement::set(state,this,false); - if(hasTransparency && action->isRenderingDelayedPaths()) { + if (hasTransparency && action->isRenderingDelayedPaths()) { // rendering delayed paths means we are doing annotation (e.g. // always on top rendering). To render transparency correctly in // this case, we shall use openGL transparency blend. Override diff --git a/src/Mod/Part/Gui/SoBrepFaceSet.h b/src/Mod/Part/Gui/SoBrepFaceSet.h index bab9e49414..17e33c7aed 100644 --- a/src/Mod/Part/Gui/SoBrepFaceSet.h +++ b/src/Mod/Part/Gui/SoBrepFaceSet.h @@ -38,6 +38,8 @@ class SoTextureCoordinateBundle; namespace PartGui { +class ViewProviderPartExt; + /** * First some words to the history and the reason why we have this class: * In older FreeCAD versions we had an own Inventor node for each sub-element of a shape with its own highlight node. @@ -79,6 +81,8 @@ class PartGuiExport SoBrepFaceSet : public SoIndexedFaceSet { public: static void initClass(); SoBrepFaceSet(); + + void setViewProvider(ViewProviderPartExt* vp) { viewProvider = vp; } SoMFInt32 partIndex; @@ -154,6 +158,9 @@ private: // Define some VBO pointer for the current mesh class VBO; std::unique_ptr pimpl; + + // backreference to viewprovider that owns this node + ViewProviderPartExt* viewProvider = nullptr; }; } // namespace PartGui diff --git a/src/Mod/Part/Gui/SoBrepPointSet.cpp b/src/Mod/Part/Gui/SoBrepPointSet.cpp index d674011d3f..9dec883f45 100644 --- a/src/Mod/Part/Gui/SoBrepPointSet.cpp +++ b/src/Mod/Part/Gui/SoBrepPointSet.cpp @@ -44,7 +44,9 @@ #endif #include +#include +#include "ViewProviderExt.h" #include "SoBrepPointSet.h" @@ -81,6 +83,18 @@ void SoBrepPointSet::GLRender(SoGLRenderAction *action) return; if(selContext2->checkGlobal(ctx)) ctx = selContext2; + + // for pickgeometry, add this node to delayed path if it is highlighted and render it on + // top of everything else (highest priority) + if (Gui::Selection().isPickGeometryActive() && ctx && ctx->isHighlighted() + && !ctx->isHighlightAll() && ctx->highlightIndex >= 0 + && !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) { + viewProvider->setFaceHighlightActive(true); + Gui::SoDelayedAnnotationsElement::addDelayedPath(action->getState(), + action->getCurPath()->copy(), + 300); + return; + } if(ctx && ctx->highlightIndex == std::numeric_limits::max()) { if(ctx->selectionIndex.empty() || ctx->isSelectAll()) { @@ -121,8 +135,15 @@ void SoBrepPointSet::GLRender(SoGLRenderAction *action) } if(ctx2 && !ctx2->selectionIndex.empty()) renderSelection(action,ctx2,false); - else + else if (Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths) { + glPushAttrib(GL_DEPTH_BUFFER_BIT); + glDepthFunc(GL_ALWAYS); inherited::GLRender(action); + glPopAttrib(); + } + else { + inherited::GLRender(action); + } // Workaround for #0000433 //#if !defined(FC_OS_WIN32) diff --git a/src/Mod/Part/Gui/SoBrepPointSet.h b/src/Mod/Part/Gui/SoBrepPointSet.h index 1dd87e8c55..d9ff7f9f2a 100644 --- a/src/Mod/Part/Gui/SoBrepPointSet.h +++ b/src/Mod/Part/Gui/SoBrepPointSet.h @@ -36,6 +36,8 @@ class SoTextureCoordinateBundle; namespace PartGui { +class ViewProviderPartExt; + class PartGuiExport SoBrepPointSet : public SoPointSet { using inherited = SoPointSet; @@ -44,6 +46,8 @@ class PartGuiExport SoBrepPointSet : public SoPointSet { public: static void initClass(); SoBrepPointSet(); + + void setViewProvider(ViewProviderPartExt* vp) { viewProvider = vp; } protected: ~SoBrepPointSet() override = default; @@ -64,6 +68,9 @@ private: SelContextPtr selContext2; Gui::SoFCSelectionCounter selCounter; uint32_t packedColor{0}; + + // backreference to viewprovider that owns this node + ViewProviderPartExt* viewProvider = nullptr; }; } // namespace PartGui diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index 3de944e65f..c2c326b808 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -191,6 +191,7 @@ ViewProviderPartExt::ViewProviderPartExt() coords = new SoCoordinate3(); coords->ref(); faceset = new SoBrepFaceSet(); + faceset->setViewProvider(this); faceset->ref(); norm = new SoNormal; norm->ref(); @@ -198,8 +199,10 @@ ViewProviderPartExt::ViewProviderPartExt() normb->value = SoNormalBinding::PER_VERTEX_INDEXED; normb->ref(); lineset = new SoBrepEdgeSet(); + lineset->setViewProvider(this); lineset->ref(); nodeset = new SoBrepPointSet(); + nodeset->setViewProvider(this); nodeset->ref(); pcFaceBind = new SoMaterialBinding(); @@ -447,9 +450,9 @@ void ViewProviderPartExt::attach(App::DocumentObject *pcFeat) // normal viewing with edges and points pcNormalRoot->addChild(pcPointsRoot); - pcNormalRoot->addChild(wireframe); pcNormalRoot->addChild(offset); pcNormalRoot->addChild(pcFlatRoot); + pcNormalRoot->addChild(wireframe); // just faces with no edges or points pcFlatRoot->addChild(pShapeHints); diff --git a/src/Mod/Part/Gui/ViewProviderExt.h b/src/Mod/Part/Gui/ViewProviderExt.h index 1f6950d50f..b5d62dfcdb 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.h +++ b/src/Mod/Part/Gui/ViewProviderExt.h @@ -156,6 +156,9 @@ public: bool allowOverride(const App::DocumentObject &) const override; + void setFaceHighlightActive(bool active) { faceHighlightActive = active; } + bool isFaceHighlightActive() const { return faceHighlightActive; } + /** @name Edit methods */ //@{ void setupContextMenu(QMenu*, QObject*, const char*) override; @@ -213,6 +216,7 @@ protected: bool VisualTouched; bool NormalsFromUV; + bool faceHighlightActive = false; private: Gui::ViewProviderFaceTexture texture;