Is my add-on redundant? (and if not, how to use with non-property objects within blenders API)

Hi,

I am currently working on an add-on that is supposed to assign bones to so called “BoneClusters” for easy management and replication (the name BoneGroups was allready taken…)

Why would you need this?

If you wanted to use for example both FK and IK with your armature, some sources (e.g. the first half of this video: https://www.youtube.com/watch?v=WyjJA0v6uCU) suggest that you create multiple identical rigs. One that actually deforms the mesh and one more for each method of animation you want to use (IK, FK, MoCap, etc.). Those access rigs will act as “drivers” for your original rig.

The method demonstrated in the video makes perfect sense and works in theory but has some severe flaws. In reality you would have to copy each and every bone in your armature (bossibly hundreds of bones), possibly rename the copies and then connect the original bones to their respective counterparts via constraints. By hand! And one iteration of this lengthy procedure will only get you one “copy-rig” at a time… Not to speak of the mess that would occur, if you made changes or additions to your original rig afterwards.

So the solution I thought of was to:

  • Assign your bones (doesn’t necessarily have to be your entire rig) to an instance of BoneCluster.

  • Append that instance to a RNA-list inside your armature (no keep it nice and persistent).

  • Give the user some utility options like deleting a cluster, adding or removing bones…

  • And most importantly copying the bones inside your cluster into a new “copy_cluster”. This operator would automatically rename all the copied bones (e.g. some_prefix + original_name) and save a reference to the orginal cluster within the new one, so it can adapt to any changes that you may apply to the original bones

To make my idea mode understandable, here is my full source code (of course work in progress):

import bpy
import os

os.system("cls")

class BoneCluster():
    """Simple class that is used to store groups of bones and some details about them
    Bones can be assigned to more than one instance of BoneCluster"""
    def __init__(self, name, parentCluster, prefix, bones, constraints):
        #The instances name. Will be set and read by the user
        self.name = name
        #If this instance of BoneCluster is a copy (thus a child) of another instance, a reference to it's "parent" will be stored
        self.parentCluster = parentCluster
        #If this instance of BoneCluster is a copy (thus a child) of another instance, all of the bones names inside it will begin with this prefix
        self.prefix = prefix
        #List of all bones, that are assigned to this instance
        self.bones = bones
        #List of all bone-constraints that link the bones inside this instance of BoneCluster to the corrsponding bones of it's respective parent
        self.constraints = constraints

def selectBoneClusterByString(selectorString, boneClusterList):
    """This function finds a certain instance of BoneCluster, inside a given boneClusterList, by it's name
    It is used to compensate for the lack of a possibility to pass an instance of BoneCluster directly into an operator"""
    blessedCluster = None
    for boneCluster in boneClusterList:
        if selectorString == boneCluster.name:
            blessedCluster = boneCluster
    return blessedCluster

def redrawProps(context):
    """Short function that keeps the UI up to date with changes caused by one of the custom operators"""
    for region in context.area.regions:
        if region.type == 'UI':
            region.tag_redraw()

class CreateNewBoneCluster(bpy.types.Operator):
    """This operator creates a new instance of BoneCluster inside the active armatures boneClusterList"""
    bl_idname = "multi_rigs.create_new_bone_cluster"
    bl_label = "New BoneCluster"

    bpy.ops.object.mode_set(mode='EDIT')

    scn = bpy.context.scene
    name = bpy.props.StringProperty(
        name="name",
        description="Name of the new BoneCluster",
        default="NewBoneCluster")
    fromSelected = bpy.props.BoolProperty(
        name="fromSelected",
        description="Assign selected bones to new cluster?",
        default=False)

    def execute(self, context):
        arm = bpy.context.active_object
        validName = True
        errorMessage = None

        for boneCluster in arm.boneClusterList:
            if self.name == boneCluster.name:
                validName = False
                errorMessage = "Invalid Name: Name allready Taken"
        if self.name == "":
            validName = False
            errorMessage = "Invalid Name: Name empty"

        if validName:
            if self.fromSelected == False:
                newBoneCluster = BoneCluster(
                    name=self.name,
                    parentCluster=None,
                    prefix="",
                    bones=[],
                    constraints=[])
            else:
                tempBones = bpy.context.selected_bones
                newBoneCluster = BoneCluster(
                    name=self.name,
                    parentCluster=None,
                    prefix=None,
                    bones=tempBones,
                    constraints=[])
            arm.boneClusterList.append(newBoneCluster)
            redrawProps(context=bpy.context)
            self.report({'INFO'}, "Successfully created \"%s\"" %(newBoneCluster.name))
            return{'FINISHED'}
        else:
            self.report({'ERROR'}, errorMessage)
            return{'CANCELLED'}

    def invoke(self, context, event):
        wm = bpy.context.window_manager
        return wm.invoke_props_dialog(self)
        execute(self, context)

    @classmethod
    def register(cls):
        bpy.types.Object.boneClusterList = []

    @classmethod
    def unregister(cls):
        del bpy.types.Object.boneClusterList

class DeleteBoneCluster(bpy.types.Operator):
    """This operator deletes an instance of BoneCluster out of the active armatures boneClusterList"""
    bl_idname = "multi_rigs.delete_bone_cluster"
    bl_label = "Delete BoneCluster"

    bpy.ops.object.mode_set(mode='EDIT')

    pendingKill = None
    currentClusterName = bpy.props.StringProperty()

    def execute(self, context):
        scn = bpy.context.scene
        arm = bpy.context.active_object
        #this is disgustingly annoying!
        self.pendingKill = selectBoneClusterByString(self.currentClusterName, arm.boneClusterList)

        if self.pendingKill is not None:
            arm.boneClusterList.remove(self.pendingKill)
            redrawTools(context=bpy.context)
            self.report({'INFO'}, "Successfully deleted %s" %(self.pendingKill.name))
            return{'FINISHED'}
        else:
            self.report({'ERROR'}, "BoneCluster not part of this object")
            return{'CANCELLED'}

class MultiRigsFrontend(bpy.types.Panel):
    """Creates a panel inside the properties-window under the category object"""
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_category = "Data"
    bl_label = "MultiRigs"
    bl_idname = "multi_rigs_frontend"

    def draw(self, context):
        lay = self.layout
    #I would love to use a layout.template_list() where I could display and manipulate my instances of BoneCluster
    #unfortunately the list won't accept my BoneCluster()-objects here...


def register():
    bpy.utils.register_class(CreateNewBoneCluster)
    bpy.utils.register_class(DeleteBoneCluster)
    bpy.utils.register_class(MultiRigsFrontend)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    try:
        unregister()
    except Exception as e:
        print(e)
        pass
    register()

Sorry for the long read, this turned out way longer than I wished it to be…

Finally my questions:

  1. Am I implementing something that you can allready do in vanilla blender, thus is my add-on redundand? Was switching betwen IK and FK in one rig ever a problem and if so is it stil (note that the link, I shared, is discussing this problem, using a fairly old version of Blender).

  2. If you read my code, you’ll notice that I had to implement a really annoying and “unclean” workaround to pass my instances of BoneCluster to my operators. Now I’m afraid it is impossible to pass them into a layout.template_list() (a list that looks and behaves like the material list in you properties panel). Any suggestions how to setup my stucture smarter, so I can circumvent those problems?

I’m not sure to understand what your addon is doing…
Let’s suppose I’ve made a simple rig, now I want to add IK controlers for the arm, I assign the arm to a BoneCluster that make a copy of the arm that I can tweak to make a IK ?

It’s true that it’s a bit tedious to setup all these constraints, generally if you do a lot of rigging you end-up doing your own autorig system to speed up the process, or use one available like riggify , auto-rig pro or blendrig.

I’m not sure to understand what your addon is doing…

Sorry if I was unclear.
The idea was to assign your bones (in your example an arm) to a BoneCluster (let’s call said cluster ARM). This cluster can be copied (let’s call that copy COPY_ARM). The plugin aims so setup all the constraints between ARM and COPY_ARM automatically and keep COPY_ARM up to date if you decide to change ARM (e.g. add or delete some of ARM’s bones).

[…] generally if you do a lot of rigging you end-up doing your own autorig system to speed up the process […]

You got it, that’s exactly what I’m doing.
I was just thinking, that if I invest the ammount of time and patience into creating such a custom “autorig-system”, I should walk the extra mile and package it in a nice UI so it could be used by friends and collegues as well.

What I was originally asking for are two things:

  1. Am I trying to write something that allready exists? (I spent some time trying out riggify, but to my knowlege it was not exactly, what I am trying to setup here)

  2. Is there a smarter way of implementing the data-structures that I want to use, possibly a structure that works a little bit better with blenders built-in operators

Ok !
It sound indeed nice to be able to duplicate a bone chain and have constraints and names automatically setup.

Having chains to automatically update, well, if that not a big thing to implement it’s ok. Generally you start working only on deforming bones, so the skinning is good and you know you have all the bones needed to create deformation. Then adding or removing bones in a chain shouldn’t append that often.
IMO you shouldn’t invest much time in this updating stuff.
It may also get complicated to handle once you add other bones like pole targets in the case of an IK chain.
In some rigs, you may have also more deforming bones than in the limbs :

You have IK forearm and IK arm, but the deforming arm bone can be split in two BBones to allow rubber-hose / cartoony style animation.

So IMO while this could be a nice help in many case, there may be other things to implement to speed-up the whole rigging process.