9f2acc5e26814974a66f1f7e097f116cf06dc7e8
[blender-staging.git] / release / scripts / modules / animsys_refactor.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 """
22 This module has utility functions for renaming
23 rna values in fcurves and drivers.
24
25 The main function to use is: update_data_paths(...)
26 """
27
28 IS_TESTING = False
29
30 def drepr(string):
31     # is there a less crappy way to do this in python?, re.escape also escapes
32     # single quotes strings so cant use it.
33     return '"%s"' % repr(string)[1:-1].replace("\"", "\\\"").replace("\\'","'")
34
35 class DataPathBuilder(object):
36     __slots__ = ("data_path", )
37     """ Dummy class used to parse fcurve and driver data paths.
38     """
39     def __init__(self, attrs):
40         self.data_path = attrs
41
42     def __getattr__(self, attr):
43         str_value = ".%s" % attr
44         return DataPathBuilder(self.data_path + (str_value, ))
45
46     def __getitem__(self, key):
47         if type(key) is int:
48             str_value = '[%d]' % key
49         elif type(key) is str:
50             str_value = '[%s]' % drepr(key)
51         else:
52             raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key)))
53         return DataPathBuilder(self.data_path + (str_value, ))
54
55     def resolve(self, real_base, rna_update_from_map=None):
56         """ Return (attribute, value) pairs.
57         """
58         pairs = []
59         base = real_base
60         for item in self.data_path:
61             if base is not Ellipsis:
62                 try:
63                     # this only works when running with an old blender
64                     # where the old path will resolve
65                     base = eval("base" + item)
66                 except:
67                     base_new = Ellipsis
68                     # guess the new name
69                     if item.startswith("."):
70                         for item_new in rna_update_from_map.get(item[1:], ()):
71                             try:
72                                 print("base." + item_new)
73                                 base_new = eval("base." + item_new)
74                                 break  # found, dont keep looking
75                             except:
76                                 pass
77
78                     if base_new is Ellipsis:
79                         print("Failed to resolve data path:", self.data_path)
80                     base = base_new
81
82             pairs.append((item, base))
83         return pairs
84
85 import bpy
86
87
88 def id_iter():
89     type_iter = type(bpy.data.objects)
90
91     for attr in dir(bpy.data):
92         data_iter = getattr(bpy.data, attr, None)
93         if type(data_iter) == type_iter:
94             for id_data in data_iter:
95                 if id_data.library is None:
96                     yield id_data
97
98
99 def anim_data_actions(anim_data):
100     actions = []
101     actions.append(anim_data.action)
102     for track in anim_data.nla_tracks:
103         for strip in track.strips:
104             actions.append(strip.action)
105
106     # filter out None
107     return [act for act in actions if act]
108
109
110 def classes_recursive(base_type, clss=None):
111     if clss is None:
112         clss = [base_type]
113     else:
114         clss.append(base_type)
115
116     for base_type_iter in base_type.__bases__:
117         if base_type_iter is not object:
118             classes_recursive(base_type_iter, clss)
119
120     return clss
121
122
123 def find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map):
124     # note!, id_data can be ID type or a node tree
125     # ignore ID props for now
126     if data_path.startswith("["):
127         return data_path
128
129     # recursive path fixing, likely will be one in most cases.
130     data_path_builder = eval("DataPathBuilder(tuple())." + data_path)
131     data_resolve = data_path_builder.resolve(id_data, rna_update_from_map)
132
133     path_new = [pair[0] for pair in data_resolve]
134
135     # print(data_resolve)
136     data_base = id_data
137
138     for i, (attr, data) in enumerate(data_resolve):
139         if data is Ellipsis:
140             break
141
142         if attr.startswith("."):
143             # try all classes
144             for data_base_type in classes_recursive(type(data_base)):
145                 attr_new = rna_update_dict.get(data_base_type.__name__, {}).get(attr[1:])
146                 if attr_new:
147                     path_new[i] = "." + attr_new
148
149         # set this as the base for further properties
150         data_base = data
151
152     data_path_new = "".join(path_new)[1:]  # skip the first "."
153     return data_path_new
154
155
156 def update_data_paths(rna_update):
157     ''' rna_update triple [(class_name, from, to), ...]
158     '''
159
160     # make a faster lookup dict
161     rna_update_dict = {}
162     for ren_class, ren_from, ren_to in rna_update:
163         rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to
164
165     rna_update_from_map = {}
166     for ren_class, ren_from, ren_to in rna_update:
167         rna_update_from_map.setdefault(ren_from, []).append(ren_to)
168
169     for id_data in id_iter():
170
171         # check node-trees too
172         anim_data_ls = [(id_data, getattr(id_data, "animation_data", None))]
173         node_tree = getattr(id_data, "node_tree", None)
174         if node_tree:
175             anim_data_ls.append((node_tree, node_tree.animation_data))
176
177         for anim_data_base, anim_data in anim_data_ls:
178             if anim_data is None:
179                 continue
180
181             for fcurve in anim_data.drivers:
182                 data_path = fcurve.data_path
183                 data_path_new = find_path_new(anim_data_base, data_path, rna_update_dict, rna_update_from_map)
184                 # print(data_path_new)
185                 if data_path_new != data_path:
186                     if not IS_TESTING:
187                         fcurve.data_path = data_path_new
188                         fcurve.driver.is_valid = True; # reset to allow this to work again
189                     print("driver-fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
190
191                 for var in fcurve.driver.variables:
192                     if var.type == 'SINGLE_PROP':
193                         for tar in var.targets:
194                             id_data_other = tar.id
195                             data_path = tar.data_path
196
197                             if id_data_other and data_path:
198                                 data_path_new = find_path_new(id_data_other, data_path, rna_update_dict, rna_update_from_map)
199                                 # print(data_path_new)
200                                 if data_path_new != data_path:
201                                     if not IS_TESTING:
202                                         tar.data_path = data_path_new
203                                     print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new))
204
205             for action in anim_data_actions(anim_data):
206                 for fcu in action.fcurves:
207                     data_path = fcu.data_path
208                     data_path_new = find_path_new(anim_data_base, data_path, rna_update_dict, rna_update_from_map)
209                     # print(data_path_new)
210                     if data_path_new != data_path:
211                         if not IS_TESTING:
212                             fcu.data_path = data_path_new
213                         print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
214
215
216 if __name__ == "__main__":
217
218     # Example, should be called externally
219     # (class, from, to)
220     replace_ls = [
221         ('AnimVizMotionPaths', 'frame_after', 'frame_after'),
222         ('AnimVizMotionPaths', 'frame_before', 'frame_before'),
223         ('AnimVizOnionSkinning', 'frame_after', 'frame_after'),
224     ]
225
226     update_data_paths(replace_ls)