diff --git a/InitGui.py b/InitGui.py index 5dae647..adc7d13 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,4 +1,5 @@ import os + import FreeCADGui as Gui import FreeCAD as App @@ -15,7 +16,10 @@ class AssemblyHandbookWorkbench(Gui.Workbench): MenuText = "Assembly handbook" ToolTip = "A workbench for automating creation of an assembly handbook" Icon = ahb_icon - toolbox = [] + + dev = True # indicates development mode (enables additional tools, log, debug checks, etc.) + + context = None def GetClassName(self): return "Gui::PythonWorkbench" @@ -23,20 +27,15 @@ class AssemblyHandbookWorkbench(Gui.Workbench): def Initialize(self): """ This function is called at the first activation of the workbench. - here is the place to import all the commands """ - #from freecad.assembly_handbook import my_numpy_function - #App.Console.PrintMessage("switching to workbench_starterkit\n") - #App.Console.PrintMessage("run a numpy function: sqrt(100) = {}\n".format(my_numpy_function.my_foo(100))) - - self.appendToolbar("Tools", self.toolbox) - self.appendMenu("Tools", self.toolbox) + self.registerCommands() def Activated(self): """ code which should be computed when a user switch to this workbench """ - pass + if self.context is None: + self.initializeContext() def Deactivated(self): """ @@ -44,6 +43,46 @@ class AssemblyHandbookWorkbench(Gui.Workbench): """ pass -Gui.addWorkbench(AssemblyHandbookWorkbench()) + def initializeContext(self): + import ahb_context + self.context = ahb_context.AHB_Context() + + def registerCommands(self): + toolbox = [] + + self.importModule('ahb_cmd_export_csv') + toolbox.append("AHB_exportCsv") + + self.importModule('ahb_cmd_set_active_stage') + toolbox.append("AHB_setActiveStage") -print("Assembly Handbook workbench GUI loaded") + self.importModule('ahb_cmd_set_part_stage') + toolbox.append("AHB_setPartStage") + + if self.dev: + self.importModule('ahb_cmd_reload') + toolbox.append("AHB_reload") + + self.appendToolbar("Assembly Handbook", toolbox) + self.appendMenu("Assembly Handbook", toolbox) + + def reload(self): + # Used for development only + self.removeToolbar("Assembly Handbook") + self.removeMenu("Assembly Handbook") + + import importlib + import ahb_context + importlib.reload(ahb_context) + + self.initializeContext() + self.registerCommands() + pass + + def importModule(self, moduleName): + import importlib + module = importlib.import_module(moduleName) + if self.dev: + importlib.reload(module) + +Gui.addWorkbench(AssemblyHandbookWorkbench()) diff --git a/ahb_cmd_export_csv.py b/ahb_cmd_export_csv.py new file mode 100644 index 0000000..591d650 --- /dev/null +++ b/ahb_cmd_export_csv.py @@ -0,0 +1,58 @@ +import os + +import FreeCADGui as Gui +import FreeCAD as App + +class AHB_ExportCsv: + def GetResources(self): + return {"MenuText": "CSV export", + "ToolTip": "Export part list as a CSV file", + "Pixmap": "" + } + + def IsActive(self): + return True + + def Activated(self): + doc = App.activeDocument() + + fileName: str = doc.FileName + if fileName is None: + raise BaseException("You must save your FreeCAD document before exporting to CSV") + + csvFileName = os.path.splitext(fileName)[0] + ".csv" + + print("Exporting parts to CSV file " + csvFileName + "...") + + def readProp(obj, prop, default): + if prop in obj.PropertiesList: + return obj.getPropertyByName(prop) + else: + return default + + parts = [] + obj: App.DocumentObject + for obj in doc.Objects: + if obj.TypeId == 'Part::Feature': + parts.append((readProp(obj, 'Base_Layer', '').replace(',', '-') + "," + readProp(obj, 'Base_Reference', '').replace(',', '-'), "=\"" + obj.Label.replace(',', '-') + "\"")) + + parts.sort() + + with open(csvFileName, 'w', encoding='utf8') as csvFile: + csvFile.write("Group, Reference, Name, Quantity\n") + + quantity = 0 + prevPart = parts[0] + for partIdx in range(len(parts) + 1): + part = parts[partIdx] if partIdx < len(parts) else ("eof", "eof") + if part[0] == prevPart[0]: + quantity += 1 + else: + csvFile.write(prevPart[0] + "," + prevPart[1] + "," + str(quantity) + "\n") + quantity = 1 + prevPart = part + + print("CSV file exported") + +from ahb_command import AHB_CommandWrapper +AHB_CommandWrapper.addGuiCommand('AHB_exportCsv', AHB_ExportCsv()) diff --git a/ahb_cmd_reload.py b/ahb_cmd_reload.py new file mode 100644 index 0000000..15615bc --- /dev/null +++ b/ahb_cmd_reload.py @@ -0,0 +1,20 @@ +import FreeCADGui as Gui +import FreeCAD as App + +class AHB_Reload: + def GetResources(self): + return {"MenuText": "[dev] Reload workbench", + "ToolTip": "Reload the workbench without restarting FreeCAD (development tool)", + "Pixmap": "" + } + + def IsActive(self): + return True + + def Activated(self): + print("Reloading AssemblyHandbookWorkbench") + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") + workbench.reload() + +from ahb_command import AHB_CommandWrapper +AHB_CommandWrapper.addGuiCommand('AHB_reload', AHB_Reload()) diff --git a/ahb_cmd_set_active_stage.py b/ahb_cmd_set_active_stage.py new file mode 100644 index 0000000..e178c77 --- /dev/null +++ b/ahb_cmd_set_active_stage.py @@ -0,0 +1,29 @@ +import FreeCADGui as Gui +import FreeCAD as App +from PySide import QtGui + + +class AHB_SetActiveStage: + def GetResources(self): + return {"MenuText": "Set active stage", + "ToolTip": "Sets the stage which is the active stage, or creates a new stage if it doesn't exist", + "Pixmap": "" + } + + def IsActive(self): + return True + + def Activated(self, stageId = None): + if stageId is None: + reply = QtGui.QInputDialog.getText(None, "Set Active Stage", "Enter the stage number:") + if reply[1]: + # user clicked OK + stageId = int(reply[0]) + else: + return + + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") + workbench.context.setActiveStage(stageId) + +from ahb_command import AHB_CommandWrapper +AHB_CommandWrapper.addGuiCommand('AHB_setActiveStage', AHB_SetActiveStage()) diff --git a/ahb_cmd_set_part_stage.py b/ahb_cmd_set_part_stage.py new file mode 100644 index 0000000..12fec02 --- /dev/null +++ b/ahb_cmd_set_part_stage.py @@ -0,0 +1,33 @@ +from typing import Optional + +import FreeCADGui as Gui +import FreeCAD as App + +class AHB_SetPartStage: + def GetResources(self): + return {"MenuText": "Set part stage", + "ToolTip": "Sets the stage for this part to the currently active stage", + "Pixmap": "" + } + + def IsActive(self): + return True + + def Activated(self, stageId: Optional[int] = None): + workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") + if stageId is None: + stageId = workbench.context.getActiveStage() + if stageId is None: + raise BaseException("You must activate a stage before using this command") + + obj: App.DocumentObject + for obj in Gui.Selection.getSelection(): + if obj is not None and obj.TypeId == 'Part::Feature': + if not "AssemblyHandbook_Stage" in obj.PropertiesList: + obj.addProperty("App::PropertyInteger", "AssemblyHandbook_Stage", "AssemblyHandbook") + if obj.AssemblyHandbook_Stage != stageId: + obj.AssemblyHandbook_Stage = stageId + workbench.context.onPartStageChanged(obj) + +from ahb_command import AHB_CommandWrapper +AHB_CommandWrapper.addGuiCommand('AHB_setPartStage', AHB_SetPartStage()) diff --git a/ahb_command.py b/ahb_command.py new file mode 100644 index 0000000..21210df --- /dev/null +++ b/ahb_command.py @@ -0,0 +1,28 @@ +import FreeCADGui as Gui + +global ahb_commands +ahb_commands = {} + +class AHB_CommandWrapper: + def __init__(self, command_name): + self.command_name = command_name + + def __getattr__(self, attrName): + instance = ahb_commands[self.command_name] + result = getattr(instance, attrName) + if callable(result): + # replace self + def f(*args, **kwargs): return result.__func__(instance, *args, **kwargs) + return f + else: + return result + + @staticmethod + def addGuiCommand(command_name, command_instance): + #print(("adding" if not command_name in ahb_commands else "updating") + " command " + command_name) + ahb_commands.update({ command_name: command_instance }) + Gui.addCommand(command_name, AHB_CommandWrapper(command_name)) + + @staticmethod + def listGuiCommands(): + return ahb_commands.keys() diff --git a/ahb_context.py b/ahb_context.py new file mode 100644 index 0000000..bca0daa --- /dev/null +++ b/ahb_context.py @@ -0,0 +1,49 @@ +import FreeCADGui as Gui +import FreeCAD as App +import random + +def _rgb(r, g, b): + return r/255.0, g/255.0, b/255.0, 0.0 + +_colors = [ + _rgb(94, 224, 174), + _rgb(223, 234, 70), + _rgb(35, 120, 200), + _rgb(188, 80, 33), + _rgb(26, 242, 69), + _rgb(13, 41, 107), + _rgb(153, 9, 52), + _rgb(211, 119, 0), + _rgb(34, 130, 7), + _rgb(221, 79, 249) +] + +class AHB_Context: + _activeStageId: int = None + + def setActiveStage(self, stageId): + self._activeStageId = stageId + + def getActiveStage(self): + return self._activeStageId + + def onPartStageChanged(self, part: App.DocumentObject): + allStages = set() + + doc = App.ActiveDocument + for obj in doc.Objects: + if obj.TypeId == 'Part::Feature': + if 'AssemblyHandbook_Stage' in obj.PropertiesList: + allStages.add(obj.AssemblyHandbook_Stage) + + allStages = list(allStages) + allStages.sort() + + if not 'AssemblyHandbook_Stage' in part.PropertiesList: + part.ViewObject.ShapeColor = (0.8,0.8,0.8,0.0) + + for obj in doc.Objects: + if obj.TypeId == 'Part::Feature': + if 'AssemblyHandbook_Stage' in obj.PropertiesList: + stageIndex = allStages.index(obj.AssemblyHandbook_Stage) + obj.ViewObject.ShapeColor = _colors[stageIndex % len(_colors)]