Merge branch 'blender-v2.92-release'
[blender-addons.git] / io_anim_bvh / __init__.py
blob3a17632eda160e82bfeca4e61ea6d293c1d66029
1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
19 # <pep8-80 compliant>
21 bl_info = {
22 "name": "BioVision Motion Capture (BVH) format",
23 "author": "Campbell Barton",
24 "version": (1, 0, 0),
25 "blender": (2, 81, 6),
26 "location": "File > Import-Export",
27 "description": "Import-Export BVH from armature objects",
28 "warning": "",
29 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_bvh.html",
30 "support": 'OFFICIAL',
31 "category": "Import-Export",
34 if "bpy" in locals():
35 import importlib
36 if "import_bvh" in locals():
37 importlib.reload(import_bvh)
38 if "export_bvh" in locals():
39 importlib.reload(export_bvh)
41 import bpy
42 from bpy.props import (
43 StringProperty,
44 FloatProperty,
45 IntProperty,
46 BoolProperty,
47 EnumProperty,
49 from bpy_extras.io_utils import (
50 ImportHelper,
51 ExportHelper,
52 orientation_helper,
53 axis_conversion,
57 @orientation_helper(axis_forward='-Z', axis_up='Y')
58 class ImportBVH(bpy.types.Operator, ImportHelper):
59 """Load a BVH motion capture file"""
60 bl_idname = "import_anim.bvh"
61 bl_label = "Import BVH"
62 bl_options = {'REGISTER', 'UNDO'}
64 filename_ext = ".bvh"
65 filter_glob: StringProperty(default="*.bvh", options={'HIDDEN'})
67 target: EnumProperty(
68 items=(
69 ('ARMATURE', "Armature", ""),
70 ('OBJECT', "Object", ""),
72 name="Target",
73 description="Import target type",
74 default='ARMATURE',
76 global_scale: FloatProperty(
77 name="Scale",
78 description="Scale the BVH by this value",
79 min=0.0001, max=1000000.0,
80 soft_min=0.001, soft_max=100.0,
81 default=1.0,
83 frame_start: IntProperty(
84 name="Start Frame",
85 description="Starting frame for the animation",
86 default=1,
88 use_fps_scale: BoolProperty(
89 name="Scale FPS",
90 description=(
91 "Scale the framerate from the BVH to the current scenes, "
92 "otherwise each BVH frame maps directly to a Blender frame"
94 default=False,
96 update_scene_fps: BoolProperty(
97 name="Update Scene FPS",
98 description=(
99 "Set the scene framerate to that of the BVH file (note that this "
100 "nullifies the 'Scale FPS' option, as the scale will be 1:1)"
102 default=False,
104 update_scene_duration: BoolProperty(
105 name="Update Scene Duration",
106 description="Extend the scene's duration to the BVH duration (never shortens the scene)",
107 default=False,
109 use_cyclic: BoolProperty(
110 name="Loop",
111 description="Loop the animation playback",
112 default=False,
114 rotate_mode: EnumProperty(
115 name="Rotation",
116 description="Rotation conversion",
117 items=(
118 ('QUATERNION', "Quaternion",
119 "Convert rotations to quaternions"),
120 ('NATIVE', "Euler (Native)",
121 "Use the rotation order defined in the BVH file"),
122 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
123 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
124 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
125 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
126 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
127 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
129 default='NATIVE',
132 def execute(self, context):
133 keywords = self.as_keywords(
134 ignore=(
135 "axis_forward",
136 "axis_up",
137 "filter_glob",
140 global_matrix = axis_conversion(
141 from_forward=self.axis_forward,
142 from_up=self.axis_up,
143 ).to_4x4()
145 keywords["global_matrix"] = global_matrix
147 from . import import_bvh
148 return import_bvh.load(context, report=self.report, **keywords)
150 def draw(self, context):
151 pass
154 class BVH_PT_import_main(bpy.types.Panel):
155 bl_space_type = 'FILE_BROWSER'
156 bl_region_type = 'TOOL_PROPS'
157 bl_label = ""
158 bl_parent_id = "FILE_PT_operator"
159 bl_options = {'HIDE_HEADER'}
162 @classmethod
163 def poll(cls, context):
164 sfile = context.space_data
165 operator = sfile.active_operator
167 return operator.bl_idname == "IMPORT_ANIM_OT_bvh"
169 def draw(self, context):
170 layout = self.layout
171 layout.use_property_split = True
172 layout.use_property_decorate = False # No animation.
174 sfile = context.space_data
175 operator = sfile.active_operator
177 layout.prop(operator, "target")
180 class BVH_PT_import_transform(bpy.types.Panel):
181 bl_space_type = 'FILE_BROWSER'
182 bl_region_type = 'TOOL_PROPS'
183 bl_label = "Transform"
184 bl_parent_id = "FILE_PT_operator"
186 @classmethod
187 def poll(cls, context):
188 sfile = context.space_data
189 operator = sfile.active_operator
191 return operator.bl_idname == "IMPORT_ANIM_OT_bvh"
193 def draw(self, context):
194 layout = self.layout
195 layout.use_property_split = True
196 layout.use_property_decorate = False # No animation.
198 sfile = context.space_data
199 operator = sfile.active_operator
201 layout.prop(operator, "global_scale")
202 layout.prop(operator, "rotate_mode")
203 layout.prop(operator, "axis_forward")
204 layout.prop(operator, "axis_up")
207 class BVH_PT_import_animation(bpy.types.Panel):
208 bl_space_type = 'FILE_BROWSER'
209 bl_region_type = 'TOOL_PROPS'
210 bl_label = "Animation"
211 bl_parent_id = "FILE_PT_operator"
213 @classmethod
214 def poll(cls, context):
215 sfile = context.space_data
216 operator = sfile.active_operator
218 return operator.bl_idname == "IMPORT_ANIM_OT_bvh"
220 def draw(self, context):
221 layout = self.layout
222 layout.use_property_split = True
223 layout.use_property_decorate = False # No animation.
225 sfile = context.space_data
226 operator = sfile.active_operator
228 layout.prop(operator, "frame_start")
229 layout.prop(operator, "use_fps_scale")
230 layout.prop(operator, "use_cyclic")
232 layout.prop(operator, "update_scene_fps")
233 layout.prop(operator, "update_scene_duration")
236 class ExportBVH(bpy.types.Operator, ExportHelper):
237 """Save a BVH motion capture file from an armature"""
238 bl_idname = "export_anim.bvh"
239 bl_label = "Export BVH"
241 filename_ext = ".bvh"
242 filter_glob: StringProperty(
243 default="*.bvh",
244 options={'HIDDEN'},
247 global_scale: FloatProperty(
248 name="Scale",
249 description="Scale the BVH by this value",
250 min=0.0001, max=1000000.0,
251 soft_min=0.001, soft_max=100.0,
252 default=1.0,
254 frame_start: IntProperty(
255 name="Start Frame",
256 description="Starting frame to export",
257 default=0,
259 frame_end: IntProperty(
260 name="End Frame",
261 description="End frame to export",
262 default=0,
264 rotate_mode: EnumProperty(
265 name="Rotation",
266 description="Rotation conversion",
267 items=(
268 ('NATIVE', "Euler (Native)",
269 "Use the rotation order defined in the BVH file"),
270 ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
271 ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
272 ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
273 ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
274 ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
275 ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
277 default='NATIVE',
279 root_transform_only: BoolProperty(
280 name="Root Translation Only",
281 description="Only write out translation channels for the root bone",
282 default=False,
285 @classmethod
286 def poll(cls, context):
287 obj = context.object
288 return obj and obj.type == 'ARMATURE'
290 def invoke(self, context, event):
291 self.frame_start = context.scene.frame_start
292 self.frame_end = context.scene.frame_end
294 return super().invoke(context, event)
296 def execute(self, context):
297 if self.frame_start == 0 and self.frame_end == 0:
298 self.frame_start = context.scene.frame_start
299 self.frame_end = context.scene.frame_end
301 keywords = self.as_keywords(
302 ignore=(
303 "axis_forward",
304 "axis_up",
305 "check_existing",
306 "filter_glob",
310 from . import export_bvh
311 return export_bvh.save(context, **keywords)
313 def draw(self, context):
314 pass
317 class BVH_PT_export_transform(bpy.types.Panel):
318 bl_space_type = 'FILE_BROWSER'
319 bl_region_type = 'TOOL_PROPS'
320 bl_label = "Transform"
321 bl_parent_id = "FILE_PT_operator"
323 @classmethod
324 def poll(cls, context):
325 sfile = context.space_data
326 operator = sfile.active_operator
328 return operator.bl_idname == "EXPORT_ANIM_OT_bvh"
330 def draw(self, context):
331 layout = self.layout
332 layout.use_property_split = True
333 layout.use_property_decorate = False # No animation.
335 sfile = context.space_data
336 operator = sfile.active_operator
338 layout.prop(operator, "global_scale")
339 layout.prop(operator, "rotate_mode")
340 layout.prop(operator, "root_transform_only")
343 class BVH_PT_export_animation(bpy.types.Panel):
344 bl_space_type = 'FILE_BROWSER'
345 bl_region_type = 'TOOL_PROPS'
346 bl_label = "Animation"
347 bl_parent_id = "FILE_PT_operator"
349 @classmethod
350 def poll(cls, context):
351 sfile = context.space_data
352 operator = sfile.active_operator
354 return operator.bl_idname == "EXPORT_ANIM_OT_bvh"
356 def draw(self, context):
357 layout = self.layout
358 layout.use_property_split = True
359 layout.use_property_decorate = False # No animation.
361 sfile = context.space_data
362 operator = sfile.active_operator
364 col = layout.column(align=True)
365 col.prop(operator, "frame_start", text="Frame Start")
366 col.prop(operator, "frame_end", text="End")
369 def menu_func_import(self, context):
370 self.layout.operator(ImportBVH.bl_idname, text="Motion Capture (.bvh)")
373 def menu_func_export(self, context):
374 self.layout.operator(ExportBVH.bl_idname, text="Motion Capture (.bvh)")
377 classes = (
378 ImportBVH,
379 BVH_PT_import_main,
380 BVH_PT_import_transform,
381 BVH_PT_import_animation,
382 ExportBVH,
383 BVH_PT_export_transform,
384 BVH_PT_export_animation,
387 def register():
388 for cls in classes:
389 bpy.utils.register_class(cls)
391 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
392 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
395 def unregister():
396 for cls in classes:
397 bpy.utils.unregister_class(cls)
399 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
400 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
402 if __name__ == "__main__":
403 register()