From 436fef03ed213ea8bf564cf653db37f0e99433bf Mon Sep 17 00:00:00 2001 From: paddle Date: Fri, 31 Oct 2025 08:00:53 +0100 Subject: [PATCH] Assembly: Highlight joint elements on joint selection --- src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 119 ++++++++++++------ src/Mod/Assembly/Gui/ViewProviderAssembly.h | 3 + src/Mod/Part/Gui/SoBrepEdgeSet.cpp | 112 ++++++++++++++++- 3 files changed, 191 insertions(+), 43 deletions(-) diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 57907cfe94..81a9768622 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include @@ -63,6 +65,7 @@ #include #include #include +#include #include #include @@ -1372,17 +1375,13 @@ void ViewProviderAssembly::applyIsolationRecursively( state.visibility = current->Visibility.getValue(); if (vpl) { state.selectable = vpl->Selectable.getValue(); - state.overrideMaterial = vpl->OverrideMaterial.getValue(); - state.shapeMaterial = vpl->ShapeMaterial.getValue(); } - else { // vpg + else { state.selectable = vpg->Selectable.getValue(); - state.shapeMaterial = vpg->ShapeAppearance.getValue()[0]; } stateBackup[current] = state; if (mode == IsolateMode::Hidden) { - stateBackup[current] = state; current->Visibility.setValue(isolate); return; } @@ -1391,39 +1390,23 @@ void ViewProviderAssembly::applyIsolationRecursively( current->Visibility.setValue(true); } - App::Material mat = App::Material::getDefaultAppearance(); - float trans = mode == IsolateMode::Transparent ? 0.8 : 1.0; - mat.transparency = trans; - if (vpl) { vpl->Selectable.setValue(isolate); - if (!isolate) { - vpl->OverrideMaterial.setValue(true); - vpl->ShapeMaterial.setValue(mat); - } } else if (vpg) { vpg->Selectable.setValue(isolate); - if (!isolate) { - vpg->ShapeAppearance.setValue(mat); + } - // Note, this geometric object could have a link linking to it in the assembly - // and this link may be in isolate set! If so it will inherit the isolation - // from 'current'! So we need to manually handle it to visible. - const std::vector inList = current->getInList(); - for (auto* child : inList) { - if (child->isDerivedFrom() && child->getLinkedObject() == current) { - // In this case we need to reverse isolate this! - auto* childVp = freecad_cast( - Gui::Application::Instance->getViewProvider(child) - ); + if (!isolate) { + float trans = mode == IsolateMode::Transparent ? 0.8 : 1.0; + Base::Color transparentColor(App::Material::getDefaultAppearance().diffuseColor); + transparentColor.setTransparency(trans); + std::map colorMap; + colorMap["Face"] = transparentColor; // The "Face" wildcard targets all faces - // we give the child the color the current had before we changed it - childVp->OverrideMaterial.setValue(true); - childVp->ShapeMaterial.setValue(state.shapeMaterial); - } - } - } + Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::Color, true); + action.swapColors(colorMap); + action.apply(vp->getRoot()); } } @@ -1466,6 +1449,8 @@ void ViewProviderAssembly::isolateJointReferences(App::DocumentObject* joint, Is std::set isolateSet = {part1, part2}; isolateComponents(isolateSet, mode); + + highlightJointElements(joint); } void ViewProviderAssembly::clearIsolate() @@ -1473,6 +1458,8 @@ void ViewProviderAssembly::clearIsolate() if (isolatedJoint) { isolatedJoint->Visibility.setValue(isolatedJointVisibilityBackup); isolatedJoint = nullptr; + + clearJointElementHighlight(); } for (const auto& pair : stateBackup) { @@ -1483,25 +1470,75 @@ void ViewProviderAssembly::clearIsolate() } component->Visibility.setValue(state.visibility); - - if (auto* vpl = dynamic_cast( - Gui::Application::Instance->getViewProvider(component) - )) { + auto* vp = Gui::Application::Instance->getViewProvider(component); + if (auto* vpl = dynamic_cast(vp)) { vpl->Selectable.setValue(state.selectable); - vpl->ShapeMaterial.setValue(state.shapeMaterial); - vpl->OverrideMaterial.setValue(state.overrideMaterial); } - else if (auto* vpg = dynamic_cast( - Gui::Application::Instance->getViewProvider(component) - )) { + else if (auto* vpg = dynamic_cast(vp)) { vpg->Selectable.setValue(state.selectable); - vpg->ShapeAppearance.setValue(state.shapeMaterial); } + Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::Color, true); + action.apply(vp->getRoot()); } stateBackup.clear(); } +void ViewProviderAssembly::highlightJointElements(App::DocumentObject* joint) +{ + clearJointElementHighlight(); + + SbColor defaultHighlightColor(0.8f, 0.1f, 0.1f); + uint32_t defaultPacked = defaultHighlightColor.getPackedValue(); + ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View"); + uint32_t packedColor = hGrp->GetUnsigned("HighlightColor", defaultPacked); + Base::Color highlightColor(packedColor); + + std::set processedElements; + + const char* refNames[] = {"Reference1", "Reference2"}; + for (const char* refName : refNames) { + auto* propRef = dynamic_cast(joint->getPropertyByName(refName)); + if (!propRef || propRef->getSubValues().empty()) { + continue; + } + const auto& el = propRef->getSubValues()[0]; + + if (el.empty() || processedElements.count(el)) { + continue; + } + processedElements.insert(el); // Mark as processed. + + auto* path = static_cast(new SoPath(20)); + SoDetail* detail = nullptr; + + if (this->getDetailPath(el.c_str(), path, true, detail)) { + const char* elementNameCStr = Data::findElementName(el.c_str()); + if (!elementNameCStr || !elementNameCStr[0]) { + continue; + } + std::string elementName(elementNameCStr); + + Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::Color, true); + + std::map colorMap; + colorMap[elementName] = highlightColor; + action.swapColors(colorMap); + + path->ref(); + action.apply(path); + } + delete detail; + } +} + +void ViewProviderAssembly::clearJointElementHighlight() +{ + Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::Color, true); + // An empty color map tells nodes to clear their secondary color. + action.apply(this->getRoot()); +} + void ViewProviderAssembly::slotAboutToOpenTransaction(const std::string& cmdName) { Q_UNUSED(cmdName); diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.h b/src/Mod/Assembly/Gui/ViewProviderAssembly.h index 76a2dabdd4..32ab6f8bbd 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.h +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.h @@ -277,6 +277,9 @@ private: App::DocumentObject* isolatedJoint {nullptr}; bool isolatedJointVisibilityBackup {false}; + void highlightJointElements(App::DocumentObject* joint); + void clearJointElementHighlight(); + void applyIsolationRecursively( App::DocumentObject* current, std::set& isolateSet, diff --git a/src/Mod/Part/Gui/SoBrepEdgeSet.cpp b/src/Mod/Part/Gui/SoBrepEdgeSet.cpp index 7ed1d5f145..4c0b11e754 100644 --- a/src/Mod/Part/Gui/SoBrepEdgeSet.cpp +++ b/src/Mod/Part/Gui/SoBrepEdgeSet.cpp @@ -62,7 +62,7 @@ using namespace PartGui; SO_NODE_SOURCE(SoBrepEdgeSet) -struct SoBrepEdgeSet::SelContext: Gui::SoFCSelectionContext +struct SoBrepEdgeSet::SelContext: Gui::SoFCSelectionContextEx { std::vector hl, sl; }; @@ -86,7 +86,7 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction* action) SelContextPtr ctx2; SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext(this, selContext, ctx2); - if (ctx2 && ctx2->selectionIndex.empty()) { + if (ctx2 && ctx2->selectionIndex.empty() && ctx2->colors.empty()) { return; } @@ -128,6 +128,79 @@ void SoBrepEdgeSet::GLRender(SoGLRenderAction* action) ctx = selContext2; } + bool hasColorOverride = (ctx2 && !ctx2->colors.empty()); + if (hasColorOverride) { + state->push(); + + const SoCoordinateElement* coords; + const SbVec3f* normals; + const int32_t* cindices; + const int32_t* nindices; + const int32_t* tindices; + const int32_t* mindices; + int numcindices; + SbBool normalCacheUsed; + this->getVertexData( + state, + coords, + normals, + cindices, + nindices, + tindices, + mindices, + numcindices, + false, + normalCacheUsed + ); + + SoMaterialBundle mb(action); + mb.sendFirst(); + + std::vector packedColors; + std::vector matIdx; + bool hasTransparency = false; + + // CORRECTED way to get default packed color + const SoLazyElement* lazyElem = SoLazyElement::getInstance(state); + packedColors.push_back( + lazyElem->getDiffuse(state, 0).getPackedValue(lazyElem->getTransparency(state, 0)) + ); + + int linecount = 0; + for (int i = 0; i < numcindices; i++) { + if (cindices[i] < 0) { + auto it = ctx2->colors.find(linecount); + if (it != ctx2->colors.end()) { + packedColors.push_back(ctx2->packColor(it->second, hasTransparency)); + matIdx.push_back(packedColors.size() - 1); + } + else { + auto it_all = ctx2->colors.find(-1); + if (it_all != ctx2->colors.end()) { + packedColors.push_back(ctx2->packColor(it_all->second, hasTransparency)); + matIdx.push_back(packedColors.size() - 1); + } + else { + matIdx.push_back(0); + } + } + linecount++; + } + } + + if (!matIdx.empty()) { + SoLazyElement::setPacked(state, this, packedColors.size(), packedColors.data(), hasTransparency); + SoMaterialBindingElement::set(state, this, SoMaterialBindingElement::PER_PART_INDEXED); + this->materialIndex.setValues(0, matIdx.size(), matIdx.data()); + } + + inherited::GLRender(action); + + this->materialIndex.deleteValues(0); + state->pop(); + return; + } + if (ctx && ctx->highlightIndex == std::numeric_limits::max()) { if (ctx->selectionIndex.empty() || ctx->isSelectAll()) { if (ctx2) { @@ -478,6 +551,40 @@ void SoBrepEdgeSet::doAction(SoAction* action) Gui::SoSelectionElementAction* selaction = static_cast(action); switch (selaction->getType()) { + case Gui::SoSelectionElementAction::Color: + if (selaction->isSecondary()) { + const auto& colors = selaction->getColors(); + + // Case 1: The color map is empty. This is a "clear" command. + if (colors.empty()) { + // We must find and remove any existing secondary context for this node. + if (Gui::SoFCSelectionRoot::removeActionContext(action, this)) { + touch(); + } + return; + } + + // Case 2: The color map is NOT empty. This is a "set color" command. + static std::string element("Edge"); + bool hasEdgeColors = false; + for (const auto& [name, color] : colors) { + if (name.empty() || boost::starts_with(name, element)) { + hasEdgeColors = true; + break; + } + } + + if (hasEdgeColors) { + auto ctx = Gui::SoFCSelectionRoot::getActionContext(action, this); + selCounter.checkAction(selaction, ctx); + ctx->selectAll(); + + if (ctx->setColors(colors, element)) { + touch(); + } + } + } + return; case Gui::SoSelectionElementAction::None: { if (selaction->isSecondary()) { if (Gui::SoFCSelectionRoot::removeActionContext(action, this)) { @@ -490,6 +597,7 @@ void SoBrepEdgeSet::doAction(SoAction* action) if (ctx) { ctx->selectionIndex.clear(); ctx->sl.clear(); + ctx->colors.clear(); touch(); } }