from collections import namedtuple from .utils import proxylogger as logger, objName def propGet(self,obj): return getattr(obj,self.Name) 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,default=None): self.Name = name self.Type = tp self.Group = group self.Doc = doc self.Enum = enum self.get = getter.__get__(self,self.__class__) self.Internal = internal self.Default = default 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' _propGroup = 'Base' _proxyName = '_proxy' _registry = {} Info = namedtuple('ProxyTypeInfo', ('Types','TypeMap','TypeNameMap','TypeNames','PropInfo')) @classmethod def getMetaName(mcs): return mcs.__name__ @classmethod def getInfo(mcs): if not getattr(mcs,'_info',None): mcs._info = mcs.Info([],{},{},[],{}) mcs._registry[mcs.getMetaName()] = mcs._info return mcs._info @classmethod def reload(mcs): info = mcs.getInfo() mcs._info = None for tp in info.Types: tp._idx = -1 mcs.getInfo().Types.append(tp) mcs.register(tp) @classmethod def getType(mcs,tp): if isinstance(tp,str): return mcs.getInfo().TypeNameMap[tp] if not isinstance(tp,int): tp = mcs.getTypeID(tp) return mcs.getInfo().TypeMap[tp] @classmethod def getTypeID(mcs,obj): return getattr(obj,mcs._typeID,-1) @classmethod def setTypeID(mcs,obj,tp): setattr(obj,mcs._typeID,tp) @classmethod def getTypeName(mcs,obj): return getattr(obj,mcs._typeEnum,None) @classmethod def setTypeName(mcs,obj,tp): setattr(obj,mcs._typeEnum,tp) @classmethod def getProxy(mcs,obj): return getattr(obj.Proxy,mcs._proxyName,None) @classmethod def setProxy(mcs,obj): cls = mcs.getType(mcs.getTypeName(obj)) proxy = mcs.getProxy(obj) if type(proxy) is not cls: logger.debug('attaching {}, {} -> {}', objName(obj),type(proxy).__name__,cls.__name__,frame=1) if proxy: mcs.detach(obj) if mcs.getTypeID(obj) != cls._id: mcs.setTypeID(obj,cls._id) props = cls.getPropertyInfoList() if props: oprops = obj.PropertiesList for key in props: prop = mcs.getPropertyInfo(key) value = None if prop.Name in oprops: if obj.getTypeIdOfProperty(prop.Name)==prop.Type: continue value = prop.get(obj) obj.removeProperty(prop.Name) obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc) if prop.Enum: setattr(obj,prop.Name,prop.Enum) try: if value is not None: setattr(obj,prop.Name,value) continue except Exception: pass if prop.Default is not None: setattr(obj,prop.Name,prop.Default) setattr(obj.Proxy,mcs._proxyName,cls(obj)) obj.ViewObject.signalChangeIcon() return obj @classmethod def detach(mcs,obj,detachAll=False): proxy = mcs.getProxy(obj) if proxy: logger.debug('detaching {}<{}>',objName(obj), proxy.__class__.__name__) for key in proxy.getPropertyInfoList(): prop = mcs.getPropertyInfo(key) obj.removeProperty(prop.Name) callback = getattr(proxy,'onDetach',None) if callback: callback(obj) setattr(obj.Proxy,mcs._proxyName,None) if detachAll: obj.removeProperty(mcs._typeID) obj.removeProperty(mcs._typeEnum) @classmethod def setDefaultTypeID(mcs,obj,name=None): info = mcs.getInfo() if not name: name = info.TypeNames[0] mcs.setTypeID(obj,info.TypeNameMap[name]._id) @classmethod def attach(mcs,obj,checkType=True): info = mcs.getInfo() if not info.TypeNames: logger.error('"{}" has no registered types', mcs.getMetaName()) return if checkType: if mcs._typeID not in obj.PropertiesList: obj.addProperty("App::PropertyInteger", mcs._typeID,mcs._propGroup,'',0,False,True) mcs.setDefaultTypeID(obj) if mcs._typeEnum not in obj.PropertiesList: logger.debug('type enum {}, {}',mcs._typeEnum, mcs._propGroup) obj.addProperty("App::PropertyEnumeration", mcs._typeEnum,mcs._propGroup,'',2) mcs.setTypeName(obj,info.TypeNames) idx = 0 try: idx = mcs.getType(obj)._idx except KeyError: logger.warn('{} has unknown {} type {}', objName(obj),mcs.getMetaName(),mcs.getTypeID(obj)) mcs.setTypeName(obj,idx) return mcs.setProxy(obj) @classmethod def onChanged(mcs,obj,prop): if prop == mcs._typeEnum: if mcs.getProxy(obj): return mcs.attach(obj,False) elif prop == mcs._typeID: if mcs.getProxy(obj): cls = mcs.getType(mcs.getTypeID(obj)) if mcs.getTypeName(obj)!=cls.getName(): mcs.setTypeName(obj,cls._idx) def __init__(cls, name, bases, attrs): super(ProxyType,cls).__init__(name,bases,attrs) mcs = cls.__class__ mcs.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 or its derived type. 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 info = mcs.getInfo() if cls._id in info.TypeMap: raise RuntimeError('Duplicate {} type id {}, {} conflict with ' '{}'.format(mcs.getMetaName(),cls._id,cls.getName(), info.TypeMap[cls._id].getName())) info.TypeMap[cls._id] = cls info.TypeNameMap[cls.getName()] = cls info.TypeNames.append(cls.getName()) cls._idx = len(info.TypeNames)-1 logger.trace('register {} "{}":{},{}', mcs.getMetaName(),cls.getName(),cls._id,cls._idx) @classmethod def addPropertyInfo(mcs,info,duplicate): props = mcs.getInfo().PropInfo key = info.Name i = 1 while key in props: if not duplicate: raise RuntimeError('Duplicate property "{}"'.format(info.Name)) key = key+str(i) i = i+1 props[key] = info return key @classmethod def getPropertyInfo(mcs,key): return mcs.getInfo().PropInfo[key] def getPropertyValues(cls,obj): props = [] mcs = cls.__class__ for key in cls.getPropertyInfoList(): prop = mcs.getPropertyInfo(key) if not prop.Internal: props.append(prop.get(obj)) return props def getPropertyInfoList(cls): return [] def copyProperties(cls,obj,target): mcs = cls.__class__ for key in cls.getPropertyInfoList(): prop = mcs.getPropertyInfo(key) if not prop.Internal: setattr(target,prop.Name,prop.get(obj)) def getName(cls): return cls.__name__