diff --git a/InitGui.py b/InitGui.py index 6d3e303..d4e5aa7 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,4 +1,5 @@ import FreeCAD, FreeCADGui +from collections import OrderedDict class Assembly3Workbench(FreeCADGui.Workbench): import asm3 @@ -10,38 +11,37 @@ class Assembly3Workbench(FreeCADGui.Workbench): def Activated(self): self.observer.attach() + from asm3.gui import AsmCmdManager + for cmd in AsmCmdManager.getInfo().Types: + cmd.workbenchActivated() def Deactivated(self): self.observer.detach() + from asm3.gui import AsmCmdManager + for cmd in AsmCmdManager.getInfo().Types: + cmd.workbenchDeactivated() def Initialize(self): - import asm3 - cmdInfo = asm3.gui.AsmCmdType.getInfo() - cmds = cmdInfo.TypeNames - asm3.utils.logger.debug(cmds) - self.appendToolbar('asm3',cmds) - self.appendMenu('&Assembly3', cmds) - self.appendToolbar('asm3 Constraint', - asm3.constraint.Constraint.CommandList) - self.observer = asm3.gui.SelectionObserver( - cmdInfo.Types + asm3.constraint.Constraint.Commands) + from asm3.gui import AsmCmdManager,SelectionObserver + cmdSet = set() + for name,cmds in AsmCmdManager.Toolbars.items(): + cmdSet.update(cmds) + self.appendToolbar(name,[cmd.getName() for cmd in cmds]) + for name,cmds in AsmCmdManager.Menus.items(): + cmdSet.update(cmds) + self.appendMenu(name,[cmd.getName() for cmd in cmds]) + self.observer = SelectionObserver(cmdSet) # FreeCADGui.addPreferencePage( # ':/assembly3/ui/assembly3_prefs.ui','Assembly3') def ContextMenu(self, _recipient): - import asm3 - cmds = [] - for cmd in asm3.gui.AsmCmdType.getInfo().Types: - if cmd.IsActive: - cmds.append(cmd.getName()) - if cmds: - self.appendContextMenu('Assembly',cmds) - - cmds.clear() - for cmd in asm3.constraint.Constraint.Commands: - if cmd.IsActive: - cmds.append(cmd.getName()) - if cmds: - self.appendContextMenu('Constraint',cmds) + from asm3.gui import AsmCmdManager + menus = OrderedDict() + for cmd in AsmCmdManager.getInfo().Types: + name = cmd.getContextMenuName() + if name: + menus.setdefault(name,[]).append(cmd.getName()) + for name,cmds in menus.items(): + self.appendContextMenu(name,cmds) FreeCADGui.addWorkbench(Assembly3Workbench) diff --git a/__init__.py b/__init__.py index 78dfd87..e98f755 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,6 @@ import FreeCAD, FreeCADGui, Part -from asm3 import proxy,utils,assembly,solver,constraint,system,gui +from asm3 import proxy,utils,gui,solver,constraint,system,assembly from asm3.utils import logger -from asm3.assembly import Assembly,AsmConstraint try: from asm3 import sys_slvs except ImportError as e: @@ -12,6 +11,7 @@ except ImportError as e: logger.error('failed to import sympy: {}'.format(e)) def test(): + from asm3.assembly import Assembly doc = FreeCAD.newDocument() cylinder1 = doc.addObject('Part::Cylinder','cylinder1') cylinder1.Visibility = False diff --git a/constraint.py b/constraint.py index 98be56f..d5553ad 100644 --- a/constraint.py +++ b/constraint.py @@ -2,6 +2,7 @@ from future.utils import with_metaclass from collections import namedtuple import FreeCAD, FreeCADGui import asm3.utils as utils +from asm3.gui import AsmCmdManager from asm3.utils import logger, objName from asm3.proxy import ProxyType, PropertyInfo, propGet, propGetValue @@ -146,8 +147,21 @@ def _a(solver,partInfo,subname,shape): class ConstraintCommand: + _toolbarName = 'Assembly3 Constraints' + _menuGroupName = '' + def __init__(self,tp): self.tp = tp + self._id = 100 + tp._id + + def workbenchActivated(self): + pass + + def workbenchDeactivated(self): + pass + + def getContextMenuName(self): + pass def getName(self): return 'asm3Add'+self.tp.getName() @@ -173,7 +187,7 @@ class ConstraintCommand: else: self.tp._active = True - def deactive(self): + def onClearSelection(self): self.tp._active = False class Constraint(ProxyType): @@ -183,18 +197,11 @@ class Constraint(ProxyType): _typeEnum = 'ConstraintType' _disabled = 'Disabled' - CommandList = [] - Commands = [] - - def register(cls): - super(Constraint,cls).register() + @classmethod + def register(mcs,cls): + super(Constraint,mcs).register(cls) if cls._menuItem: - mcs = cls.__class__ - cmd = ConstraintCommand(cls) - name = cmd.getName() - mcs.CommandList.append(name) - mcs.Commands.append(cmd) - FreeCADGui.addCommand(name,cmd) + AsmCmdManager.register(ConstraintCommand(cls)) @classmethod def attach(mcs,obj,checkType=True): @@ -263,7 +270,7 @@ class Base(with_metaclass(Constraint,object)): _props = [] _iconName = 'Assembly_ConstraintGeneral.svg' - _menuText = 'Add a "{}" constraint' + _menuText = 'Create "{}" constraint' _active = False _menuItem = False diff --git a/gui.py b/gui.py index 54e69b4..1f84647 100644 --- a/gui.py +++ b/gui.py @@ -1,7 +1,8 @@ from future.utils import with_metaclass +from collections import OrderedDict import FreeCAD, FreeCADGui +import asm3 from asm3.utils import logger,objName,addIconToFCAD -from asm3.assembly import isTypeOf,Assembly,AsmConstraint,AsmElementLink from asm3.proxy import ProxyType class SelectionObserver: @@ -24,7 +25,7 @@ class SelectionObserver: def clearSelection(self,*_args): for cmd in self.cmds: - cmd.deactive() + cmd.onClearSelection() def attach(self): if not self._attached: @@ -39,21 +40,38 @@ class SelectionObserver: self.clearSelection('') -class AsmCmdType(ProxyType): - def register(cls): - super(AsmCmdType,cls).register() - if cls._id >= 0: - FreeCADGui.addCommand(cls.getName(),cls()) - -class AsmCmdBase(with_metaclass(AsmCmdType,object)): - _id = -1 - _active = True +class AsmCmdManager(ProxyType): + Toolbars = OrderedDict() + Menus = OrderedDict() + _defaultMenuGroupName = '&Assembly3' @classmethod + def register(mcs,cls): + if cls._id < 0: + return + super(AsmCmdManager,mcs).register(cls) + FreeCADGui.addCommand(cls.getName(),cls) + if cls._toolbarName: + mcs.Toolbars.setdefault(cls._toolbarName,[]).append(cls) + if cls._menuGroupName is not None: + name = cls._menuGroupName + if not name: + name = mcs._defaultMenuGroupName + mcs.Menus.setdefault(name,[]).append(cls) + + def workbenchActivated(cls): + pass + + def workbenchDeactivated(cls): + pass + + def getContextMenuName(cls): + if cls.IsActive() and cls._contextMenuName: + return cls._contextMenuName + def getName(cls): return 'asm3'+cls.__name__[3:] - @classmethod def GetResources(cls): return { 'Pixmap':addIconToFCAD(cls._iconName), @@ -61,41 +79,45 @@ class AsmCmdBase(with_metaclass(AsmCmdType,object)): 'ToolTip':cls.getToolTip() } - @classmethod def getMenuText(cls): return cls._menuText - @classmethod def getToolTip(cls): return getattr(cls,'_tooltip',cls.getMenuText()) - @classmethod def IsActive(cls): if cls._active and cls._id>=0 and FreeCAD.ActiveDocument: return True - @classmethod def checkActive(cls): pass - @classmethod - def deactive(cls): + def onClearSelection(cls): pass +class AsmCmdBase(with_metaclass(AsmCmdManager,object)): + _id = -1 + _active = True + _toolbarName = 'Assembly3' + _menuGroupName = '' + _contextMenuName = 'Assembly' + class AsmCmdNew(AsmCmdBase): _id = 0 _menuText = 'Create assembly' _iconName = 'Assembly_New_Assembly.svg' - def Activated(self): - Assembly.make() + @classmethod + def Activated(cls): + asm3.assembly.Assembly.make() class AsmCmdSolve(AsmCmdBase): _id = 1 _menuText = 'Solve constraints' _iconName = 'AssemblyWorkbench.svg' - def Activated(self): + @classmethod + def Activated(cls): import asm3.solver as solver solver.solve() @@ -107,17 +129,19 @@ class AsmCmdMove(AsmCmdBase): @classmethod def getSelection(cls): + from asm3.assembly import isTypeOf,AsmElementLink sels = FreeCADGui.Selection.getSelection() if len(sels)==1 and isTypeOf(sels[0],AsmElementLink): return sels[0].ViewObject - def Activated(self): - vobj = self.getSelection() + @classmethod + def Activated(cls): + vobj = cls.getSelection() if vobj: doc = FreeCADGui.editDocument() if doc: doc.resetEdit() - vobj.UseCenterballDragger = self._useCenterballDragger + vobj.UseCenterballDragger = cls._useCenterballDragger vobj.doubleClicked() @classmethod @@ -125,7 +149,7 @@ class AsmCmdMove(AsmCmdBase): cls._active = True if cls.getSelection() else False @classmethod - def deactive(cls): + def onClearSelection(cls): cls._active = False class AsmCmdAxialMove(AsmCmdMove): diff --git a/proxy.py b/proxy.py index ba24eb9..fccb719 100644 --- a/proxy.py +++ b/proxy.py @@ -56,7 +56,7 @@ class ProxyType(type): for tp in info.Types: tp._idx = -1 mcs.getInfo().Types.append(tp) - tp.register() + mcs.register(tp) @classmethod def getType(mcs,tp): @@ -181,23 +181,27 @@ class ProxyType(type): def __init__(cls, name, bases, attrs): super(ProxyType,cls).__init__(name,bases,attrs) - cls._idx = -1 mcs = cls.__class__ - mcs.getInfo().Types.append(cls) - cls.register() + mcs.register(cls) - def register(cls): + @classmethod + def register(mcs,cls): ''' Register a class to this meta class To make the registration automatic at the class definition time, simply set __metaclass__ of that class to ProxyType of its derived type. - You can also call this methode directly to register an unrelated class + It is defined as a meta class method in order for you to call this + method directly to register an unrelated class ''' + cls._idx = -1 + mcs.getInfo().Types.append(cls) + callback = getattr(cls,'onRegister',None) + if callback: + callback() if cls._id < 0: return - mcs = cls.__class__ info = mcs.getInfo() if cls._id in info.TypeMap: raise RuntimeError('Duplicate {} type id {}'.format(