2023-08-15 14:20:26 +00:00
|
|
|
# SPDX-FileCopyrightText: 2010-2023 Blender Authors
|
2023-06-15 03:09:04 +00:00
|
|
|
#
|
2022-02-10 22:07:11 +00:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
2010-08-18 18:00:52 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
This module has utility functions for renaming
|
|
|
|
rna values in fcurves and drivers.
|
|
|
|
|
2014-02-12 21:51:33 +00:00
|
|
|
Currently unused, but might become useful later again.
|
2010-08-18 18:00:52 +00:00
|
|
|
"""
|
|
|
|
|
2014-04-28 11:26:01 +00:00
|
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
|
|
|
2010-08-18 18:00:52 +00:00
|
|
|
IS_TESTING = False
|
|
|
|
|
2011-03-27 05:23:14 +00:00
|
|
|
|
2014-04-28 11:26:01 +00:00
|
|
|
def classes_recursive(base_type, clss=None):
|
|
|
|
if clss is None:
|
|
|
|
clss = [base_type]
|
|
|
|
else:
|
|
|
|
clss.append(base_type)
|
|
|
|
|
|
|
|
for base_type_iter in base_type.__bases__:
|
|
|
|
if base_type_iter is not object:
|
|
|
|
classes_recursive(base_type_iter, clss)
|
|
|
|
|
|
|
|
return clss
|
|
|
|
|
|
|
|
|
2014-10-28 17:42:06 +00:00
|
|
|
class DataPathBuilder:
|
2014-04-28 11:26:01 +00:00
|
|
|
"""Dummy class used to parse fcurve and driver data paths."""
|
2011-11-19 16:17:35 +00:00
|
|
|
__slots__ = ("data_path", )
|
2012-01-01 08:52:54 +00:00
|
|
|
|
2010-08-18 18:00:52 +00:00
|
|
|
def __init__(self, attrs):
|
|
|
|
self.data_path = attrs
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
2024-04-27 06:06:51 +00:00
|
|
|
str_value = ".{:s}".format(attr)
|
2010-08-18 18:00:52 +00:00
|
|
|
return DataPathBuilder(self.data_path + (str_value, ))
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-08-18 18:00:52 +00:00
|
|
|
def __getitem__(self, key):
|
2011-03-27 03:14:14 +00:00
|
|
|
if type(key) is int:
|
2024-04-27 06:06:51 +00:00
|
|
|
str_value = '[{:d}]'.format(key)
|
2011-03-27 03:14:14 +00:00
|
|
|
elif type(key) is str:
|
2024-04-27 06:06:51 +00:00
|
|
|
str_value = '["{:s}"]'.format(bpy.utils.escape_identifier(key))
|
2011-03-27 03:14:14 +00:00
|
|
|
else:
|
2024-04-27 06:06:51 +00:00
|
|
|
raise Exception("unsupported accessor {!r} of type {!r} (internal error)".format(key, type(key)))
|
2010-08-18 18:00:52 +00:00
|
|
|
return DataPathBuilder(self.data_path + (str_value, ))
|
|
|
|
|
2014-04-28 11:26:01 +00:00
|
|
|
def resolve(self, real_base, rna_update_from_map, fcurve, log):
|
|
|
|
"""Return (attribute, value) pairs."""
|
2010-08-18 18:00:52 +00:00
|
|
|
pairs = []
|
|
|
|
base = real_base
|
|
|
|
for item in self.data_path:
|
|
|
|
if base is not Ellipsis:
|
2014-04-28 11:26:01 +00:00
|
|
|
base_new = Ellipsis
|
|
|
|
# find the new name
|
|
|
|
if item.startswith("."):
|
|
|
|
for class_name, item_new, options in (
|
|
|
|
rna_update_from_map.get(item[1:], []) +
|
|
|
|
[(None, item[1:], None)]
|
|
|
|
):
|
|
|
|
if callable(item_new):
|
|
|
|
# No type check here, callback is assumed to know what it's doing.
|
|
|
|
base_new, item_new = item_new(base, class_name, item[1:], fcurve, options)
|
|
|
|
if base_new is not Ellipsis:
|
2011-10-17 06:58:07 +00:00
|
|
|
break # found, don't keep looking
|
2014-04-28 11:26:01 +00:00
|
|
|
else:
|
|
|
|
# Type check!
|
|
|
|
type_ok = True
|
|
|
|
if class_name is not None:
|
|
|
|
type_ok = False
|
|
|
|
for base_type in classes_recursive(type(base)):
|
|
|
|
if base_type.__name__ == class_name:
|
|
|
|
type_ok = True
|
|
|
|
break
|
|
|
|
if type_ok:
|
|
|
|
try:
|
2023-03-01 11:12:18 +00:00
|
|
|
# print("base." + item_new)
|
2014-04-28 11:26:01 +00:00
|
|
|
base_new = eval("base." + item_new)
|
|
|
|
break # found, don't keep looking
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
item_new = "." + item_new
|
|
|
|
else:
|
|
|
|
item_new = item
|
|
|
|
try:
|
|
|
|
base_new = eval("base" + item_new)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if base_new is Ellipsis:
|
|
|
|
print("Failed to resolve data path:", self.data_path, file=log)
|
|
|
|
base = base_new
|
|
|
|
else:
|
|
|
|
item_new = item
|
|
|
|
|
|
|
|
pairs.append((item_new, base))
|
2010-08-18 18:00:52 +00:00
|
|
|
return pairs
|
|
|
|
|
|
|
|
|
|
|
|
def id_iter():
|
|
|
|
type_iter = type(bpy.data.objects)
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-08-18 18:00:52 +00:00
|
|
|
for attr in dir(bpy.data):
|
|
|
|
data_iter = getattr(bpy.data, attr, None)
|
|
|
|
if type(data_iter) == type_iter:
|
|
|
|
for id_data in data_iter:
|
|
|
|
if id_data.library is None:
|
|
|
|
yield id_data
|
|
|
|
|
|
|
|
|
|
|
|
def anim_data_actions(anim_data):
|
|
|
|
actions = []
|
|
|
|
actions.append(anim_data.action)
|
|
|
|
for track in anim_data.nla_tracks:
|
|
|
|
for strip in track.strips:
|
|
|
|
actions.append(strip.action)
|
|
|
|
|
|
|
|
# filter out None
|
|
|
|
return [act for act in actions if act]
|
|
|
|
|
|
|
|
|
2014-04-28 11:26:01 +00:00
|
|
|
def find_path_new(id_data, data_path, rna_update_from_map, fcurve, log):
|
2010-10-15 11:43:34 +00:00
|
|
|
# note!, id_data can be ID type or a node tree
|
2010-08-18 18:00:52 +00:00
|
|
|
# ignore ID props for now
|
|
|
|
if data_path.startswith("["):
|
|
|
|
return data_path
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-08-18 18:00:52 +00:00
|
|
|
# recursive path fixing, likely will be one in most cases.
|
|
|
|
data_path_builder = eval("DataPathBuilder(tuple())." + data_path)
|
2014-04-28 11:26:01 +00:00
|
|
|
data_resolve = data_path_builder.resolve(id_data, rna_update_from_map, fcurve, log)
|
2010-08-18 18:00:52 +00:00
|
|
|
|
|
|
|
path_new = [pair[0] for pair in data_resolve]
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2014-04-28 11:26:01 +00:00
|
|
|
return "".join(path_new)[1:] # skip the first "."
|
2010-08-18 18:00:52 +00:00
|
|
|
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2014-04-28 11:26:01 +00:00
|
|
|
def update_data_paths(rna_update, log=sys.stdout):
|
|
|
|
"""
|
|
|
|
rna_update triple [(class_name, from, to or to_callback, callback options), ...]
|
|
|
|
to_callback is a function with this signature: update_cb(base, class_name, old_path, fcurve, options)
|
|
|
|
where base is current object, class_name is the expected type name of base (callback has to handle
|
|
|
|
this), old_path it the org name of base's property, fcurve is the affected fcurve (!),
|
|
|
|
and options is an opaque data.
|
|
|
|
class_name, fcurve and options may be None!
|
2012-07-03 09:02:41 +00:00
|
|
|
"""
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-08-20 06:09:58 +00:00
|
|
|
rna_update_from_map = {}
|
2014-04-28 11:26:01 +00:00
|
|
|
for ren_class, ren_from, ren_to, options in rna_update:
|
|
|
|
rna_update_from_map.setdefault(ren_from, []).append((ren_class, ren_to, options))
|
2010-08-18 18:00:52 +00:00
|
|
|
|
|
|
|
for id_data in id_iter():
|
2010-10-15 11:43:34 +00:00
|
|
|
# check node-trees too
|
|
|
|
anim_data_ls = [(id_data, getattr(id_data, "animation_data", None))]
|
|
|
|
node_tree = getattr(id_data, "node_tree", None)
|
|
|
|
if node_tree:
|
|
|
|
anim_data_ls.append((node_tree, node_tree.animation_data))
|
|
|
|
|
|
|
|
for anim_data_base, anim_data in anim_data_ls:
|
|
|
|
if anim_data is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
for fcurve in anim_data.drivers:
|
2011-03-26 23:42:51 +00:00
|
|
|
data_path = fcurve.data_path
|
2014-04-28 11:26:01 +00:00
|
|
|
data_path_new = find_path_new(anim_data_base, data_path, rna_update_from_map, fcurve, log)
|
2011-03-26 23:42:51 +00:00
|
|
|
# print(data_path_new)
|
|
|
|
if data_path_new != data_path:
|
|
|
|
if not IS_TESTING:
|
|
|
|
fcurve.data_path = data_path_new
|
2011-03-27 05:23:14 +00:00
|
|
|
fcurve.driver.is_valid = True # reset to allow this to work again
|
2024-04-27 06:06:51 +00:00
|
|
|
print(
|
|
|
|
"driver-fcurve ({:s}): {:s} -> {:s}".format(id_data.name, data_path, data_path_new),
|
|
|
|
file=log,
|
|
|
|
)
|
2011-03-26 23:42:51 +00:00
|
|
|
|
2010-10-15 11:43:34 +00:00
|
|
|
for var in fcurve.driver.variables:
|
|
|
|
if var.type == 'SINGLE_PROP':
|
|
|
|
for tar in var.targets:
|
|
|
|
id_data_other = tar.id
|
|
|
|
data_path = tar.data_path
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-10-15 11:43:34 +00:00
|
|
|
if id_data_other and data_path:
|
2014-04-28 11:26:01 +00:00
|
|
|
data_path_new = find_path_new(id_data_other, data_path, rna_update_from_map, None, log)
|
2010-10-15 11:43:34 +00:00
|
|
|
# print(data_path_new)
|
|
|
|
if data_path_new != data_path:
|
|
|
|
if not IS_TESTING:
|
|
|
|
tar.data_path = data_path_new
|
2024-04-27 06:06:51 +00:00
|
|
|
print(
|
|
|
|
"driver ({:s}): {:s} -> {:s}".format(
|
|
|
|
id_data_other.name,
|
|
|
|
data_path,
|
|
|
|
data_path_new,
|
|
|
|
),
|
|
|
|
file=log,
|
|
|
|
)
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-10-15 11:43:34 +00:00
|
|
|
for action in anim_data_actions(anim_data):
|
|
|
|
for fcu in action.fcurves:
|
|
|
|
data_path = fcu.data_path
|
2014-04-28 11:26:01 +00:00
|
|
|
data_path_new = find_path_new(anim_data_base, data_path, rna_update_from_map, fcu, log)
|
2010-10-15 11:43:34 +00:00
|
|
|
# print(data_path_new)
|
|
|
|
if data_path_new != data_path:
|
|
|
|
if not IS_TESTING:
|
|
|
|
fcu.data_path = data_path_new
|
2024-04-27 06:06:51 +00:00
|
|
|
print("fcurve ({:s}): {:s} -> {:s}".format(id_data.name, data_path, data_path_new), file=log)
|
2010-08-18 18:00:52 +00:00
|
|
|
|
2010-08-20 06:09:58 +00:00
|
|
|
|
2010-08-18 18:00:52 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
# Example, should be called externally
|
2014-04-28 11:26:01 +00:00
|
|
|
# (class, from, to or to_callback, callback_options)
|
2010-08-18 18:00:52 +00:00
|
|
|
replace_ls = [
|
2014-04-28 11:26:01 +00:00
|
|
|
("AnimVizMotionPaths", "frame_after", "frame_after", None),
|
|
|
|
("AnimVizMotionPaths", "frame_before", "frame_before", None),
|
|
|
|
("AnimVizOnionSkinning", "frame_after", "frame_after", None),
|
2010-08-18 18:00:52 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
update_data_paths(replace_ls)
|