From 62cbaf73368bcdb4a1ec1a85dbc207e7eda3110a Mon Sep 17 00:00:00 2001 From: paddle Date: Thu, 22 Jan 2026 14:21:28 +0100 Subject: [PATCH 1/8] Assembly: Fix "deactivated by activating a App::Part, assembly stay in edit" Fix "Activating a body in a part in an assembly deactivates the assembly and activate the part" Fix "A manually deactivated assembly is still restoring later" --- src/Gui/ActiveObjectList.cpp | 8 ++++ src/Gui/ActiveObjectList.h | 1 + src/Gui/Document.h | 4 +- src/Gui/ViewProviderPart.cpp | 4 +- src/Gui/ViewProviderPart.h | 3 +- src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 45 ++++++++++++++----- src/Mod/Assembly/Gui/ViewProviderAssembly.h | 2 + src/Mod/PartDesign/Gui/Utils.cpp | 6 ++- 8 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/Gui/ActiveObjectList.cpp b/src/Gui/ActiveObjectList.cpp index 6d27c0f22c..49e64b32fa 100644 --- a/src/Gui/ActiveObjectList.cpp +++ b/src/Gui/ActiveObjectList.cpp @@ -187,6 +187,9 @@ void Gui::ActiveObjectList::setObject( } if (!obj) { + if (_Doc) { + _Doc->signalActivatedVP(nullptr, name); + } return; } @@ -202,6 +205,11 @@ void Gui::ActiveObjectList::setObject( _ObjectMap[name] = info; setHighlight(info, mode, true); + + auto vp = freecad_cast(Application::Instance->getViewProvider(obj)); + if (vp) { + vp->getDocument()->signalActivatedVP(vp, name); + } } bool Gui::ActiveObjectList::hasObject(const char* name) const diff --git a/src/Gui/ActiveObjectList.h b/src/Gui/ActiveObjectList.h index fee647cae4..b34b0d6f05 100644 --- a/src/Gui/ActiveObjectList.h +++ b/src/Gui/ActiveObjectList.h @@ -106,5 +106,6 @@ private: static const char PDBODYKEY[] = "pdbody"; static const char PARTKEY[] = "part"; +static const char ASSEMBLYKEY[] = "assembly"; #endif diff --git a/src/Gui/Document.h b/src/Gui/Document.h index b8a1213561..ea95612a4a 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -121,8 +121,10 @@ public: signalChangedObject; /// signal on renamed Object mutable fastsignals::signal signalRelabelObject; - /// signal on activated Object + /// signal on activated Object (relay of App activation signal) mutable fastsignals::signal signalActivatedObject; + /// signal on activated Object in the tree (bold item) + mutable fastsignals::signal signalActivatedVP; /// signal on entering in edit mode mutable fastsignals::signal signalInEdit; /// signal on leaving edit mode diff --git a/src/Gui/ViewProviderPart.cpp b/src/Gui/ViewProviderPart.cpp index 7c576959a6..a0db89d0c2 100644 --- a/src/Gui/ViewProviderPart.cpp +++ b/src/Gui/ViewProviderPart.cpp @@ -77,7 +77,7 @@ void ViewProviderPart::setupContextMenu(QMenu* menu, QObject* receiver, const ch ViewProviderGeometryObject::setupContextMenu(menu, receiver, member); } -bool ViewProviderPart::isActivePart() +bool ViewProviderPart::isActivePart(const char* key) { App::DocumentObject* activePart = nullptr; auto activeDoc = Gui::Application::Instance->activeDocument(); @@ -89,7 +89,7 @@ bool ViewProviderPart::isActivePart() return false; } - activePart = activeView->getActiveObject(PARTKEY); + activePart = activeView->getActiveObject(key); if (activePart == this->getObject()) { return true; diff --git a/src/Gui/ViewProviderPart.h b/src/Gui/ViewProviderPart.h index 5d9ec8db4a..336d2fd105 100644 --- a/src/Gui/ViewProviderPart.h +++ b/src/Gui/ViewProviderPart.h @@ -23,6 +23,7 @@ #ifndef GUI_VIEWPROVIDER_ViewProviderPart_H #define GUI_VIEWPROVIDER_ViewProviderPart_H +#include "ActiveObjectList.h" #include "ViewProviderGeometryObject.h" #include "ViewProviderOriginGroup.h" #include "ViewProviderFeaturePython.h" @@ -44,7 +45,7 @@ public: bool doubleClicked() override; void setupContextMenu(QMenu* menu, QObject* receiver, const char* member) override; - bool isActivePart(); + bool isActivePart(const char* key = PARTKEY); void toggleActivePart(); /// deliver the icon shown in the tree view diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 5056ad9201..5892bc4e98 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -143,7 +143,7 @@ void ViewProviderAssembly::setupContextMenu(QMenu* menu, QObject* receiver, cons QAction* act = menu->addAction(QObject::tr("Active object")); act->setCheckable(true); - act->setChecked(isActivePart()); + act->setChecked(isActivePart(ASSEMBLYKEY)); func->trigger(act, [this]() { this->doubleClicked(); }); ViewProviderDragger::setupContextMenu(menu, receiver, member); // NOLINT @@ -153,6 +153,7 @@ bool ViewProviderAssembly::doubleClicked() { if (isInEditMode()) { autoCollapseOnDeactivation = true; + getDocument()->setEditRestore(false); getDocument()->resetEdit(); } else { @@ -280,6 +281,7 @@ bool ViewProviderAssembly::setEdit(int mode) { if (mode == ViewProvider::Default) { // Ask that this edit mode be restored. For example if it is quit to edit a sketch. + Base::Console().warning("setEdit\n"); getDocument()->setEditRestore(true); autoCollapseOnDeactivation = false; @@ -290,7 +292,7 @@ bool ViewProviderAssembly::setEdit(int mode) "Gui.getDocument(appDoc).ActiveView.setActiveObject('%s', " "appDoc.getObject('%s'))", this->getObject()->getDocument()->getName(), - PARTKEY, + ASSEMBLYKEY, this->getObject()->getNameInDocument() ); @@ -310,6 +312,13 @@ bool ViewProviderAssembly::setEdit(int mode) UpdateSolverInformation(); }); + connectActivatedVP = getDocument()->signalActivatedVP.connect(std::bind( + &ViewProviderAssembly::slotActivatedVP, + this, + std::placeholders::_1, + std::placeholders::_2 + )); + assembly->solve(); return true; @@ -334,13 +343,15 @@ void ViewProviderAssembly::unsetEdit(int mode) } // Set the part as not 'Activated' ie not bold in the tree. - Gui::Command::doCommand( - Gui::Command::Gui, - "appDoc = App.getDocument('%s')\n" - "Gui.getDocument(appDoc).ActiveView.setActiveObject('%s', None)", - this->getObject()->getDocument()->getName(), - PARTKEY - ); + if (isActivePart(ASSEMBLYKEY)) { + Gui::Command::doCommand( + Gui::Command::Gui, + "appDoc = App.getDocument('%s')\n" + "Gui.getDocument(appDoc).ActiveView.setActiveObject('%s', None)", + this->getObject()->getDocument()->getName(), + ASSEMBLYKEY + ); + } Gui::TaskView::TaskView* taskView = Gui::Control().taskPanel(); if (taskView) { @@ -349,12 +360,26 @@ void ViewProviderAssembly::unsetEdit(int mode) } connectSolverUpdate.disconnect(); + connectActivatedVP.disconnect(); return; } ViewProviderPart::unsetEdit(mode); } +void ViewProviderAssembly::slotActivatedVP(const Gui::ViewProviderDocumentObject* vp, const char* name) +{ + if (name && strcmp(name, ASSEMBLYKEY) == 0) { + + // If the new active VP is NOT this assembly (meaning we lost activation or it was cleared) + if (vp != this && isInEditMode()) { + autoCollapseOnDeactivation = true; + getDocument()->setEditRestore(false); + getDocument()->resetEdit(); + } + } +} + void ViewProviderAssembly::setDragger() { // Create the dragger coin object @@ -404,7 +429,7 @@ App::DocumentObject* ViewProviderAssembly::getActivePart() const if (!activeView) { return nullptr; } - return activeView->getActiveObject(PARTKEY); + return activeView->getActiveObject(ASSEMBLYKEY); } bool ViewProviderAssembly::keyPressed(bool pressed, int key) diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.h b/src/Mod/Assembly/Gui/ViewProviderAssembly.h index 9ac9499f7e..a6b37aa697 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.h +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.h @@ -263,6 +263,7 @@ private: ); void slotAboutToOpenTransaction(const std::string& cmdName); + void slotActivatedVP(const Gui::ViewProviderDocumentObject* vp, const char* name); struct ComponentState { @@ -289,6 +290,7 @@ private: TaskAssemblyMessages* taskSolver; + fastsignals::connection connectActivatedVP; fastsignals::connection connectSolverUpdate; fastsignals::scoped_connection m_preTransactionConn; }; diff --git a/src/Mod/PartDesign/Gui/Utils.cpp b/src/Mod/PartDesign/Gui/Utils.cpp index 4a3e7ed1d8..47179c40bb 100644 --- a/src/Mod/PartDesign/Gui/Utils.cpp +++ b/src/Mod/PartDesign/Gui/Utils.cpp @@ -284,7 +284,11 @@ App::Part* getActivePart() { Gui::MDIView* activeView = Gui::Application::Instance->activeView(); if (activeView) { - return activeView->getActiveObject(PARTKEY); + auto* obj = activeView->getActiveObject(PARTKEY); + if (!obj) { + obj = activeView->getActiveObject(ASSEMBLYKEY); + } + return obj; } else { return nullptr; From b87899f4ca98e688fa35037fc56b0e0fc4ba78d8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:19:10 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 5892bc4e98..a864c7a620 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -312,12 +312,14 @@ bool ViewProviderAssembly::setEdit(int mode) UpdateSolverInformation(); }); - connectActivatedVP = getDocument()->signalActivatedVP.connect(std::bind( - &ViewProviderAssembly::slotActivatedVP, - this, - std::placeholders::_1, - std::placeholders::_2 - )); + connectActivatedVP = getDocument()->signalActivatedVP.connect( + std::bind( + &ViewProviderAssembly::slotActivatedVP, + this, + std::placeholders::_1, + std::placeholders::_2 + ) + ); assembly->solve(); From 5e882f1773ac3527a384e3550e67f6088790abd6 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 23 Jan 2026 17:38:46 +0100 Subject: [PATCH 3/8] Update ActiveObjectList.cpp --- src/Gui/ActiveObjectList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gui/ActiveObjectList.cpp b/src/Gui/ActiveObjectList.cpp index 49e64b32fa..ef0e681a11 100644 --- a/src/Gui/ActiveObjectList.cpp +++ b/src/Gui/ActiveObjectList.cpp @@ -188,7 +188,7 @@ void Gui::ActiveObjectList::setObject( if (!obj) { if (_Doc) { - _Doc->signalActivatedVP(nullptr, name); + _Doc->signalActivatedViewProvider(nullptr, name); } return; } @@ -208,7 +208,7 @@ void Gui::ActiveObjectList::setObject( auto vp = freecad_cast(Application::Instance->getViewProvider(obj)); if (vp) { - vp->getDocument()->signalActivatedVP(vp, name); + vp->getDocument()->signalActivatedViewProvider(vp, name); } } From 44d383b5f2090101b99721c65c6726af4b6a24f6 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 23 Jan 2026 17:39:17 +0100 Subject: [PATCH 4/8] Update Document.h --- src/Gui/Document.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Gui/Document.h b/src/Gui/Document.h index ea95612a4a..e86ab6bfab 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -124,7 +124,7 @@ public: /// signal on activated Object (relay of App activation signal) mutable fastsignals::signal signalActivatedObject; /// signal on activated Object in the tree (bold item) - mutable fastsignals::signal signalActivatedVP; + mutable fastsignals::signal signalActivatedViewProvider; /// signal on entering in edit mode mutable fastsignals::signal signalInEdit; /// signal on leaving edit mode @@ -374,3 +374,4 @@ private: #endif // GUI_DOCUMENT_H + From 9c6ea807574ba552c1a97e9dbddd42de6aecb61e Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 23 Jan 2026 17:40:01 +0100 Subject: [PATCH 5/8] Update ViewProviderAssembly.cpp --- src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index a864c7a620..5bebe97c0a 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -281,7 +281,6 @@ bool ViewProviderAssembly::setEdit(int mode) { if (mode == ViewProvider::Default) { // Ask that this edit mode be restored. For example if it is quit to edit a sketch. - Base::Console().warning("setEdit\n"); getDocument()->setEditRestore(true); autoCollapseOnDeactivation = false; @@ -312,7 +311,7 @@ bool ViewProviderAssembly::setEdit(int mode) UpdateSolverInformation(); }); - connectActivatedVP = getDocument()->signalActivatedVP.connect( + connectActivatedVP = getDocument()->signalActivatedViewProvider.connect( std::bind( &ViewProviderAssembly::slotActivatedVP, this, From dfada102f741d083a7429c4cfb2d7ab1133c13af Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 23 Jan 2026 17:42:12 +0100 Subject: [PATCH 6/8] Update Document.h --- src/Gui/Document.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Gui/Document.h b/src/Gui/Document.h index e86ab6bfab..0aa7ed4403 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -374,4 +374,3 @@ private: #endif // GUI_DOCUMENT_H - From 31abd385142e3652ebb4c9ee5bb1a43082a29295 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:42:37 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Gui/Document.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Gui/Document.h b/src/Gui/Document.h index 0aa7ed4403..892dc96eb1 100644 --- a/src/Gui/Document.h +++ b/src/Gui/Document.h @@ -124,7 +124,8 @@ public: /// signal on activated Object (relay of App activation signal) mutable fastsignals::signal signalActivatedObject; /// signal on activated Object in the tree (bold item) - mutable fastsignals::signal signalActivatedViewProvider; + mutable fastsignals::signal + signalActivatedViewProvider; /// signal on entering in edit mode mutable fastsignals::signal signalInEdit; /// signal on leaving edit mode From 9b6de802319b878293cc4de2ec0180472e2e30fe Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 26 Jan 2026 11:35:38 +0100 Subject: [PATCH 8/8] Update UtilsAssembly.py --- src/Mod/Assembly/UtilsAssembly.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index ca26472864..f6defc2738 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -43,6 +43,10 @@ def activePartOrAssembly(): if doc is None or doc.ActiveView is None: return None + activeAssembly = doc.ActiveView.getActiveObject("assembly") + + if activeAssembly: + return activeAssembly return doc.ActiveView.getActiveObject("part")