3
$\begingroup$

How do you generate Blender Properties dynamically, and assign them on the fly?

Generating properties dynamically is inherently useful when parsing semi-structured data from an external source such as in an XML or JSON document, but there's isn't really a straightforward way to accomplish this.

$\endgroup$
3
$\begingroup$

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.

$\endgroup$
  • $\begingroup$ Im kinda confused, I understand how this works.. but could you explain how I could add a property to the bpy.types.Object class ? $\endgroup$ – Skylumz Jun 8 '18 at 7:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.