1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Script copyright (C) Campbell Barton
4 # fixes from Andrea Rugliancich
16 root_transform_only
=False,
19 def ensure_rot_order(rot_order_str
):
20 if set(rot_order_str
) != {'X', 'Y', 'Z'}:
24 from mathutils
import Matrix
, Euler
25 from math
import degrees
27 file = open(filepath
, "w", encoding
="utf8", newline
="\n")
32 # Build a dictionary of children.
36 # initialize with blank lists
37 for bone
in arm
.bones
:
38 children
[bone
.name
] = []
40 # keep bone order from armature, no sorting, not esspential but means
41 # we can maintain order from import -> export which secondlife incorrectly expects.
42 for bone
in arm
.bones
:
43 children
[getattr(bone
.parent
, "name", None)].append(bone
.name
)
45 # bone name list in the order that the bones are written
50 file.write("HIERARCHY\n")
52 def write_recursive_nodes(bone_name
, indent
):
53 my_children
= children
[bone_name
]
55 indent_str
= "\t" * indent
57 bone
= arm
.bones
[bone_name
]
58 pose_bone
= obj
.pose
.bones
[bone_name
]
60 node_locations
[bone_name
] = loc
62 if rotate_mode
== "NATIVE":
63 rot_order_str
= ensure_rot_order(pose_bone
.rotation_mode
)
65 rot_order_str
= rotate_mode
67 # make relative if we can
69 loc
= loc
- node_locations
[bone
.parent
.name
]
72 file.write("%sJOINT %s\n" % (indent_str
, bone_name
))
74 file.write("%sROOT %s\n" % (indent_str
, bone_name
))
76 file.write("%s{\n" % indent_str
)
77 file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, *(loc
* global_scale
)))
78 if (bone
.use_connect
or root_transform_only
) and bone
.parent
:
79 file.write("%s\tCHANNELS 3 %srotation %srotation %srotation\n" % (indent_str
, *rot_order_str
))
81 file.write("%s\tCHANNELS 6 Xposition Yposition Zposition %srotation %srotation %srotation\n" % (indent_str
, *rot_order_str
))
84 # store the location for the children
85 # to get their relative offset
88 for child_bone
in my_children
:
89 serialized_names
.append(child_bone
)
90 write_recursive_nodes(child_bone
, indent
+ 1)
94 file.write("%s\tEnd Site\n" % indent_str
)
95 file.write("%s\t{\n" % indent_str
)
96 loc
= bone
.tail_local
- node_locations
[bone_name
]
97 file.write("%s\t\tOFFSET %.6f %.6f %.6f\n" % (indent_str
, *(loc
* global_scale
)))
98 file.write("%s\t}\n" % indent_str
)
100 file.write("%s}\n" % indent_str
)
102 if len(children
[None]) == 1:
103 key
= children
[None][0]
104 serialized_names
.append(key
)
107 write_recursive_nodes(key
, indent
)
110 # Write a dummy parent node, with a dummy key name
111 # Just be sure it's not used by another bone!
114 while key
in children
:
117 file.write("ROOT %s\n" % key
)
119 file.write("\tOFFSET 0.0 0.0 0.0\n")
120 file.write("\tCHANNELS 0\n") # Xposition Yposition Zposition Xrotation Yrotation Zrotation
124 for child_bone
in children
[None]:
125 serialized_names
.append(child_bone
)
126 write_recursive_nodes(child_bone
, indent
)
130 # redefine bones as sorted by serialized_names
131 # so we can write motion
135 # Bone name, used as key in many places.
137 "parent", # decorated bone parent, set in a later loop
138 # Blender armature bone.
142 # Blender pose matrix.
144 # Blender rest matrix (armature space).
146 # Blender rest matrix (local space).
150 # Rest_arm_mat inverted.
152 # Rest_local_mat inverted.
154 # Last used euler to preserve euler compatibility in between keyframes.
156 # Is the bone disconnected to the parent bone?
160 # Needed for the euler order when converting from a matrix.
161 "rot_order_str_reverse",
164 _eul_order_lookup
= {
173 def __init__(self
, bone_name
):
174 self
.name
= bone_name
175 self
.rest_bone
= arm
.bones
[bone_name
]
176 self
.pose_bone
= obj
.pose
.bones
[bone_name
]
178 if rotate_mode
== "NATIVE":
179 self
.rot_order_str
= ensure_rot_order(self
.pose_bone
.rotation_mode
)
181 self
.rot_order_str
= rotate_mode
182 self
.rot_order_str_reverse
= self
.rot_order_str
[::-1]
184 self
.rot_order
= DecoratedBone
._eul
_order
_lookup
[self
.rot_order_str
]
186 self
.pose_mat
= self
.pose_bone
.matrix
188 # mat = self.rest_bone.matrix # UNUSED
189 self
.rest_arm_mat
= self
.rest_bone
.matrix_local
190 self
.rest_local_mat
= self
.rest_bone
.matrix
193 self
.pose_imat
= self
.pose_mat
.inverted()
194 self
.rest_arm_imat
= self
.rest_arm_mat
.inverted()
195 self
.rest_local_imat
= self
.rest_local_mat
.inverted()
198 self
.prev_euler
= Euler((0.0, 0.0, 0.0), self
.rot_order_str_reverse
)
199 self
.skip_position
= ((self
.rest_bone
.use_connect
or root_transform_only
) and self
.rest_bone
.parent
)
201 def update_posedata(self
):
202 self
.pose_mat
= self
.pose_bone
.matrix
203 self
.pose_imat
= self
.pose_mat
.inverted()
207 return "[\"%s\" child on \"%s\"]\n" % (self
.name
, self
.parent
.name
)
209 return "[\"%s\" root bone]\n" % (self
.name
)
211 bones_decorated
= [DecoratedBone(bone_name
) for bone_name
in serialized_names
]
214 bones_decorated_dict
= {dbone
.name
: dbone
for dbone
in bones_decorated
}
215 for dbone
in bones_decorated
:
216 parent
= dbone
.rest_bone
.parent
218 dbone
.parent
= bones_decorated_dict
[parent
.name
]
219 del bones_decorated_dict
220 # finish assigning parents
222 scene
= context
.scene
223 frame_current
= scene
.frame_current
225 file.write("MOTION\n")
226 file.write("Frames: %d\n" % (frame_end
- frame_start
+ 1))
227 file.write("Frame Time: %.6f\n" % (1.0 / (scene
.render
.fps
/ scene
.render
.fps_base
)))
229 for frame
in range(frame_start
, frame_end
+ 1):
230 scene
.frame_set(frame
)
232 for dbone
in bones_decorated
:
233 dbone
.update_posedata()
235 for dbone
in bones_decorated
:
236 trans
= Matrix
.Translation(dbone
.rest_bone
.head_local
)
237 itrans
= Matrix
.Translation(-dbone
.rest_bone
.head_local
)
240 mat_final
= dbone
.parent
.rest_arm_mat
@ dbone
.parent
.pose_imat
@ dbone
.pose_mat
@ dbone
.rest_arm_imat
241 mat_final
= itrans
@ mat_final
@ trans
242 loc
= mat_final
.to_translation() + (dbone
.rest_bone
.head_local
- dbone
.parent
.rest_bone
.head_local
)
244 mat_final
= dbone
.pose_mat
@ dbone
.rest_arm_imat
245 mat_final
= itrans
@ mat_final
@ trans
246 loc
= mat_final
.to_translation() + dbone
.rest_bone
.head
248 # keep eulers compatible, no jumping on interpolation.
249 rot
= mat_final
.to_euler(dbone
.rot_order_str_reverse
, dbone
.prev_euler
)
251 if not dbone
.skip_position
:
252 file.write("%.6f %.6f %.6f " % (loc
* global_scale
)[:])
254 file.write("%.6f %.6f %.6f " % (degrees(rot
[dbone
.rot_order
[0]]), degrees(rot
[dbone
.rot_order
[1]]), degrees(rot
[dbone
.rot_order
[2]])))
256 dbone
.prev_euler
= rot
262 scene
.frame_set(frame_current
)
264 print("BVH Exported: %s frames:%d\n" % (filepath
, frame_end
- frame_start
+ 1))
268 context
, filepath
="",
272 rotate_mode
="NATIVE",
273 root_transform_only
=False,
277 frame_start
=frame_start
,
279 global_scale
=global_scale
,
280 rotate_mode
=rotate_mode
,
281 root_transform_only
=root_transform_only
,