From caca713d8d41713330f9f0e9e5c333f676fa7f6a Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Sat, 7 Oct 2017 04:23:57 +0800 Subject: [PATCH] Create workbench --- Gui/Resources/icons/Assembly_Move.svg | 369 ++++++++++++++++++ ...ing_Part.svg => Assembly_New_Assembly.svg} | 0 InitGui.py | 27 ++ __init__.py | 2 +- assembly.py | 74 +++- constraint.py | 124 +++++- gui.py | 67 ++++ proxy.py | 27 +- solver.py | 31 +- sys_sympy.py | 4 +- system.py | 2 +- utils.py | 21 +- 12 files changed, 701 insertions(+), 47 deletions(-) create mode 100644 Gui/Resources/icons/Assembly_Move.svg rename Gui/Resources/icons/{Assembly_Add_Existing_Part.svg => Assembly_New_Assembly.svg} (100%) create mode 100644 InitGui.py create mode 100644 gui.py diff --git a/Gui/Resources/icons/Assembly_Move.svg b/Gui/Resources/icons/Assembly_Move.svg new file mode 100644 index 0000000..6d85c93 --- /dev/null +++ b/Gui/Resources/icons/Assembly_Move.svg @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Draft_Move + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_Move.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson + + + + + arrow + move + arrows + compass + cross + + + Four equally sized arrow heads at 90° to eachother, all joined at the tail + + + + diff --git a/Gui/Resources/icons/Assembly_Add_Existing_Part.svg b/Gui/Resources/icons/Assembly_New_Assembly.svg similarity index 100% rename from Gui/Resources/icons/Assembly_Add_Existing_Part.svg rename to Gui/Resources/icons/Assembly_New_Assembly.svg diff --git a/InitGui.py b/InitGui.py new file mode 100644 index 0000000..456ff4a --- /dev/null +++ b/InitGui.py @@ -0,0 +1,27 @@ +import FreeCAD, FreeCADGui + +class Assembly3Workbench(FreeCADGui.Workbench): + import asm3 + MenuText = 'Assembly 3' + Icon = asm3.utils.addIconToFCAD('AssemblyWorkbench.svg') + + def Activated(self): + import asm3 + asm3.constraint.Observer.attach() + + def Deactivated(self): + import asm3 + asm3.constraint.Observer.detach() + + def Initialize(self): + import asm3 + cmds = asm3.gui.AsmCmdType.getInfo().TypeNames + asm3.utils.logger.debug(cmds) + self.appendToolbar('asm3',cmds) + self.appendMenu('&Assembly3', cmds) + self.appendToolbar('asm3 Constraint', + asm3.constraint.Constraint.CommandList) + # FreeCADGui.addPreferencePage( + # ':/assembly3/ui/assembly3_prefs.ui','Assembly3') + +FreeCADGui.addWorkbench(Assembly3Workbench) diff --git a/__init__.py b/__init__.py index 60ed1f9..78dfd87 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,5 @@ import FreeCAD, FreeCADGui, Part -from asm3 import utils,assembly,solver,constraint,system +from asm3 import proxy,utils,assembly,solver,constraint,system,gui from asm3.utils import logger from asm3.assembly import Assembly,AsmConstraint try: diff --git a/assembly.py b/assembly.py index c546da2..92b92ca 100644 --- a/assembly.py +++ b/assembly.py @@ -121,6 +121,7 @@ class AsmPartGroup(AsmGroup): obj = parent.Document.addObject("App::FeaturePython",name, AsmPartGroup(parent),None,True) ViewProviderAsmPartGroup(obj.ViewObject) + obj.purgeTouched() return obj @@ -130,6 +131,11 @@ class ViewProviderAsmPartGroup(ViewProviderAsmBase): def onDelete(self,_obj,_subs): return False + def canDropObject(self,obj): + return isTypeOf(obj,Assembly) or not isTypeOf(obj,AsmBase) + + def canDropObjects(self): + return True class AsmElement(AsmBase): def __init__(self,parent): @@ -373,7 +379,7 @@ class AsmElementLink(AsmBase): if not ret: # It is from a non assembly child part, then use our own element # group as the holder for elements - ret = [Assembly.Selection(assembly.obj,owner,subname)] + ret = [Assembly.Info(assembly.obj,owner,subname)] if not isTypeOf(ret[-1].Object,AsmPartGroup): raise RuntimeError('Invalid element link ' + subname) @@ -436,7 +442,8 @@ class AsmElementLink(AsmBase): if not isTypeOf(part,Assembly,True) and \ not Constraint.isDisabled(self.parent.obj) and \ not Constraint.isLocked(self.parent.obj): - getter = getattr(part.getLinkedObject(True),'getLinkExtProperty') + getter = getattr(part.getLinkedObject(True), + 'getLinkExtProperty',None) # special treatment of link array (i.e. when ElementCount!=0), we # allow the array element to be moveable by the solver @@ -543,6 +550,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmBase): class AsmConstraint(AsmGroup): def __init__(self,parent): + self._initializing = True self.elements = None self.parent = getProxy(parent,AsmConstraintGroup) super(AsmConstraint,self).__init__() @@ -585,8 +593,10 @@ class AsmConstraint(AsmGroup): obj.recompute() def execute(self,_obj): - self.checkSupport() - self.getElements(True) + if not getattr(self,'_initializing',False) and\ + getattr(self,'parent',None): + self.checkSupport() + self.getElements(True) return False def getElements(self,refresh=False): @@ -691,6 +701,8 @@ class AsmConstraint(AsmGroup): for e in selection.Elements: AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e)) + cstr.Proxy._initializing = False + cstr.recompute() return cstr @@ -727,6 +739,7 @@ class AsmConstraintGroup(AsmGroup): obj = parent.Document.addObject("App::FeaturePython",name, AsmConstraintGroup(parent),None,True) ViewProviderAsmConstraintGroup(obj.ViewObject) + obj.purgeTouched() return obj @@ -750,6 +763,7 @@ class AsmElementGroup(AsmGroup): obj = parent.Document.addObject("App::FeaturePython",name, AsmElementGroup(parent),None,True) ViewProviderAsmElementGroup(obj.ViewObject) + obj.purgeTouched() return obj @@ -797,17 +811,23 @@ class Assembly(AsmGroup): System.touch(obj) return False # return False to call LinkBaseExtension::execute() - def onSolverChanged(self): + def onSolverChanged(self,setup=False): for obj in self.getConstraintGroup().Group: + # setup==True usually means we are restoring, so try to restore the + # non-touched state if possible, since recompute() below will touch + # the constraint object + touched = not setup or 'Touched' in obj.State obj.recompute() + if not touched: + obj.purgeTouched() def buildShape(self): + import Part obj = self.obj - if not obj.BuildShape: - obj.Shape.nullify() + if obj.BuildShape == BuildShapeNone: + obj.Shape = Part.Shape() return - import Part shape = [] partGroup = self.getPartGroup(obj) group = partGroup.Group @@ -855,7 +875,8 @@ class Assembly(AsmGroup): # all groups exist. The order of the group is important to make sure # correct rendering and picking behavior self.getPartGroup(True) - self.onSolverChanged() + + self.onSolverChanged(True) def onChanged(self, obj, prop): if prop == 'BuildShape': @@ -958,10 +979,28 @@ class Assembly(AsmGroup): "Part::FeaturePython",name,Assembly(),None,True) ViewProviderAssembly(obj.ViewObject) obj.Visibility = True + obj.purgeTouched() return obj Info = namedtuple('AssemblyInfo',('Assembly','Object','Subname')) + @staticmethod + def find(sels=None): + 'Find all assembly objects among the current selection' + objs = set() + if sels is None: + sels = FreeCADGui.Selection.getSelectionEx('',False) + for sel in sels: + if not sel.SubElementNames: + if isTypeOf(sel.Object,Assembly): + objs.add(sel.Object) + continue + for subname in sel.SubElementNames: + ret = Assembly.findChild(sel.Object,subname,recursive=True) + if ret: + objs.add(ret[-1].Assembly) + return tuple(objs) + @staticmethod def findChild(obj,subname,childType=None, recursive=False,relativeToChild=True): @@ -991,7 +1030,7 @@ class Assembly(AsmGroup): if isTypeOf(obj,Assembly,True): assembly = obj subs = subname if isinstance(subname,list) else subname.split('.') - for i,name in enumerate(subs[:-2]): + for i,name in enumerate(subs[:-1]): obj = obj.getSubObject(name+'.',1) if not obj: raise RuntimeError('Cannot find sub object {}'.format(name)) @@ -1043,12 +1082,21 @@ class ViewProviderAssembly(ViewProviderAsmGroup): def canDragObjects(self): return False - def canDropObject(self,_child): - return False + @property + def PartGroup(self): + return self.ViewObject.Object.Proxy.getPartGroup() + + def canDropObject(self,obj): + self.PartGroup.ViewObject.canDropObject(obj) def canDropObjects(self): - return False + return True + + def dropObjectEx(self,_vobj,obj,owner,subname): + self.PartGroup.ViewObject.dropObject(obj,owner,subname) def getIcon(self): return System.getIcon(self.ViewObject.Object) + + diff --git a/constraint.py b/constraint.py index 897221d..09f60a5 100644 --- a/constraint.py +++ b/constraint.py @@ -145,14 +145,83 @@ def _a(solver,partInfo,subname,shape): return _c(solver,partInfo,subname,shape,True) +class ConstraintCommand: + def __init__(self,tp): + self.tp = tp + + def GetResources(self): + return self.tp.GetResources() + + def Activated(self): + from asm3.assembly import AsmConstraint + AsmConstraint.make(self.tp._id) + + def IsActive(self): + return FreeCADGui.ActiveDocument and self.tp._active + +class SelectionObserver: + def __init__(self): + self._attached = False + + def onChanged(self): + from asm3.assembly import AsmConstraint + for cls in Constraint._cmdTypes: + try: + AsmConstraint.getSelection() + except Exception as e: + logger.trace('selection "{}" exception: {}'.format( + cls.getName(),e.message),frame=1) + cls._active = False + else: + cls._active = True + + def addSelection(self,*_args): + self.onChanged() + + def removeSelection(self,*_args): + self.onChanged() + + def setSelection(self,*_args): + self.onChanged() + + def clearSelection(self,*_args): + logger.trace('selection cleared') + for cls in Constraint._cmdTypes: + cls._active = False + + def attach(self): + if not self._attached: + FreeCADGui.Selection.addObserver(self) + self._attached = True + self.onChanged() + + def detach(self): + if self._attached: + FreeCADGui.Selection.removeObserver(self) + self._attached = False + self.clearSelection('') + +Observer = SelectionObserver() + class Constraint(ProxyType): 'constraint meta class' _typeID = '_ConstraintType' _typeEnum = 'ConstraintType' - _disabled = 'Disabled' + CommandList = [] + _cmdTypes = [] + + def register(cls): + super(Constraint,cls).register() + if cls._menuItem: + name = 'asm3Add'+cls.getName() + mcs = cls.__class__ + mcs.CommandList.append(name) + mcs._cmdTypes.append(cls) + FreeCADGui.addCommand(name,ConstraintCommand(cls)) + @classmethod def attach(mcs,obj,checkType=True): if checkType: @@ -220,8 +289,12 @@ class Base(with_metaclass(Constraint,object)): _props = [] _iconName = 'Assembly_ConstraintGeneral.svg' + _menuText = 'Add a "{}" constraint' + _active = False + _menuItem = False + def __init__(self,_obj): - self._supported = True + pass @classmethod def getPropertyInfoList(cls): @@ -295,10 +368,29 @@ class Base(with_metaclass(Constraint,object)): else: logger.warn('{} no constraint func'.format(cstrName(obj))) + @classmethod + def getMenuText(cls): + return cls._menuText.format(cls.getName()) + + @classmethod + def getToolTip(cls): + tooltip = getattr(cls,'_tooltip',None) + if not tooltip: + return cls.getMenuText() + return tooltip.format(cls.getName()) + + @classmethod + def GetResources(cls): + return {'Pixmap':utils.addIconToFCAD(cls._iconName,_iconPath), + 'MenuText':cls.getMenuText(), + 'ToolTip':cls.getToolTip()} + class Locked(Base): _id = 0 _iconName = 'Assembly_ConstraintLock.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to fix part(s)' @classmethod def prepare(cls,obj,solver): @@ -309,6 +401,7 @@ class Locked(Base): def check(cls,_group): pass + class BaseMulti(Base): _id = -1 _entityDef = (_wa,) @@ -414,23 +507,36 @@ class PlaneCoincident(BaseCascade): _id = 35 _iconName = 'Assembly_ConstraintCoincidence.svg' _props = ['Cascade','Offset'] + _menuItem = True + _tooltip = \ + 'Add a "{}" constraint to conincide planes of two or more parts.\n'\ + 'The planes are coincided at their centers with an optional distance.' class PlaneAlignment(BaseCascade): _id = 37 _iconName = 'Assembly_ConstraintAlignment.svg' _props = ['Cascade','Offset'] + _menuItem = True + _tooltip = 'Add a "{}" constraint to rotate planes of two or more parts\n'\ + 'into the same orientation' class AxialAlignment(BaseMulti): _id = 36 _iconName = 'Assembly_ConstraintAxial.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\ + 'The planes are aligned at the direction of their surface normal axis.' class SameOrientation(BaseMulti): _id = 2 _entityDef = (_n,) _iconName = 'Assembly_ConstraintOrientation.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\ + 'The planes are aligned to have the same orientation (i.e. rotation)' class Angle(Base): @@ -439,6 +545,9 @@ class Angle(Base): _workplane = True _props = ["Angle","Supplement"] _iconName = 'Assembly_ConstraintAngle.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to set the angle of planes or linear\n'\ + 'edges of two parts.' class Perpendicular(Base): @@ -446,19 +555,28 @@ class Perpendicular(Base): _entityDef = (_ln,_ln) _workplane = True _iconName = 'Assembly_ConstraintPerpendicular.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\ + 'parts perpendicular.' class Parallel(Base): - _id = 29 + _id = -1 _entityDef = (_ln,_ln) _workplane = True _iconName = 'Assembly_ConstraintParallel.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\ + 'parts parallel.' class MultiParallel(BaseMulti): _id = 291 _entityDef = (_ln,) _iconName = 'Assembly_ConstraintMultiParallel.svg' + _menuItem = True + _tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\ + 'or more parts parallel.' class PointsCoincident(Base): diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..532cde4 --- /dev/null +++ b/gui.py @@ -0,0 +1,67 @@ +from future.utils import with_metaclass +import FreeCAD, FreeCADGui +from asm3.utils import logger,objName,addIconToFCAD +from asm3.assembly import Assembly,AsmConstraint +from asm3.proxy import ProxyType + +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 + + @classmethod + def getName(cls): + return 'asm3'+cls.__name__[3:] + + @classmethod + def GetResources(cls): + return { + 'Pixmap':addIconToFCAD(cls._iconName), + 'MenuText':cls.getMenuText(), + '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 FreeCAD.ActiveDocument and cls._active: + return True + +class AsmCmdNew(AsmCmdBase): + _id = 0 + _menuText = 'Create a new assembly' + _iconName = 'Assembly_New_Assembly.svg' + + def Activated(self): + Assembly.make() + +class AsmCmdSolve(AsmCmdBase): + _id = 1 + _menuText = 'Solve the constraints of assembly(s)' + _iconName = 'AssemblyWorkbench.svg' + + def Activated(self): + import asm3.solver as solver + solver.solve() + + +class AsmCmdMove(AsmCmdBase): + _id = 2 + _menuText = 'Move assembly' + _iconName = 'Assembly_Move.svg' + + def Activated(self): + pass + diff --git a/proxy.py b/proxy.py index 9cb0b04..ba24eb9 100644 --- a/proxy.py +++ b/proxy.py @@ -9,6 +9,8 @@ def propGetValue(self,obj): return getattr(getattr(obj,self.Name),'Value') class PropertyInfo(object): + 'For holding information to create dynamic properties' + def __init__(self,host,name,tp,doc='', enum=None, getter=propGet,group='Base',internal=False,duplicate=False): self.Name = name @@ -21,9 +23,15 @@ class PropertyInfo(object): self.Key = host.addPropertyInfo(self,duplicate) class ProxyType(type): + ''' + Meta class for managing other "proxy" like classes whose instances can be + dynamically attached to or detached from FCAD FeaturePython Proxy objects. + In other word, it is meant for managing proxies of Proxies + ''' + _typeID = '_ProxyType' _typeEnum = 'ProxyType' - _typeGroup = 'Base' + _propGroup = 'Base' _proxyName = '_proxy' _registry = {} @@ -48,7 +56,7 @@ class ProxyType(type): for tp in info.Types: tp._idx = -1 mcs.getInfo().Types.append(tp) - mcs.register(tp) + tp.register() @classmethod def getType(mcs,tp): @@ -99,8 +107,6 @@ class ProxyType(type): obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc) if prop.Enum: setattr(obj,prop.Name,prop.Enum) - else: - obj.setPropertyStatus(prop.Name,'-Hidden') setattr(obj.Proxy,mcs._proxyName,cls(obj)) obj.ViewObject.signalChangeIcon() @@ -114,7 +120,6 @@ class ProxyType(type): proxy.__class__.__name__)) for key in proxy.getPropertyInfoList(): prop = mcs.getPropertyInfo(key) - # obj.setPropertyStatus(prop.Name,'Hidden') obj.removeProperty(prop.Name) callback = getattr(proxy,'onDetach',None) if callback: @@ -143,14 +148,14 @@ class ProxyType(type): if checkType: if mcs._typeID not in obj.PropertiesList: obj.addProperty("App::PropertyInteger", - mcs._typeID,mcs._typeGroup,'',0,False,True) + mcs._typeID,mcs._propGroup,'',0,False,True) mcs.setDefaultTypeID(obj) if mcs._typeEnum not in obj.PropertiesList: logger.debug('type enum {}, {}'.format(mcs._typeEnum, - mcs._typeGroup)) + mcs._propGroup)) obj.addProperty("App::PropertyEnumeration", - mcs._typeEnum,mcs._typeGroup,'',2) + mcs._typeEnum,mcs._propGroup,'',2) mcs.setTypeName(obj,info.TypeNames) idx = 0 @@ -179,10 +184,9 @@ class ProxyType(type): cls._idx = -1 mcs = cls.__class__ mcs.getInfo().Types.append(cls) - mcs.register(cls) + cls.register() - @classmethod - def register(mcs,cls): + def register(cls): ''' Register a class to this meta class @@ -193,6 +197,7 @@ class ProxyType(type): ''' if cls._id < 0: return + mcs = cls.__class__ info = mcs.getInfo() if cls._id in info.TypeMap: raise RuntimeError('Duplicate {} type id {}'.format( diff --git a/solver.py b/solver.py index e074ddc..2c76895 100644 --- a/solver.py +++ b/solver.py @@ -22,15 +22,10 @@ class Solver(object): self.system = System.getSystem(assembly) cstrs = assembly.Proxy.getConstraints() if not cstrs: - self.system.log('no constraint found in assembly ' + logger.warn('no constraint found in assembly ' '{}'.format(objName(assembly))) return - parts = assembly.Proxy.getPartGroup().Group - if len(parts)<=1: - self.system.log('not enough parts in {}'.format(objName(assembly))) - return - self._fixedGroup = 2 self.group = 1 # the solving group self._partMap = {} @@ -41,16 +36,24 @@ class Solver(object): self.system.GroupHandle = self._fixedGroup + firstPart = None + firstPartName = None for cstr in cstrs: if Constraint.isLocked(cstr): Constraint.prepare(cstr,self) elif not Constraint.isDisabled(cstr) and \ System.isConstraintSupported( assembly,Constraint.getTypeName(cstr)): + if not firstPart: + elements = cstr.Proxy.getElements() + if elements: + info = elements[0].Proxy.getInfo() + firstPart = info.Part + firstPartName = info.PartName self._cstrs.append(cstr) if not self._fixedParts: - self.system.log('lock first part {}'.format(objName(parts[0]))) - self._fixedParts.add(parts[0]) + self.system.log('lock first part {}'.format(firstPartName)) + self._fixedParts.add(firstPart) for cstr in self._cstrs: self.system.log('preparing {}'.format(cstrName(cstr))) @@ -162,9 +165,17 @@ class Solver(object): self._partMap[info.Part] = partInfo return partInfo -def solve(objs=None,recursive=True,reportFailed=True,recompute=True): +def solve(objs=None,recursive=None,reportFailed=True,recompute=True): if not objs: - objs = FreeCAD.ActiveDocument.Objects + sels = FreeCADGui.Selection.getSelectionEx('',False) + if len(sels): + objs = asm.Assembly.find() + if not objs: + raise RuntimeError('No assembly found in selection') + else: + objs = FreeCAD.ActiveDocument.Objects + if recursive is None: + recursive = True elif not isinstance(objs,(list,tuple)): objs = [objs] diff --git a/sys_sympy.py b/sys_sympy.py index 0e33564..3a7ae4d 100644 --- a/sys_sympy.py +++ b/sys_sympy.py @@ -14,12 +14,12 @@ class _AlgoType(ProxyType): _typeID = '_AlgorithmType' _typeEnum = 'AlgorithmType' - _typeGroup = 'SolverAlgorithm' + _propGroup = 'SolverAlgorithm' _proxyName = '_algo' def _makeProp(name,doc='',tp='App::PropertyFloat',group=None): if not group: - group = _AlgoType._typeGroup + group = _AlgoType._propGroup info = PropertyInfo(_AlgoType,name,tp,doc,duplicate=True,group=group) return info.Key diff --git a/system.py b/system.py index 0f0f8ec..748d528 100644 --- a/system.py +++ b/system.py @@ -9,7 +9,7 @@ class System(ProxyType): _typeID = '_SolverType' _typeEnum = 'SolverType' - _typeGroup = 'Solver' + _propGroup = 'Solver' _iconName = 'Assembly_Assembly_Tree.svg' @classmethod diff --git a/utils.py b/utils.py index c58750b..fa27664 100644 --- a/utils.py +++ b/utils.py @@ -5,6 +5,11 @@ Most of the functions are borrowed directly from assembly2lib.py or lib3D.py in assembly2 ''' +import FreeCAD, FreeCADGui, Part +import numpy as np +from asm3.FCADLogger import FCADLogger +logger = FCADLogger('asm3') + import sys, os modulePath = os.path.dirname(os.path.realpath(__file__)) @@ -29,12 +34,16 @@ def getIcon(obj,disabled=False,path=None): obj._iconDisabled = QIcon(pixmap) return obj._iconDisabled - -import FreeCAD, FreeCADGui, Part -import numpy as np - -import asm3.FCADLogger -logger = asm3.FCADLogger.FCADLogger('assembly3') +def addIconToFCAD(iconFile,path=None): + iconName = ':asm3/icons/' + iconFile + if not path: + path = iconPath + try: + path = os.path.join(path,iconFile) + FreeCADGui.addIcon(iconName,path) + except AssertionError: + pass + return iconName def objName(obj): if obj.Label == obj.Name: