File headers: use SPDX license identifiers
[blender-addons.git] / io_anim_bvh / __init__.py
blob43e582d5b92f08a6cc2bf619fb7a34446801b5a2
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # <pep8-80 compliant>
5 bl_info = {
6 "name": "BioVision Motion Capture (BVH) format",
7 "author": "Campbell Barton",
8 "version": (1, 0, 0),
9 "blender": (2, 81, 6),
10 "location": "File > Import-Export",
11 "description": "Import-Export BVH from armature objects",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_bvh.html",
14 "support": 'OFFICIAL',
15 "category": "Import-Export",
18 if "bpy" in locals():
19 import importlib
20 if "import_bvh" in locals():
21 importlib.reload(import_bvh)
22 if "export_bvh" in locals():
23 importlib.reload(export_bvh)
25 import bpy
26 from bpy.props import (
27 StringProperty,
28 FloatProperty,
29 IntProperty,
30 BoolProperty,
31 EnumProperty,
33 from bpy_extras.io_utils import (
34 ImportHelper,
35 ExportHelper,
36 orientation_helper,
37 axis_conversion,
41 @orientation_helper(axis_forward='-Z', axis_up='Y')
42 class ImportBVH(bpy.types.Operator, ImportHelper):
43 """Load a BVH motion capture file"""
44 bl_idname = "import_anim.bvh"
45 bl_label = "Import BVH"
46 bl_options = {'REGISTER', 'UNDO'}
48 filename_ext = ".bvh"
49 filter_glob: StringProperty(default="*.bvh", options={'HIDDEN'})
51 target: EnumProperty(
52 items=(
53 ('ARMATURE', "Armature", ""),
54 ('OBJECT', "Object", ""),
56 name="Target",
57 description="Import target type",
58 default='ARMATURE',
60 global_scale: FloatProperty(
61 name="Scale",
62 description="Scale the BVH by this value",
63 min=0.0001, max=1000000.0,
64 soft_min=0.001, soft_max=100.0,
65 default=1.0,
67 frame_start: IntProperty(
68 name="Start Frame",
69 description="Starting frame for the animation",
70 default=1,
72 use_fps_scale: BoolProperty(
73 name="Scale FPS",
74 description=(
75 "Scale the framerate from the BVH to the current scenes, "
76 "otherwise each BVH frame maps directly to a Blender frame"
78 default=False,
80 update_scene_fps: BoolProperty(
81 name="Update Scene FPS",
82 description=(
83 "Set the scene framerate to that of the BVH file (note that this "
84 "nullifies the 'Scale FPS' option, as the scale will be 1:1)"
86 default=False,
88 update_scene_duration: BoolProperty(
89 name="Update Scene Duration",
90 description="Extend the scene's duration to the BVH duration (never shortens the scene)",
91 default=False,
93 use_cyclic: BoolProperty(
94 name="Loop",
95 description="Loop the animation playback",
96 default=False,
98 rotate_mode: EnumProperty(
99 name="Rotation",
100 description="Rotation conversion",
101 items=(
102 ('QUATERNION', "Quaternion",
103 "Convert rotations to quaternions"),
104 ('NATIVE', "Euler (Native)",
105 "Use the rotation order defined in the BVH file"),
106 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
107 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
108 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
109 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
110 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
111 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
113 default='NATIVE',
116 def execute(self, context):
117 keywords = self.as_keywords(
118 ignore=(
119 "axis_forward",
120 "axis_up",
121 "filter_glob",
124 global_matrix = axis_conversion(
125 from_forward=self.axis_forward,
126 from_up=self.axis_up,
127 ).to_4x4()
129 keywords["global_matrix"] = global_matrix
131 from . import import_bvh
132 return import_bvh.load(context, report=self.report, **keywords)
134 def draw(self, context):
135 pass
138 class BVH_PT_import_main(bpy.types.Panel):
139 bl_space_type = 'FILE_BROWSER'
140 bl_region_type = 'TOOL_PROPS'
141 bl_label = ""
142 bl_parent_id = "FILE_PT_operator"
143 bl_options = {'HIDE_HEADER'}
146 @classmethod
147 def poll(cls, context):
148 sfile = context.space_data
149 operator = sfile.active_operator
151 return operator.bl_idname == "IMPORT_ANIM_OT_bvh"
153 def draw(self, context):
154 layout = self.layout
155 layout.use_property_split = True
156 layout.use_property_decorate = False # No animation.
158 sfile = context.space_data
159 operator = sfile.active_operator
161 layout.prop(operator, "target")
164 class BVH_PT_import_transform(bpy.types.Panel):
165 bl_space_type = 'FILE_BROWSER'
166 bl_region_type = 'TOOL_PROPS'
167 bl_label = "Transform"
168 bl_parent_id = "FILE_PT_operator"
170 @classmethod
171 def poll(cls, context):
172 sfile = context.space_data
173 operator = sfile.active_operator
175 return operator.bl_idname == "IMPORT_ANIM_OT_bvh"
177 def draw(self, context):
178 layout = self.layout
179 layout.use_property_split = True
180 layout.use_property_decorate = False # No animation.
182 sfile = context.space_data
183 operator = sfile.active_operator
185 layout.prop(operator, "global_scale")
186 layout.prop(operator, "rotate_mode")
187 layout.prop(operator, "axis_forward")
188 layout.prop(operator, "axis_up")
191 class BVH_PT_import_animation(bpy.types.Panel):
192 bl_space_type = 'FILE_BROWSER'
193 bl_region_type = 'TOOL_PROPS'
194 bl_label = "Animation"
195 bl_parent_id = "FILE_PT_operator"
197 @classmethod
198 def poll(cls, context):
199 sfile = context.space_data
200 operator = sfile.active_operator
202 return operator.bl_idname == "IMPORT_ANIM_OT_bvh"
204 def draw(self, context):
205 layout = self.layout
206 layout.use_property_split = True
207 layout.use_property_decorate = False # No animation.
209 sfile = context.space_data
210 operator = sfile.active_operator
212 layout.prop(operator, "frame_start")
213 layout.prop(operator, "use_fps_scale")
214 layout.prop(operator, "use_cyclic")
216 layout.prop(operator, "update_scene_fps")
217 layout.prop(operator, "update_scene_duration")
220 class ExportBVH(bpy.types.Operator, ExportHelper):
221 """Save a BVH motion capture file from an armature"""
222 bl_idname = "export_anim.bvh"
223 bl_label = "Export BVH"
225 filename_ext = ".bvh"
226 filter_glob: StringProperty(
227 default="*.bvh",
228 options={'HIDDEN'},
231 global_scale: FloatProperty(
232 name="Scale",
233 description="Scale the BVH by this value",
234 min=0.0001, max=1000000.0,
235 soft_min=0.001, soft_max=100.0,
236 default=1.0,
238 frame_start: IntProperty(
239 name="Start Frame",
240 description="Starting frame to export",
241 default=0,
243 frame_end: IntProperty(
244 name="End Frame",
245 description="End frame to export",
246 default=0,
248 rotate_mode: EnumProperty(
249 name="Rotation",
250 description="Rotation conversion",
251 items=(
252 ('NATIVE', "Euler (Native)",
253 "Use the rotation order defined in the BVH file"),
254 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
255 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
256 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
257 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
258 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
259 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
261 default='NATIVE',
263 root_transform_only: BoolProperty(
264 name="Root Translation Only",
265 description="Only write out translation channels for the root bone",
266 default=False,
269 @classmethod
270 def poll(cls, context):
271 obj = context.object
272 return obj and obj.type == 'ARMATURE'
274 def invoke(self, context, event):
275 self.frame_start = context.scene.frame_start
276 self.frame_end = context.scene.frame_end
278 return super().invoke(context, event)
280 def execute(self, context):
281 if self.frame_start == 0 and self.frame_end == 0:
282 self.frame_start = context.scene.frame_start
283 self.frame_end = context.scene.frame_end
285 keywords = self.as_keywords(
286 ignore=(
287 "axis_forward",
288 "axis_up",
289 "check_existing",
290 "filter_glob",
294 from . import export_bvh
295 return export_bvh.save(context, **keywords)
297 def draw(self, context):
298 pass
301 class BVH_PT_export_transform(bpy.types.Panel):
302 bl_space_type = 'FILE_BROWSER'
303 bl_region_type = 'TOOL_PROPS'
304 bl_label = "Transform"
305 bl_parent_id = "FILE_PT_operator"
307 @classmethod
308 def poll(cls, context):
309 sfile = context.space_data
310 operator = sfile.active_operator
312 return operator.bl_idname == "EXPORT_ANIM_OT_bvh"
314 def draw(self, context):
315 layout = self.layout
316 layout.use_property_split = True
317 layout.use_property_decorate = False # No animation.
319 sfile = context.space_data
320 operator = sfile.active_operator
322 layout.prop(operator, "global_scale")
323 layout.prop(operator, "rotate_mode")
324 layout.prop(operator, "root_transform_only")
327 class BVH_PT_export_animation(bpy.types.Panel):
328 bl_space_type = 'FILE_BROWSER'
329 bl_region_type = 'TOOL_PROPS'
330 bl_label = "Animation"
331 bl_parent_id = "FILE_PT_operator"
333 @classmethod
334 def poll(cls, context):
335 sfile = context.space_data
336 operator = sfile.active_operator
338 return operator.bl_idname == "EXPORT_ANIM_OT_bvh"
340 def draw(self, context):
341 layout = self.layout
342 layout.use_property_split = True
343 layout.use_property_decorate = False # No animation.
345 sfile = context.space_data
346 operator = sfile.active_operator
348 col = layout.column(align=True)
349 col.prop(operator, "frame_start", text="Frame Start")
350 col.prop(operator, "frame_end", text="End")
353 def menu_func_import(self, context):
354 self.layout.operator(ImportBVH.bl_idname, text="Motion Capture (.bvh)")
357 def menu_func_export(self, context):
358 self.layout.operator(ExportBVH.bl_idname, text="Motion Capture (.bvh)")
361 classes = (
362 ImportBVH,
363 BVH_PT_import_main,
364 BVH_PT_import_transform,
365 BVH_PT_import_animation,
366 ExportBVH,
367 BVH_PT_export_transform,
368 BVH_PT_export_animation,
371 def register():
372 for cls in classes:
373 bpy.utils.register_class(cls)
375 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
376 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
379 def unregister():
380 for cls in classes:
381 bpy.utils.unregister_class(cls)
383 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
384 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
386 if __name__ == "__main__":
387 register()