see related here: https://blenderartists.org/forum/archive/index.php/t-266184.html
To solve this we're going to make use of blender's bpy.utils.register_class()
method and the type()
method.
First, we need a method to generate our dynamic properties. In this case, you can use a decorator function to make things a bit easier:
def dynamic(func):
"""
@:keyword: dynamic - Function Generator that creates properties for Blender data types on the fly
:param func: Function to generate
:return: dynamic_property(): returns decorated function
"""
def dynamic_property(*args, **kwargs):
"""
:param args: first argument is always *prop_dict (from func in outer scope)
:parameter *prop_dict: One Blender Property Mapped to a dict: e.g. prop_dict = {'mapname': bpy.props.StringProperty(default="some_path_to_map")}
:param kwargs: specific keywords needed by enclosed methods
:return: Pointer to newly registered/assigned property
"""
Prop = type(str("Parameters"), (bpy.types.PropertyGroup,), func(*args))
bpy.utils.register_class(Prop)
PropPointer = bpy.props.PointerProperty(name=func(kwargs.get('param_type')), type=Prop)
setattr(func(kwargs.get('blender_type')), func(kwargs.get('param_type')), PropPointer)
return PropPointer
return dynamic_property
This decorated function generates a function that returns a pointer to a new class instance called Prop
, which subclasses bpy.types.PropertyGroup
. func(*args)
is always a 1 item dictionary. (Further details on the dict contents below)
I also found it useful to create a class to hold the variables I need to pass to the decorated function. (To extend functionality, just create a subclass and override the blender_type
variable with whatever type you need + any other variables needed for your implementation)
from bpy.types import Material
class BlenderProperty(object):
def __init__(self, **kwargs):
self.name = kwargs.setdefault("name", None)
self.prop_dict = {}
self.blender_type = Material
self.param_type = "TestParameters"
self._prop = None
@property
def prop(self):
prop_ptr = self.create_property(self.prop_dict, blender_type=self.blender_type, param_type= self.param_type)
self._prop = prop_ptr
return self._prop
@staticmethod
@dynamic
def create_property(prop_dict, **kwargs):
return prop_dict
For testing purposes, let's use a fake dictionary and fill it with some stuff. Then assign this dictionary to a BlenderProperty.prop_dict
fake_dict = {}
prop_list = ['Prop1', 'prop2', 'prop3']
for prop in prop_list:
fake_dict[prop] = {'mapname': bpy.props.StringProperty(default="some_path_to_map")}
new_blender_prop = BlenderProperty(name=str(prop))
new_blender_prop.prop_dict = fake_dict
print(new_blender_prop.prop)
Calling new_blender_prop.prop
calls the decorated getter function for this class's private variable prop
, which just so happens to be a pointer to the dynamically created property inside of blender.