3959904e0c5793b8200ae91a8befc297d78b517a
[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 class DataPathBuilder(object):
31     __slots__ = ("data_path", )
32     """ Dummy class used to parse fcurve and driver data paths.
33     """
34     def __init__(self, attrs):
35         self.data_path = attrs
36
37     def __getattr__(self, attr):
38         str_value = ".%s" % attr
39         return DataPathBuilder(self.data_path + (str_value, ))
40         
41     def __getitem__(self, key):
42         str_value = '["%s"]' % key
43         return DataPathBuilder(self.data_path + (str_value, ))
44
45     def resolve(self, real_base):
46         """ Return (attribute, value) pairs.
47         """
48         pairs = []
49         base = real_base
50         for item in self.data_path:
51             if base is not Ellipsis:
52                 try:
53                     base = eval("base" + item)
54                 except:
55                     print("Failed to resolve data path:", self.data_path)
56                     base = Ellipsis
57
58             pairs.append((item, base))
59         return pairs
60
61 import bpy
62
63
64 def id_iter():
65     type_iter = type(bpy.data.objects)
66     
67     for attr in dir(bpy.data):
68         data_iter = getattr(bpy.data, attr, None)
69         if type(data_iter) == type_iter:
70             for id_data in data_iter:
71                 if id_data.library is None:
72                     yield id_data
73
74
75 def anim_data_actions(anim_data):
76     actions = []
77     actions.append(anim_data.action)
78     for track in anim_data.nla_tracks:
79         for strip in track.strips:
80             actions.append(strip.action)
81
82     # filter out None
83     return [act for act in actions if act]
84
85
86 def classes_recursive(base_type, clss=None):
87     if clss is None:
88         clss = [base_type]
89     else:
90         clss.append(base_type)
91
92     for base_type_iter in base_type.__bases__:
93         if base_type_iter is not object:
94             classes_recursive(base_type_iter, clss)
95
96     return clss
97
98
99 def find_path_new(id_data, data_path, rna_update_dict):
100     # ignore ID props for now
101     if data_path.startswith("["):
102         return data_path
103     
104     # recursive path fixing, likely will be one in most cases.
105     data_path_builder = eval("DataPathBuilder(tuple())." + data_path)
106     data_resolve = data_path_builder.resolve(id_data)
107
108     path_new = [pair[0] for pair in data_resolve]
109     
110     # print(data_resolve)
111     data_base = id_data
112
113     for i, (attr, data) in enumerate(data_resolve):
114         if data is Ellipsis:
115             break
116
117         if attr.startswith("."):
118             # try all classes
119             for data_base_type in classes_recursive(type(data_base)):
120                 attr_new = rna_update_dict.get(data_base_type.__name__, {}).get(attr[1:])
121                 if attr_new:
122                     path_new[i] = "." + attr_new
123
124         # set this as the base for further properties
125         data_base = data
126     
127     data_path_new = "".join(path_new)[1:] # skip the first "."
128     return data_path_new
129
130
131 def update_data_paths(rna_update):
132     ''' rna_update triple [(class_name, from, to), ...]
133     '''
134     
135     # make a faster lookup dict
136     rna_update_dict = {}
137     for ren_class, ren_from, ren_to in rna_update:
138         rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to
139
140     for id_data in id_iter():
141         anim_data = getattr(id_data, "animation_data", None)
142         if anim_data is None:
143             continue
144         
145         for fcurve in anim_data.drivers:
146             for var in fcurve.driver.variables:
147                 if var.type == 'SINGLE_PROP':
148                     for tar in var.targets:
149                         id_data_other = tar.id
150                         data_path = tar.data_path
151                         
152                         if id_data_other and data_path:
153                             data_path_new = find_path_new(id_data_other, data_path, rna_update_dict)
154                             # print(data_path_new)
155                             if data_path_new != data_path:
156                                 if not IS_TESTING:
157                                     tar.data_path = data_path_new
158                                 print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new))
159                 
160             
161         
162         for action in anim_data_actions(anim_data):
163             for fcu in action.fcurves:
164                 data_path = fcu.data_path
165                 data_path_new = find_path_new(id_data, data_path, rna_update_dict)
166                 # print(data_path_new)
167                 if data_path_new != data_path:
168                     if not IS_TESTING:
169                         fcu.data_path = data_path_new
170                     print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new))
171
172   
173 if __name__ == "__main__":
174
175     # Example, should be called externally
176     # (class, from, to)
177     replace_ls = [
178         ('AnimVizMotionPaths', 'after_current', 'frame_after'),
179         ('AnimVizMotionPaths', 'before_current', 'frame_before'),
180         ('AnimVizOnionSkinning', 'after_current', 'frame_after'),
181     ]
182
183     update_data_paths(replace_ls)