Welcome to mirror list, hosted at ThFree Co, Russian Federation.

animsys_refactor.py « modules « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 8cc91873b0ed5a7828c2d0b0dc8a332cbf82e4ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# ##### 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.

The main function to use is: update_data_paths(...) 
"""

IS_TESTING = False

class DataPathBuilder(object):
    __slots__ = ("data_path", )
    """ Dummy class used to parse fcurve and driver data paths.
    """
    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):
        str_value = '["%s"]' % key
        return DataPathBuilder(self.data_path + (str_value, ))

    def resolve(self, real_base, rna_update_from_map=None):
        """ Return (attribute, value) pairs.
        """
        pairs = []
        base = real_base
        for item in self.data_path:
            if base is not Ellipsis:
                try:
                    # this only works when running with an old blender
                    # where the old path will resolve 
                    base = eval("base" + item)
                except:
                    base_new = Ellipsis
                    # guess the new name
                    if item.startswith("."):
                        for item_new in rna_update_from_map.get(item[1:], ()):
                            try:
                                print("base." + item_new)
                                base_new = eval("base." + item_new)
                                break # found, dont keep looking
                            except:
                                pass

                    if base_new is Ellipsis:
                        print("Failed to resolve data path:", self.data_path)
                    base = base_new

            pairs.append((item, base))
        return pairs

import bpy


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 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


def find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map):
    # 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)

    path_new = [pair[0] for pair in data_resolve]
    
    # print(data_resolve)
    data_base = id_data

    for i, (attr, data) in enumerate(data_resolve):
        if data is Ellipsis:
            break

        if attr.startswith("."):
            # try all classes
            for data_base_type in classes_recursive(type(data_base)):
                attr_new = rna_update_dict.get(data_base_type.__name__, {}).get(attr[1:])
                if attr_new:
                    path_new[i] = "." + attr_new

        # set this as the base for further properties
        data_base = data
    
    data_path_new = "".join(path_new)[1:] # skip the first "."
    return data_path_new


def update_data_paths(rna_update):
    ''' rna_update triple [(class_name, from, to), ...]
    '''
    
    # make a faster lookup dict
    rna_update_dict = {}
    for ren_class, ren_from, ren_to in rna_update:
        rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to
        
    rna_update_from_map = {}
    for ren_class, ren_from, ren_to in rna_update:
        rna_update_from_map.setdefault(ren_from, []).append(ren_to)

    for id_data in id_iter():
        anim_data = getattr(id_data, "animation_data", None)
        if anim_data is None:
            continue
        
        for fcurve in anim_data.drivers:
            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_dict, rna_update_from_map)
                            # 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))
                
            
        
        for action in anim_data_actions(anim_data):
            for fcu in action.fcurves:
                data_path = fcu.data_path
                data_path_new = find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map)
                # 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))


if __name__ == "__main__":

    # Example, should be called externally
    # (class, from, to)
    replace_ls = [
        ('AnimVizMotionPaths', 'frame_after', 'frame_after'),
        ('AnimVizMotionPaths', 'frame_before', 'frame_before'),
        ('AnimVizOnionSkinning', 'frame_after', 'frame_after'),
    ]

    update_data_paths(replace_ls)