1 # SPDX-FileCopyrightText: 2011 Campbell Barton
3 # SPDX-License-Identifier: GPL-2.0-or-later
15 root_transform_only
=False,
18 def ensure_rot_order(rot_order_str
):
19 if set(rot_order_str
) != {'X', 'Y', 'Z'}:
23 from mathutils
import Matrix
, Euler
24 from math
import degrees
26 file = open(filepath
, "w", encoding
="utf8", newline
="\n")
31 # Build a dictionary of children.
35 # initialize with blank lists
36 for bone
in arm
.bones
:
37 children
[bone
.name
] = []
39 # keep bone order from armature, no sorting, not esspential but means
40 # we can maintain order from import -> export which secondlife incorrectly expects.
41 for bone
in arm
.bones
:
42 children
[getattr(bone
.parent
, "name", None)].append(bone
.name
)
44 # bone name list in the order that the bones are written
49 file.write("HIERARCHY\n")
51 def write_recursive_nodes(bone_name
, indent
):
52 my_children
= children
[bone_name
]
54 indent_str
= "\t" * indent
56 bone
= arm
.bones
[bone_name
]
57 pose_bone
= obj
.pose
.bones
[bone_name
]
59 node_locations
[bone_name
] = loc
61 if rotate_mode
== "NATIVE":
62 rot_order_str
= ensure_rot_order(pose_bone
.rotation_mode
)
64 rot_order_str
= rotate_mode
66 # make relative if we can
68 loc
= loc
- node_locations
[bone
.parent
.name
]
71 file.write("%sJOINT %s\n" % (indent_str
, bone_name
))
73 file.write("%sROOT %s\n" % (indent_str
, bone_name
))
75 file.write("%s{\n" % indent_str
)
76 file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, *(loc
* global_scale
)))
77 if (bone
.use_connect
or root_transform_only
) and bone
.parent
:
78 file.write("%s\tCHANNELS 3 %srotation %srotation %srotation\n" % (indent_str
, *rot_order_str
))
81 "%s\tCHANNELS 6 Xposition Yposition Zposition %srotation %srotation %srotation\n" % (
88 # store the location for the children
89 # to get their relative offset
92 for child_bone
in my_children
:
93 serialized_names
.append(child_bone
)
94 write_recursive_nodes(child_bone
, indent
+ 1)
98 file.write("%s\tEnd Site\n" % indent_str
)
99 file.write("%s\t{\n" % indent_str
)
100 loc
= bone
.tail_local
- node_locations
[bone_name
]
101 file.write("%s\t\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, *(loc
* global_scale
)))
102 file.write("%s\t}\n" % indent_str
)
104 file.write("%s}\n" % indent_str
)
106 if len(children
[None]) == 1:
107 key
= children
[None][0]
108 serialized_names
.append(key
)
111 write_recursive_nodes(key
, indent
)
114 # Write a dummy parent node, with a dummy key name
115 # Just be sure it's not used by another bone!
118 while key
in children
:
121 file.write("ROOT %s\n" % key
)
123 file.write("\tOFFSET 0.0 0.0 0.0\n")
124 file.write("\tCHANNELS 0\n") # Xposition Yposition Zposition Xrotation Yrotation Zrotation
128 for child_bone
in children
[None]:
129 serialized_names
.append(child_bone
)
130 write_recursive_nodes(child_bone
, indent
)
134 # redefine bones as sorted by serialized_names
135 # so we can write motion
139 # Bone name, used as key in many places.
141 "parent", # decorated bone parent, set in a later loop
142 # Blender armature bone.
146 # Blender pose matrix.
148 # Blender rest matrix (armature space).
150 # Blender rest matrix (local space).
154 # Rest_arm_mat inverted.
156 # Rest_local_mat inverted.
158 # Last used euler to preserve euler compatibility in between keyframes.
160 # Is the bone disconnected to the parent bone?
164 # Needed for the euler order when converting from a matrix.
165 "rot_order_str_reverse",
168 _eul_order_lookup
= {
177 def __init__(self
, bone_name
):
178 self
.name
= bone_name
179 self
.rest_bone
= arm
.bones
[bone_name
]
180 self
.pose_bone
= obj
.pose
.bones
[bone_name
]
182 if rotate_mode
== "NATIVE":
183 self
.rot_order_str
= ensure_rot_order(self
.pose_bone
.rotation_mode
)
185 self
.rot_order_str
= rotate_mode
186 self
.rot_order_str_reverse
= self
.rot_order_str
[::-1]
188 self
.rot_order
= DecoratedBone
._eul
_order
_lookup
[self
.rot_order_str
]
190 self
.pose_mat
= self
.pose_bone
.matrix
192 # mat = self.rest_bone.matrix # UNUSED
193 self
.rest_arm_mat
= self
.rest_bone
.matrix_local
194 self
.rest_local_mat
= self
.rest_bone
.matrix
197 self
.pose_imat
= self
.pose_mat
.inverted()
198 self
.rest_arm_imat
= self
.rest_arm_mat
.inverted()
199 self
.rest_local_imat
= self
.rest_local_mat
.inverted()
202 self
.prev_euler
= Euler((0.0, 0.0, 0.0), self
.rot_order_str_reverse
)
203 self
.skip_position
= ((self
.rest_bone
.use_connect
or root_transform_only
) and self
.rest_bone
.parent
)
205 def update_posedata(self
):
206 self
.pose_mat
= self
.pose_bone
.matrix
207 self
.pose_imat
= self
.pose_mat
.inverted()
211 return "[\"%s\" child on \"%s\"]\n" % (self
.name
, self
.parent
.name
)
213 return "[\"%s\" root bone]\n" % (self
.name
)
215 bones_decorated
= [DecoratedBone(bone_name
) for bone_name
in serialized_names
]
218 bones_decorated_dict
= {dbone
.name
: dbone
for dbone
in bones_decorated
}
219 for dbone
in bones_decorated
:
220 parent
= dbone
.rest_bone
.parent
222 dbone
.parent
= bones_decorated_dict
[parent
.name
]
223 del bones_decorated_dict
224 # finish assigning parents
226 scene
= context
.scene
227 frame_current
= scene
.frame_current
229 file.write("MOTION\n")
230 file.write("Frames: %d\n" % (frame_end
- frame_start
+ 1))
231 file.write("Frame Time: %.6f\n" % (1.0 / (scene
.render
.fps
/ scene
.render
.fps_base
)))
233 for frame
in range(frame_start
, frame_end
+ 1):
234 scene
.frame_set(frame
)
236 for dbone
in bones_decorated
:
237 dbone
.update_posedata()
239 for dbone
in bones_decorated
:
240 trans
= Matrix
.Translation(dbone
.rest_bone
.head_local
)
241 itrans
= Matrix
.Translation(-dbone
.rest_bone
.head_local
)
244 mat_final
= dbone
.parent
.rest_arm_mat
@ dbone
.parent
.pose_imat
@ dbone
.pose_mat
@ dbone
.rest_arm_imat
245 mat_final
= itrans
@ mat_final
@ trans
246 loc
= mat_final
.to_translation() + (dbone
.rest_bone
.head_local
- dbone
.parent
.rest_bone
.head_local
)
248 mat_final
= dbone
.pose_mat
@ dbone
.rest_arm_imat
249 mat_final
= itrans
@ mat_final
@ trans
250 loc
= mat_final
.to_translation() + dbone
.rest_bone
.head
252 # keep eulers compatible, no jumping on interpolation.
253 rot
= mat_final
.to_euler(dbone
.rot_order_str_reverse
, dbone
.prev_euler
)
255 if not dbone
.skip_position
:
256 file.write("%.6f %.6f %.6f " % (loc
* global_scale
)[:])
259 "%.6f %.6f %.6f " % (
260 degrees(rot
[dbone
.rot_order
[0]]),
261 degrees(rot
[dbone
.rot_order
[1]]),
262 degrees(rot
[dbone
.rot_order
[2]]),
266 dbone
.prev_euler
= rot
272 scene
.frame_set(frame_current
)
274 print("BVH Exported: %s frames:%d\n" % (filepath
, frame_end
- frame_start
+ 1))
278 context
, filepath
="",
282 rotate_mode
="NATIVE",
283 root_transform_only
=False,
287 frame_start
=frame_start
,
289 global_scale
=global_scale
,
290 rotate_mode
=rotate_mode
,
291 root_transform_only
=root_transform_only
,