Animation: Add "Frame Channel" operators
When animating it is often useful to frame the Graph Editor/Dope Sheet to whatever frames are in a given channel. This patch adds the option to frame on selected channels OR frame on whatever channel is under the cursor. If a preview range is set it will only focus on keys in that range. Supports FCurve and keyframe data Frame to selected is called with * Right click in the channel section -> Frame Selected Channels * or in Channel → Frame Selected Channels * or hitting . on the numpad Frame to channel under cursor is done with ALT + Middle Mouse Button Co-authored-by: Christoph Lendenfeld <chris.lenden@gmail.com> Pull Request #104523
This commit is contained in:
@@ -3513,6 +3513,9 @@ def km_animation_channels(params):
|
||||
("anim.channels_ungroup", {"type": 'G', "value": 'PRESS', "ctrl": True, "alt": True}, None),
|
||||
# Menus.
|
||||
*_template_items_context_menu("DOPESHEET_MT_channel_context_menu", params.context_menu_event),
|
||||
# View
|
||||
("anim.channel_view_pick", {"type": 'MIDDLEMOUSE', "value": 'PRESS', "alt": True}, None),
|
||||
("anim.channels_view_selected", {"type": 'NUMPAD_PERIOD', "value": 'PRESS'}, None),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
@@ -474,6 +474,9 @@ class DOPESHEET_MT_channel(Menu):
|
||||
layout.separator()
|
||||
layout.operator("anim.channels_fcurves_enable")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("anim.channels_view_selected")
|
||||
|
||||
|
||||
class DOPESHEET_MT_key(Menu):
|
||||
bl_label = "Key"
|
||||
@@ -601,6 +604,9 @@ class DOPESHEET_MT_gpencil_channel(Menu):
|
||||
layout.separator()
|
||||
layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("anim.channels_view_selected")
|
||||
|
||||
|
||||
class DOPESHEET_MT_gpencil_key(Menu):
|
||||
bl_label = "Key"
|
||||
@@ -689,6 +695,9 @@ class DOPESHEET_MT_channel_context_menu(Menu):
|
||||
# This menu is used from the graph editor too.
|
||||
is_graph_editor = context.area.type == 'GRAPH_EDITOR'
|
||||
|
||||
layout.separator()
|
||||
layout.operator("anim.channels_view_selected")
|
||||
|
||||
layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
|
||||
layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
|
||||
layout.separator()
|
||||
|
||||
@@ -239,6 +239,9 @@ class GRAPH_MT_channel(Menu):
|
||||
layout.separator()
|
||||
layout.operator("anim.channels_fcurves_enable")
|
||||
|
||||
layout.separator()
|
||||
layout.operator("anim.channels_view_selected")
|
||||
|
||||
|
||||
class GRAPH_MT_key(Menu):
|
||||
bl_label = "Key"
|
||||
|
||||
@@ -373,6 +373,8 @@ bool BKE_fcurve_calc_range(
|
||||
|
||||
/**
|
||||
* Calculate the extents of F-Curve's data.
|
||||
* \param range Only calculate the bounds of the FCurve in the given range.
|
||||
* Does the full range if NULL.
|
||||
*/
|
||||
bool BKE_fcurve_calc_bounds(const struct FCurve *fcu,
|
||||
float *xmin,
|
||||
@@ -380,7 +382,8 @@ bool BKE_fcurve_calc_bounds(const struct FCurve *fcu,
|
||||
float *ymin,
|
||||
float *ymax,
|
||||
bool do_sel_only,
|
||||
bool include_handles);
|
||||
bool include_handles,
|
||||
const float range[2]);
|
||||
|
||||
/**
|
||||
* Return an array of keyed frames, rounded to `interval`.
|
||||
|
||||
@@ -557,10 +557,11 @@ int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[],
|
||||
/* ...................................... */
|
||||
|
||||
/* Helper for calc_fcurve_* functions -> find first and last BezTriple to be used. */
|
||||
static short get_fcurve_end_keyframes(const FCurve *fcu,
|
||||
BezTriple **first,
|
||||
BezTriple **last,
|
||||
const bool do_sel_only)
|
||||
static bool get_fcurve_end_keyframes(const FCurve *fcu,
|
||||
BezTriple **first,
|
||||
BezTriple **last,
|
||||
const bool do_sel_only,
|
||||
const float range[2])
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
@@ -573,11 +574,31 @@ static short get_fcurve_end_keyframes(const FCurve *fcu,
|
||||
return found;
|
||||
}
|
||||
|
||||
int first_index = 0;
|
||||
int last_index = fcu->totvert - 1;
|
||||
|
||||
if (range != NULL) {
|
||||
/* If a range is passed in find the first and last keyframe within that range. */
|
||||
bool replace = false;
|
||||
first_index = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, range[0], fcu->totvert, &replace);
|
||||
last_index = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, range[1], fcu->totvert, &replace);
|
||||
|
||||
/* If first and last index are the same, no keyframes were found in the range. */
|
||||
if (first_index == last_index) {
|
||||
return found;
|
||||
}
|
||||
|
||||
/* The binary search returns an index where a keyframe would be inserted,
|
||||
so it needs to be clamped to ensure it is in range of the array. */
|
||||
first_index = clamp_i(first_index, 0, fcu->totvert - 1);
|
||||
last_index = clamp_i(last_index - 1, 0, fcu->totvert - 1);
|
||||
}
|
||||
|
||||
/* Only include selected items? */
|
||||
if (do_sel_only) {
|
||||
/* Find first selected. */
|
||||
BezTriple *bezt = fcu->bezt;
|
||||
for (int i = 0; i < fcu->totvert; bezt++, i++) {
|
||||
for (int i = first_index; i <= last_index; i++) {
|
||||
BezTriple *bezt = &fcu->bezt[i];
|
||||
if (BEZT_ISSEL_ANY(bezt)) {
|
||||
*first = bezt;
|
||||
found = true;
|
||||
@@ -586,8 +607,8 @@ static short get_fcurve_end_keyframes(const FCurve *fcu,
|
||||
}
|
||||
|
||||
/* Find last selected. */
|
||||
bezt = ARRAY_LAST_ITEM(fcu->bezt, BezTriple, fcu->totvert);
|
||||
for (int i = 0; i < fcu->totvert; bezt--, i++) {
|
||||
for (int i = last_index; i >= first_index; i--) {
|
||||
BezTriple *bezt = &fcu->bezt[i];
|
||||
if (BEZT_ISSEL_ANY(bezt)) {
|
||||
*last = bezt;
|
||||
found = true;
|
||||
@@ -596,9 +617,8 @@ static short get_fcurve_end_keyframes(const FCurve *fcu,
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Use the whole array. */
|
||||
*first = fcu->bezt;
|
||||
*last = ARRAY_LAST_ITEM(fcu->bezt, BezTriple, fcu->totvert);
|
||||
*first = &fcu->bezt[first_index];
|
||||
*last = &fcu->bezt[last_index];
|
||||
found = true;
|
||||
}
|
||||
|
||||
@@ -611,23 +631,25 @@ bool BKE_fcurve_calc_bounds(const FCurve *fcu,
|
||||
float *ymin,
|
||||
float *ymax,
|
||||
const bool do_sel_only,
|
||||
const bool include_handles)
|
||||
const bool include_handles,
|
||||
const float range[2])
|
||||
{
|
||||
float xminv = 999999999.0f, xmaxv = -999999999.0f;
|
||||
float yminv = 999999999.0f, ymaxv = -999999999.0f;
|
||||
bool foundvert = false;
|
||||
|
||||
const bool use_range = range != NULL;
|
||||
|
||||
if (fcu->totvert) {
|
||||
if (fcu->bezt) {
|
||||
BezTriple *bezt_first = NULL, *bezt_last = NULL;
|
||||
|
||||
if (xmin || xmax) {
|
||||
/* Get endpoint keyframes. */
|
||||
foundvert = get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only);
|
||||
BezTriple *bezt_first = NULL, *bezt_last = NULL;
|
||||
foundvert = get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only, range);
|
||||
|
||||
if (bezt_first) {
|
||||
BLI_assert(bezt_last != NULL);
|
||||
|
||||
foundvert = true;
|
||||
if (include_handles) {
|
||||
xminv = min_fff(xminv, bezt_first->vec[0][0], bezt_first->vec[1][0]);
|
||||
xmaxv = max_fff(xmaxv, bezt_last->vec[1][0], bezt_last->vec[2][0]);
|
||||
@@ -645,6 +667,9 @@ bool BKE_fcurve_calc_bounds(const FCurve *fcu,
|
||||
|
||||
int i;
|
||||
for (bezt = fcu->bezt, i = 0; i < fcu->totvert; prevbezt = bezt, bezt++, i++) {
|
||||
if (use_range && (bezt->vec[1][0] < range[0] || bezt->vec[1][0] > range[1])) {
|
||||
continue;
|
||||
}
|
||||
if ((do_sel_only == false) || BEZT_ISSEL_ANY(bezt)) {
|
||||
/* Keyframe itself. */
|
||||
yminv = min_ff(yminv, bezt->vec[1][1]);
|
||||
@@ -717,10 +742,10 @@ bool BKE_fcurve_calc_bounds(const FCurve *fcu,
|
||||
}
|
||||
|
||||
if (xmin) {
|
||||
*xmin = 0.0f;
|
||||
*xmin = use_range ? range[0] : 0.0f;
|
||||
}
|
||||
if (xmax) {
|
||||
*xmax = 1.0f;
|
||||
*xmax = use_range ? range[1] : 1.0f;
|
||||
}
|
||||
|
||||
if (ymin) {
|
||||
@@ -745,7 +770,7 @@ bool BKE_fcurve_calc_range(
|
||||
BezTriple *bezt_first = NULL, *bezt_last = NULL;
|
||||
|
||||
/* Get endpoint keyframes. */
|
||||
get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only);
|
||||
get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only, NULL);
|
||||
|
||||
if (bezt_first) {
|
||||
BLI_assert(bezt_last != NULL);
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "ED_anim_api.h"
|
||||
#include "ED_armature.h"
|
||||
#include "ED_keyframes_edit.h" /* XXX move the select modes out of there! */
|
||||
#include "ED_markers.h"
|
||||
#include "ED_object.h"
|
||||
#include "ED_screen.h"
|
||||
#include "ED_select_utils.h"
|
||||
@@ -3638,6 +3639,281 @@ static void ANIM_OT_channel_select_keys(wmOperatorType *ot)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name View Channel Operator
|
||||
* \{ */
|
||||
|
||||
static void get_normalized_fcurve_bounds(FCurve *fcu,
|
||||
bAnimContext *ac,
|
||||
const bAnimListElem *ale,
|
||||
const bool include_handles,
|
||||
const float range[2],
|
||||
rctf *r_bounds)
|
||||
{
|
||||
const bool fcu_selection_only = false;
|
||||
BKE_fcurve_calc_bounds(fcu,
|
||||
&r_bounds->xmin,
|
||||
&r_bounds->xmax,
|
||||
&r_bounds->ymin,
|
||||
&r_bounds->ymax,
|
||||
fcu_selection_only,
|
||||
include_handles,
|
||||
range);
|
||||
const short mapping_flag = ANIM_get_normalization_flags(ac);
|
||||
|
||||
const float min_height = 0.01f;
|
||||
const float height = BLI_rctf_size_y(r_bounds);
|
||||
if (height < min_height) {
|
||||
r_bounds->ymin -= (min_height - height) / 2;
|
||||
r_bounds->ymax += (min_height - height) / 2;
|
||||
}
|
||||
|
||||
float offset;
|
||||
const float unit_fac = ANIM_unit_mapping_get_factor(
|
||||
ac->scene, ale->id, fcu, mapping_flag, &offset);
|
||||
|
||||
r_bounds->ymin = (r_bounds->ymin + offset) * unit_fac;
|
||||
r_bounds->ymax = (r_bounds->ymax + offset) * unit_fac;
|
||||
}
|
||||
|
||||
static void get_gpencil_bounds(bGPDlayer *gpl, const float range[2], rctf *r_bounds)
|
||||
{
|
||||
bool found_start = false;
|
||||
int start_frame = 0;
|
||||
int end_frame = 1;
|
||||
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
|
||||
if (gpf->framenum < range[0]) {
|
||||
continue;
|
||||
}
|
||||
if (gpf->framenum > range[1]) {
|
||||
break;
|
||||
}
|
||||
if (!found_start) {
|
||||
start_frame = gpf->framenum;
|
||||
found_start = true;
|
||||
}
|
||||
end_frame = gpf->framenum;
|
||||
}
|
||||
r_bounds->xmin = start_frame;
|
||||
r_bounds->xmax = end_frame;
|
||||
r_bounds->ymin = 0;
|
||||
r_bounds->ymax = 1;
|
||||
}
|
||||
|
||||
static bool get_channel_bounds(bAnimContext *ac,
|
||||
bAnimListElem *ale,
|
||||
const float range[2],
|
||||
const bool include_handles,
|
||||
rctf *r_bounds)
|
||||
{
|
||||
bool found_bounds = false;
|
||||
switch (ale->datatype) {
|
||||
case ALE_GPFRAME:
|
||||
bGPDlayer *gpl = (bGPDlayer *)ale->data;
|
||||
get_gpencil_bounds(gpl, range, r_bounds);
|
||||
found_bounds = true;
|
||||
break;
|
||||
case ALE_FCURVE:
|
||||
FCurve *fcu = (FCurve *)ale->key_data;
|
||||
get_normalized_fcurve_bounds(fcu, ac, ale, include_handles, range, r_bounds);
|
||||
found_bounds = true;
|
||||
break;
|
||||
}
|
||||
return found_bounds;
|
||||
}
|
||||
|
||||
static void get_view_range(Scene *scene, const bool use_preview_range, float r_range[2])
|
||||
{
|
||||
if (use_preview_range && scene->r.flag & SCER_PRV_RANGE) {
|
||||
r_range[0] = scene->r.psfra;
|
||||
r_range[1] = scene->r.pefra;
|
||||
}
|
||||
else {
|
||||
r_range[0] = -FLT_MAX;
|
||||
r_range[1] = FLT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pad the given rctf with regions that could block the view.
|
||||
* For example Markers and Time Scrubbing. */
|
||||
static void add_region_padding(bContext *C, bAnimContext *ac, rctf *bounds)
|
||||
{
|
||||
BLI_rctf_scale(bounds, 1.1f);
|
||||
|
||||
const float pad_top = UI_TIME_SCRUB_MARGIN_Y;
|
||||
const float pad_bottom = BLI_listbase_is_empty(ED_context_get_markers(C)) ?
|
||||
V2D_SCROLL_HANDLE_HEIGHT :
|
||||
UI_MARKER_MARGIN_Y;
|
||||
BLI_rctf_pad_y(bounds, ac->region->winy, pad_bottom, pad_top);
|
||||
}
|
||||
|
||||
/* Find the window region in the bAnimContext area and move it to bounds. */
|
||||
static void move_graph_view(bContext *C, bAnimContext *ac, rctf *bounds, const int smooth_viewtx)
|
||||
{
|
||||
LISTBASE_FOREACH (ARegion *, region, &ac->area->regionbase) {
|
||||
if (region->regiontype == RGN_TYPE_WINDOW) {
|
||||
UI_view2d_smooth_view(C, region, bounds, smooth_viewtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int graphkeys_view_selected_channels_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
bAnimContext ac;
|
||||
|
||||
/* Get editor data. */
|
||||
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
ListBase anim_data = {NULL, NULL};
|
||||
const int filter = (ANIMFILTER_SEL | ANIMFILTER_NODUPLIS | ANIMFILTER_DATA_VISIBLE |
|
||||
ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
|
||||
size_t anim_data_length = ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
||||
|
||||
if (anim_data_length == 0) {
|
||||
WM_report(RPT_WARNING, "No channels to operate on");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
float range[2];
|
||||
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
|
||||
get_view_range(ac.scene, use_preview_range, range);
|
||||
|
||||
rctf bounds = {.xmin = FLT_MAX, .xmax = -FLT_MAX, .ymin = FLT_MAX, .ymax = -FLT_MAX};
|
||||
|
||||
bAnimListElem *ale;
|
||||
const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
|
||||
|
||||
bool valid_bounds = false;
|
||||
for (ale = anim_data.first; ale; ale = ale->next) {
|
||||
rctf channel_bounds;
|
||||
const bool found_bounds = get_channel_bounds(
|
||||
&ac, ale, range, include_handles, &channel_bounds);
|
||||
if (found_bounds) {
|
||||
BLI_rctf_union(&bounds, &channel_bounds);
|
||||
valid_bounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_bounds) {
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
add_region_padding(C, &ac, &bounds);
|
||||
|
||||
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
|
||||
move_graph_view(C, &ac, &bounds, smooth_viewtx);
|
||||
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static bool channel_view_poll(bContext *C)
|
||||
{
|
||||
return ED_operator_action_active(C) || ED_operator_graphedit_active(C);
|
||||
}
|
||||
|
||||
static void ANIM_OT_channels_view_selected(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers */
|
||||
ot->name = "Frame Selected Channels";
|
||||
ot->idname = "ANIM_OT_channels_view_selected";
|
||||
ot->description = "Reset viewable area to show the selected channels";
|
||||
|
||||
/* API callbacks */
|
||||
ot->exec = graphkeys_view_selected_channels_exec;
|
||||
ot->poll = channel_view_poll;
|
||||
|
||||
ot->flag = 0;
|
||||
|
||||
ot->prop = RNA_def_boolean(ot->srna,
|
||||
"include_handles",
|
||||
true,
|
||||
"Include Handles",
|
||||
"Include handles of keyframes when calculating extents");
|
||||
|
||||
ot->prop = RNA_def_boolean(ot->srna,
|
||||
"use_preview_range",
|
||||
true,
|
||||
"Use Preview Range",
|
||||
"Ignore frames outside of the preview range");
|
||||
}
|
||||
|
||||
static int graphkeys_channel_view_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
bAnimContext ac;
|
||||
|
||||
if (ANIM_animdata_get_context(C, &ac) == 0) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
ListBase anim_data = {NULL, NULL};
|
||||
const int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_NODUPLIS |
|
||||
ANIMFILTER_LIST_CHANNELS);
|
||||
ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype);
|
||||
|
||||
bAnimListElem *ale;
|
||||
const int channel_index = animchannels_channel_get(&ac, event->mval);
|
||||
ale = BLI_findlink(&anim_data, channel_index);
|
||||
if (ale == NULL) {
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
float range[2];
|
||||
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
|
||||
get_view_range(ac.scene, use_preview_range, range);
|
||||
|
||||
rctf bounds;
|
||||
const bool include_handles = RNA_boolean_get(op->ptr, "include_handles");
|
||||
const bool found_bounds = get_channel_bounds(&ac, ale, range, include_handles, &bounds);
|
||||
|
||||
if (!found_bounds) {
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
add_region_padding(C, &ac, &bounds);
|
||||
|
||||
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
|
||||
move_graph_view(C, &ac, &bounds, smooth_viewtx);
|
||||
|
||||
ANIM_animdata_freelist(&anim_data);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void ANIM_OT_channel_view_pick(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers */
|
||||
ot->name = "Frame Channel Under Cursor";
|
||||
ot->idname = "ANIM_OT_channel_view_pick";
|
||||
ot->description = "Reset viewable area to show the channel under the cursor";
|
||||
|
||||
/* API callbacks */
|
||||
ot->invoke = graphkeys_channel_view_pick_invoke;
|
||||
ot->poll = channel_view_poll;
|
||||
|
||||
ot->flag = 0;
|
||||
|
||||
ot->prop = RNA_def_boolean(ot->srna,
|
||||
"include_handles",
|
||||
true,
|
||||
"Include Handles",
|
||||
"Include handles of keyframes when calculating extents");
|
||||
|
||||
ot->prop = RNA_def_boolean(ot->srna,
|
||||
"use_preview_range",
|
||||
true,
|
||||
"Use Preview Range",
|
||||
"Ignore frames outside of the preview range");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Operator Registration
|
||||
* \{ */
|
||||
@@ -3657,6 +3933,9 @@ void ED_operatortypes_animchannels(void)
|
||||
WM_operatortype_append(ANIM_OT_channels_setting_disable);
|
||||
WM_operatortype_append(ANIM_OT_channels_setting_toggle);
|
||||
|
||||
WM_operatortype_append(ANIM_OT_channel_view_pick);
|
||||
WM_operatortype_append(ANIM_OT_channels_view_selected);
|
||||
|
||||
WM_operatortype_append(ANIM_OT_channels_delete);
|
||||
|
||||
/* XXX does this need to be a separate operator? */
|
||||
|
||||
@@ -91,7 +91,7 @@ void get_graph_keyframe_extents(bAnimContext *ac,
|
||||
|
||||
/* Get range. */
|
||||
if (BKE_fcurve_calc_bounds(
|
||||
fcu, &txmin, &txmax, &tymin, &tymax, do_sel_only, include_handles)) {
|
||||
fcu, &txmin, &txmax, &tymin, &tymax, do_sel_only, include_handles, NULL)) {
|
||||
short mapping_flag = ANIM_get_normalization_flags(ac);
|
||||
|
||||
/* Apply NLA scaling. */
|
||||
|
||||
Reference in New Issue
Block a user