From 8ec6605fc44c966de11094a307637ae2a41334ff Mon Sep 17 00:00:00 2001 From: tetektoza Date: Wed, 28 May 2025 11:27:11 +0200 Subject: [PATCH] BIM: Add support for deactivation active object to BIM Views Tree (#21570) * BIM: Use checkbox in model tree for Activation/Deactivation of WP * BIM: Set active object after deactivating current object if it exists Currently we can get into a scenario where user can activate two working planes, one after another. For example, Level, and then Level001. If they activate both, and then deactivate Level001, working plane switches back to Level. But, we didn't set the object as the active one, so user didn't have clear information that they can deactivate it, only the working plane was switching it. So this patch sets the object as the active one, if it exists. * BIM: Add support for deactivation active object to BIM Views Tree As the title says - it adds the checkbox that's similarly done in Part workbench, so user can select/deselect the item and if they had previous active object, it will also fall back to the previous object. Also, moved out part of the common logic from ArchBuildingPart and BimViews to utils. * BIM: Handle correct context on activating WP for NativeIFC/BIM * BIM: Remove redundant logic from BIM Views upon double click As all of the logic is being handled now in `activate` function in BimViews, this logic is redundant * BIM: Rename button for taskbar and BIM Views from Activate to Active --- src/Mod/BIM/ArchBuildingPart.py | 50 ++++++++++++++-------- src/Mod/BIM/bimcommands/BimViews.py | 41 +++++++++--------- src/Mod/Draft/WorkingPlane.py | 1 + src/Mod/Draft/draftutils/utils.py | 65 +++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 40 deletions(-) diff --git a/src/Mod/BIM/ArchBuildingPart.py b/src/Mod/BIM/ArchBuildingPart.py index 48b24dd7c8..f335579d6c 100644 --- a/src/Mod/BIM/ArchBuildingPart.py +++ b/src/Mod/BIM/ArchBuildingPart.py @@ -811,15 +811,14 @@ class ViewProviderBuildingPart: if FreeCADGui.activeWorkbench().name() != 'BIMWorkbench': return if (not hasattr(vobj,"DoubleClickActivates")) or vobj.DoubleClickActivates: + menuTxt = translate("Arch", "Active") + actionActivate = QtGui.QAction(menuTxt, menu) + actionActivate.setCheckable(True) if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == self.Object: - menuTxt = translate("Arch", "Deactivate") + actionActivate.setChecked(True) else: - menuTxt = translate("Arch", "Activate") - actionActivate = QtGui.QAction(menuTxt, - menu) - QtCore.QObject.connect(actionActivate, - QtCore.SIGNAL("triggered()"), - self.activate) + actionActivate.setChecked(False) + actionActivate.triggered.connect(lambda _: self.activate(actionActivate)) menu.addAction(actionActivate) actionSetWorkingPlane = QtGui.QAction(QtGui.QIcon(":/icons/Draft_SelectPlane.svg"), @@ -859,17 +858,15 @@ class ViewProviderBuildingPart: self.cloneUp) menu.addAction(actionCloneUp) - def activate(self): + def activate(self, action=None): + from draftutils.utils import toggle_working_plane vobj = self.Object.ViewObject - - if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == self.Object: - FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", None) - if vobj.SetWorkingPlane: - self.setWorkingPlane(restore=True) - elif (not hasattr(vobj,"DoubleClickActivates")) or vobj.DoubleClickActivates: - FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", self.Object) - if vobj.SetWorkingPlane: - self.setWorkingPlane() + + if (not hasattr(vobj,"DoubleClickActivates")) or vobj.DoubleClickActivates: + if toggle_working_plane(self.Object, action, restore=True): + print("Setting active working plane to: ", self.Object.Label) + else: + print("Deactivating working plane from: ", self.Object.Label) FreeCADGui.Selection.clearSelection() @@ -883,7 +880,24 @@ class ViewProviderBuildingPart: autoclip = vobj.AutoCutView if restore: if wp.label.rstrip("*") == self.Object.Label: - wp._previous() + prev_data = wp._previous() + if prev_data: + prev_label = prev_data.get("label", "").rstrip("*") + prev_obj = None + for obj in FreeCAD.ActiveDocument.Objects: + if hasattr(obj, "Label") and obj.Label == prev_label: + prev_obj = obj + break + + if prev_obj: + # check in which context we need to set the active object + context = "Arch" + obj_type = Draft.getType(prev_obj) + if obj_type == "IfcBuildingStorey": + context = "NativeIFC" + FreeCADGui.ActiveDocument.ActiveView.setActiveObject(context, prev_obj) + print(f"Set active object to: {prev_obj.Label} (context: {context})") + if autoclip: vobj.CutView = False else: diff --git a/src/Mod/BIM/bimcommands/BimViews.py b/src/Mod/BIM/bimcommands/BimViews.py index d708fe5363..588dd1f530 100644 --- a/src/Mod/BIM/bimcommands/BimViews.py +++ b/src/Mod/BIM/bimcommands/BimViews.py @@ -86,7 +86,7 @@ class BIM_Views: # set button self.dialog.menu = QtGui.QMenu() - for button in [("Activate", translate("BIM","Activate (default)")), + for button in [("Active", translate("BIM","Active (default)")), ("AddLevel", translate("BIM","Add level")), ("AddProxy", translate("BIM","Add proxy")), ("Delete", translate("BIM","Delete")), @@ -97,10 +97,11 @@ class BIM_Views: action = QtGui.QAction(button[1]) # Make the "Activate" button bold, as this is the default one - if button[0] == "Activate": + if button[0] == "Active": font = action.font() font.setBold(True) action.setFont(font) + action.setCheckable(True) self.dialog.menu.addAction(action) setattr(self.dialog,"button"+button[0], action) @@ -115,7 +116,6 @@ class BIM_Views: self.dialog.buttonRename.setIcon( QtGui.QIcon(":/icons/accessories-text-editor.svg") ) - self.dialog.buttonActivate.setIcon(QtGui.QIcon(":/icons/edit_OK.svg")) # set tooltips self.dialog.buttonAddLevel.setToolTip(translate("BIM","Creates a new level")) @@ -125,7 +125,7 @@ class BIM_Views: self.dialog.buttonIsolate.setToolTip(translate("BIM","Turns all items off except the selected ones")) self.dialog.buttonSaveView.setToolTip(translate("BIM","Saves the current camera position to the selected items")) self.dialog.buttonRename.setToolTip(translate("BIM","Renames the selected item")) - self.dialog.buttonActivate.setToolTip(translate("BIM","Activates the selected item")) + self.dialog.buttonActive.setToolTip(translate("BIM","Activates the selected item")) # connect signals self.dialog.buttonAddLevel.triggered.connect(self.addLevel) @@ -135,7 +135,7 @@ class BIM_Views: self.dialog.buttonIsolate.triggered.connect(self.isolate) self.dialog.buttonSaveView.triggered.connect(self.saveView) self.dialog.buttonRename.triggered.connect(self.rename) - self.dialog.buttonActivate.triggered.connect(self.activate) + self.dialog.buttonActive.triggered.connect(lambda: BIM_Views.activate(self.dialog)) self.dialog.tree.itemClicked.connect(self.select) self.dialog.tree.itemDoubleClicked.connect(show) self.dialog.viewtree.itemDoubleClicked.connect(show) @@ -431,14 +431,16 @@ class BIM_Views: item = vm.tree.selectedItems()[-1] vm.tree.editItem(item, 0) @staticmethod - def activate(): + def activate(dialog=None): + from draftutils.utils import toggle_working_plane vm = findWidget() if vm: if vm.tree.selectedItems(): - if vm.tree.selectedItems(): - item = vm.tree.selectedItems()[-1] - obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0)) - obj.ViewObject.Proxy.setWorkingPlane() + item = vm.tree.selectedItems()[-1] + obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0)) + if obj: + toggle_working_plane(obj, None, restore=True, dialog=dialog) + FreeCADGui.Selection.clearSelection() def editObject(self, item, column): "renames or edit height of the actual object" @@ -564,6 +566,10 @@ class BIM_Views: if selobj: if Draft.getType(selobj).startswith("Ifc"): self.dialog.buttonAddProxy.setEnabled(False) + if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == selobj: + self.dialog.buttonActive.setChecked(True) + else: + self.dialog.buttonActive.setChecked(False) self.dialog.menu.exec_(self.dialog.tree.mapToGlobal(pos)) def getViews(self): @@ -655,19 +661,10 @@ def show(item, column=None): vparam.SetBool("RadialGradient", False) else: # case 3: This is maybe a BuildingPart. Place the WP on it") - BIM_Views.activate() + type = Draft.getType(obj) + if type == "BuildingPart" or type == "IfcBuildingStorey": + BIM_Views.activate() - # perform stored interactions - if getattr(obj.ViewObject, "SetWorkingPlane", False): - obj.ViewObject.Proxy.setWorkingPlane() - if getattr(obj.ViewObject, "DoubleClickActivates", True): - if Draft.getType(obj) == "BuildingPart": - FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", obj) - elif Draft.getType(obj) == "IfcBuildingStorey": - FreeCADGui.ActiveDocument.ActiveView.setActiveObject("NativeIFC", obj) - else: - FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", None) - FreeCADGui.ActiveDocument.ActiveView.setActiveObject("NativeIFC", None) if vm: # store the last double-clicked item for the BIM WPView command if isinstance(item, str) or ( diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index 3f26173a4c..9516d97fd1 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -1667,6 +1667,7 @@ class PlaneGui(PlaneBase): self.set_parameters(self._history["data_list"][idx]) self._history["idx"] = idx self._update_all(_hist_add=False) + return self._history["data_list"][idx] def _next(self): idx = self._history["idx"] diff --git a/src/Mod/Draft/draftutils/utils.py b/src/Mod/Draft/draftutils/utils.py index a0e2c8fab9..342350249c 100644 --- a/src/Mod/Draft/draftutils/utils.py +++ b/src/Mod/Draft/draftutils/utils.py @@ -1068,4 +1068,69 @@ def pyopen(file, mode='r', buffering=-1, encoding=None, errors=None, newline=Non encoding = 'utf-8' return open(file, mode, buffering, encoding, errors, newline, closefd, opener) +def toggle_working_plane(obj, action=None, restore=False, dialog=None): + """Toggle the active state of a working plane object. + + This function handles the common logic for activating and deactivating + working plane objects like BuildingParts and WorkingPlaneProxies. + It can be used by different modules that need to implement similar + working plane activation behavior. + + Parameters + ---------- + obj : App::DocumentObject + The object to activate or deactivate as a working plane. + action : QAction, optional + The action button that triggered this function, to update its checked state. + restore : bool, optional + If True, will restore the previous working plane when deactivating. + Defaults to False. + dialog : QDialog, optional + If provided, will update the checked state of the activate button in the dialog. + + Returns + ------- + bool + True if the object was activated, False if it was deactivated. + """ + import FreeCADGui + import Draft + + # Determine the appropriate context based on object type + context = "Arch" + obj_type = get_type(obj) + if obj_type == "IfcBuildingStorey": + context = "NativeIFC" + + # Check if the object is already active in its context + is_active_arch = (FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == obj) + is_active_ifc = (FreeCADGui.ActiveDocument.ActiveView.getActiveObject("NativeIFC") == obj) + is_active = is_active_arch or is_active_ifc + if is_active: + # Deactivate the object + if is_active_arch: + FreeCADGui.ActiveDocument.ActiveView.setActiveObject("Arch", None) + if is_active_ifc: + FreeCADGui.ActiveDocument.ActiveView.setActiveObject("NativeIFC", None) + + if hasattr(obj, "ViewObject") and hasattr(obj.ViewObject, "Proxy") and \ + hasattr(obj.ViewObject.Proxy, "setWorkingPlane"): + obj.ViewObject.Proxy.setWorkingPlane(restore=True) + if action: + action.setChecked(False) + if dialog and hasattr(dialog, "buttonActive"): + dialog.buttonActive.setChecked(False) + return False + else: + # Activate the object + FreeCADGui.ActiveDocument.ActiveView.setActiveObject(context, obj) + if hasattr(obj, "ViewObject") and hasattr(obj.ViewObject, "Proxy") and \ + hasattr(obj.ViewObject.Proxy, "setWorkingPlane"): + obj.ViewObject.Proxy.setWorkingPlane() + if action: + action.setChecked(True) + if dialog and hasattr(dialog, "buttonActive"): + dialog.buttonActive.setChecked(True) + return True + ## @}