From d48a47f1208c2e468cb6285265bcb174b6e7ac46 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 14 May 2024 09:30:49 +0200 Subject: [PATCH] BIM: Fixed project creation and refactored status bar toggle system --- src/Mod/BIM/Init.py | 4 +- src/Mod/BIM/bimcommands/BimProject.py | 3 + src/Mod/BIM/nativeifc/ifc_commands.py | 10 +- src/Mod/BIM/nativeifc/ifc_import.py | 58 +++++------ src/Mod/BIM/nativeifc/ifc_observer.py | 84 ++-------------- src/Mod/BIM/nativeifc/ifc_status.py | 133 ++++++++++++++++++-------- src/Mod/BIM/nativeifc/ifc_tools.py | 5 +- 7 files changed, 140 insertions(+), 157 deletions(-) diff --git a/src/Mod/BIM/Init.py b/src/Mod/BIM/Init.py index aa6572ca15..10f29ae49f 100644 --- a/src/Mod/BIM/Init.py +++ b/src/Mod/BIM/Init.py @@ -22,8 +22,8 @@ # add import/export types -FreeCAD.addImportType("Industry Foundation Classes (*.ifc)","importers.exportIFC") -# FreeCAD.addExportType("Industry Foundation Classes (*.ifc)","importIFC") +FreeCAD.addExportType("Industry Foundation Classes (*.ifc)","importers.exportIFC") +# FreeCAD.addImportType("Industry Foundation Classes (*.ifc)","importIFC") FreeCAD.addImportType("Industry Foundation Classes (*.ifc)", "nativeifc.ifc_import") FreeCAD.addExportType("Industry Foundation Classes - IFCJSON (*.ifcJSON)","importers.exportIFC") FreeCAD.addImportType("Wavefront OBJ - Arch module (*.obj *.OBJ)","importers.importOBJ") diff --git a/src/Mod/BIM/bimcommands/BimProject.py b/src/Mod/BIM/bimcommands/BimProject.py index d827957bd2..f6482b67a5 100644 --- a/src/Mod/BIM/bimcommands/BimProject.py +++ b/src/Mod/BIM/bimcommands/BimProject.py @@ -41,6 +41,9 @@ class BIM_Project: "Create an empty NativeIFC project"), } + def IsActive(self): + return not hasattr(FreeCAD.ActiveDocument, "IfcFilePath") + def Activated(self): from nativeifc import ifc_tools project = ifc_tools.create_document(FreeCAD.ActiveDocument) diff --git a/src/Mod/BIM/nativeifc/ifc_commands.py b/src/Mod/BIM/nativeifc/ifc_commands.py index e55961d21c..404ebe0123 100644 --- a/src/Mod/BIM/nativeifc/ifc_commands.py +++ b/src/Mod/BIM/nativeifc/ifc_commands.py @@ -190,9 +190,8 @@ class IFC_Save: def IsActive(self): doc = FreeCAD.ActiveDocument - if getattr(doc, "IfcFilePath", None): - if getattr(getattr(doc, "Proxy", None), "ifcfile", None): - return True + if hasattr(doc, "IfcFilePath"): + return True return False def Activated(self): @@ -223,9 +222,8 @@ class IFC_SaveAs: def IsActive(self): doc = FreeCAD.ActiveDocument - if getattr(doc, "IfcFilePath", None): - if getattr(getattr(doc, "Proxy", None), "ifcfile", None): - return True + if hasattr(doc, "IfcFilePath"): + return True return False def Activated(self): diff --git a/src/Mod/BIM/nativeifc/ifc_import.py b/src/Mod/BIM/nativeifc/ifc_import.py index 638f86cdf2..7c4792cf3c 100644 --- a/src/Mod/BIM/nativeifc/ifc_import.py +++ b/src/Mod/BIM/nativeifc/ifc_import.py @@ -36,7 +36,7 @@ if FreeCAD.GuiUp: import Arch_rc -params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC") +PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC") def open(filename): @@ -78,7 +78,7 @@ def insert( except: document = FreeCAD.newDocument() if singledoc is None: - singledoc = params.GetBool("SingleDoc", False) + singledoc = PARAMS.GetBool("SingleDoc", False) if singledoc: prj_obj = ifc_tools.convert_document(document, filename, shapemode, strategy) QtCore.QTimer.singleShot(100, toggle_lock_on) @@ -87,17 +87,17 @@ def insert( document, filename, shapemode, strategy ) QtCore.QTimer.singleShot(100, toggle_lock_off) - if params.GetBool("LoadOrphans", True): + if PARAMS.GetBool("LoadOrphans", True): ifc_tools.load_orphans(prj_obj) - if not silent and params.GetBool("LoadMaterials", False): + if not silent and PARAMS.GetBool("LoadMaterials", False): ifc_materials.load_materials(prj_obj) - if params.GetBool("LoadLayers", False): + if PARAMS.GetBool("LoadLayers", False): ifc_layers.load_layers(prj_obj) - if params.GetBool("LoadPsets", False): + if PARAMS.GetBool("LoadPsets", False): ifc_psets.load_psets(prj_obj) document.recompute() # print a reference to the IFC file on the console - if FreeCAD.GuiUp and params.GetBool("IfcFileToConsole", False): + if FreeCAD.GuiUp and PARAMS.GetBool("IfcFileToConsole", False): if isinstance(prj_obj, FreeCAD.DocumentObject): pstr = "FreeCAD.getDocument('{}').{}.Proxy.ifcfile" pstr = pstr.format(prj_obj.Document.Name, prj_obj.Name) @@ -128,19 +128,19 @@ def get_options(strategy=None, shapemode=None, switchwb=None, silent=False): 2 = all children """ - psets = params.GetBool("LoadPsets", False) - materials = params.GetBool("LoadMaterials", False) - layers = params.GetBool("LoadLayers", False) - singledoc = params.GetBool("SingleDoc", False) + psets = PARAMS.GetBool("LoadPsets", False) + materials = PARAMS.GetBool("LoadMaterials", False) + layers = PARAMS.GetBool("LoadLayers", False) + singledoc = PARAMS.GetBool("SingleDoc", False) if strategy is None: - strategy = params.GetInt("ImportStrategy", 0) + strategy = PARAMS.GetInt("ImportStrategy", 0) if shapemode is None: - shapemode = params.GetInt("ShapeMode", 0) + shapemode = PARAMS.GetInt("ShapeMode", 0) if switchwb is None: - switchwb = params.GetBool("SwitchWB", True) + switchwb = PARAMS.GetBool("SwitchWB", True) if silent: return strategy, shapemode, switchwb - ask = params.GetBool("AskAgain", False) + ask = PARAMS.GetBool("AskAgain", False) if ask and FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui @@ -166,22 +166,22 @@ def get_options(strategy=None, shapemode=None, switchwb=None, silent=False): materials = dlg.checkLoadMaterials.isChecked() layers = dlg.checkLoadLayers.isChecked() singledoc = dlg.comboSingleDoc.currentIndex() - params.SetInt("ImportStrategy", strategy) - params.SetInt("ShapeMode", shapemode) - params.SetBool("SwitchWB", switchwb) - params.SetBool("AskAgain", ask) - params.SetBool("LoadPsets", psets) - params.SetBool("LoadMaterials", materials) - params.SetBool("LoadLayers", layers) - params.SetBool("SingleDoc", bool(1 - singledoc)) + PARAMS.SetInt("ImportStrategy", strategy) + PARAMS.SetInt("ShapeMode", shapemode) + PARAMS.SetBool("SwitchWB", switchwb) + PARAMS.SetBool("AskAgain", ask) + PARAMS.SetBool("LoadPsets", psets) + PARAMS.SetBool("LoadMaterials", materials) + PARAMS.SetBool("LoadLayers", layers) + PARAMS.SetBool("SingleDoc", bool(1 - singledoc)) return strategy, shapemode, switchwb def get_project_type(silent=False): """Gets the type of project to make""" - ask = params.GetBool("ProjectAskAgain", True) - ptype = params.GetBool("ProjectFull", False) + ask = PARAMS.GetBool("ProjectAskAgain", True) + ptype = PARAMS.GetBool("ProjectFull", False) if silent: return ptype if ask and FreeCAD.GuiUp: @@ -192,8 +192,8 @@ def get_project_type(silent=False): result = dlg.exec_() ask = not (dlg.checkBox.isChecked()) ptype = bool(result) - params.SetBool("ProjectAskAgain", ask) - params.SetBool("ProjectFull", ptype) + PARAMS.SetBool("ProjectAskAgain", ask) + PARAMS.SetBool("ProjectFull", ptype) return ptype @@ -201,11 +201,11 @@ def get_project_type(silent=False): def toggle_lock_on(): - ifc_status.toggle_lock(True) + ifc_status.on_toggle_lock(True, noconvert=True, setchecked=True) def toggle_lock_off(): - ifc_status.toggle_lock(False) + ifc_status.on_toggle_lock(False, noconvert=True, setchecked=True) def unset_modified(): diff --git a/src/Mod/BIM/nativeifc/ifc_observer.py b/src/Mod/BIM/nativeifc/ifc_observer.py index ee02911570..c0531cd747 100644 --- a/src/Mod/BIM/nativeifc/ifc_observer.py +++ b/src/Mod/BIM/nativeifc/ifc_observer.py @@ -32,14 +32,16 @@ params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC") def add_observer(): """Adds an observer to the running FreeCAD instance""" - observer = ifc_observer() - FreeCAD.addDocumentObserver(observer) + FreeCAD.BIMobserver = ifc_observer() + FreeCAD.addDocumentObserver(FreeCAD.BIMobserver) def remove_observer(): """Removes this observer if present""" - pass # TODO implement this!! + if hasattr(FreeCAD, "BIMobserver"): + FreeCAD.removeDocumentObserver(FreeCAD.BIMobserver) + del FreeCAD.BIMobserver class ifc_observer: @@ -99,9 +101,6 @@ class ifc_observer: ] if len(child) == 1: child[0].StepId = new_id - ifc_status.toggle_lock(True) - else: - ifc_status.toggle_lock(False) def slotCreatedObject(self, obj): """If this is an IFC document, turn the object into IFC""" @@ -119,16 +118,9 @@ class ifc_observer: def slotActivateDocument(self, doc): """Check if we need to lock""" - from PySide2 import QtCore # lazy loading from nativeifc import ifc_status + ifc_status.on_activate() - if hasattr(doc, "IfcFilePath"): - ifc_status.toggle_lock(True) - else: - ifc_status.toggle_lock(False) - if not hasattr(doc, "Proxy"): - # this is a new file, wait a bit to make sure all components are populated - QtCore.QTimer.singleShot(1000, self.propose_conversion) # implementation methods @@ -207,67 +199,3 @@ class ifc_observer: newobj = ifc_tools.aggregate(obj, doc) ifc_geometry.add_geom_properties(newobj) doc.recompute() - - def propose_conversion(self): - """Propose a conversion of the current document""" - - from nativeifc import ifc_status # lazy loading - - doc = FreeCAD.ActiveDocument - if not getattr(FreeCAD, "IsOpeningIFC", False): - if not hasattr(doc, "Proxy"): - if not getattr(doc, "Objects", True): - if not doc.FileName: - if not hasattr(doc, "IfcFilePath"): - # this is really a new, empty document - import FreeCADGui - import Arch_rc - from PySide import QtCore, QtGui # lazy loading - - if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": - return - if not params.GetBool("SingleDocAskAgain", True): - if params.GetBool("SingleDoc", True): - self.full = params.GetBool("ProjectFull", False) - QtCore.QTimer.singleShot( - 1000, self.convert_document - ) - return - else: - ifc_status.toggle_lock(False) - return - d = os.path.dirname(__file__) - dlg = FreeCADGui.PySideUic.loadUi( - os.path.join(d, "ui", "dialogConvertDocument.ui") - ) - dlg.checkStructure.setChecked( - params.GetBool("ProjectFull", False) - ) - result = dlg.exec_() - self.full = dlg.checkStructure.isChecked() - params.SetBool( - "SingleDocAskAgain", not dlg.checkAskAgain.isChecked() - ) - if result: - params.SetBool("SingleDoc", True) - params.SetBool("ProjectFull", self.full) - QtCore.QTimer.singleShot(1000, self.convert_document) - else: - params.SetBool("SingleDoc", False) - - def convert_document(self): - """Converts the active document""" - - from nativeifc import ifc_tools # lazy loading - from nativeifc import ifc_status - - doc = FreeCAD.ActiveDocument - ifc_tools.convert_document(doc, strategy=2, silent=True) - if self.full: - import Arch - - site = ifc_tools.aggregate(Arch.makeSite(), doc) - building = ifc_tools.aggregate(Arch.makeBuilding(), site) - storey = ifc_tools.aggregate(Arch.makeFloor(), building) - ifc_status.toggle_lock(True) - doc.recompute() diff --git a/src/Mod/BIM/nativeifc/ifc_status.py b/src/Mod/BIM/nativeifc/ifc_status.py index 0f55d715cf..30c9e3db72 100644 --- a/src/Mod/BIM/nativeifc/ifc_status.py +++ b/src/Mod/BIM/nativeifc/ifc_status.py @@ -46,20 +46,63 @@ def set_status_widget(statuswidget): lock_button.setIcon(icon) lock_button.setCheckable(True) doc = FreeCAD.ActiveDocument + statuswidget.addAction(lock_button) + statuswidget.lock_button = lock_button if doc and "IfcFilePath" in doc.PropertiesList: checked = True else: - checked = params.GetBool("SingleDoc", False) - toggle_lock(checked) - set_button(lock_button, checked) - lock_button.triggered.connect(do_lock) - lock_button.triggered.connect(toggle_lock) - statuswidget.addAction(lock_button) - statuswidget.lock_button = lock_button + checked = False + # set the button first, without converting the document + lock_button.setChecked(checked) + on_toggle_lock(checked, noconvert=True) + lock_button.triggered.connect(on_toggle_lock) -def toggle_lock(checked=False): - """Sets the lock button on/off""" +def on_toggle_lock(checked=None, noconvert=False, setchecked=False): + """When the toolbar button is pressed""" + + if checked is None: + checked = get_lock_status() + set_menu(checked) + set_button(checked, setchecked) + if not noconvert: + if checked: + lock_document() + else: + unlock_document() + + +def on_open(): + """What happens when opening an existing document""" + + pass # TODO implement + + +def on_activate(): + """What happens when activating a document""" + + from PySide import QtGui # lazy import + + doc = FreeCAD.ActiveDocument + if doc and "IfcFilePath" in doc.PropertiesList: + checked = True + else: + checked = False + mw = FreeCADGui.getMainWindow() + statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") + if hasattr(statuswidget, "lock_button"): + statuswidget.lock_button.setChecked(checked) + on_toggle_lock(checked, noconvert=True) + + +def on_new(): + """What happens when creating a new document""" + + pass # TODO implement + + +def set_menu(locked=False): + """Sets the File menu items""" from PySide import QtCore, QtGui # lazy loading @@ -67,7 +110,7 @@ def toggle_lock(checked=False): mw = FreeCADGui.getMainWindow() wb = FreeCADGui.activeWorkbench() save_action = mw.findChild(QtGui.QAction, "Std_Save") - if checked and "IFC_Save" in FreeCADGui.listCommands(): + if locked and "IFC_Save" in FreeCADGui.listCommands(): if not hasattr(FreeCADGui,"IFC_WBManipulator"): FreeCADGui.IFC_WBManipulator = IFC_WBManipulator() # we need to void the shortcut otherwise it keeps active @@ -84,40 +127,33 @@ def toggle_lock(checked=False): FreeCADGui.removeWorkbenchManipulator(FreeCADGui.IFC_WBManipulator) del FreeCADGui.IFC_WBManipulator wb.reloadActive() - # set the lock button - statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") - if hasattr(statuswidget, "lock_button"): - set_button(statuswidget.lock_button, checked) -def set_button(lock_button, checked): +def set_button(checked=False, setchecked=False): """Sets the lock button""" from PySide import QtGui # lazy loading - if checked: - lock_button.setChecked(True) - lock_button.setToolTip(text_on) - icon = QtGui.QIcon(":/icons/IFC.svg") - lock_button.setIcon(icon) - else: - lock_button.setChecked(False) - lock_button.setToolTip(text_off) - image = QtGui.QImage(":/icons/IFC.svg") - grayscale = image.convertToFormat(QtGui.QImage.Format_Grayscale8) - grayscale = grayscale.convertToFormat(image.format()) - grayscale.setAlphaChannel(image) - icon = QtGui.QIcon(QtGui.QPixmap.fromImage(grayscale)) - lock_button.setIcon(icon) - - -def do_lock(checked): - """Locks or unlocks the document""" - - if checked: - lock_document() - else: - unlock_document() + mw = FreeCADGui.getMainWindow() + statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") + if hasattr(statuswidget, "lock_button"): + lock_button = statuswidget.lock_button + if checked: + lock_button.setToolTip(text_on) + icon = QtGui.QIcon(":/icons/IFC.svg") + lock_button.setIcon(icon) + if setchecked: + lock_button.setChecked(True) + else: + lock_button.setToolTip(text_off) + image = QtGui.QImage(":/icons/IFC.svg") + grayscale = image.convertToFormat(QtGui.QImage.Format_Grayscale8) + grayscale = grayscale.convertToFormat(image.format()) + grayscale.setAlphaChannel(image) + icon = QtGui.QIcon(QtGui.QPixmap.fromImage(grayscale)) + lock_button.setIcon(icon) + if setchecked: + lock_button.setChecked(False) def unlock_document(): @@ -203,7 +239,7 @@ def lock_document(): FreeCAD.Console.PrintError( "Unable to lock this document because it contains several IFC documents\n" ) - QtCore.QTimer.singleShot(100, toggle_lock) + QtCore.QTimer.singleShot(100, on_toggle_lock) elif doc.Objects: # 3 there is no project but objects doc.openTransaction("Lock document") @@ -220,7 +256,10 @@ def lock_document(): doc.recompute() else: # 4 this is an empty document - ifc_tools.convert_document(doc, silent=True) + doc.openTransaction("Create IFC document") + ifc_tools.convert_document(doc) + doc.commitTransaction() + doc.recompute() def find_toplevel(objs): @@ -248,6 +287,20 @@ def find_toplevel(objs): return nobjs +def get_lock_status(): + """Returns the status of the IFC lock button""" + + if not FreeCAD.GuiUp: + return PARAMS.GetBool("SingleDoc") + from PySide import QtGui + mw = FreeCADGui.getMainWindow() + statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") + if hasattr(statuswidget, "lock_button"): + return statuswidget.lock_button.isChecked() + else: + return False + + # add entry to File menu # https://github.com/FreeCAD/FreeCAD/pull/10933 class IFC_WBManipulator: diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py index 158e6286c6..9049550c1f 100644 --- a/src/Mod/BIM/nativeifc/ifc_tools.py +++ b/src/Mod/BIM/nativeifc/ifc_tools.py @@ -45,6 +45,7 @@ from nativeifc import ifc_objects from nativeifc import ifc_viewproviders from nativeifc import ifc_import from nativeifc import ifc_layers +from nativeifc import ifc_status SCALE = 1000.0 # IfcOpenShell works in meters, FreeCAD works in mm SHORT = False # If True, only Step ID attribute is created @@ -55,7 +56,7 @@ PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC") def create_document(document, filename=None, shapemode=0, strategy=0, silent=False): """Creates a IFC document object in the given FreeCAD document or converts that - document into an IFC document, depending on the value of SingleDoc preference. + document into an IFC document, depending on the state of the statusbar lock button. filename: If not given, a blank IFC document is created shapemode: 0 = full shape @@ -66,7 +67,7 @@ def create_document(document, filename=None, shapemode=0, strategy=0, silent=Fal 2 = all children """ - if PARAMS.GetBool("SingleDoc", False): + if ifc_status.get_lock_status(): return convert_document(document, filename, shapemode, strategy, silent) else: return create_document_object(document, filename, shapemode, strategy, silent)