pep8 cleanup
[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
31 def drepr(string):
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("\\'", "'")
35
36
37 class DataPathBuilder(object):
38     __slots__ = ("data_path", )
39     """ Dummy class used to parse fcurve and driver data paths.
40     """
41     def __init__(self, attrs):
42         self.data_path = attrs
43
44     def __getattr__(self, attr):
45         str_value = ".%s" % attr
46         return DataPathBuilder(self.data_path + (str_value, ))
47
48     def __getitem__(self, key):
49         if type(key) is int:
50             str_value = '[%d]' % key
51         elif type(key) is str:
52             str_value = '[%s]' % drepr(key)
53         else:
54             raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key)))
55         return DataPathBuilder(self.data_path + (str_value, ))
56
57     def resolve(self, real_base, rna_update_from_map=None):
58         """ Return (attribute, value) pairs.
59         """
60         pairs = []
61         base = real_base
62         for item in self.data_path:
63             if base is not Ellipsis:
64                 try:
65                     # this only works when running with an old blender
66                     # where the old path will resolve
67                     base = eval("base" + item)
68                 except:
69                     base_new = Ellipsis
70                     # guess the new name
71                     if item.startswith("."):
72                         for item_new in rna_update_from_map.get(item[1:], ()):
73                             try:
74                                 print("base." + item_new)
75                                 base_new = eval("base." + item_new)
76                                 break  # found, dont keep looking
77                             except:
78                                 pass
79
80                     if base_new is Ellipsis:
81                         print("Failed to resolve data path:", self.data_path)
82                     base = base_new
83
84             pairs.append((item, base))
85         return pairs
86
87 import bpy
88
89
90 def id_iter():
91     type_iter = type(bpy.data.objects)
92
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:
98                     yield id_data
99
100
101 def anim_data_actions(anim_data):
102     actions = []
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)
107
108     # filter out None
109     return [act for act in actions if act]
110
111
112 def classes_recursive(base_type, clss=None):
113     if clss is None:
114         clss = [base_type]
115     else:
116         clss.append(base_type)
117
118     for base_type_iter in base_type.__bases__:
119         if base_type_iter is not object:
120             classes_recursive(base_type_iter, clss)
121
122     return clss
123
124
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("["):
129         return data_path
130
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)
134
135     path_new = [pair[0] for pair in data_resolve]
136
137     # print(data_resolve)
138     data_base = id_data
139
140     for i, (attr, data) in enumerate(data_resolve):
141         if data is Ellipsis:
142             break
143
144         if attr.startswith("."):
145             # try all classes
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:])
148                 if attr_new:
149                     path_new[i] = "." + attr_new
150
151         # set this as the base for further properties
152         data_base = data
153
154     data_path_new = "".join(path_new)[1:]  # skip the first "."
155     return data_path_new
156
157
158 def update_data_paths(rna_update):
159     ''' rna_update triple [(class_name, from, to), ...]
160     '''
161
162     # make a faster lookup dict
163     rna_update_dict = {}
164     for ren_class, ren_from, ren_to in rna_update:
165         rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to
166
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)
170
171     for id_data in id_iter():
172
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)
176         if node_tree:
177             anim_data_ls.append((node_tree, node_tree.animation_data))
178
179         for anim_data_base, anim_data in anim_data_ls:
180             if anim_data is None:
181                 continue
182
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:
188                     if not IS_TESTING:
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))
192
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
198
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:
203                                     if not IS_TESTING:
204                                         tar.data_path = data_path_new
205                                     print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new))
206
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:
213                         if not IS_TESTING:
214                             fcu.data_path = data_path_new
215                         print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
216
217
218 if __name__ == "__main__":
219
220     # Example, should be called externally
221     # (class, from, to)
222     replace_ls = [
223         ('AnimVizMotionPaths', 'frame_after', 'frame_after'),
224         ('AnimVizMotionPaths', 'frame_before', 'frame_before'),
225         ('AnimVizOnionSkinning', 'frame_after', 'frame_after'),
226     ]
227
228     update_data_paths(replace_ls)