Saturday, August 7, 2010

Meta-Programming Part2: rbpy Internals

RNA Introspection
Blender's bpy API is generated by RNA introspection; and this information is still preserved at the Python-level, each object either has a bl_rna attribute or a get_rna() function that contains all the C-level introspection information. Knowing the names and types of all function arguments and their returns allows for more efficient remote wrappers that do less data marshaling because they know what type the server is sending them.

Blender objects are not passed directly to the class/function wrapper generator; instead, they are first converted into an intermediate class during introspection because some special-case hacks must be applied. The intermediate class "genRNA" is then passed to the wrapper generator that packages the information and gets it ready for pickling. Pickling saves the user from having to run introspection every time they run their script, and also is the only workaround for transferring data from Python3 back to Python2 (Blender uses Python3 while RPython is a subset of Python2).

When rbpy is launched again as a subprocess this time from Python2, it loads the introspection pickle and generates the final remote wrapper classes. The __init__ of all objects uses *args so it can take any number of arguments or types. Normally this is not RPython compatible because a tuple can not be iterated over, to workaround this we use additional meta-programming to unroll the *args in a RPython safe manner, the technique is detailed here.

Below is the function that does the job of RNA introspection from a live blender object and generation of the intermediate class.

def reflect_RNA_instance( ob, classpath=None ):
class genRNA(object):
if isinstance(ob,bpy.types.Object): type = ob.type
genRNA.__name__ = sn = rpython_shadow_name( ob, classpath )
genRNA._class_name_ = sn
if classpath: genRNA._bpy_class_name_ = classpath
else: genRNA._bpy_class_name_ = ob.__class__.__module__+'.'+ob.__class__.__name__

if ob.__class__.__name__ == 'bpy_ops_submodule': # bpy.ops..
for opname in dir(ob):
func = getattr( ob, opname )
rna = func.get_rna()
args = []
for prop in rna.bl_rna.properties:
if prop.identifier == 'rna_type': continue
args.append( reflect_RNA_prop( prop ) )
d = lambda:()
d._argument_info = args
d._return_info = None
setattr( genRNA, opname, d )

elif ob.__class__.__name__=='bpy_prop_collection': # bpy.data..
genRNA.length = 0 # these should not be a special case and all GET funcs should assign, can setattr work with nonconcretes?
genRNA.GET_length = d = lambda:(); d._return_info = int
d = lambda:(); d._return_info = (str,)
setattr( genRNA, 'keys', d )
d = lambda:(); d._return_info = object
setattr( genRNA, 'get', d )
d = lambda:(); d._return_info = object
setattr( genRNA, 'new', d )
d = lambda:(); d._return_info = (object,)
setattr( genRNA, 'values', d )

elif ob.__class__.__name__ == 'vector':
genRNA.vec = [.0,.0,.0] # specialcase, use v.vec = v.to_tuple()
genRNA.angle = d = lambda:(); d._return_info = float
genRNA.copy = d = lambda:(); d._return_info = object
genRNA.cross = d = lambda:(); d._return_info = object
genRNA.difference = d = lambda:(); d._return_info = object
genRNA.dot = d = lambda:(); d._return_info = float
genRNA.lerp = d = lambda:(); d._return_info = object
genRNA.negate = d = lambda:(); d._return_info = object
genRNA.normalize = d = lambda:(); d._return_info = object
genRNA.project = d = lambda:(); d._return_info = object
genRNA.reflect = d = lambda:(); d._return_info = object
genRNA.to_tuple = d = lambda:(); d._return_info = (float,)
for p in 'length magnitude x y z w xyz'.split():
dummy = lambda:(); dummy._return_info = []; dummy._argument_info = float
setattr( genRNA, 'SET_%s' %p, dummy )
d = lambda:(); d._return_info = float; d._argument_info = None
setattr( genRNA, 'GET_%s' %p, d )

else:
for prop in ob.bl_rna.properties:
#if not prop.is_readonly:
attr = getattr(ob, prop.identifier)
if hasattr( genRNA, prop.identifier ) and prop.identifier != 'type':
print( 'this should never happen', prop.identifier )
raise SyntaxError
dummy = lambda:()
dummy._argument_info = ai = reflect_RNA_prop( prop )
dummy._return_info = [] # do not return from a SET
setattr( genRNA, 'SET_%s' %prop.identifier, dummy )
d = lambda:()
d._argument_info = None
d._return_info = ri = reflect_RNA_prop( prop, attr )
setattr( genRNA, 'GET_%s' %prop.identifier, d )
#if prop.identifier == 'location': raise

for bfunc in ob.bl_rna.functions:
if hasattr( genRNA, bfunc.identifier ) or bfunc.identifier=='select': # what is this select bug? TODO check
print( 'this should never happen', bfunc.identifier )
raise SyntaxError
args = []
returns = []
for prop in bfunc.parameters:
if prop.use_output: returns.append( reflect_RNA_prop( prop ) )
else: args.append( reflect_RNA_prop( prop ) )
d2 = lambda:()
d2._argument_info = args
d2._return_info = returns
setattr( genRNA, bfunc.identifier, d2 )
return genRNA



1 comment:

  1. Pity blogger totoally screws the indentation, but I can kind of follow it..

    ReplyDelete