I made addon for additive animation layers, now I want it faster/more reliable

Long/Back Story

The only plans for additive animation layers seem to be well over a year off but before I heard any plans existing in the first place, I made an addon to automatically adjust keyframes to the proper/expected values to work with the NLA editor’s blend types.

The addon

I then adjusted it to be optimized better to be able to run continuously, outside of the original modal operator, then made videos showing it in action: https://blenderartists.org/t/1100149

What my addon does is check the current visual value of keyframes on the current frame, and compares it with the combined value of previous layers, then it sets the keyframe to the difference between the two, or the “offset” value.
Whereas the original keyframe is set to the visual value, which when added to the previous values, basically doubles the result.

I was planning on rewriting it to be faster/run less but then while actually using it, I found it doesn’t work with Motion Paths.
The reason being after you make a transform with Autokey enabled, motion paths will update and refresh the frame before the bpy.app.handlers.scene_update_pre/post runs, which is where the addon searches for the “intended” value to change it to the offset value.
So, because the values are updated to the wrong original combined results, it can’t find the intended values to perform the fix.

My addon performs a lot of loop checks to find values to perform it’s fixes, however those could be mitigated if I instead got the value they’re looking for:

  1. Is there a way to observe the transform operators (move/rotate/scale), to find either if they ran successfuly, and if a keyframe was created after using it (autokey)
    This is to find those offset numbers that are shown in the header, when you run them.
  2. Adjust the keyed value after that’s run

I’d like to get the transform values but if that weren’t possible, I could still rely on my older long roundabout process, however, I would still need to fix the motion paths because they would still ruin the result.

Is there a way to:

  1. observe when an object/bone is being moved via the transform operators, or their numeric attribute fields are being changed (ex. you click a field, then enter a number)., as well as whether or not it was confirmed, and what that value is?
  2. adjust the value being keyframed when you insert a key, manually or with auto keyframe?
  3. disable the auto update on motion paths?

i considered writing the addon into Blender’s code, for when it runs the NLA value finder but the problem was that plugging the same math/process there, doesn’t work like I expect, so I gave up. I just gave it another try, and failed, so I asked for core/internal code help here but if I can get it working faster as an addon as well, then using a modified verson of Blender wouldn’t be needed (since it’s easier to share addons)

  1. Yes you can check the object location via the use of a modal operator
  2. Yes there is an update callback functionality for properties that can be called every time a prop changes
  3. I am not so sure about that one, you will have to look at the API ref docs.

I wish I could help you with the internal code, but Blender source is a labyrinth without a map and being 2 million lines of code makes it very challenging and slow to learn how it works. But its high modular and Blender uses a ton of internal functions to do even the most basic functionality so everything is possible even if you don’t intend to change the source code itself but extend it.

It’s not really possible to listen to updates of individual properties. The best you can do is to use the scene_update handlers and check is_updated per ID, to do your changes after the scene has been evaluated.
https://docs.blender.org/api/2.79/bpy.app.handlers.html#bpy.app.handlers.scene_update_post
https://docs.blender.org/api/2.79/bpy.types.ID.html#bpy.types.ID.is_updated

There isn’t any finer control to hook into Blender’s animation evaluation. For custom Python created properties there is an update callback, but I don’t think that’s what this is about.

I would not recommend using modal operators for this kind of thing.

I stand corrected on update, why modal op is a bad solution in this case ? Too performance expensive ?

Modal operators are for listening to keyboard/mouse/timer events and handling them. But changes to the scene can be made without such events as well, or by a different operator handling the same event after your modal operator.

1 Like

ah ok so basically you say its an overkill, gotcha :slight_smile:

Thanks for replies,
@kilon

1. No, I'm not asking about checking for an object's location with my operator or anything like that.
 The modal operator is already running.

 When you start moving an object/bone, you’re doing so with a modal operator. (ie bpy.ops.transform.translate() )
 When the operator is done running, it sets the object’s location to where you moved it, and inserts a keyframe.

 This is the same for the operators of the other properties as well (rotate/scale/skew/etc)
 The operator knows where the object was and how far it moved (as shown in the header).

 I don’t want to find the object’s values, I want to find the operator that’s running, and look at its values (changes)
 I want it to tell my operator how far it moved things, and not just tell Blender that it {“FINISHED”}.


 The exception being when you change property values directly, but that’s a method I’m very willing to ignore.

For example, when you start moving something, I believe the transform operator runs something like this on the 3DView’s screen (as well as when doing it in other screens, like the dopesheet):

active_screen = bpy.context.screen.areas[0];
active_screen.header_text_set('Dx:', x_offset, 'Dy:', y_offset, 'Dz:', z_offset, '(', average_offset, ')' );

Another thought would be to read the last action that’s printed in the info window, but again, I don’t know how ¯\_(ツ)_/¯


@brecht

I was completely unaware of is_updated/_data,

However, since it can’t be used on FCurves/Keyframes, and the only directly related items it can be used on is object and every-bone in object, I will have to think of a way to use it elsewhere.

For example I rewrote the addon (then scrapped that rewrite) to check the selected objects and bone continuously, for changes but checking the is_updated can skip a little more instead (and see if the changes were applied)

I looked into your part about custom properties update but I can’t find anything related to that; only for the types.Id that already exist I was looking to add keyframes and/or point their value to one, and check if they change.



In my scrapped, rewrite script, I had to run a modal operator (pass-through) to tell the main operator if Alt+G/R/S were pressed.
This is to say whether or not the “clear” operators were called, because when using motion_paths, the scene automatically refreshes the pose/animation (with the incorrect values) and so it can’t find the intended “reset” values to perform the fix. (edit: wait, no, when the resets are called, it was my op that updated paths, because that’s when it doesn’t auto-update)


So, my current line of thought is

  1. seeing when and what operators were just ran
  2. seeing what those operators did
  3. have my operator do something, one time, based on that information.

Any info on that?

As far as I know it’s not possible to do detect which operators have run, or what they did. The best I can think of is to store the previous values somewhere yourself and then compare if they changed, but that’s not a great solution.

I was looking into setting up dynamic classes because I had no idea how they actually work, I was just re-using things that other people setup (such as the examples in the doc), and was thinking about a way to store additive keyframes.

I found that bpy.context.window_manager.operators[-1] gives access to the last operator used in the bottom-left window history thing. (you can also view more ops further back)
This is a new and holy **** moment for me, so I could be wrong on when/where/how I can use it.

I can access the values from it, as well as previous actions in the history.
I can also modify the values but the changes don’t update anything else by just changing them here. For example, multiplying the transform values here won’t multiply them on the object unless I tweak it in the panel manually.
bpy.ops.ed.undo_redo() can re-apply changes but tweaking values like in the properties panel, doesn’t show up here, and calling the op would have to be done carefully to not be heavy.

I re-looked into reading the Info window again after learning this, which DOES log tweaks but alas, it was another fail, though I did find the Console editor has a history property storing the text your run in it, which still leaves me with hope for Info.

edit: my hope’s gone.
I found operators that copy items from Info to clipboard, or puts them all into a textblock/file but they’re internal/C ops, that uses something like: ReportList *reports = CTX_wm_reports(C);, which I can only assume is only an internal thing, inaccessible via python.

My only option for the Info route is generating the entire history as a textblock, then reading lines from that.