Need help transferring math from python addon to Blender core (for additive animation layers)

When using the NLA editor for layered animation, it defaults to REPLACE blend mode. When you switch the blend mode to ADD (or SUBTRACT/REPLACE), Blender doesn’t change the values or read them differently, it simply performs those math functions on the curve values as they were set.
When new keyframes are inserted, they also insert for the REPLACE blend mode.

I made an addon that automatically tweaks keyframe values according to the set blend mode. So, when an object’s value is 4 and you move it 3 units forward, making the final value 7, and the blend mode is ADD, it doesn’t add 7 + 4 = 11, but instead adds 4 + 3 = 7 (the intended value).

The python math I use is:

diffference = (abs(old_value-new_value) * (-1 if new_value < old_value else 1) )
if blend_mode == 'ADD':		keyframe_value.co[1] = difference;
if blend_mode == 'SUBTRACT':	keyframe_value.co[1] = difference * -1 ;

old_value = combined curve value from the previous NLA tracks (using math copied from how Blender already reads track layers)
new_value = The currently set property/value, which by default, the keyframe is set to.
(note: I perform extra steps when blend is MULTIPLY)

When I try to apply this same math/logic to the NLA’s curve reader values in anim_sys.c, instead of modifying the keyframes (ie, take out the middleman), the results are consistently not what I wanted.

Either the values get read as REPLACE (erasing the lower animation), double the results, or inverts things.

Original code section:
/* accumulate (i.e. blend) the given value on to the channel it affects */
static void nlaevalchan_accumulate(NlaEvalChannel *nec, NlaEvalStrip *nes, float value, bool newChan)
{
	NlaStrip *strip = nes->strip;
	short blendmode = strip->blendmode;
	float inf = strip->influence;

	/* for replace blend mode, and if this is the first strip,
	 * just replace the value regardless of the influence */
	if (newChan && blendmode == NLASTRIP_MODE_REPLACE) {
		nec->value = value;
		return;
	}
	
	/* if this is being performed as part of transition evaluation, incorporate
	 * an additional weighting factor for the influence
	 */
	if (nes->strip_mode == NES_TIME_TRANSITION_END)
		inf *= nes->strip_time;

	/* optimisation: no need to try applying if there is no influence */
	if (IS_EQF(inf, 0.0f)) return;

	/* perform blending */
	switch (blendmode) {
		case NLASTRIP_MODE_ADD:
			/* simply add the scaled value on to the stack */
			nec->value += (value * inf);/
			break;

		case NLASTRIP_MODE_SUBTRACT:
			/* simply subtract the scaled value from the stack */
			nec->value -= (value * inf);
			break;

		case NLASTRIP_MODE_MULTIPLY:
			/* multiply the scaled value with the stack */
			/* Formula Used:
			 *     result = fac * (a * b) + (1 - fac) * a
			 */
			nec->value = inf * (nec->value * value)  +   (1 - inf) * nec->value;
			break;

		case NLASTRIP_MODE_REPLACE:
		default: /* TODO: do we really want to blend by default? it seems more uses might prefer add... */
			/* do linear interpolation
			 *	- the influence of the accumulated data (elsewhere, that is called dstweight)
			 *	  is 1 - influence, since the strip's influence is srcweight
			 */
			nec->value = nec->value * (1.0f - inf)   +   (value * inf);
			break;
	}
}
When I plug my code in:
/* accumulate (i.e. blend) the given value on to the channel it affects */
static void nlaevalchan_accumulate(NlaEvalChannel *nec, NlaEvalStrip *nes, float value, bool newChan)
{
	NlaStrip *strip = nes->strip;
	short blendmode = strip->blendmode;
	float inf = strip->influence;

	/* for replace blend mode, and if this is the first strip,
	 * just replace the value regardless of the influence */
	if (newChan && blendmode == NLASTRIP_MODE_REPLACE) {
		nec->value = value;
		return;
	}
	
	/* diffference = (abs(old_value-new_value) * (-1 if new_value < old_value else 1) ); */
	float difference = fabs(nec->value - value);
	if (value < nec->value) difference *= -1.0f;
	else difference *= 1.0f;

	/* if this is being performed as part of transition evaluation, incorporate
	 * an additional weighting factor for the influence
	 */
	if (nes->strip_mode == NES_TIME_TRANSITION_END)
		inf *= nes->strip_time;

	/* optimisation: no need to try applying if there is no influence */
	if (IS_EQF(inf, 0.0f)) return;

	/* perform blending */
	switch (blendmode) {
		case NLASTRIP_MODE_ADD:
			/* simply add the scaled value on to the stack */
			nec->value += (difference * inf);
			break;

		case NLASTRIP_MODE_SUBTRACT:
			/* simply subtract the scaled value from the stack */
			nec->value -= (difference * inf);
			break;

		case NLASTRIP_MODE_MULTIPLY:
			/* multiply the scaled value with the stack */
			/* Formula Used:
			 *     result = fac * (a * b) + (1 - fac) * a
			 */
			nec->value = inf * (nec->value * difference)  +   (1 - inf) * nec->value;
			break;

		case NLASTRIP_MODE_REPLACE:
		default: /* TODO: do we really want to blend by default? it seems more uses might prefer add... */
			/* do linear interpolation
			 *	- the influence of the accumulated data (elsewhere, that is called dstweight)
			 *	  is 1 - influence, since the strip's influence is srcweight
			 */
			nec->value = nec->value * (1.0f - inf)   +   (value * inf);
			break;
	}
}

The math looks like it should work to me, since when changing the keyframe to the difference in python, that “difference” is the value that the “value” internal uses, so swapping the value with the difference should get the same result.

The problem is this particular code basically overwrites the previous animation data.

I don’t know if there’s a property that I set incorrectly or in the wrong location, or even what language this stuff is written in.

I wanted to write this internally because it would be THE fastest implementation, instead of all the checks I do to find the values in python, and without running too many times, dropping performance. Here’s a link to the proof-of-concept addon+video demonstration: https://blenderartists.org/t/1100149

Anyone know what’s wrong with this code and how to get it working?
Maybe I needed to update a value somewhere else as well, or position them differently?

The code you modified evaluates the NLA animation, it’s not where it inserts the keyframe. That’s in insert_keyframe in source/blender/editors/animation/keyframing.c.

There seems to be some code already to adjust the frame based on the NLA strip through BKE_nla_tweakedit_remap, so maybe that’s a good starting point.

While replying, I found reason for the problem
I'm under the impression that you misunderstand my goal here (internally).

I don’t want Blender to change anything, I just want it to change how it reads them.

I didn’t even bother trying to modify how it creates keyframes or move things, because

  1. I just found out this is C, because I assume C++ files end with .cpp, and
  2. the value my simple math needs, to perform the intended quick change, needs the previous value from combining the NLA layers, which the evaluation part gets.

I mean, if I were to try to do what I assume you think I was trying to do, I would need to alter the Insert Keyframe Value to find the offset from the transform operator/action, and find the active action’s Blend type, and switch the Value out using those.

But figuring out where to make changes for that, and "how" to do it (seeing as how I don’t actually know the language) would be a much bigger task.

Yes, I modified the evaluation of the NLA strips, but my problem is why doesn’t it work, like I expect it to?

I was rewriting the math to re-explain it, and found the answer.
It’s adding the difference like I want it to, it’s just that the difference keeps changing to where the math ends with the final value equaling the new value.

Reason it doesn’t work like it does in python is that I get and use the difference once, so the difference doesn’t constantly change, cancelling itself out.


Welp, that makes the action I wanted to perform for the goal I want, impossible without adding things that I don’t have the knowledge/skill to create.
So, tossing it in the trash.

Maybe I’ll look into the keyframe insertion thing, to see if I can alter it to see the active blend mode and insert the offset from transforms, but I’m not expecting anything to come of it.