From 3aebf0aead8f4444e072ad49f558833a77c3170f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 26 Oct 2021 23:05:52 -0500 Subject: [PATCH] GUI: Handle packaged Workbenches and their icons --- src/App/FreeCADInit.py | 57 ++++++++++++++++----------- src/Gui/FreeCADGuiInit.py | 81 +++++++++++++++++++++++++++++---------- src/Gui/Workbench.cpp | 4 +- 3 files changed, 96 insertions(+), 46 deletions(-) diff --git a/src/App/FreeCADInit.py b/src/App/FreeCADInit.py index cafbffcd8a..0d6cf25899 100644 --- a/src/App/FreeCADInit.py +++ b/src/App/FreeCADInit.py @@ -100,10 +100,7 @@ def InitApplications(): Lib64Dir = os.path.realpath(Lib64Dir) if os.path.exists(Lib64Dir): libpaths.append(Lib64Dir) - if sys.version_info[0] == 3: - LibPyDir = FreeCAD.getHomePath()+'lib-py3' - else: - LibPyDir = FreeCAD.getHomePath()+'lib-py2' + LibPyDir = FreeCAD.getHomePath()+'lib-py3' LibPyDir = os.path.realpath(LibPyDir) if (os.path.exists(LibPyDir)): libpaths.append(LibPyDir) @@ -168,31 +165,45 @@ def InitApplications(): # proper python modules this can eventuelly be removed. sys.path = [ModDir] + libpaths + [ExtDir] + sys.path + def RunInitPy(Dir): + InstallFile = os.path.join(Dir,"Init.py") + if (os.path.exists(InstallFile)): + try: + with open(file=InstallFile, encoding="utf-8") as f: + exec(f.read()) + except Exception as inst: + Log('Init: Initializing ' + Dir + '... failed\n') + Log('-'*100+'\n') + Log(traceback.format_exc()) + Log('-'*100+'\n') + Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') + Err('Please look into the log file for further information\n') + else: + Log('Init: Initializing ' + Dir + '... done\n') + else: + Log('Init: Initializing ' + Dir + '(Init.py not found)... ignore\n') + for Dir in ModDict.values(): if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): sys.path.insert(0,Dir) PathExtension.append(Dir) - InstallFile = os.path.join(Dir,"Init.py") - if (os.path.exists(InstallFile)): - try: - # XXX: This looks scary securitywise... - if sys.version_info.major < 3: - with open(InstallFile) as f: - exec(f.read()) - else: - with open(file=InstallFile, encoding="utf-8") as f: - exec(f.read()) - except Exception as inst: - Log('Init: Initializing ' + Dir + '... failed\n') - Log('-'*100+'\n') - Log(traceback.format_exc()) - Log('-'*100+'\n') - Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') - Err('Please look into the log file for further information\n') + MetadataFile = os.path.join(Dir, "package.xml") + if os.path.exists(MetadataFile): + meta = FreeCAD.Metadata(MetadataFile) + content = meta.Content + if "workbench" in content: + workbenches = content["workbench"] + for workbench in workbenches: + subdirectory = workbench.Name if not workbench.Subdirectory else workbench.Subdirectory + subdirectory = os.path.join(Dir, subdirectory) + classname = workbench.Classname + sys.path.insert(0,subdirectory) + PathExtension.append(subdirectory) + RunInitPy(subdirectory) else: - Log('Init: Initializing ' + Dir + '... done\n') + continue # The package content says there are no workbenches here, so just skip else: - Log('Init: Initializing ' + Dir + '(Init.py not found)... ignore\n') + RunInitPy(Dir) extension_modules = [] diff --git a/src/Gui/FreeCADGuiInit.py b/src/Gui/FreeCADGuiInit.py index 66ada4808d..419d589d5b 100644 --- a/src/Gui/FreeCADGuiInit.py +++ b/src/Gui/FreeCADGuiInit.py @@ -42,6 +42,7 @@ class Workbench: """The workbench base class.""" MenuText = "" ToolTip = "" + Icon = None def Initialize(self): """Initializes this workbench.""" @@ -117,30 +118,56 @@ def InitApplications(): ModDirs = FreeCAD.__ModDirs__ #print ModDirs Log('Init: Searching modules...\n') + + def RunInitGuiPy(Dir): + InstallFile = os.path.join(Dir,"InitGui.py") + if (os.path.exists(InstallFile)): + try: + with open(file=InstallFile, encoding="utf-8") as f: + exec(f.read()) + except Exception as inst: + Log('Init: Initializing ' + Dir + '... failed\n') + Log('-'*100+'\n') + Log(traceback.format_exc()) + Log('-'*100+'\n') + Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') + Err('Please look into the log file for further information\n') + else: + Log('Init: Initializing ' + Dir + '... done\n') + else: + Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') + for Dir in ModDirs: if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): - InstallFile = os.path.join(Dir,"InitGui.py") - if (os.path.exists(InstallFile)): - try: - # XXX: This looks scary securitywise... - if sys.version_info.major < 3: - with open(InstallFile) as f: - exec(f.read()) - else: - with open(file=InstallFile, encoding="utf-8") as f: - exec(f.read()) - except Exception as inst: - Log('Init: Initializing ' + Dir + '... failed\n') - Log('-'*100+'\n') - Log(traceback.format_exc()) - Log('-'*100+'\n') - Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') - Err('Please look into the log file for further information\n') - else: - Log('Init: Initializing ' + Dir + '... done\n') - else: - Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') + MetadataFile = os.path.join(Dir, "package.xml") + if os.path.exists(MetadataFile): + meta = FreeCAD.Metadata(MetadataFile) + content = meta.Content + if "workbench" in content: + FreeCAD.Gui.addIconPath(Dir) + workbenches = content["workbench"] + for workbench_metadata in workbenches: + subdirectory = workbench_metadata.Name if not workbench_metadata.Subdirectory else workbench_metadata.Subdirectory + subdirectory = os.path.join(Dir, subdirectory) + RunInitGuiPy(subdirectory) + # Brute force this for now: + classname = workbench_metadata.Classname + wb_handle = FreeCAD.Gui.getWorkbench(classname) + if wb_handle: + GeneratePackageIcon(dir, subdirectory, workbench_metadata, wb_handle) + else: + Log("Failed to get handle to {classname} -- no icon can be generated, check classname in package.xml\n") + + + + + else: + continue # The package content says there are no workbenches here, so just skip + else: + RunInitGuiPy(Dir) + + extension_modules = [] try: import pkgutil @@ -169,6 +196,18 @@ def InitApplications(): except ImportError as inst: Err('During initialization the error "' + str(inst) + '" occurred\n') +def GeneratePackageIcon(dir:str, subdirectory:str, workbench_metadata:FreeCAD.Metadata, wb_handle:Workbench) -> None: + relative_filename = workbench_metadata.Icon + if not relative_filename: + # Although a required element, this content item does not have an icon. Just bail out + return + absolute_filename = os.path.join(subdirectory, relative_filename) + if wb_handle.Icon: + Wrn(f"Packaged workbench {workbench_metadata.Name} specified icon in class {workbench_metadata.Classname}:\n") + Wrn(f" ... Overwriting that specification with package.xml data") + wb_handle.Icon = absolute_filename + + Log ('Init: Running FreeCADGuiInit.py start script...\n') diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 4abc96ada3..56ef605757 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -176,8 +176,8 @@ using namespace Gui; * \section moredetails More details and limitations * One of the key concepts of the workbench framework is to load a module at runtime when the user needs some function that it * provides. So, if the user doesn't need a module it never gets loaded into RAM. This speeds up the startup procedure of - * FreeCAD and saves memory. + * * At startup FreeCAD scans all module directories and invokes InitGui.py. So an item for a workbench gets created. If the user * clicks on such an item the matching module gets loaded, the C++ workbench gets registered and activated. * @@ -192,7 +192,7 @@ using namespace Gui; * w=Workbench() # creates a standard workbench (the same as StdWorkbench in C++) * w.MenuText = "My Workbench" # the text that will appear in the combo box * dir(w) # lists all available function of the object - * FreeCADGui.addWorkbench(w) # Creates an item for our workbenmch now + * FreeCADGui.addWorkbench(w) # Creates an item for our workbench now * # Note: We must first add the workbench to run some initialization code * # Then we are ready to customize the workbench * list = ["Std_Test1", "Std_Test2", "Std_Test3"] # creates a list of new functions