Add Python Class to Blender Object

Greetings all,

I am trying to extend a Blender Object by assigning a Python class to the object using the PointerProperty type. I know that the normal procedure is to simply assign the primitive data to an Object by using the properties that are built into the Blender API. The difficulty lies in the fact that the class structure is needed outside of Blender, and maintaining two versions will not be possible. I have found a way to assign a class; however, it appears that the PointerProperty always points to the same data in memory regardless of the Object. The other concern is that it appears that the class constructor is never called, and, therefore, only the class wide properties and not the instance properties are available. I have included an example that displays the noted behavior below. Does anyone know of a way in which to assign a Python class to a Blender object? Thank you in advance for your assistance.

Keith

# class_test.py
import bpy
# Define a class that exists outside of Blender
class test_class():
    a_string = ""
    an_int   = 0
    
    def __init__(self,in_str="",in_int=0):
        self.a_string = in_str
        self.an_int   = in_int
        return
#
# Define a class to register with Belnder
class BlenderTestClass(bpy.types.PropertyGroup):
    the_class = test_class()
    
    def __init__(self,in_str="",in_int=0):
        self.the_class = test_class(in_str,in_int)
        return
#
# Register the class
bpy.utils.register_class(BlenderTestClass)
#
# Assign the property class to an Object
bpy.types.Object.object_test_class = \
    bpy.props.PointerProperty(type=bpy.types.BlenderTestClass)
#
# Remove all of the meshes and add 2 cubes
bpy.ops.object.select_by_type(type="MESH")
bpy.ops.object.delete()
bpy.ops.mesh.primitive_cube_add(location=(-2,0,0))
cube1 = bpy.context.object
bpy.ops.mesh.primitive_cube_add(location=(2,0,0))
cube2 = bpy.context.object
#
# Set cube1 properties
cube1.object_test_class.the_class.a_string = "I am cube1"
cube1.object_test_class.the_class.an_int   = 1
print("Cube1 class data")
print("a_string = %s" %cube1.object_test_class.the_class.a_string)
print("an_int   = %d
" %cube1.object_test_class.the_class.an_int)
#
# Cube2 has the same values as cube1!
print("Cube2 class data")
print("a_string = %s" %cube2.object_test_class.the_class.a_string)
print("an_int   = %d
" %cube2.object_test_class.the_class.an_int)
print(cube1.object_test_class.the_class is cube2.object_test_class.the_class)

I have found a way to work around this problem. Below is an example script that will run both inside Blender and in a stand alone Python 3.2 environment.

I found that Blender treats all properties that are not the Blender specific properties (IntProperty, StringProperty, etc.) as static variables unlike a standard Python environment. In order to allow a class to exist outside of Blender, I simply had to create my own definition of these properties. The first step is to try to import the bpy module and, if it does not exist, define the properties. This is a cumbersome way to work around the problem and it would be insightful to see other solutions.

Keith

# class_test_2.py
# Try to import bpy
try:
    import bpy
except:
    pass
# If bpy is now in the locals define the properties. Otherwise
# we must define them separately.
if "bpy" in locals():
    IntProperty    = bpy.props.IntProperty
    StringProperty = bpy.props.StringProperty
    FloatProperty  = bpy.props.FloatProperty
    PropertyGroup  = bpy.types.PropertyGroup
else:
    class IntProperty:
        name = ""
        def __init__(self,name=""):
            self.name = name
            return
        
    class StringProperty:
        name = ""
        def __init__(self,name=""):
            self.name = name
            return
        
    class FloatProperty:
        name = ""
        def __init__(self,name=""):
            self.name = name
            return
        
    class PropertyGroup:
        pass
#
# Define my class
class my_class:
    my_int = IntProperty(name="my_int")
    my_str = StringProperty(name="my_str")
    
    def __init__(self,in_int=0,in_str=""):
        self.my_int = in_int
        self.my_str = in_str
        return
    
    def hello(self):
        print("Hi there!")
        print("My string is '%s'" %self.my_str)
        print("My integer is: %d" %self.my_int)
        return
    
#
class my_other_class(my_class):
    my_flt = FloatProperty(name="my_flt")
    
    def __init__(self,in_int=0,in_str="",in_flt=0.):
        my_class.__init__(self,in_int,in_str)
        self.my_flt = in_flt
        return
    
    def hello(self):
        my_class.hello(self)
        print("My float is: %f" %self.my_flt)
    
# Define a blender class which will inherit from my_class and
# PropertyGroup
class BL_my_class(my_other_class,PropertyGroup):
    def __init__(self,in_int=0,in_str="",in_flt=0.):
        my_other_class.__init__(self,in_int,in_str,in_flt)
        return
    
    def __new__(cls,in_int=0,in_str="",in_flt=0):
        return my_other_class(in_int,in_str,in_flt)
    
if __name__ == "__main__":
    print("
################# Start Test ###########################")
    if "bpy" in locals():
        bpy.utils.register_class(BL_my_class)
        bpy.types.Object.test = bpy.props.PointerProperty( \
                type=BL_my_class, name="test" \
            )
        # Remove all of the meshes and add 2 cubes
        bpy.ops.object.select_by_type(type="MESH")
        bpy.ops.object.delete()
        # Add cube 1
        bpy.ops.mesh.primitive_cube_add(location=(-2,0,0))
        cube1 = bpy.context.object
        print("
Cube1 class data")
        cube1.test.__init__(1,"I am Cube 1",5)
        cube1.test.hello()
        # Add cube 2
        print("
Cube2 class data")
        bpy.ops.mesh.primitive_cube_add(location=(2,0,0))
        cube2 = bpy.context.object
        cube2.test.my_str = "I am Cube 2"
        cube2.test.my_int = 99
        cube2.test.my_flt = 3.14
        cube2.test.hello()
        # An independent instance
        a = BL_my_class(2,"Another entry!",0.707)
        print("
Independent instance")
        a.hello()
    else:
        print("
Cube1 class data")
        cube1 = BL_my_class(1,"I am Cube 1", 5)
        cube1.hello()
        print("
Cube2 class data")
        cube2 = BL_my_class()
        cube2.my_str = "I am Cube 2"
        cube2.my_int = 99
        cube2.my_flt = 3.14
        cube2.hello()
#
    print("################### End Test ###########################
")