Core/Gui: Render primitives on top of the scene in PickGeometry

+ added some better grouping for items, which are assigned per object
  right now. For example, if we exceed 10 items per object it gets an
  additional group.

Co-authored-by: realthunder <realthunder@users.noreply.github.com>
This commit is contained in:
tetektoza
2025-06-25 11:28:25 +02:00
parent 5e0b74dce6
commit edfeff975e
17 changed files with 614 additions and 91 deletions

View File

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

View File

@@ -29,17 +29,23 @@
#include <GL/gl.h>
#endif
#include <Inventor/elements/SoCacheElement.h>
#include <algorithm>
#endif
#include "So3DAnnotation.h"
#include <Gui/Selection/Selection.h>
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<SoDelayedAnnotationsElement*>(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<SoDelayedAnnotationsElement*>(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<SoDelayedAnnotationsElement*>(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);

View File

@@ -27,6 +27,7 @@
#include <Inventor/elements/SoElement.h>
#include <Inventor/elements/SoSubElement.h>
#include <FCGlobal.h>
#include <vector>
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<PriorityPath> priorityPaths;
SoPathList paths;
};

View File

@@ -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;
}

View File

@@ -291,6 +291,8 @@ private:
class GuiExport SelectionSingleton : public Base::Subject<const SelectionChanges&>
{
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<void (const SelectionChanges& msg)> signalSelectionChanged;

View File

@@ -31,11 +31,15 @@
#include <QTextStream>
#include <QToolButton>
#include <QVBoxLayout>
#include <set>
#endif
#include <App/ComplexGeoData.h>
#include <App/Document.h>
#include <App/ElementNamingUtils.h>
#include <App/GeoFeature.h>
#include <App/IndexedName.h>
#include <Base/Console.h>
#include "SelectionView.h"
#include "Application.h"
@@ -727,75 +731,100 @@ struct SubMenuInfo {
PickData SelectionMenu::doPick(const std::vector<PickData> &sels)
{
clear();
Gui::Selection().setPickGeometryActive(true);
// store reference to selections for use in onHover
currentSelections = &sels;
std::vector<PickData> selsCopy = sels;
currentSelections = &selsCopy;
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);
// Connect submenu hovered signals as well
connect(typeMenu, &QMenu::hovered, this, &SelectionMenu::onHover);
} else {
action = addAction(icon, text);
}
action->setData(idx);
}
}
std::map<std::string, SubMenuInfo> 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<PickData> &selections, std::map<std::string, SubMenuInfo> &menus)
{
std::map<App::DocumentObject*, QIcon> icons;
std::set<std::string> createdElementTypes;
std::set<std::string> 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<std::string, SubMenuInfo> &menus, const std::vector<PickData> &selections)
{
std::vector<std::string> preferredOrder = {"Object", "Solid", "Face", "Edge", "Vertex", "Wire", "Shell", "Compound", "CompSolid"};
std::vector<std::map<std::string, SubMenuInfo>::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<PickData> &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<App::DocumentObject*, QIcon> &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<std::string, SubMenuInfo> &menus, std::set<std::string> &createdTypes)
{
auto geoFeature = dynamic_cast<App::GeoFeature*>(sobj->getLinkedObject(true));
if (geoFeature) {
std::vector<const char*> 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<PickData> &selections, std::map<std::string, SubMenuInfo> &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<App::GeoFeature*>(sobj->getLinkedObject(true));
if (geoFeature) {
std::vector<const char*> 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<PickData> &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<PickData> &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"

View File

@@ -27,6 +27,10 @@
#include "Selection.h"
#include <QMenu>
#include <QPointer>
#include <map>
#include <set>
#include <string>
#include <vector>
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<PickData> &sels);
private:
void processSelections(std::vector<PickData> &selections, std::map<std::string, SubMenuInfo> &menus);
void buildMenuStructure(std::map<std::string, SubMenuInfo> &menus, const std::vector<PickData> &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<App::DocumentObject*, QIcon> &icons);
void addGeoFeatureTypes(App::DocumentObject* sobj, std::map<std::string, SubMenuInfo> &menus, std::set<std::string> &createdTypes);
void addWholeObjectSelection(const PickData &sel, App::DocumentObject* sobj, std::vector<PickData> &selections,
std::map<std::string, SubMenuInfo> &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<PickData> &selections);
void createGroupedMenu(ElementInfo &elementInfo, QMenu *parentMenu, const std::string &label,
const std::string &elementType, const std::vector<PickData> &selections);
QPointer<QMenu> activeMenu;
QPointer<QAction> activeAction;
const std::vector<PickData>* currentSelections;

View File

@@ -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<SoFullPath*>(new SoPath(2));
pathToHighlight->ref();
pathToHighlight->append(vp->getRoot());
}
}
} else {
detail = vp->getDetail(subName);
pathToHighlight = static_cast<SoFullPath*>(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<SoFullPath*>(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<SoFullPath*>(sa.getPath()->copy());
currentHighlightPath->ref();
}
if (useNewSelection.getValue())

View File

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

View File

@@ -44,10 +44,18 @@
# include <Inventor/elements/SoLineWidthElement.h>
# include <Inventor/errors/SoDebugError.h>
# include <Inventor/misc/SoState.h>
# include <Inventor/nodes/SoGroup.h>
# include <Inventor/actions/SoSearchAction.h>
#endif
#include <Gui/Selection/SoFCUnifiedSelection.h>
#include <Gui/Selection/Selection.h>
#include <Base/Console.h>
#include "SoBrepEdgeSet.h"
#include "SoBrepFaceSet.h"
#include "ViewProviderExt.h"
#include <Gui/Inventor/So3DAnnotation.h>
using namespace PartGui;
@@ -79,6 +87,20 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction *action)
SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext<SelContext>(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())

View File

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

View File

@@ -73,8 +73,12 @@
#include <Gui/SoFCInteractiveElement.h>
#include <Gui/Selection/SoFCSelectionAction.h>
#include <Gui/Selection/SoFCUnifiedSelection.h>
#include <Gui/Inventor/So3DAnnotation.h>
#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<const SoFaceDetail*>(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

View File

@@ -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<VBO> pimpl;
// backreference to viewprovider that owns this node
ViewProviderPartExt* viewProvider = nullptr;
};
} // namespace PartGui

View File

@@ -44,7 +44,9 @@
#endif
#include <Gui/Selection/SoFCUnifiedSelection.h>
#include <Gui/Inventor/So3DAnnotation.h>
#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<int>::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)

View File

@@ -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

View File

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

View File

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