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 #####
22 "name": "BioVision Motion Capture (BVH) format",
23 "author": "Campbell Barton",
25 "blender": (2, 81, 6),
26 "location": "File > Import-Export",
27 "description": "Import-Export BVH from armature objects",
29 "doc_url": "{BLENDER_MANUAL_URL}/addons/import_export/anim_bvh.html",
30 "support": 'OFFICIAL',
31 "category": "Import-Export",
36 if "import_bvh" in locals():
37 importlib
.reload(import_bvh
)
38 if "export_bvh" in locals():
39 importlib
.reload(export_bvh
)
42 from bpy
.props
import (
49 from bpy_extras
.io_utils
import (
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'}
65 filter_glob
: StringProperty(default
="*.bvh", options
={'HIDDEN'})
69 ('ARMATURE', "Armature", ""),
70 ('OBJECT', "Object", ""),
73 description
="Import target type",
76 global_scale
: FloatProperty(
78 description
="Scale the BVH by this value",
79 min=0.0001, max=1000000.0,
80 soft_min
=0.001, soft_max
=100.0,
83 frame_start
: IntProperty(
85 description
="Starting frame for the animation",
88 use_fps_scale
: BoolProperty(
91 "Scale the framerate from the BVH to the current scenes, "
92 "otherwise each BVH frame maps directly to a Blender frame"
96 update_scene_fps
: BoolProperty(
97 name
="Update Scene FPS",
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)"
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)",
109 use_cyclic
: BoolProperty(
111 description
="Loop the animation playback",
114 rotate_mode
: EnumProperty(
116 description
="Rotation conversion",
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"),
132 def execute(self
, context
):
133 keywords
= self
.as_keywords(
140 global_matrix
= axis_conversion(
141 from_forward
=self
.axis_forward
,
142 from_up
=self
.axis_up
,
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
):
154 class BVH_PT_import_main(bpy
.types
.Panel
):
155 bl_space_type
= 'FILE_BROWSER'
156 bl_region_type
= 'TOOL_PROPS'
158 bl_parent_id
= "FILE_PT_operator"
159 bl_options
= {'HIDE_HEADER'}
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
):
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"
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
):
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"
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
):
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(
247 global_scale
: FloatProperty(
249 description
="Scale the BVH by this value",
250 min=0.0001, max=1000000.0,
251 soft_min
=0.001, soft_max
=100.0,
254 frame_start
: IntProperty(
256 description
="Starting frame to export",
259 frame_end
: IntProperty(
261 description
="End frame to export",
264 rotate_mode
: EnumProperty(
266 description
="Rotation conversion",
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"),
279 root_transform_only
: BoolProperty(
280 name
="Root Translation Only",
281 description
="Only write out translation channels for the root bone",
286 def poll(cls
, context
):
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(
310 from . import export_bvh
311 return export_bvh
.save(context
, **keywords
)
313 def draw(self
, context
):
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"
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
):
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"
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
):
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)")
380 BVH_PT_import_transform
,
381 BVH_PT_import_animation
,
383 BVH_PT_export_transform
,
384 BVH_PT_export_animation
,
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
)
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__":