fix(gui): resolve PartDesign toolbars and breadcrumb when editing body inside assembly
Some checks failed
Build and Test / build (pull_request) Has been cancelled

When double-clicking a PartDesign Body inside an Assembly, the editing
context resolver failed to show PartDesign toolbars or a proper
breadcrumb. Three root causes:

1. Priority preemption: assembly.edit (priority 90) always matched
   because the Assembly VP remained in edit mode, blocking all
   PartDesign contexts (priorities 30-40).

2. Wrong active-object key: PartDesign matchers only queried the
   "part" active object, but ViewProviderBody::toggleActiveBody()
   sets "part" to the containing App::Part (which is the Assembly
   itself). The Body is set under "pdbody", which was never queried.

3. Missing refresh trigger: ActiveObjectList::setObject() fires
   Document::signalActivatedViewProvider, but EditingContextResolver
   had no connection to it, so setActiveObject("pdbody", body) never
   triggered a context re-resolve.

Changes:
- Forward signalActivatedViewProvider from Gui::Document to
  Gui::Application (same pattern as signalInEdit/signalResetEdit)
- Connect EditingContextResolver to the new application-level signal
- Add getActivePdBodyObject() helper querying the "pdbody" key
- Add partdesign.in_assembly context (priority 95) that matches when
  a Body is active pdbody while an Assembly is in edit
- Update partdesign.body and partdesign.feature matchers to check
  pdbody before falling back to the part key
- Add Assembly > Body breadcrumb with Blue > Mauve coloring
- Update label resolution to prefer pdbody name
This commit is contained in:
forbes
2026-02-21 09:43:51 -06:00
parent 0f8fa0be86
commit 5883ac8a0d
3 changed files with 103 additions and 2 deletions

View File

@@ -1046,6 +1046,9 @@ void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc)
);
pDoc->signalInEdit.connect(std::bind(&Gui::Application::slotInEdit, this, sp::_1));
pDoc->signalResetEdit.connect(std::bind(&Gui::Application::slotResetEdit, this, sp::_1));
pDoc->signalActivatedViewProvider.connect(
std::bind(&Gui::Application::slotActivatedViewProvider, this, sp::_1, sp::_2)
);
// NOLINTEND
signalNewDocument(*pDoc, isMainDoc);
@@ -1352,6 +1355,12 @@ void Application::slotResetEdit(const Gui::ViewProviderDocumentObject& vp)
this->signalResetEdit(vp);
}
void Application::slotActivatedViewProvider(
const Gui::ViewProviderDocumentObject* vp, const char* name)
{
this->signalActivatedViewProvider(vp, name);
}
void Application::onLastWindowClosed(Gui::Document* pcDoc)
{
try {

View File

@@ -153,6 +153,9 @@ public:
fastsignals::signal<void(const Gui::ViewProviderDocumentObject&)> signalInEdit;
/// signal on leaving edit mode
fastsignals::signal<void(const Gui::ViewProviderDocumentObject&)> signalResetEdit;
/// signal on activated view-provider (active-object change, e.g. "pdbody", "part")
fastsignals::signal<void(const Gui::ViewProviderDocumentObject*, const char*)>
signalActivatedViewProvider;
/// signal on changing user edit mode
fastsignals::signal<void(int)> signalUserEditModeChanged;
//@}
@@ -174,6 +177,7 @@ protected:
void slotActivatedObject(const ViewProvider&);
void slotInEdit(const Gui::ViewProviderDocumentObject&);
void slotResetEdit(const Gui::ViewProviderDocumentObject&);
void slotActivatedViewProvider(const Gui::ViewProviderDocumentObject*, const char*);
public:
/// message when a GuiDocument is about to vanish

View File

@@ -121,6 +121,9 @@ EditingContextResolver::EditingContextResolver()
app.signalActiveDocument.connect([this](const Document& doc) { onActiveDocument(doc); });
app.signalActivateView.connect([this](const MDIView* view) { onActivateView(view); });
app.signalActivateWorkbench.connect([this](const char*) { refresh(); });
app.signalActivatedViewProvider.connect(
[this](const ViewProviderDocumentObject*, const char*) { refresh(); }
);
}
EditingContextResolver::~EditingContextResolver()
@@ -172,6 +175,23 @@ static App::DocumentObject* getActivePartObject()
return view->getActiveObject<App::DocumentObject*>("part");
}
// ---------------------------------------------------------------------------
// Helper: get the active "pdbody" object from the active view
// ---------------------------------------------------------------------------
static App::DocumentObject* getActivePdBodyObject()
{
auto* guiDoc = Application::Instance->activeDocument();
if (!guiDoc) {
return nullptr;
}
auto* view = guiDoc->getActiveView();
if (!view) {
return nullptr;
}
return view->getActiveObject<App::DocumentObject*>("pdbody");
}
// ---------------------------------------------------------------------------
// Helper: get the label of the active "part" object
// ---------------------------------------------------------------------------
@@ -213,6 +233,34 @@ static QString getInEditLabel()
void EditingContextResolver::registerBuiltinContexts()
{
// --- PartDesign body active inside an assembly (supersedes assembly.edit) ---
registerContext({
/*.id =*/QStringLiteral("partdesign.in_assembly"),
/*.labelTemplate =*/QStringLiteral("Body: {name}"),
/*.color =*/QLatin1String(CatppuccinMocha::Mauve),
/*.toolbars =*/
{QStringLiteral("Part Design Helper Features"),
QStringLiteral("Part Design Modeling Features"),
QStringLiteral("Part Design Dress-Up Features"),
QStringLiteral("Part Design Transformation Features"),
QStringLiteral("Sketcher")},
/*.priority =*/95,
/*.match =*/
[]() {
auto* body = getActivePdBodyObject();
if (!body || !objectIsDerivedFrom(body, "PartDesign::Body")) {
return false;
}
// Only match when we're inside an assembly edit session
auto* doc = Application::Instance->activeDocument();
if (!doc) {
return false;
}
auto* vp = doc->getInEdit();
return vp && vpObjectIsDerivedFrom(vp, "Assembly::AssemblyObject");
},
});
// --- Sketcher edit (highest priority — VP in edit) ---
registerContext({
/*.id =*/QStringLiteral("sketcher.edit"),
@@ -272,7 +320,10 @@ void EditingContextResolver::registerBuiltinContexts()
/*.priority =*/40,
/*.match =*/
[]() {
auto* obj = getActivePartObject();
auto* obj = getActivePdBodyObject();
if (!obj) {
obj = getActivePartObject();
}
if (!obj || !objectIsDerivedFrom(obj, "PartDesign::Body")) {
return false;
}
@@ -301,7 +352,10 @@ void EditingContextResolver::registerBuiltinContexts()
/*.priority =*/30,
/*.match =*/
[]() {
auto* obj = getActivePartObject();
auto* obj = getActivePdBodyObject();
if (!obj) {
obj = getActivePartObject();
}
return obj && objectIsDerivedFrom(obj, "PartDesign::Body");
},
});
@@ -488,6 +542,13 @@ EditingContext EditingContextResolver::resolve() const
if (label.contains(QStringLiteral("{name}"))) {
// For edit-mode contexts, use the in-edit object name
QString name = getInEditLabel();
if (name.isEmpty()) {
// Try pdbody first for PartDesign contexts
auto* bodyObj = getActivePdBodyObject();
if (bodyObj) {
name = QString::fromUtf8(bodyObj->Label.getValue());
}
}
if (name.isEmpty()) {
name = getActivePartLabel();
}
@@ -548,6 +609,25 @@ QStringList EditingContextResolver::buildBreadcrumb(const EditingContext& ctx) c
return crumbs;
}
// Assembly > Body breadcrumb for in-assembly part editing
if (ctx.id == QStringLiteral("partdesign.in_assembly")) {
auto* guiDoc = Application::Instance->activeDocument();
if (guiDoc) {
auto* vp = guiDoc->getInEdit();
if (vp) {
auto* vpd = dynamic_cast<ViewProviderDocumentObject*>(vp);
if (vpd && vpd->getObject()) {
crumbs << QString::fromUtf8(vpd->getObject()->Label.getValue());
}
}
}
auto* body = getActivePdBodyObject();
if (body) {
crumbs << QString::fromUtf8(body->Label.getValue());
}
return crumbs;
}
// Always start with the active part/body/assembly label
QString partLabel = getActivePartLabel();
if (!partLabel.isEmpty()) {
@@ -582,6 +662,14 @@ QStringList EditingContextResolver::buildBreadcrumbColors(const EditingContext&
{
QStringList colors;
if (ctx.id == QStringLiteral("partdesign.in_assembly")) {
for (int i = 0; i < ctx.breadcrumb.size(); ++i) {
colors << (i == 0 ? QLatin1String(CatppuccinMocha::Blue)
: QLatin1String(CatppuccinMocha::Mauve));
}
return colors;
}
if (ctx.breadcrumb.size() <= 1) {
colors << ctx.color;
return colors;