Merge branch 'master' into blender2.8
[blender.git] / release / scripts / startup / keyingsets_builtins.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 Built-In Keying Sets
23 None of these Keying Sets should be removed, as these are needed by various parts of Blender in order for them
24 to work correctly.
25
26 Beware also about changing the order that these are defined here, since this can result in old files referring to the
27 wrong Keying Set as the active one, potentially resulting in lost (i.e. unkeyed) animation.
28
29 Note that these classes cannot be subclassed further; only direct subclasses of KeyingSetInfo
30 are supported.
31 """
32
33 import bpy
34 import keyingsets_utils
35 from bpy.types import KeyingSetInfo
36
37 ###############################
38 # Built-In KeyingSets
39
40
41 # "Defines"
42 # Keep these in sync with those in ED_keyframing.h!
43 ANIM_KS_LOCATION_ID = "Location"
44 ANIM_KS_ROTATION_ID = "Rotation"
45 ANIM_KS_SCALING_ID = "Scaling"
46 ANIM_KS_LOC_ROT_SCALE_ID = "LocRotScale"
47 ANIM_KS_AVAILABLE_ID = "Available"
48 ANIM_KS_WHOLE_CHARACTER_ID = "WholeCharacter"
49 ANIM_KS_WHOLE_CHARACTER_SELECTED_ID = "WholeCharacterSelected"
50
51
52 # Location
53 class BUILTIN_KSI_Location(KeyingSetInfo):
54     """Insert a keyframe on each of the location channels"""
55     bl_idname = ANIM_KS_LOCATION_ID
56     bl_label = "Location"
57
58     # poll - use predefined callback for selected bones/objects
59     poll = keyingsets_utils.RKS_POLL_selected_items
60
61     # iterator - use callback for selected bones/objects
62     iterator = keyingsets_utils.RKS_ITER_selected_item
63
64     # generator - use callback for location
65     generate = keyingsets_utils.RKS_GEN_location
66
67
68 # Rotation
69 class BUILTIN_KSI_Rotation(KeyingSetInfo):
70     """Insert a keyframe on each of the rotation channels"""
71     bl_idname = ANIM_KS_ROTATION_ID
72     bl_label = "Rotation"
73
74     # poll - use predefined callback for selected bones/objects
75     poll = keyingsets_utils.RKS_POLL_selected_items
76
77     # iterator - use callback for selected bones/objects
78     iterator = keyingsets_utils.RKS_ITER_selected_item
79
80     # generator - use callback for rotation
81     generate = keyingsets_utils.RKS_GEN_rotation
82
83
84 # Scale
85 class BUILTIN_KSI_Scaling(KeyingSetInfo):
86     """Insert a keyframe on each of the scale channels"""
87     bl_idname = ANIM_KS_SCALING_ID
88     bl_label = "Scaling"
89
90     # poll - use predefined callback for selected bones/objects
91     poll = keyingsets_utils.RKS_POLL_selected_items
92
93     # iterator - use callback for selected bones/objects
94     iterator = keyingsets_utils.RKS_ITER_selected_item
95
96     # generator - use callback for scaling
97     generate = keyingsets_utils.RKS_GEN_scaling
98
99 # ------------
100
101
102 # LocRot
103 class BUILTIN_KSI_LocRot(KeyingSetInfo):
104     """Insert a keyframe on each of the location and rotation channels"""
105     bl_label = "LocRot"
106
107     # poll - use predefined callback for selected bones/objects
108     poll = keyingsets_utils.RKS_POLL_selected_items
109
110     # iterator - use callback for selected bones/objects
111     iterator = keyingsets_utils.RKS_ITER_selected_item
112
113     # generator
114     def generate(self, context, ks, data):
115         # location
116         keyingsets_utils.RKS_GEN_location(self, context, ks, data)
117         # rotation
118         keyingsets_utils.RKS_GEN_rotation(self, context, ks, data)
119
120
121 # LocScale
122 class BUILTIN_KSI_LocScale(KeyingSetInfo):
123     """Insert a keyframe on each of the location and scale channels"""
124     bl_label = "LocScale"
125
126     # poll - use predefined callback for selected bones/objects
127     poll = keyingsets_utils.RKS_POLL_selected_items
128
129     # iterator - use callback for selected bones/objects
130     iterator = keyingsets_utils.RKS_ITER_selected_item
131
132     # generator
133     def generate(self, context, ks, data):
134         # location
135         keyingsets_utils.RKS_GEN_location(self, context, ks, data)
136         # scale
137         keyingsets_utils.RKS_GEN_scaling(self, context, ks, data)
138
139
140 # LocRotScale
141 class BUILTIN_KSI_LocRotScale(KeyingSetInfo):
142     """Insert a keyframe on each of the location, rotation, and scale channels"""
143     bl_idname = ANIM_KS_LOC_ROT_SCALE_ID
144     bl_label = "LocRotScale"
145
146     # poll - use predefined callback for selected bones/objects
147     poll = keyingsets_utils.RKS_POLL_selected_items
148
149     # iterator - use callback for selected bones/objects
150     iterator = keyingsets_utils.RKS_ITER_selected_item
151
152     # generator
153     def generate(self, context, ks, data):
154         # location
155         keyingsets_utils.RKS_GEN_location(self, context, ks, data)
156         # rotation
157         keyingsets_utils.RKS_GEN_rotation(self, context, ks, data)
158         # scale
159         keyingsets_utils.RKS_GEN_scaling(self, context, ks, data)
160
161
162 # RotScale
163 class BUILTIN_KSI_RotScale(KeyingSetInfo):
164     """Insert a keyframe on each of the rotation and scale channels"""
165     bl_label = "RotScale"
166
167     # poll - use predefined callback for selected bones/objects
168     poll = keyingsets_utils.RKS_POLL_selected_items
169
170     # iterator - use callback for selected bones/objects
171     iterator = keyingsets_utils.RKS_ITER_selected_item
172
173     # generator
174     def generate(self, context, ks, data):
175         # rotation
176         keyingsets_utils.RKS_GEN_rotation(self, context, ks, data)
177         # scaling
178         keyingsets_utils.RKS_GEN_scaling(self, context, ks, data)
179
180 # ------------
181
182
183 # Bendy Bones
184 class BUILTIN_KSI_BendyBones(KeyingSetInfo):
185     """Insert a keyframe for each of the BBone shape properties"""
186     bl_label = "BBone Shape"
187
188     # poll - use callback for selected bones
189     poll = keyingsets_utils.RKS_POLL_selected_bones
190
191     # iterator - use callback for selected bones
192     iterator = keyingsets_utils.RKS_ITER_selected_bones
193
194     # generator - use generator for bendy bone properties
195     generate = keyingsets_utils.RKS_GEN_bendy_bones
196
197 # ------------
198
199
200 # VisualLocation
201 class BUILTIN_KSI_VisualLoc(KeyingSetInfo):
202     """Insert a keyframe on each of the location channels, taking into account effects of constraints """
203     """and relationships"""
204     bl_label = "Visual Location"
205
206     bl_options = {'INSERTKEY_VISUAL'}
207
208     # poll - use predefined callback for selected bones/objects
209     poll = keyingsets_utils.RKS_POLL_selected_items
210
211     # iterator - use callback for selected bones/objects
212     iterator = keyingsets_utils.RKS_ITER_selected_item
213
214     # generator - use callback for location
215     generate = keyingsets_utils.RKS_GEN_location
216
217
218 # VisualRotation
219 class BUILTIN_KSI_VisualRot(KeyingSetInfo):
220     """Insert a keyframe on each of the rotation channels, taking into account effects of constraints """
221     """and relationships"""
222     bl_label = "Visual Rotation"
223
224     bl_options = {'INSERTKEY_VISUAL'}
225
226     # poll - use predefined callback for selected bones/objects
227     poll = keyingsets_utils.RKS_POLL_selected_items
228
229     # iterator - use callback for selected bones/objects
230     iterator = keyingsets_utils.RKS_ITER_selected_item
231
232     # generator - use callback for rotation
233     generate = keyingsets_utils.RKS_GEN_rotation
234
235
236 # VisualScaling
237 class BUILTIN_KSI_VisualScaling(KeyingSetInfo):
238     """Insert a keyframe on each of the scale channels, taking into account effects of constraints """
239     """and relationships"""
240     bl_label = "Visual Scaling"
241
242     bl_options = {'INSERTKEY_VISUAL'}
243
244     # poll - use predefined callback for selected bones/objects
245     poll = keyingsets_utils.RKS_POLL_selected_items
246
247     # iterator - use callback for selected bones/objects
248     iterator = keyingsets_utils.RKS_ITER_selected_item
249
250     # generator - use callback for location
251     generate = keyingsets_utils.RKS_GEN_scaling
252
253
254 # VisualLocRot
255 class BUILTIN_KSI_VisualLocRot(KeyingSetInfo):
256     """Insert a keyframe on each of the location and rotation channels, taking into account effects of constraints """
257     """and relationships"""
258     bl_label = "Visual LocRot"
259
260     bl_options = {'INSERTKEY_VISUAL'}
261
262     # poll - use predefined callback for selected bones/objects
263     poll = keyingsets_utils.RKS_POLL_selected_items
264
265     # iterator - use callback for selected bones/objects
266     iterator = keyingsets_utils.RKS_ITER_selected_item
267
268     # generator
269     def generate(self, context, ks, data):
270         # location
271         keyingsets_utils.RKS_GEN_location(self, context, ks, data)
272         # rotation
273         keyingsets_utils.RKS_GEN_rotation(self, context, ks, data)
274
275
276 # VisualLocScale
277 class BUILTIN_KSI_VisualLocScale(KeyingSetInfo):
278     """Insert a keyframe on each of the location and scaling channels, taking into account effects of constraints """
279     """and relationships"""
280     bl_label = "Visual LocScale"
281
282     bl_options = {'INSERTKEY_VISUAL'}
283
284     # poll - use predefined callback for selected bones/objects
285     poll = keyingsets_utils.RKS_POLL_selected_items
286
287     # iterator - use callback for selected bones/objects
288     iterator = keyingsets_utils.RKS_ITER_selected_item
289
290     # generator
291     def generate(self, context, ks, data):
292         # location
293         keyingsets_utils.RKS_GEN_location(self, context, ks, data)
294         # scaling
295         keyingsets_utils.RKS_GEN_scaling(self, context, ks, data)
296
297
298 # VisualLocRotScale
299 class BUILTIN_KSI_VisualLocRotScale(KeyingSetInfo):
300     """Insert a keyframe on each of the location, rotation and scaling channels, taking into account effects """
301     """of constraints and relationships"""
302     bl_label = "Visual LocRotScale"
303
304     bl_options = {'INSERTKEY_VISUAL'}
305
306     # poll - use predefined callback for selected bones/objects
307     poll = keyingsets_utils.RKS_POLL_selected_items
308
309     # iterator - use callback for selected bones/objects
310     iterator = keyingsets_utils.RKS_ITER_selected_item
311
312     # generator
313     def generate(self, context, ks, data):
314         # location
315         keyingsets_utils.RKS_GEN_location(self, context, ks, data)
316         # rotation
317         keyingsets_utils.RKS_GEN_rotation(self, context, ks, data)
318         # scaling
319         keyingsets_utils.RKS_GEN_scaling(self, context, ks, data)
320
321
322 # VisualRotScale
323 class BUILTIN_KSI_VisualRotScale(KeyingSetInfo):
324     """Insert a keyframe on each of the rotation and scaling channels, taking into account effects of constraints """
325     """and relationships"""
326     bl_label = "Visual RotScale"
327
328     bl_options = {'INSERTKEY_VISUAL'}
329
330     # poll - use predefined callback for selected bones/objects
331     poll = keyingsets_utils.RKS_POLL_selected_items
332
333     # iterator - use callback for selected bones/objects
334     iterator = keyingsets_utils.RKS_ITER_selected_item
335
336     # generator
337     def generate(self, context, ks, data):
338         # rotation
339         keyingsets_utils.RKS_GEN_rotation(self, context, ks, data)
340         # scaling
341         keyingsets_utils.RKS_GEN_scaling(self, context, ks, data)
342
343 # ------------
344
345
346 # Available
347 class BUILTIN_KSI_Available(KeyingSetInfo):
348     """Insert a keyframe on each of the already existing F-Curves"""
349     bl_idname = ANIM_KS_AVAILABLE_ID
350     bl_label = "Available"
351
352     # poll - selected objects or selected object with animation data
353     def poll(ksi, context):
354         ob = context.active_object
355         if ob:
356             # TODO: this fails if one animation-less object is active, but many others are selected
357             return ob.animation_data and ob.animation_data.action
358         else:
359             return bool(context.selected_objects)
360
361     # iterator - use callback for selected bones/objects
362     iterator = keyingsets_utils.RKS_ITER_selected_item
363
364     # generator - use callback for doing this
365     generate = keyingsets_utils.RKS_GEN_available
366
367 ###############################
368
369
370 # All properties that are likely to get animated in a character rig
371 class BUILTIN_KSI_WholeCharacter(KeyingSetInfo):
372     """Insert a keyframe for all properties that are likely to get animated in a character rig """
373     """(useful when blocking out a shot)"""
374     bl_idname = ANIM_KS_WHOLE_CHARACTER_ID
375     bl_label = "Whole Character"
376
377     # these prefixes should be avoided, as they are not really bones
378     # that animators should be touching (or need to touch)
379     badBonePrefixes = (
380         'DEF',
381         'GEO',
382         'MCH',
383         'ORG',
384         'COR',
385         'VIS',
386         # ... more can be added here as you need in your own rigs ...
387     )
388
389     # poll - pose-mode on active object only
390     def poll(ksi, context):
391         return ((context.active_object) and (context.active_object.pose) and
392                 (context.active_object.mode == 'POSE'))
393
394     # iterator - all bones regardless of selection
395     def iterator(ksi, context, ks):
396         for bone in context.active_object.pose.bones:
397             if not bone.name.startswith(BUILTIN_KSI_WholeCharacter.badBonePrefixes):
398                 ksi.generate(context, ks, bone)
399
400     # generator - all unlocked bone transforms + custom properties
401     def generate(ksi, context, ks, bone):
402         # loc, rot, scale - only include unlocked ones
403         if not bone.bone.use_connect:
404             ksi.doLoc(ks, bone)
405
406         if bone.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
407             ksi.doRot4d(ks, bone)
408         else:
409             ksi.doRot3d(ks, bone)
410         ksi.doScale(ks, bone)
411
412         # bbone properties?
413         ksi.doBBone(context, ks, bone)
414
415         # custom props?
416         ksi.doCustomProps(ks, bone)
417
418     # ----------------
419
420     # helper to add some bone's property to the Keying Set
421     def addProp(ksi, ks, bone, prop, index=-1, use_groups=True):
422         # add the property name to the base path
423         id_path = bone.path_from_id()
424         id_block = bone.id_data
425
426         if prop.startswith('['):
427             # custom properties
428             path = id_path + prop
429         else:
430             # standard transforms/properties
431             path = keyingsets_utils.path_add_property(id_path, prop)
432
433         # add Keying Set entry for this...
434         if use_groups:
435             ks.paths.add(id_block, path, index, group_method='NAMED', group_name=bone.name)
436         else:
437             ks.paths.add(id_block, path, index)
438
439     # ----------------
440
441     # location properties
442     def doLoc(ksi, ks, bone):
443         if bone.lock_location == (False, False, False):
444             ksi.addProp(ks, bone, "location")
445         else:
446             for i in range(3):
447                 if not bone.lock_location[i]:
448                     ksi.addProp(ks, bone, "location", i)
449
450     # rotation properties
451     def doRot4d(ksi, ks, bone):
452         # rotation mode affects the property used
453         if bone.rotation_mode == 'QUATERNION':
454             prop = "rotation_quaternion"
455         elif bone.rotation_mode == 'AXIS_ANGLE':
456             prop = "rotation_axis_angle"
457
458         # add rotation properties if they will
459         if bone.lock_rotations_4d:
460             # can check individually
461             if (bone.lock_rotation == (False, False, False)) and (bone.lock_rotation_w is False):
462                 ksi.addProp(ks, bone, prop)
463             else:
464                 if bone.lock_rotation_w is False:
465                     ksi.addProp(ks, bone, prop, 0)  # w = 0
466
467                 for i in range(3):
468                     if not bone.lock_rotation[i]:
469                         ksi.addProp(ks, bone, prop, i + 1)  # i + 1, since here x/y/z = 1,2,3, and w=0
470         elif True not in bone.lock_rotation:
471             # if axis-angle rotations get locked as eulers, then it's too messy to allow anything
472             # other than all open unless we keyframe the whole lot
473             ksi.addProp(ks, bone, prop)
474
475     def doRot3d(ksi, ks, bone):
476         if bone.lock_rotation == (False, False, False):
477             ksi.addProp(ks, bone, "rotation_euler")
478         else:
479             for i in range(3):
480                 if not bone.lock_rotation[i]:
481                     ksi.addProp(ks, bone, "rotation_euler", i)
482
483     # scale properties
484     def doScale(ksi, ks, bone):
485         if bone.lock_scale == (0, 0, 0):
486             ksi.addProp(ks, bone, "scale")
487         else:
488             for i in range(3):
489                 if not bone.lock_scale[i]:
490                     ksi.addProp(ks, bone, "scale", i)
491
492     # ----------------
493
494     # bendy bone properties
495     def doBBone(ksi, context, ks, pchan):
496         bone = pchan.bone
497
498         # This check is crude, but is the best we can do for now
499         # It simply adds all of these if the bbone has segments
500         # (and the bone is a control bone). This may lead to some
501         # false positives...
502         if bone.bbone_segments > 1:
503             keyingsets_utils.RKS_GEN_bendy_bones(ksi, context, ks, pchan)
504
505     # ----------------
506
507     # custom properties
508     def doCustomProps(ksi, ks, bone):
509
510         prop_type_compat = {bpy.types.BoolProperty,
511                             bpy.types.IntProperty,
512                             bpy.types.FloatProperty}
513
514         # go over all custom properties for bone
515         for prop in bone.keys():
516             # ignore special "_RNA_UI" used for UI editing
517             if prop == "_RNA_UI":
518                 continue
519
520             # for now, just add all of 'em
521             prop_rna = type(bone).bl_rna.properties.get(prop, None)
522             if prop_rna is None:
523                 prop_path = '["%s"]' % prop
524                 if bone.path_resolve(prop_path, False).rna_type in prop_type_compat:
525                     ksi.addProp(ks, bone, prop_path)
526             elif prop_rna.is_animatable:
527                 ksi.addProp(ks, bone, prop)
528
529 # All properties that are likely to get animated in a character rig, only selected bones.
530 class BUILTIN_KSI_WholeCharacterSelected(KeyingSetInfo):
531     """Insert a keyframe for all properties that are likely to get animated in a character rig """
532     """(only selected bones)"""
533     bl_idname = ANIM_KS_WHOLE_CHARACTER_SELECTED_ID
534     bl_label = "Whole Character (Selected bones only)"
535
536     # iterator - all bones regardless of selection
537     def iterator(ksi, context, ks):
538         # Use either the selected bones, or all of them if none are selected.
539         bones = context.selected_pose_bones or context.active_object.pose.bones
540
541         for bone in bones:
542             if bone.name.startswith(BUILTIN_KSI_WholeCharacter.badBonePrefixes):
543                 continue
544             ksi.generate(context, ks, bone)
545
546     # Poor man's subclassing. Blender breaks when we actually subclass BUILTIN_KSI_WholeCharacter.
547     poll = BUILTIN_KSI_WholeCharacter.poll
548     generate = BUILTIN_KSI_WholeCharacter.generate
549     addProp = BUILTIN_KSI_WholeCharacter.addProp
550     doLoc = BUILTIN_KSI_WholeCharacter.doLoc
551     doRot4d = BUILTIN_KSI_WholeCharacter.doRot4d
552     doRot3d = BUILTIN_KSI_WholeCharacter.doRot3d
553     doScale = BUILTIN_KSI_WholeCharacter.doScale
554     doBBone = BUILTIN_KSI_WholeCharacter.doBBone
555     doCustomProps = BUILTIN_KSI_WholeCharacter.doCustomProps
556
557 ###############################
558
559 # Delta Location
560 class BUILTIN_KSI_DeltaLocation(KeyingSetInfo):
561     """Insert keyframes for additional location offset"""
562     bl_label = "Delta Location"
563
564     # poll - selected objects only (and only if active object in object mode)
565     poll = keyingsets_utils.RKS_POLL_selected_objects
566
567     # iterator - selected objects only
568     iterator = keyingsets_utils.RKS_ITER_selected_objects
569
570     # generator - delta location channels only
571     def generate(ksi, context, ks, data):
572         # get id-block and path info
573         id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data)
574
575         # add the property name to the base path
576         path = keyingsets_utils.path_add_property(base_path, "delta_location")
577
578         # add Keying Set entry for this...
579         if grouping:
580             ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
581         else:
582             ks.paths.add(id_block, path)
583
584
585 # Delta Rotation
586 class BUILTIN_KSI_DeltaRotation(KeyingSetInfo):
587     """Insert keyframes for additional rotation offset"""
588     bl_label = "Delta Rotation"
589
590     # poll - selected objects only (and only if active object in object mode)
591     poll = keyingsets_utils.RKS_POLL_selected_objects
592
593     # iterator - selected objects only
594     iterator = keyingsets_utils.RKS_ITER_selected_objects
595
596     # generator - delta location channels only
597     def generate(ksi, context, ks, data):
598         # get id-block and path info
599         id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data)
600
601         # add the property name to the base path
602         #   rotation mode affects the property used
603         if data.rotation_mode == 'QUATERNION':
604             path = keyingsets_utils.path_add_property(base_path, "delta_rotation_quaternion")
605         elif data.rotation_mode == 'AXIS_ANGLE':
606             # XXX: for now, this is not available yet
607             #path = path_add_property(base_path, "delta_rotation_axis_angle")
608             return
609         else:
610             path = keyingsets_utils.path_add_property(base_path, "delta_rotation_euler")
611
612         # add Keying Set entry for this...
613         if grouping:
614             ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
615         else:
616             ks.paths.add(id_block, path)
617
618
619 # Delta Scale
620 class BUILTIN_KSI_DeltaScale(KeyingSetInfo):
621     """Insert keyframes for additional scaling factor"""
622     bl_label = "Delta Scale"
623
624     # poll - selected objects only (and only if active object in object mode)
625     poll = keyingsets_utils.RKS_POLL_selected_objects
626
627     # iterator - selected objects only
628     iterator = keyingsets_utils.RKS_ITER_selected_objects
629
630     # generator - delta location channels only
631     def generate(ksi, context, ks, data):
632         # get id-block and path info
633         id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data)
634
635         # add the property name to the base path
636         path = keyingsets_utils.path_add_property(base_path, "delta_scale")
637
638         # add Keying Set entry for this...
639         if grouping:
640             ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
641         else:
642             ks.paths.add(id_block, path)
643
644 ###############################
645
646 # Note that this controls order of options in 'insert keyframe' menu.
647 # Better try to keep some logical order here beyond mere alphabetical one, also because of menu entries shortcut.
648 # See also T51867.
649 classes = (
650     BUILTIN_KSI_Available,
651     BUILTIN_KSI_Location,
652     BUILTIN_KSI_Rotation,
653     BUILTIN_KSI_Scaling,
654     BUILTIN_KSI_LocRot,
655     BUILTIN_KSI_LocRotScale,
656     BUILTIN_KSI_LocScale,
657     BUILTIN_KSI_RotScale,
658     BUILTIN_KSI_DeltaLocation,
659     BUILTIN_KSI_DeltaRotation,
660     BUILTIN_KSI_DeltaScale,
661     BUILTIN_KSI_VisualLoc,
662     BUILTIN_KSI_VisualRot,
663     BUILTIN_KSI_VisualScaling,
664     BUILTIN_KSI_VisualLocRot,
665     BUILTIN_KSI_VisualLocRotScale,
666     BUILTIN_KSI_VisualLocScale,
667     BUILTIN_KSI_VisualRotScale,
668     BUILTIN_KSI_BendyBones,
669     BUILTIN_KSI_WholeCharacter,
670     BUILTIN_KSI_WholeCharacterSelected,
671 )
672
673
674 def register():
675     from bpy.utils import register_class
676     for cls in classes:
677         register_class(cls)
678
679
680 def unregister():
681     from bpy.utils import unregister_class
682     for cls in classes:
683         unregister_class(cls)
684
685
686 if __name__ == "__main__":
687     register()