blender/release/scripts/modules/animsys_refactor.py
2014-10-28 18:42:06 +01:00

232 lines
8.8 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.
Currently unused, but might become useful later again.
"""
import sys
import bpy
IS_TESTING = False
def drepr(string):
# is there a less crappy way to do this in python?, re.escape also escapes
# single quotes strings so cant use it.
return '"%s"' % repr(string)[1:-1].replace("\"", "\\\"").replace("\\'", "'")
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
class DataPathBuilder:
"""Dummy class used to parse fcurve and driver data paths."""
__slots__ = ("data_path", )
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):
if type(key) is int:
str_value = '[%d]' % key
elif type(key) is str:
str_value = '[%s]' % drepr(key)
else:
raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key)))
return DataPathBuilder(self.data_path + (str_value, ))
def resolve(self, real_base, rna_update_from_map, fcurve, log):
"""Return (attribute, value) pairs."""
pairs = []
base = real_base
for item in self.data_path:
if base is not Ellipsis:
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:
break # found, don't keep looking
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:
#print("base." + item_new)
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))
return pairs
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 find_path_new(id_data, data_path, rna_update_from_map, fcurve, log):
# note!, id_data can be ID type or a node tree
# 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, fcurve, log)
path_new = [pair[0] for pair in data_resolve]
return "".join(path_new)[1:] # skip the first "."
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!
"""
rna_update_from_map = {}
for ren_class, ren_from, ren_to, options in rna_update:
rna_update_from_map.setdefault(ren_from, []).append((ren_class, ren_to, options))
for id_data in id_iter():
# 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:
data_path = fcurve.data_path
data_path_new = find_path_new(anim_data_base, data_path, rna_update_from_map, fcurve, log)
# print(data_path_new)
if data_path_new != data_path:
if not IS_TESTING:
fcurve.data_path = data_path_new
fcurve.driver.is_valid = True # reset to allow this to work again
print("driver-fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new), file=log)
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_from_map, None, log)
# 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),
file=log)
for action in anim_data_actions(anim_data):
for fcu in action.fcurves:
data_path = fcu.data_path
data_path_new = find_path_new(anim_data_base, data_path, rna_update_from_map, fcu, log)
# 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), file=log)
if __name__ == "__main__":
# Example, should be called externally
# (class, from, to or to_callback, callback_options)
replace_ls = [
("AnimVizMotionPaths", "frame_after", "frame_after", None),
("AnimVizMotionPaths", "frame_before", "frame_before", None),
("AnimVizOnionSkinning", "frame_after", "frame_after", None),
]
update_data_paths(replace_ls)