Unique set of properties per frame?

I’m currently working on an addon that imports/exports motion capture data for a old computer game. The animation/armature itself is being handled just fine, but in addition to the joint rotations and overall position, the game’s format also has a set of boolen flags per frame, which are used to trigger sounds at the right moment (footfall, body falling down etc). Each flag is false by default, then true for a specific frame.

Does anyone know if it’s possible for Blender to support something like that?
This is the sort of panel I have in mind:
image
It’s been a while but I already know how to create new properties, but for my previous things these have been set on an object, or on a material, but I’m not sure about frames.

I know it could be done with the keyframe system, but it seems awkward to use in this scenario. To make the flag true just for frame N, the user would have to set 3 keyframes: N-1 (false), N (true), and N+1 (false). Can it be arranged such that the user can just go to frame N on the timeline and set the relevant flag with none of the other frames being affected?

You can give scenes custom properties. Perhaps that is enough and you dont have to do it for every object.

As for the keyframes: i dont think you can get away with a single one but perhaps with two instead of three by setting the interpolation to constant.

Or perhaps you could use markers to trigger your sounds somehow.

1 Like

Thanks for your reply. You could be on to something with markers. The flags panel could also have read & write buttons, with markers whose names are based on the selected flags for that frame.

I could have the checkboxes acting like a bitfield (0, 1, 2, 4, 8 etc). The write button would add the values of the selected boxes and create a marker at the current frame with that value as the name. The read button would see if there’s a marker at the current frame and use its name to work out the correct checkboxes to select.

This seemed like a fun enough experiment. Here’s my proposal.

  • Define a property group with a bit flag enum
  • Add an operator to populate a collection property stored on the current scene
  • Display everything in a panel in the scene properties

Animation3

import bpy


class FrameFlag(bpy.types.PropertyGroup):
    flag: bpy.props.EnumProperty(
        items=(
            ("A", "Option A", "", 2**0),
            ("B", "Option B", "", 2**1),
            ("C", "Option C", "", 2**2),
            ("D", "Option D", "", 2**3),
        ),
        options = {"ENUM_FLAG"}
    )


class SyncFrameFlags(bpy.types.Operator):
    bl_idname = "scene.frame_flags_sync"
    bl_label = "Sync Frame Flags"
    
    def execute(self, context):
        scene = context.scene
        while len(scene.flags) < scene.frame_end:
            scene.flags.add()
        return {"FINISHED"}


class SceneFrameFlagPanel(bpy.types.Panel):
    bl_label = "Frame Flags"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    def draw(self, context):
        scene = context.scene
        layout = self.layout
        column = layout.column(align=True)
        if len(scene.flags) < scene.frame_end:
            column.operator("scene.frame_flags_sync")
        else:
            column.prop(scene, "frame_current")
            column.props_enum(scene.flags[scene.frame_current], "flag")


def register():
    bpy.utils.register_class(FrameFlag)
    bpy.utils.register_class(SyncFrameFlags)
    bpy.utils.register_class(SceneFrameFlagPanel)
    bpy.types.Scene.flags = bpy.props.CollectionProperty(type=FrameFlag)


if __name__ == "__main__":
    register()

Note it does not support negative frames.

2 Likes

Wow, that’s a great effort there. Many thanks! I didn’t know we could just add a type to the scene.

I’d already begun looking in to frame markers, named after the bitfield (or enum flag) value, but the above is much more user-friendly as a frame can have any number of markers with any name.

I made a tweak so instead of stopping at scene.frame_end, it stops at a user-defined max (default TBC, but I’ve noticed a performance drop at very high values).

I’ve set up my import load function to call this operator to save the user a step. Is the operator the only feasible way to get the set up the flags for the scene?

My challenge now is to add a handler for when the user increases the max frames.

Glad you found it useful ! The operator is the most straightforward way to initialize the values. You can use other means to achieve it, for example use an application handler to execute on file load. I recommend you read the part about @persistent decorator in the docs I linked.

For an automatic callback when changing the end frame you can use msgbus.
eg

import bpy

owner = object()

subscribe_to = (bpy.types.Scene, "frame_end")


def msgbus_callback(*args):
    print(f"Frame end is now {bpy.context.scene.frame_end}")


bpy.msgbus.subscribe_rna(
    key=subscribe_to,
    owner=owner,
    args=(None,),
    notify=msgbus_callback,
)

Note msgbus won’t trigger if the value is changed by code. eg bpy.context.scene.frame_end = 250 won’t fire the callback.