"Bugfix" (i.e. feature request in disguise!) [#26772] Delta Scaling,
[blender.git] / release / scripts / modules / keyingsets_utils.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 # This file defines a set of methods that are useful for various
22 # Relative Keying Set (RKS) related operations, such as: callbacks
23 # for polling, iterator callbacks, and also generate callbacks.
24 # All of these can be used in conjunction with the others.
25
26 __all__ = [
27     "path_add_property",
28     "RKS_POLL_selected_objects",
29     "RKS_POLL_selected_bones",
30     "RKS_POLL_selected_items",
31     "RKS_ITER_selected_item",
32     "RKS_GEN_available",
33     "RKS_GEN_location",
34     "RKS_GEN_rotation",
35     "RKS_GEN_scaling",
36 ]
37
38 import bpy
39
40 ###########################
41 # General Utilities
42
43
44 # Append the specified property name on the the existing path
45 def path_add_property(path, prop):
46     if len(path):
47         return path + "." + prop
48     else:
49         return prop
50
51 ###########################
52 # Poll Callbacks
53
54
55 # selected objects (active object must be in object mode)
56 def RKS_POLL_selected_objects(ksi, context):
57     if context.active_object:
58         return context.active_object.mode == 'OBJECT'
59     else:
60         return len(context.selected_objects) != 0
61
62
63 # selected bones
64 def RKS_POLL_selected_bones(ksi, context):
65     # we must be in Pose Mode, and there must be some bones selected
66     if (context.active_object) and (context.active_object.mode == 'POSE'):
67         if context.active_pose_bone or len(context.selected_pose_bones):
68             return True
69
70     # nothing selected
71     return False
72
73
74 # selected bones or objects
75 def RKS_POLL_selected_items(ksi, context):
76     return RKS_POLL_selected_bones(ksi, context) or RKS_POLL_selected_objects(ksi, context)
77
78 ###########################
79 # Iterator Callbacks
80
81
82 # all selected objects or pose bones, depending on which we've got
83 def RKS_ITER_selected_item(ksi, context, ks):
84     if (context.active_object) and (context.active_object.mode == 'POSE'):
85         for bone in context.selected_pose_bones:
86             ksi.generate(context, ks, bone)
87     else:
88         for ob in context.selected_objects:
89             ksi.generate(context, ks, ob)
90             
91 # all select objects only
92 def RKS_ITER_selected_objects(ksi, context, ks):
93     for ob in context.selected_objects:
94         ksi.generate(context, ks, ob)
95
96 ###########################
97 # Generate Callbacks
98
99
100 # 'Available' F-Curves
101 def RKS_GEN_available(ksi, context, ks, data):
102     # try to get the animation data associated with the closest
103     # ID-block to the data (neither of which may exist/be easy to find)
104     id_block = data.id_data
105     adt = getattr(id_block, "animation_data", None)
106
107     # there must also be an active action...
108     if adt is None or adt.action is None:
109         return
110
111     # if we haven't got an ID-block as 'data', try to restrict
112     # paths added to only those which branch off from here
113     # i.e. for bones
114     if id_block != data:
115         basePath = data.path_from_id()
116     else:
117         basePath = None  # this is not needed...
118
119     # for each F-Curve, include a path to key it
120     # NOTE: we don't need to set the group settings here
121     for fcu in adt.action.fcurves:
122         if basePath:
123             if basePath in fcu.data_path:
124                 ks.paths.add(id_block, fcu.data_path, index=fcu.array_index)
125         else:
126             ks.paths.add(id_block, fcu.data_path, index=fcu.array_index)
127
128 # ------
129
130
131 # get ID block and based ID path for transform generators
132 # private function
133 def get_transform_generators_base_info(data):
134     # ID-block for the data
135     id_block = data.id_data
136
137     # get base path and grouping method/name
138     if isinstance(data, bpy.types.ID):
139         # no path in this case
140         path = ""
141
142         # data on ID-blocks directly should get grouped by the KeyingSet
143         grouping = None
144     else:
145         # get the path to the ID-block
146         path = data.path_from_id()
147
148         # try to use the name of the data element to group the F-Curve
149         # else fallback on the KeyingSet name
150         grouping = getattr(data, "name", None)
151
152     # return the ID-block and the path
153     return id_block, path, grouping
154
155
156 # Location
157 def RKS_GEN_location(ksi, context, ks, data):
158     # get id-block and path info
159     id_block, base_path, grouping = get_transform_generators_base_info(data)
160
161     # add the property name to the base path
162     path = path_add_property(base_path, "location")
163
164     # add Keying Set entry for this...
165     if grouping:
166         ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
167     else:
168         ks.paths.add(id_block, path)
169
170
171 # Rotation
172 def RKS_GEN_rotation(ksi, context, ks, data):
173     # get id-block and path info
174     id_block, base_path, grouping = get_transform_generators_base_info(data)
175
176     # add the property name to the base path
177     #   rotation mode affects the property used
178     if data.rotation_mode == 'QUATERNION':
179         path = path_add_property(base_path, "rotation_quaternion")
180     elif data.rotation_mode == 'AXIS_ANGLE':
181         path = path_add_property(base_path, "rotation_axis_angle")
182     else:
183         path = path_add_property(base_path, "rotation_euler")
184
185     # add Keying Set entry for this...
186     if grouping:
187         ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
188     else:
189         ks.paths.add(id_block, path)
190
191
192 # Scaling
193 def RKS_GEN_scaling(ksi, context, ks, data):
194     # get id-block and path info
195     id_block, base_path, grouping = get_transform_generators_base_info(data)
196
197     # add the property name to the base path
198     path = path_add_property(base_path, "scale")
199
200     # add Keying Set entry for this...
201     if grouping:
202         ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
203     else:
204         ks.paths.add(id_block, path)