Tool System: use categories for tool identifiers
[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=index, group_method='NAMED', group_name=bone.name)
436         else:
437             ks.paths.add(id_block, path, index=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
531
532 class BUILTIN_KSI_WholeCharacterSelected(KeyingSetInfo):
533     """Insert a keyframe for all properties that are likely to get animated in a character rig """
534     """(only selected bones)"""
535     bl_idname = ANIM_KS_WHOLE_CHARACTER_SELECTED_ID
536     bl_label = "Whole Character (Selected bones only)"
537
538     # iterator - all bones regardless of selection
539     def iterator(ksi, context, ks):
540         # Use either the selected bones, or all of them if none are selected.
541         bones = context.selected_pose_bones_from_active_object or context.active_object.pose.bones
542
543         for bone in bones:
544             if bone.name.startswith(BUILTIN_KSI_WholeCharacter.badBonePrefixes):
545                 continue
546             ksi.generate(context, ks, bone)
547
548     # Poor man's subclassing. Blender breaks when we actually subclass BUILTIN_KSI_WholeCharacter.
549     poll = BUILTIN_KSI_WholeCharacter.poll
550     generate = BUILTIN_KSI_WholeCharacter.generate
551     addProp = BUILTIN_KSI_WholeCharacter.addProp
552     doLoc = BUILTIN_KSI_WholeCharacter.doLoc
553     doRot4d = BUILTIN_KSI_WholeCharacter.doRot4d
554     doRot3d = BUILTIN_KSI_WholeCharacter.doRot3d
555     doScale = BUILTIN_KSI_WholeCharacter.doScale
556     doBBone = BUILTIN_KSI_WholeCharacter.doBBone
557     doCustomProps = BUILTIN_KSI_WholeCharacter.doCustomProps
558
559 ###############################
560
561 # Delta Location
562
563
564 class BUILTIN_KSI_DeltaLocation(KeyingSetInfo):
565     """Insert keyframes for additional location offset"""
566     bl_label = "Delta Location"
567
568     # poll - selected objects only (and only if active object in object mode)
569     poll = keyingsets_utils.RKS_POLL_selected_objects
570
571     # iterator - selected objects only
572     iterator = keyingsets_utils.RKS_ITER_selected_objects
573
574     # generator - delta location channels only
575     def generate(ksi, context, ks, data):
576         # get id-block and path info
577         id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data)
578
579         # add the property name to the base path
580         path = keyingsets_utils.path_add_property(base_path, "delta_location")
581
582         # add Keying Set entry for this...
583         if grouping:
584             ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
585         else:
586             ks.paths.add(id_block, path)
587
588
589 # Delta Rotation
590 class BUILTIN_KSI_DeltaRotation(KeyingSetInfo):
591     """Insert keyframes for additional rotation offset"""
592     bl_label = "Delta Rotation"
593
594     # poll - selected objects only (and only if active object in object mode)
595     poll = keyingsets_utils.RKS_POLL_selected_objects
596
597     # iterator - selected objects only
598     iterator = keyingsets_utils.RKS_ITER_selected_objects
599
600     # generator - delta location channels only
601     def generate(ksi, context, ks, data):
602         # get id-block and path info
603         id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data)
604
605         # add the property name to the base path
606         #   rotation mode affects the property used
607         if data.rotation_mode == 'QUATERNION':
608             path = keyingsets_utils.path_add_property(base_path, "delta_rotation_quaternion")
609         elif data.rotation_mode == 'AXIS_ANGLE':
610             # XXX: for now, this is not available yet
611             #path = path_add_property(base_path, "delta_rotation_axis_angle")
612             return
613         else:
614             path = keyingsets_utils.path_add_property(base_path, "delta_rotation_euler")
615
616         # add Keying Set entry for this...
617         if grouping:
618             ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
619         else:
620             ks.paths.add(id_block, path)
621
622
623 # Delta Scale
624 class BUILTIN_KSI_DeltaScale(KeyingSetInfo):
625     """Insert keyframes for additional scaling factor"""
626     bl_label = "Delta Scale"
627
628     # poll - selected objects only (and only if active object in object mode)
629     poll = keyingsets_utils.RKS_POLL_selected_objects
630
631     # iterator - selected objects only
632     iterator = keyingsets_utils.RKS_ITER_selected_objects
633
634     # generator - delta location channels only
635     def generate(ksi, context, ks, data):
636         # get id-block and path info
637         id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data)
638
639         # add the property name to the base path
640         path = keyingsets_utils.path_add_property(base_path, "delta_scale")
641
642         # add Keying Set entry for this...
643         if grouping:
644             ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
645         else:
646             ks.paths.add(id_block, path)
647
648 ###############################
649
650
651 # Note that this controls order of options in 'insert keyframe' menu.
652 # Better try to keep some logical order here beyond mere alphabetical one, also because of menu entries shortcut.
653 # See also T51867.
654 classes = (
655     BUILTIN_KSI_Available,
656     BUILTIN_KSI_Location,
657     BUILTIN_KSI_Rotation,
658     BUILTIN_KSI_Scaling,
659     BUILTIN_KSI_LocRot,
660     BUILTIN_KSI_LocRotScale,
661     BUILTIN_KSI_LocScale,
662     BUILTIN_KSI_RotScale,
663     BUILTIN_KSI_DeltaLocation,
664     BUILTIN_KSI_DeltaRotation,
665     BUILTIN_KSI_DeltaScale,
666     BUILTIN_KSI_VisualLoc,
667     BUILTIN_KSI_VisualRot,
668     BUILTIN_KSI_VisualScaling,
669     BUILTIN_KSI_VisualLocRot,
670     BUILTIN_KSI_VisualLocRotScale,
671     BUILTIN_KSI_VisualLocScale,
672     BUILTIN_KSI_VisualRotScale,
673     BUILTIN_KSI_BendyBones,
674     BUILTIN_KSI_WholeCharacter,
675     BUILTIN_KSI_WholeCharacterSelected,
676 )
677
678
679 def register():
680     from bpy.utils import register_class
681     for cls in classes:
682         register_class(cls)
683
684
685 def unregister():
686     from bpy.utils import unregister_class
687     for cls in classes:
688         unregister_class(cls)
689
690
691 if __name__ == "__main__":
692     register()