0
$\begingroup$

I want to write an operator that allows to quickly add and properly set up action constraints on all the selected objects simultaneously.

The operator should have some of the same properties as the action constraints themselves. When the operator is called those operator-properties should be set in the invoke function or via the redo panel as usually. Then in the execute function of the operator the values of those operator-properties should be copied to the corresponding properties of the action constraint of each selected object.

For example action constraints have a property called transform_channel. And my operator should also contain a corresponding property transform_channel whose value will be copied to the corresponding property of the action constraints. The transform_channel enum property of my operator should have the same list of possible enum values as the corresponding property of the action constriants, that is:

'LOCATION_X', 'LOCATION_Y', 'LOCATION_Z', 'ROTATION_X', 'ROTATION_Y', 'ROTATION_Z', 'SCALE_X', 'SCALE_Y' and 'SCALE_Z'

Also ideally the tooltip (=description) should be the same.

My question is: can I somehow avoid explicitly copying those enum values and the tooltip from blenders action constraint into my code? Because this feels somehow redundant in my opinion.

The redundant version which I do not like looks something like this:

import bpy

class ActionToConstraintOperator(bpy.types.Operator):
    bl_idname = "object.action_to_constraint"
    bl_label = "Linked Action to Action Constraint"
    bl_options = {'REGISTER', 'UNDO'}

    transform_channel: bpy.props.EnumProperty(
                            name="Transform Channel",
                            description="Transform channel from the target that is used to key the Action", # <-- How to avoid this redundancy?
                            items=(
                                ('LOCATION_X', 'X Location', ''),   # <-- and this?
                                ('LOCATION_Y', 'Y Location', ''),   # <--
                                ('LOCATION_Z', 'Z Location', ''),   # <--
                                ('ROTATION_X', 'X Rotation', ''),   # <--
                                ('ROTATION_Y', 'Y Rotation', ''),   # <--
                                ('ROTATION_Z', 'Z Rotation', ''),   # <--
                                ('SCALE_X', 'X Scale', ''),     # <--
                                ('SCALE_Y', 'Y Scale', ''),     # <--
                                ('SCALE_Z', 'Z Scale', ''),     # <--
                            ),
                            default='LOCATION_X'
                         )

    def execute(self, context):            
        for obj in context.view_layer.objects.selected:
            constraint = obj.constraints.new('ACTION')
            # [code to add proper action data block and constraint target object
            # not relevant for this question]
            constraint.transform_channel = self.transform_channel
            # similarly for other properties like target range min/max and action range start/end

        return {'FINISHED'}

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


def register():
    bpy.utils.register_class(ActionToConstraintOperator)


def unregister():
    bpy.utils.unregister_class(ActionToConstraintOperator)


if __name__ == "__main__":
    register()

Basically I would rather like to have a code that looks something like this (but that does the same thing):

import bpy

class ActionToConstraintOperator(bpy.types.Operator):
    bl_idname = "object.action_to_constraint"
    bl_label = "Linked Action to Action Constraint"
    bl_options = {'REGISTER', 'UNDO'}

    transform_channel: bpy.props.SimilarEnumProperty(bpy.types.ActionConstraint.transform_channel)

    def execute(self, context):            
        for obj in context.view_layer.objects.selected:
            constraint = obj.constraints.new('ACTION')
            # [code to add proper action data block and constraint target object
            # not relevant for this question]
            constraint.transform_channel = self.transform_channel
            # similarly for other properties like target range min/max and action range start/end

        return {'FINISHED'}

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


def register():
    bpy.utils.register_class(ActionToConstraintOperator)


def unregister():
    bpy.utils.unregister_class(ActionToConstraintOperator)


if __name__ == "__main__":
    register()

Unfortunately a function like bpy.props.SimilarEnumProperty() does not exist in blenders API as far as I know. I just made this up to demonstrate what I want to do.

(I think I have already found a solution to this question, which I will post in the answer section. But I like to know if other people will agree with my answer or if they find better solutions.)

$\endgroup$
0
$\begingroup$

In an answer to a different question I learned that there is an attribute called bl_rna that apparently allows to access the list of possible items of enum-properties. I did not find a proper documentation on this bl_rna-attribute in the official Blender Python API Documentation. But still with some experimentation I came up with this:

import bpy

class ActionToConstraintOperator(bpy.types.Operator):
    bl_idname = "object.action_to_constraint"
    bl_label = "Linked Action to Action Constraint"
    bl_options = {'REGISTER', 'UNDO'}

    transform_channel: bpy.props.EnumProperty(
                        name=bpy.types.ActionConstraint.bl_rna.properties["transform_channel"].name, 
                        description=bpy.types.ActionConstraint.bl_rna.properties["transform_channel"].description,
                        items=list(map(lambda id_item: (id_item[0], id_item[1].name, id_item[1].description, id_item[1].icon, id_item[1].value),
                                        bpy.types.ActionConstraint.bl_rna.properties["transform_channel"].enum_items.items())),
                        default='LOCATION_X'
                    )

    def execute(self, context):            
        for obj in context.view_layer.objects.selected:
            constraint = obj.constraints.new('ACTION')
            # [code to add proper action data block and constraint target object
            # not relevant for this question]
            constraint.transform_channel = self.transform_channel
            # similarly for other properties like target range min/max and action range start/end

        return {'FINISHED'}

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


def register():
    bpy.utils.register_class(ActionToConstraintOperator)


def unregister():
    bpy.utils.unregister_class(ActionToConstraintOperator)

if __name__ == "__main__":
    register()

I could also access the default value with bpy.types.ActionConstraint.bl_rna.properties['transform_channel'].default. But for some reason this gives me 'ROTATION_X' while when adding a new Action-Constraint the value of transform_channel actually seems to be 'LOCATION_X' by default.

| improve this answer | |
$\endgroup$

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.