271 lines
8.5 KiB
Python
271 lines
8.5 KiB
Python
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__
|
|
|