1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
22 This module has utility functions for renaming
23 rna values in fcurves and drivers.
25 The main function to use is: update_data_paths(...)
32 # is there a less crappy way to do this in python?, re.escape also escapes
33 # single quotes strings so cant use it.
34 return '"%s"' % repr(string)[1:-1].replace("\"", "\\\"").replace("\\'", "'")
37 class DataPathBuilder(object):
38 __slots__ = ("data_path", )
39 """ Dummy class used to parse fcurve and driver data paths.
41 def __init__(self, attrs):
42 self.data_path = attrs
44 def __getattr__(self, attr):
45 str_value = ".%s" % attr
46 return DataPathBuilder(self.data_path + (str_value, ))
48 def __getitem__(self, key):
50 str_value = '[%d]' % key
51 elif type(key) is str:
52 str_value = '[%s]' % drepr(key)
54 raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key)))
55 return DataPathBuilder(self.data_path + (str_value, ))
57 def resolve(self, real_base, rna_update_from_map=None):
58 """ Return (attribute, value) pairs.
62 for item in self.data_path:
63 if base is not Ellipsis:
65 # this only works when running with an old blender
66 # where the old path will resolve
67 base = eval("base" + item)
71 if item.startswith("."):
72 for item_new in rna_update_from_map.get(item[1:], ()):
74 print("base." + item_new)
75 base_new = eval("base." + item_new)
76 break # found, dont keep looking
80 if base_new is Ellipsis:
81 print("Failed to resolve data path:", self.data_path)
84 pairs.append((item, base))
91 type_iter = type(bpy.data.objects)
93 for attr in dir(bpy.data):
94 data_iter = getattr(bpy.data, attr, None)
95 if type(data_iter) == type_iter:
96 for id_data in data_iter:
97 if id_data.library is None:
101 def anim_data_actions(anim_data):
103 actions.append(anim_data.action)
104 for track in anim_data.nla_tracks:
105 for strip in track.strips:
106 actions.append(strip.action)
109 return [act for act in actions if act]
112 def classes_recursive(base_type, clss=None):
116 clss.append(base_type)
118 for base_type_iter in base_type.__bases__:
119 if base_type_iter is not object:
120 classes_recursive(base_type_iter, clss)
125 def find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map):
126 # note!, id_data can be ID type or a node tree
127 # ignore ID props for now
128 if data_path.startswith("["):
131 # recursive path fixing, likely will be one in most cases.
132 data_path_builder = eval("DataPathBuilder(tuple())." + data_path)
133 data_resolve = data_path_builder.resolve(id_data, rna_update_from_map)
135 path_new = [pair[0] for pair in data_resolve]
137 # print(data_resolve)
140 for i, (attr, data) in enumerate(data_resolve):
144 if attr.startswith("."):
146 for data_base_type in classes_recursive(type(data_base)):
147 attr_new = rna_update_dict.get(data_base_type.__name__, {}).get(attr[1:])
149 path_new[i] = "." + attr_new
151 # set this as the base for further properties
154 data_path_new = "".join(path_new)[1:] # skip the first "."
158 def update_data_paths(rna_update):
159 ''' rna_update triple [(class_name, from, to), ...]
162 # make a faster lookup dict
164 for ren_class, ren_from, ren_to in rna_update:
165 rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to
167 rna_update_from_map = {}
168 for ren_class, ren_from, ren_to in rna_update:
169 rna_update_from_map.setdefault(ren_from, []).append(ren_to)
171 for id_data in id_iter():
173 # check node-trees too
174 anim_data_ls = [(id_data, getattr(id_data, "animation_data", None))]
175 node_tree = getattr(id_data, "node_tree", None)
177 anim_data_ls.append((node_tree, node_tree.animation_data))
179 for anim_data_base, anim_data in anim_data_ls:
180 if anim_data is None:
183 for fcurve in anim_data.drivers:
184 data_path = fcurve.data_path
185 data_path_new = find_path_new(anim_data_base, data_path, rna_update_dict, rna_update_from_map)
186 # print(data_path_new)
187 if data_path_new != data_path:
189 fcurve.data_path = data_path_new
190 fcurve.driver.is_valid = True # reset to allow this to work again
191 print("driver-fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
193 for var in fcurve.driver.variables:
194 if var.type == 'SINGLE_PROP':
195 for tar in var.targets:
196 id_data_other = tar.id
197 data_path = tar.data_path
199 if id_data_other and data_path:
200 data_path_new = find_path_new(id_data_other, data_path, rna_update_dict, rna_update_from_map)
201 # print(data_path_new)
202 if data_path_new != data_path:
204 tar.data_path = data_path_new
205 print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new))
207 for action in anim_data_actions(anim_data):
208 for fcu in action.fcurves:
209 data_path = fcu.data_path
210 data_path_new = find_path_new(anim_data_base, data_path, rna_update_dict, rna_update_from_map)
211 # print(data_path_new)
212 if data_path_new != data_path:
214 fcu.data_path = data_path_new
215 print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
218 if __name__ == "__main__":
220 # Example, should be called externally
223 ('AnimVizMotionPaths', 'frame_after', 'frame_after'),
224 ('AnimVizMotionPaths', 'frame_before', 'frame_before'),
225 ('AnimVizOnionSkinning', 'frame_after', 'frame_after'),
228 update_data_paths(replace_ls)