Fix #100718: NLA Hold Forward Inconsistency #109182

Merged
Nate Rupsis merged 14 commits from 100718-NLA-hold-forward-inconsistency into main 2023-09-11 18:40:38 +02:00
Member

Finish landing the PR for 107281 in author's absence.


Problem:
For the Action Track, 'extrapolation=Hold Forward' behaves the same as
'Hold'. Animators expect 'Hold Forward' to behave consistently with the
rest of the NLA system but it does not.

Source of the problem:
Pre-patch, the problematic behavior was implemented as a special case
(see: animsys_create_action_track_strip() in anim_sys.c). It was added
added by commit 2826c2be54. The fix
effectively removes the special handling while keeping the core intent
of the original commit, to have the Action Track evaluate fcurve
extrapolation properly.

Solution:
For the Action Track, we now properly treat extrapolation Hold_Forward
just like the rest of the NLA system (strips): as bounded on the left
and infinite on the right. In (anim_sys.c) nlastrips_ctime_get_strip(),
an additional boundary check is required since the original commit
assumed the only possible case, when NLASTRIP_FLAG_NO_TIME_MAP is
enabled, is effectively extrapolation=HOLD.

Quick Test File:
The cube is keyed at F50 and F70 with linear interpolation. The NLA has extrap=HoldForward.
Expect (PR): at frames before F50, the cube to be at world origin.
Expect (broken version): at frames before F50, the cube extrapolates towards the top left.

Finish landing the PR for [107281](https://projects.blender.org/blender/blender/pulls/107281) in author's absence. ---- Problem: For the Action Track, 'extrapolation=Hold Forward' behaves the same as 'Hold'. Animators expect 'Hold Forward' to behave consistently with the rest of the NLA system but it does not. Source of the problem: Pre-patch, the problematic behavior was implemented as a special case (see: animsys_create_action_track_strip() in anim_sys.c). It was added added by commit 2826c2be54. The fix effectively removes the special handling while keeping the core intent of the original commit, to have the Action Track evaluate fcurve extrapolation properly. Solution: For the Action Track, we now properly treat extrapolation Hold_Forward just like the rest of the NLA system (strips): as bounded on the left and infinite on the right. In (anim_sys.c) nlastrips_ctime_get_strip(), an additional boundary check is required since the original commit assumed the only possible case, when NLASTRIP_FLAG_NO_TIME_MAP is enabled, is effectively extrapolation=HOLD. Quick Test File: The cube is keyed at F50 and F70 with linear interpolation. The NLA has extrap=HoldForward. Expect (PR): at frames before F50, the cube to be at world origin. Expect (broken version): at frames before F50, the cube extrapolates towards the top left.
Nate Rupsis added the
Module
Animation & Rigging
label 2023-06-21 03:25:00 +02:00
Nate Rupsis added 1 commit 2023-06-21 03:25:10 +02:00
Nate Rupsis requested review from Sybren A. Stüvel 2023-06-21 03:25:21 +02:00
Nate Rupsis requested review from Brad Clark 2023-06-21 03:25:29 +02:00
Nate Rupsis requested review from Nathan Vegdahl 2023-06-21 03:25:44 +02:00
Nate Rupsis added this to the Animation & Rigging project 2023-06-21 03:26:00 +02:00
Brad Clark approved these changes 2023-06-21 06:11:12 +02:00
Brad Clark left a comment
Member

Approve from the animation/art side to have a working as shown NLA strip.

Approve from the animation/art side to have a working as shown NLA strip.
Member

So, after some more testing, I'm starting to question whether this is really digging into the issue deeply enough.

When tweaking a strip (such that it ends up on the action track) everything already worked correctly even before this patch. So the issue is purely about what the desired behavior is for the directly-assigned action. Some thoughts:

  1. It's weird to me that there's even an option to set the hold behavior for a directly-assigned action at all. There's no clip bounds to bound it.
  2. Right now, the specified hold behavior on the action track only has an effect if there are also NLA tracks present. When there are no NLA tracks that hold setting is ignored. This feels odd and inconsistent to me.
  3. If we resolve that inconsistency by making the hold behavior have an effect even without any NLA tracks, then there's no way to get the current (and IMO desirable behavior) where the action is still evaluated before and after the first/last key frames, which matters when there's extrapolation or modifiers on the f-curves.
So, after some more testing, I'm starting to question whether this is really digging into the issue deeply enough. When *tweaking* a strip (such that it ends up on the action track) everything already worked correctly even before this patch. So the issue is purely about what the desired behavior is for the directly-assigned action. Some thoughts: 1. It's weird to me that there's even an option to set the hold behavior for a directly-assigned action at all. There's no clip bounds to bound it. 2. Right now, the specified hold behavior on the action track only has an effect if there are also NLA tracks present. When there are no NLA tracks that hold setting is ignored. This feels odd and inconsistent to me. 3. If we resolve that inconsistency by making the hold behavior have an effect even without any NLA tracks, then there's no way to get the current (and IMO desirable behavior) where the action is still evaluated before and after the first/last key frames, which matters when there's extrapolation or modifiers on the f-curves.
Member

Without other tracks/layered workflow it really doesn't matter what those settings are.

With other tracks, right now, the setting is correct, the display is correct, the result of both of those is wrong and what this fixes.

This is less about when we tweak an action than it is about when we are creating new actions in a LAYERED workflow situation...vs using the NLA for other reasons.

Without other tracks/layered workflow it really doesn't matter what those settings are. With other tracks, right now, the setting is correct, the display is correct, the result of both of those is wrong and what this fixes. This is less about when we tweak an action than it is about when we are creating new actions in a LAYERED workflow situation...vs using the NLA for other reasons.
Member

"If we resolve that inconsistency by making the hold behavior have an effect even without any NLA tracks, then there's no way to get the current (and IMO desirable behavior) where the action is still evaluated before and after the first/last key frames, which matters when there's extrapolation or modifiers on the f-curves."

HOLD would work and does, it is hold FORWARD that is the issue, in that it does NOT only hold forward, it still acts as HOLD...

"If we resolve that inconsistency by making the hold behavior have an effect even without any NLA tracks, then there's no way to get the current (and IMO desirable behavior) where the action is still evaluated before and after the first/last key frames, which matters when there's extrapolation or modifiers on the f-curves." HOLD would work and does, it is hold FORWARD that is the issue, in that it does NOT only hold forward, it still acts as HOLD...
Member

Talked with @BClark at the animation workshop about this, and it turns out I was misunderstanding the situation. So please disregard my concerns in my last comment.

For reference: the crux of my misunderstanding was that the different hold behavior of the action track when there are no NLA tracks isn't because the action track is treated as special, but is rather because the base track is, and adding NLA tracks simply makes the action track no longer the base track. This is necessary because the base track has no tracks underneath it to blend with, and the animation values have to be set to something.

Talked with @BClark at the animation workshop about this, and it turns out I was misunderstanding the situation. So please disregard my concerns in my last comment. For reference: the crux of my misunderstanding was that the different hold behavior of the action track when there are no NLA tracks isn't because the action track is treated as special, but is rather because the *base* track is, and adding NLA tracks simply makes the action track no longer the base track. This is necessary because the base track has no tracks underneath it to blend with, and the animation values have to be set to *something*.
Sybren A. Stüvel requested changes 2023-07-03 11:56:24 +02:00
Sybren A. Stüvel left a comment
Member

I can confirm the code works!

Quick Test File:
The cube is keyed at F50 and F70 with linear interpolation. The NLA has extrap=HoldForward.
Expect (PR): at frames before F50, the cube to be at world origin.
Expect (broken version): at frames before F50, the cube extrapolates towards the top left.

@nathanvegdahl helped me a bit. The trick is that to see the behaviour, you also need a regular NLA track, and not just the action track.

As a final thought, we should think about versioning code as well. If Hold Forward is going to change behaviour, we might want to version files so that they still behave the same after loading into 4.0. It might be enough to set the extrapolation to 'Hold' whenever it was set to 'Hold Forward', as that would match how 3.6 behaved?


As a followup PR, I think it would be good to have this section of the NLA properties panel deactivated (so greyed out but still editable) whenever there are no NLA tracks.

image

Only if it's simple to do though. I'd rather see us put effort into the new model, rather than polishing the NLA. Fixes like in this PR are still good, as they actually hamper people in their workflow.

I can confirm the code works! > Quick Test File: > The cube is keyed at F50 and F70 with linear interpolation. The NLA has extrap=HoldForward. > Expect (PR): at frames before F50, the cube to be at world origin. > Expect (broken version): at frames before F50, the cube extrapolates towards the top left. @nathanvegdahl helped me a bit. The trick is that to see the behaviour, you *also* need a regular NLA track, and not just the action track. As a final thought, we should think about versioning code as well. If Hold Forward is going to change behaviour, we might want to version files so that they still behave the same after loading into 4.0. It might be enough to set the extrapolation to 'Hold' whenever it was set to 'Hold Forward', as that would match how 3.6 behaved? -------------- **As a followup PR**, I think it would be good to have this section of the NLA properties panel deactivated (so greyed out but still editable) whenever there are no NLA tracks. ![image](/attachments/028e1406-eecb-403e-b73c-ab7920711798) Only if it's simple to do though. I'd rather see us put effort into the new model, rather than polishing the NLA. Fixes like in this PR are still good, as they actually hamper people in their workflow.
@ -631,2 +631,3 @@
for (; fcurve_offset < 4 && quat_curve_fcu;
++fcurve_offset, quat_curve_fcu = quat_curve_fcu->next) {
++fcurve_offset, quat_curve_fcu = quat_curve_fcu->next)
{

Please keep formatting changes out of the patch.

Please keep formatting changes out of the patch.
nrupsis marked this conversation as resolved
@ -3252,0 +3266,4 @@
/* Must set NLASTRIP_FLAG_USR_INFLUENCE, or else the default setting overrides, and influence
* doesn't work.
*
* Must set NLASTRIP_FLAG_NO_TIME_MAP, so Action Track fcurve evaluation extends beyond its

This is a bit confusing. The comment says that NLASTRIP_FLAG_NO_TIME_MAP must be set, but the code here doesn't set it.

This is a bit confusing. The comment says that `NLASTRIP_FLAG_NO_TIME_MAP` must be set, but the code here doesn't set it.
nrupsis marked this conversation as resolved

Originally posted by @BClark in #107281:

Question, is this bug something we can target for 3.6.1 since I think we are set on yes it should work from the module meeting?

If I'm right with my above comment on versioning, in that we can keep the behaviour of files exactly the same, then I think this can go into 3.6.1. If not, then we have to do a more careful analysis.

_Originally posted by @BClark in [#107281](/blender/blender/pulls/107281#issuecomment-970310)_: > Question, is this bug something we can target for 3.6.1 since I think we are set on yes it should work from the module meeting? If I'm right with my above comment on versioning, in that we can keep the behaviour of files exactly the same, then I think this can go into 3.6.1. If not, then we have to do a more careful analysis.
Member

Just a note:
" I think it would be good to have this section of the NLA properties panel deactivated (so greyed out but still editable) whenever there are no NLA tracks."

This seems like at first glance it is a good idea but with the con of removing the ability to preset how the animation works when the clip is created/pushed the first time vs. not being able to change them until after creating a strip.

image

image

Just a note: " I think it would be good to have this section of the NLA properties panel deactivated (so greyed out but still editable) whenever there are no NLA tracks." This seems like at first glance it is a good idea but with the con of removing the ability to preset how the animation works when the clip is created/pushed the first time vs. not being able to change them until after creating a strip. ![image](/attachments/e4101331-1a4b-41f9-aefd-73d2d8d1bd05) ![image](/attachments/90010315-9514-420f-8929-49dac31ef508)

(so greyed out but still editable)

This seems like at first glance it is a good idea but with the con of removing the ability to preset how the animation works when the clip is created/pushed the first time vs. not being able to change them until after creating a strip.

I don't think you got the "but still editable" part of what I wrote. Blender has both "inactive" and "disabled" states. They both appear greyed-out, but the "inactive" state still allows changing the parameters.

>> (so greyed out but still editable) > > This seems like at first glance it is a good idea but with the con of removing the ability to preset how the animation works when the clip is created/pushed the first time vs. not being able to change them until after creating a strip. I don't think you got the "but still editable" part of what I wrote. Blender has both "inactive" and "disabled" states. They both appear greyed-out, but the "inactive" state still allows changing the parameters.
Nate Rupsis added 4 commits 2023-07-18 19:11:40 +02:00
Nate Rupsis added 1 commit 2023-07-18 20:20:14 +02:00
Author
Member

@blender-bot package

@blender-bot package
Member

Package build started. Download here when ready.

Package build started. [Download here](https://builder.blender.org/download/patch/PR109182) when ready.
Nate Rupsis added 2 commits 2023-08-17 21:07:18 +02:00
Nathan Vegdahl approved these changes 2023-08-24 14:59:59 +02:00
Nathan Vegdahl left a comment
Member

Just took another look at the code, and this looks ready to land to me. If I understand the conversation above, we want to target this for both the 3.6 LTS branch and 4.0.

@dr.sybren When you get back from vacation, can you confirm? And then we can get this landed.

Just took another look at the code, and this looks ready to land to me. If I understand the conversation above, we want to target this for both the 3.6 LTS branch and 4.0. @dr.sybren When you get back from vacation, can you confirm? And then we can get this landed.
Nathan Vegdahl requested review from Sybren A. Stüvel 2023-08-24 15:00:20 +02:00
Sybren A. Stüvel requested changes 2023-09-04 11:08:54 +02:00
Sybren A. Stüvel left a comment
Member

Almost there!

Almost there!
@ -1053,0 +1062,4 @@
continue;
}
if (&adt->nla_tracks != nullptr && adt->action != nullptr) {

Style wise, I think this condition can be merged with the one above. Or at least flipped and using continue.

Also how is &adt->nla_tracks ever going to be nullptr? That would only happen when adt = -offsetof(AnimData, nla_tracks), which is rather unlikely. I think what you're looking for is BLI_listbase_is_empty(&adt->nla_tracks).

And finally, what's the adt->action check for? Is it really important that this versioning doesn't happen when the NLA is active and an Action has been assigned? If it is, might be worth a comment that explains why.

Style wise, I think this condition can be merged with the one above. Or at least flipped and using `continue`. Also how is `&adt->nla_tracks` ever going to be `nullptr`? That would only happen when `adt = -offsetof(AnimData, nla_tracks)`, which is rather unlikely. I think what you're looking for is `BLI_listbase_is_empty(&adt->nla_tracks)`. And finally, what's the `adt->action` check for? Is it really important that this versioning doesn't happen when the NLA is active *and* an Action has been assigned? If it is, might be worth a comment that explains why.
Author
Member

The hold forward issue only occurs when there is an action and at least one NLA track. Thus, to preserve backwards functionality, yes we need to check for this. Will add a comment 👍

The hold forward issue only occurs when there is an action and at least one NLA track. Thus, to preserve backwards functionality, yes we need to check for this. Will add a comment 👍
@ -1053,0 +1067,4 @@
LISTBASE_FOREACH (NlaTrack *, track, &adt->nla_tracks) {
if(BLI_listbase_count(&track->strips) > 0){
has_nla_strip = true;

Since has_nla_strip is never set to false, you can break here. Also I feel this could be a separate function like BKE_animdata_has_nla(AnimData *adt) or BKE_nla_is_active(ID *id) or something along those lines (I'm not a fan of the "is active" as "active" in Blender is such a specific term, but can't think of a better name right now).

Since `has_nla_strip` is never set to `false`, you can `break` here. Also I feel this could be a separate function like `BKE_animdata_has_nla(AnimData *adt)` or `BKE_nla_is_active(ID *id)` or something along those lines (I'm not a fan of the "is active" as "active" in Blender is such a specific term, but can't think of a better name right now).
@ -1053,0 +1071,4 @@
}
}
/* Only update action extend mode if there's a strip present. */
if (adt->act_extendmode == NLASTRIP_EXTEND_HOLD_FORWARD && has_nla_strip) {

adt->act_extendmode == NLASTRIP_EXTEND_HOLD_FORWARD does not depend on the NLA strip being present or not. Just add if (adt->act_extendmode != NLASTRIP_EXTEND_HOLD_FORWARD) continue; to the precondition checks at the top.

`adt->act_extendmode == NLASTRIP_EXTEND_HOLD_FORWARD` does not depend on the NLA strip being present or not. Just add `if (adt->act_extendmode != NLASTRIP_EXTEND_HOLD_FORWARD) continue;` to the precondition checks at the top.
Nate Rupsis added 2 commits 2023-09-07 22:07:47 +02:00
Sybren A. Stüvel requested changes 2023-09-08 10:54:08 +02:00
@ -1055,0 +1061,4 @@
FOREACH_MAIN_ID_BEGIN (bmain, id) {
AnimData *adt = BKE_animdata_from_id(id);
/* We only want to preserve existing behavior if there's an action and 1 or more NLA strips. */
if (adt == nullptr || BLI_listbase_is_empty(&adt->nla_tracks) || adt->action == nullptr ||

Shouldn't this be using BKE_nlatrack_has_strips(&adt->nla_tracks) instead of BLI_listbase_is_empty()? The comments says to check for existence of strips, but the code checks for existence of tracks. Given that you call BKE_nlatrack_has_strips below anyway, I think you can remove the call to BLI_listbase_is_empty() here and keep the condition a bit simpler.

Shouldn't this be using `BKE_nlatrack_has_strips(&adt->nla_tracks)` instead of `BLI_listbase_is_empty()`? The comments says to check for existence of strips, but the code checks for existence of tracks. Given that you call `BKE_nlatrack_has_strips` below anyway, I think you can remove the call to `BLI_listbase_is_empty()` here and keep the condition a bit simpler.
Nate Rupsis added 2 commits 2023-09-08 17:46:19 +02:00
Sybren A. Stüvel approved these changes 2023-09-11 10:45:37 +02:00
Sybren A. Stüvel left a comment
Member

Thanks!

Thanks!
Nate Rupsis added 2 commits 2023-09-11 18:21:13 +02:00
Nate Rupsis merged commit 9da88301ef into main 2023-09-11 18:40:38 +02:00
Nate Rupsis deleted branch 100718-NLA-hold-forward-inconsistency 2023-09-11 18:40:40 +02:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No Assignees
5 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#109182
No description provided.