Anim: rename RNA Slot.id_root to Slot.target_id_type

The name `id_root` was not descriptive, and was just a hold-over from the
equivalent (now deprecated) property on the Action itself.  `target_id_type`
is more clear, reflecting that this is the type of ID the Slot is intended
to animate.

This PR also renames the corresponding `id_root_icon` to
`target_id_type_icon`.

Note that this PR updates the GLTF import/export core addon to adhere to
these name changes as well.

Pull Request: https://projects.blender.org/blender/blender/pulls/133164
This commit is contained in:
Nathan Vegdahl
2025-01-20 15:24:08 +01:00
committed by Nathan Vegdahl
parent bbfc49be83
commit 9f2ab9cba0
9 changed files with 83 additions and 74 deletions

View File

@@ -34,7 +34,7 @@ class ActionsData:
self.actions[id(action.action)] = action
else:
for slot in action.slots:
self.actions[id(action.action)].add_slot(slot.slot, slot.id_root, slot.track)
self.actions[id(action.action)].add_slot(slot.slot, slot.target_id_type, slot.track)
def get(self):
# sort animations alphabetically (case insensitive) so they have a defined order and match Blender's Action list
@@ -70,28 +70,28 @@ class ActionData:
self.action = action
self.slots = []
def add_slot(self, slot, id_root, track):
def add_slot(self, slot, target_id_type, track):
# If slot already exists with None track (so active action/slot) => Replace it with the track (NLA)
f = [s for s in self.slots if s.slot.handle == slot.handle and s.track is None]
if len(f) > 0:
self.slots.remove(f[0])
new_slot = SlotData(slot, id_root, track)
new_slot = SlotData(slot, target_id_type, track)
self.slots.append(new_slot)
def sort(self):
# Implement sorting, to be sure to get:
# TRS first, and then SK
sort_items = {'OBJECT': 1, 'KEY': 2}
self.slots.sort(key=lambda x: sort_items.get(x.id_root))
self.slots.sort(key=lambda x: sort_items.get(x.target_id_type))
def has_slots(self):
return len(self.slots) > 0
class SlotData:
def __init__(self, slot, id_root, track):
def __init__(self, slot, target_id_type, track):
self.slot = slot
self.id_root = id_root
self.target_id_type = target_id_type
self.track = track
@@ -188,7 +188,7 @@ def prepare_actions_range(export_settings):
for action_data in blender_actions.values():
blender_action = action_data.action
for slot in action_data.slots:
type_ = slot.id_root
type_ = slot.target_id_type
track = slot.track
# Frame range is set on action level, not on slot level
@@ -445,7 +445,7 @@ def gather_action_animations(obj_uuid: int,
for slot in action_data.slots:
blender_action = action_data.action
track_name = slot.track
on_type = slot.id_root
on_type = slot.target_id_type
# Set action as active, to be able to bake if needed
if on_type == "OBJECT": # Not for shapekeys!
@@ -605,7 +605,7 @@ def gather_action_animations(obj_uuid: int,
all_channels)
# If we are in a SK animation (without any TRS animation), and we need to bake
if len([a for a in blender_actions.values() if len([s for s in a.slots if s.id_root == "OBJECT"]) != 0]) == 0 and slot.id_root == "KEY":
if len([a for a in blender_actions.values() if len([s for s in a.slots if s.target_id_type == "OBJECT"]) != 0]) == 0 and slot.target_id_type == "KEY":
if export_settings['gltf_bake_animation'] is True and export_settings['gltf_force_sampling'] is True:
# We also have to check if this is a skinned mesh, because we don't have to force animation baking on this case
# (skinned meshes TRS must be ignored, says glTF specification)
@@ -618,7 +618,7 @@ def gather_action_animations(obj_uuid: int,
if channels is not None:
all_channels.extend(channels)
if len([a for a in blender_actions.values() if len([s for s in a.slots if s.id_root == "KEY"]) != 0]) == 0 \
if len([a for a in blender_actions.values() if len([s for s in a.slots if s.target_id_type == "KEY"]) != 0]) == 0 \
and export_settings['gltf_morph_anim'] \
and blender_object.type == "MESH" \
and blender_object.data is not None \
@@ -759,7 +759,7 @@ def __get_blender_actions(obj_uuid: str,
else:
# Store Action info
new_action = ActionData(blender_object.animation_data.action)
new_action.add_slot(blender_object.animation_data.action_slot, blender_object.animation_data.action_slot.id_root, None) # Active action => No track
new_action.add_slot(blender_object.animation_data.action_slot, blender_object.animation_data.action_slot.target_id_type, None) # Active action => No track
actions.add_action(new_action)
# Collect associated strips from NLA tracks.
@@ -785,7 +785,7 @@ def __get_blender_actions(obj_uuid: str,
# Store Action info
new_action = ActionData(strip.action)
new_action.add_slot(strip.action_slot, strip.action_slot.id_root, track.name)
new_action.add_slot(strip.action_slot, strip.action_slot.target_id_type, track.name)
actions.add_action(new_action)
# For caching, actions linked to SK must be after actions about TRS
@@ -803,7 +803,7 @@ def __get_blender_actions(obj_uuid: str,
else:
# Store Action info
new_action = ActionData(blender_object.data.shape_keys.animation_data.action)
new_action.add_slot(blender_object.data.shape_keys.animation_data.action_slot, blender_object.data.shape_keys.animation_data.action_slot.id_root, None)
new_action.add_slot(blender_object.data.shape_keys.animation_data.action_slot, blender_object.data.shape_keys.animation_data.action_slot.target_id_type, None)
actions.add_action(new_action)
if export_settings['gltf_animation_mode'] == "ACTIONS":
@@ -821,7 +821,7 @@ def __get_blender_actions(obj_uuid: str,
# Store Action info
new_action = ActionData(strip.action)
new_action.add_slot(strip.action_slot, strip.action_slot.id_root, track.name)
new_action.add_slot(strip.action_slot, strip.action_slot.target_id_type, track.name)
actions.add_action(new_action)
# If there are only 1 armature, include all animations, even if not in NLA
@@ -832,7 +832,7 @@ def __get_blender_actions(obj_uuid: str,
if len(export_settings['vtree'].get_all_node_of_type(VExportNode.ARMATURE)) == 1:
# Keep all actions on objects (no Shapekey animation)
for act in bpy.data.actions:
for slot in [s for s in act.slots if s.id_root == "OBJECT"]:
for slot in [s for s in act.slots if s.target_id_type == "OBJECT"]:
# We need to check this is an armature action
# Checking that at least 1 bone is animated
if not __is_armature_slot(act, slot):
@@ -847,7 +847,7 @@ def __get_blender_actions(obj_uuid: str,
continue # We ignore this action
new_action = ActionData(act)
new_action.add_slot(slot, slot.id_root, None)
new_action.add_slot(slot, slot.target_id_type, None)
actions.add_action(new_action)
export_user_extensions('gather_actions_hook', export_settings, blender_object, actions)
@@ -897,18 +897,18 @@ def __get_blender_actions_broadcast(obj_uuid, export_settings):
for slot in blender_action.slots:
if slot.id_root == "OBJECT":
if slot.target_id_type == "OBJECT":
# Do not export actions on objects without animation data
if blender_object.animation_data is None:
continue
if blender_object and blender_object.type == "ARMATURE" and __is_armature_slot(blender_action, slot):
new_action.add_slot(slot, slot.id_root, None)
new_action.add_slot(slot, slot.target_id_type, None)
elif blender_object and blender_object.type == "MESH" and not __is_armature_slot(blender_action, slot):
new_action.add_slot(slot, slot.id_root, None)
new_action.add_slot(slot, slot.target_id_type, None)
elif slot.id_root == "KEY":
elif slot.target_id_type == "KEY":
if blender_object.type != "MESH" or blender_object.data is None or blender_object.data.shape_keys is None or blender_object.data.shape_keys.animation_data is None:
continue
# Checking that the object has some SK and some animation on it
@@ -916,7 +916,7 @@ def __get_blender_actions_broadcast(obj_uuid, export_settings):
continue
if blender_object.type != "MESH":
continue
new_action.add_slot(slot, slot.id_root, None)
new_action.add_slot(slot, slot.target_id_type, None)
else:
pass # TODOSLOT slot-3

View File

@@ -75,7 +75,7 @@ def reset_sk_data(blender_object, datas, export_settings) -> None:
return
else:
# For actions
if len([i for i in datas.values() if len([s for s in i.slots if s.id_root == "KEY"]) != 0]) <= 1:
if len([i for i in datas.values() if len([s for s in i.slots if s.target_id_type == "KEY"]) != 0]) <= 1:
return
if blender_object.type != "MESH":

View File

@@ -22,7 +22,7 @@ class BlenderAnimation():
# Caches the action/slot for each object, keyed by:
# - anim_idx
# - obj_name
# - id_root
# - target_id_type
gltf.action_cache = {}
# Things we need to stash when we're done.
gltf.needs_stash = []

View File

@@ -44,20 +44,20 @@ class BlenderPointerAnim():
import_user_extensions('gather_import_animation_pointer_channel_before_hook', gltf, animation, channel)
# For some asset_type, we need to check what is the real id_root
# For some asset_type, we need to check what is the real ID type.
if asset_type == "MATERIAL":
if len(pointer_tab) == 4 and pointer_tab[1] == "materials" and \
pointer_tab[3] == "alphaCutoff":
id_root = "MATERIAL"
target_id_type = "MATERIAL"
else:
id_root = "NODETREE"
target_id_type = "NODETREE"
elif asset_type == "MATERIAL_PBR":
id_root = "NODETREE"
target_id_type = "NODETREE"
else:
id_root = asset_type
target_id_type = asset_type
action, slot = BlenderPointerAnim.get_or_create_action_and_slot(
gltf, anim_idx, asset, asset_idx, id_root, name_=name)
gltf, anim_idx, asset, asset_idx, target_id_type, name_=name)
keys = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].input)
values = BinaryData.get_data_from_accessor(gltf, animation.samplers[channel.sampler].output)
@@ -720,27 +720,27 @@ class BlenderPointerAnim():
if asset_type == "CAMERA":
name = asset.name
stash = asset.blender_object_data
id_root = "CAMERA"
target_id_type = "CAMERA"
elif asset_type == "LIGHT":
name = asset['name']
stash = asset['blender_object_data']
id_root = "LIGHT"
target_id_type = "LIGHT"
elif asset_type == "MATERIAL":
name = asset.name
stash = asset.blender_mat
id_root = "MATERIAL"
target_id_type = "MATERIAL"
elif asset_type == "NODETREE":
name = name_ if name_ is not None else asset.name
stash = asset.blender_nodetree
id_root = "NODETREE"
target_id_type = "NODETREE"
elif asset_type == "TEX_TRANSFORM":
name = name_ if name_ is not None else asset.name
stash = asset['blender_nodetree']
id_root = "NODETREE"
target_id_type = "NODETREE"
elif asset_type == "EXT":
name = name_ if name_ is not None else asset.name
stash = asset['blender_nodetree']
id_root = "NODETREE"
target_id_type = "NODETREE"
objects = gltf.action_cache.get(anim_idx)
if not objects:
@@ -768,10 +768,10 @@ class BlenderPointerAnim():
action.layers[0].strips[0].channelbags.new(slot)
gltf.action_cache[anim_idx]['object_slots'][name] = {}
gltf.action_cache[anim_idx]['object_slots'][name][slot.id_root] = (action, slot)
gltf.action_cache[anim_idx]['object_slots'][name][slot.target_id_type] = (action, slot)
else:
# We have slots, check if we have the right slot (based on id_root)
ac_sl = slots.get(id_root)
# We have slots, check if we have the right slot (based on target_id_type)
ac_sl = slots.get(target_id_type)
if not ac_sl:
action = gltf.action_cache[anim_idx]['action']
slot = action.slots.new(stash.id_type, "Slot")
@@ -779,7 +779,7 @@ class BlenderPointerAnim():
action.layers[0].strips[0].channelbags.new(slot)
gltf.action_cache[anim_idx]['object_slots'][name][slot.id_root] = (action, slot)
gltf.action_cache[anim_idx]['object_slots'][name][slot.target_id_type] = (action, slot)
else:
action, slot = ac_sl

View File

@@ -146,9 +146,9 @@ def get_or_create_action_and_slot(gltf, vnode_idx, anim_idx, path):
action.layers[0].strips[0].channelbags.new(slot)
gltf.action_cache[anim_idx]['object_slots'][obj.name] = {}
gltf.action_cache[anim_idx]['object_slots'][obj.name][slot.id_root] = (action, slot)
gltf.action_cache[anim_idx]['object_slots'][obj.name][slot.target_id_type] = (action, slot)
else:
# We have slots, check if we have the right slot (based on id_root)
# We have slots, check if we have the right slot (based on target_id_type)
ac_sl = slots.get(use_id)
if not ac_sl:
action = gltf.action_cache[anim_idx]['action']
@@ -166,7 +166,7 @@ def get_or_create_action_and_slot(gltf, vnode_idx, anim_idx, path):
action.layers[0].strips[0].channelbags.new(slot)
gltf.action_cache[anim_idx]['object_slots'][obj.name][slot.id_root] = (action, slot)
gltf.action_cache[anim_idx]['object_slots'][obj.name][slot.target_id_type] = (action, slot)
else:
action, slot = ac_sl

View File

@@ -696,8 +696,8 @@ class DOPESHEET_PT_action_slot(Panel):
# Draw the ID type of the slot.
try:
enum_items = slot.bl_rna.properties['id_root'].enum_items
idtype_label = enum_items[slot.id_root].name
enum_items = slot.bl_rna.properties['target_id_type'].enum_items
idtype_label = enum_items[slot.target_id_type].name
except (KeyError, IndexError, AttributeError) as ex:
idtype_label = str(ex)
@@ -706,7 +706,7 @@ class DOPESHEET_PT_action_slot(Panel):
split.label(text="Type")
split.alignment = 'LEFT'
split.label(text=idtype_label, icon_value=slot.id_root_icon)
split.label(text=idtype_label, icon_value=slot.target_id_type_icon)
#######################################

View File

@@ -69,7 +69,7 @@ const EnumPropertyItem rna_enum_strip_type_items[] = {
/* Cannot use rna_enum_dummy_DEFAULT_items because the UNSPECIFIED entry needs
* to exist as it is the default. */
const EnumPropertyItem default_ActionSlot_id_root_items[] = {
const EnumPropertyItem default_ActionSlot_target_id_type_items[] = {
{0,
"UNSPECIFIED",
ICON_NONE,
@@ -294,7 +294,7 @@ static std::optional<std::string> rna_ActionSlot_path(const PointerRNA *ptr)
return fmt::format("slots[\"{}\"]", identifier_esc);
}
int rna_ActionSlot_id_root_icon_get(PointerRNA *ptr)
int rna_ActionSlot_target_id_type_icon_get(PointerRNA *ptr)
{
animrig::Slot &slot = rna_data_slot(ptr);
return UI_icon_from_idcode(slot.idtype);
@@ -1492,17 +1492,24 @@ static std::optional<std::string> rna_DopeSheet_path(const PointerRNA *ptr)
return "dopesheet";
}
static const EnumPropertyItem *rna_ActionSlot_id_root_itemf(bContext * /* C */,
PointerRNA * /* ptr */,
PropertyRNA * /* prop */,
bool *r_free)
/**
* Used for both `action.id_root` and `slot.target_id_type`.
*
* Note that `action.id_root` is deprecated, as it is only relevant to legacy
* Animato actions. So in practice this function is primarily here for
* `slot.target_id_type`.
*/
static const EnumPropertyItem *rna_ActionSlot_target_id_type_itemf(bContext * /* C */,
PointerRNA * /* ptr */,
PropertyRNA * /* prop */,
bool *r_free)
{
/* These items don't change, as the ID types are hard-coded. So better to
* cache the list of enum items. */
static EnumPropertyItem *_rna_ActionSlot_id_root_items = nullptr;
static EnumPropertyItem *_rna_ActionSlot_target_id_type_items = nullptr;
if (_rna_ActionSlot_id_root_items) {
return _rna_ActionSlot_id_root_items;
if (_rna_ActionSlot_target_id_type_items) {
return _rna_ActionSlot_target_id_type_items;
}
int totitem = 0;
@@ -1520,7 +1527,7 @@ static const EnumPropertyItem *rna_ActionSlot_id_root_itemf(bContext * /* C */,
i++;
}
RNA_enum_item_add(&items, &totitem, &default_ActionSlot_id_root_items[0]);
RNA_enum_item_add(&items, &totitem, &default_ActionSlot_target_id_type_items[0]);
RNA_enum_item_end(&items, &totitem);
@@ -1529,11 +1536,11 @@ static const EnumPropertyItem *rna_ActionSlot_id_root_itemf(bContext * /* C */,
* Blender use memory after it is freed:
*
* >>> slot = C.object.animation_data.action_slot
* >>> enum_item = s.bl_rna.properties['id_root'].enum_items[slot.id_root]
* >>> enum_item = s.bl_rna.properties['target_id_type'].enum_items[slot.target_id_type]
* >>> print(enum_item.name)
*/
*r_free = false;
_rna_ActionSlot_id_root_items = items;
_rna_ActionSlot_target_id_type_items = items;
BKE_blender_atexit_register(MEM_freeN, items);
@@ -1990,18 +1997,18 @@ static void rna_def_action_slot(BlenderRNA *brna)
"Used when connecting an Action to a data-block, to find the correct slot handle. This is "
"the display name, prefixed by two characters determined by the slot's ID type");
prop = RNA_def_property(srna, "id_root", PROP_ENUM, PROP_NONE);
prop = RNA_def_property(srna, "target_id_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "idtype");
RNA_def_property_enum_items(prop, default_ActionSlot_id_root_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_ActionSlot_id_root_itemf");
RNA_def_property_enum_items(prop, default_ActionSlot_target_id_type_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_ActionSlot_target_id_type_itemf");
RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop, "ID Root Type", "Type of data-block that can be animated by this slot");
prop, "Target ID Type", "Type of data-block that this slot is intended to animate");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_ID);
prop = RNA_def_property(srna, "id_root_icon", PROP_INT, PROP_NONE);
RNA_def_property_int_funcs(prop, "rna_ActionSlot_id_root_icon_get", nullptr, nullptr);
prop = RNA_def_property(srna, "target_id_type_icon", PROP_INT, PROP_NONE);
RNA_def_property_int_funcs(prop, "rna_ActionSlot_target_id_type_icon_get", nullptr, nullptr);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "name_display", PROP_STRING, PROP_NONE);
@@ -2695,8 +2702,8 @@ static void rna_def_action_legacy(BlenderRNA *brna, StructRNA *srna)
* but is still available/editable in 'emergencies' */
prop = RNA_def_property(srna, "id_root", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "idroot");
RNA_def_property_enum_items(prop, default_ActionSlot_id_root_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_ActionSlot_id_root_itemf");
RNA_def_property_enum_items(prop, default_ActionSlot_target_id_type_items);
RNA_def_property_enum_funcs(prop, nullptr, nullptr, "rna_ActionSlot_target_id_type_itemf");
RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT);
RNA_def_property_ui_text(prop,
"ID Root Type",

View File

@@ -27,13 +27,13 @@ class ActionSlotCreationTest(unittest.TestCase):
slot3 = self.action.slots.new('LIGHT', "Bob")
self.assertEqual("OBBob", slot1.identifier)
self.assertEqual('OBJECT', slot1.id_root)
self.assertEqual('OBJECT', slot1.target_id_type)
self.assertEqual("CABob", slot2.identifier)
self.assertEqual('CAMERA', slot2.id_root)
self.assertEqual('CAMERA', slot2.target_id_type)
self.assertEqual("LABob", slot3.identifier)
self.assertEqual('LIGHT', slot3.id_root)
self.assertEqual('LIGHT', slot3.target_id_type)
def test_same_name_same_type(self):
slot1 = self.action.slots.new('OBJECT', "Bob")
@@ -41,13 +41,13 @@ class ActionSlotCreationTest(unittest.TestCase):
slot3 = self.action.slots.new('OBJECT', "Bob")
self.assertEqual("OBBob", slot1.identifier)
self.assertEqual('OBJECT', slot1.id_root)
self.assertEqual('OBJECT', slot1.target_id_type)
self.assertEqual("OBBob.001", slot2.identifier)
self.assertEqual('OBJECT', slot2.id_root)
self.assertEqual('OBJECT', slot2.target_id_type)
self.assertEqual("OBBob.002", slot3.identifier)
self.assertEqual('OBJECT', slot3.id_root)
self.assertEqual('OBJECT', slot3.target_id_type)
def test_invalid_arguments(self):
with self.assertRaises(TypeError):

View File

@@ -124,7 +124,7 @@ class NLAStripActionSlotSelectionTest(AbstractNlaStripTest):
strip1 = track.strips.new("name", 1, action)
self.assertEqual(action.slots[0], strip1.action_slot)
self.assertEqual('OBJECT', action.slots[0].id_root, "Slot should have been rooted to object")
self.assertEqual('OBJECT', action.slots[0].target_id_type, "Slot should have been rooted to object")
strip2 = track.strips.new("name", 10, action)
self.assertEqual(action.slots[0], strip2.action_slot)
@@ -144,11 +144,13 @@ class NLAStripActionSlotSelectionTest(AbstractNlaStripTest):
strip = track.strips.new("name", 1, action1)
self.assertEqual(action1.slots[0], strip.action_slot)
self.assertEqual('OBJECT', action1.slots[0].id_root, "Slot of Action 1 should have been rooted to object")
self.assertEqual('OBJECT', action1.slots[0].target_id_type,
"Slot of Action 1 should have been rooted to object")
strip.action = action2
self.assertEqual(action2.slots[0], strip.action_slot)
self.assertEqual('OBJECT', action2.slots[0].id_root, "Slot of Action 2 should have been rooted to object")
self.assertEqual('OBJECT', action2.slots[0].target_id_type,
"Slot of Action 2 should have been rooted to object")
if __name__ == "__main__":