blender/release/scripts/modules/animsys_refactor.py
Campbell Barton 4f5f868a52 rna data path names which are more likely to break animations.
Added an operator "Update Animation Data",
access from the search menu to update drivers and fcurves.
2010-08-20 06:09:58 +00:00

202 lines
6.7 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
"""
This module has utility functions for renaming
rna values in fcurves and drivers.
The main function to use is: update_data_paths(...)
"""
IS_TESTING = False
class DataPathBuilder(object):
__slots__ = ("data_path", )
""" Dummy class used to parse fcurve and driver data paths.
"""
def __init__(self, attrs):
self.data_path = attrs
def __getattr__(self, attr):
str_value = ".%s" % attr
return DataPathBuilder(self.data_path + (str_value, ))
def __getitem__(self, key):
str_value = '["%s"]' % key
return DataPathBuilder(self.data_path + (str_value, ))
def resolve(self, real_base, rna_update_from_map=None):
""" Return (attribute, value) pairs.
"""
pairs = []
base = real_base
for item in self.data_path:
if base is not Ellipsis:
try:
# this only works when running with an old blender
# where the old path will resolve
base = eval("base" + item)
except:
base_new = Ellipsis
# guess the new name
if item.startswith("."):
for item_new in rna_update_from_map.get(item[1:], ()):
try:
print("base." + item_new)
base_new = eval("base." + item_new)
break # found, dont keep looking
except:
pass
if base_new is Ellipsis:
print("Failed to resolve data path:", self.data_path)
base = base_new
pairs.append((item, base))
return pairs
import bpy
def id_iter():
type_iter = type(bpy.data.objects)
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]
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
def find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map):
# ignore ID props for now
if data_path.startswith("["):
return data_path
# recursive path fixing, likely will be one in most cases.
data_path_builder = eval("DataPathBuilder(tuple())." + data_path)
data_resolve = data_path_builder.resolve(id_data, rna_update_from_map)
path_new = [pair[0] for pair in data_resolve]
# print(data_resolve)
data_base = id_data
for i, (attr, data) in enumerate(data_resolve):
if data is Ellipsis:
break
if attr.startswith("."):
# try all classes
for data_base_type in classes_recursive(type(data_base)):
attr_new = rna_update_dict.get(data_base_type.__name__, {}).get(attr[1:])
if attr_new:
path_new[i] = "." + attr_new
# set this as the base for further properties
data_base = data
data_path_new = "".join(path_new)[1:] # skip the first "."
return data_path_new
def update_data_paths(rna_update):
''' rna_update triple [(class_name, from, to), ...]
'''
# make a faster lookup dict
rna_update_dict = {}
for ren_class, ren_from, ren_to in rna_update:
rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to
rna_update_from_map = {}
for ren_class, ren_from, ren_to in rna_update:
rna_update_from_map.setdefault(ren_from, []).append(ren_to)
for id_data in id_iter():
anim_data = getattr(id_data, "animation_data", None)
if anim_data is None:
continue
for fcurve in anim_data.drivers:
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
if id_data_other and data_path:
data_path_new = find_path_new(id_data_other, data_path, rna_update_dict, rna_update_from_map)
# print(data_path_new)
if data_path_new != data_path:
if not IS_TESTING:
tar.data_path = data_path_new
print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new))
for action in anim_data_actions(anim_data):
for fcu in action.fcurves:
data_path = fcu.data_path
data_path_new = find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map)
# print(data_path_new)
if data_path_new != data_path:
if not IS_TESTING:
fcu.data_path = data_path_new
print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
if __name__ == "__main__":
# Example, should be called externally
# (class, from, to)
replace_ls = [
('AnimVizMotionPaths', 'frame_after', 'frame_after'),
('AnimVizMotionPaths', 'frame_before', 'frame_before'),
('AnimVizOnionSkinning', 'frame_after', 'frame_after'),
]
update_data_paths(replace_ls)