def get_bind_data(self, b_armature): """Get the required bind data of an armature. Used by standalone KF import and export. """ self.bind_data = {} if b_armature: for b_bone in b_armature.data.bones: n_bind_scale, n_bind_rot, n_bind_trans = math.decompose_srt(math.get_object_bind(b_bone)) self.bind_data[b_bone.name] = (n_bind_rot.inverted(), n_bind_trans)
def get_bind_data(b_armature): """Get the required bind data of an armature. Used by standalone KF import and export. """ if b_armature: bind_data = {} for b_bone in b_armature.data.bones: n_bone_bind_scale, n_bone_bind_rot, n_bone_bind_trans = math.decompose_srt( math.get_bind_matrix(b_bone)) bind_data[b_bone.name] = (n_bone_bind_scale, n_bone_bind_rot.inverted(), n_bone_bind_trans) return bind_data
def import_transforms(self, n_block, b_obj, bone_name=None): """Loads an animation attached to a nif block.""" # find keyframe controller n_kfc = math.find_controller(n_block, NifFormat.NiKeyframeController) # try again if not n_kfc: n_kfc = math.find_controller(n_block, NifFormat.NiTransformController) if n_kfc: # skeletal animation if bone_name: bone_bm = math.import_matrix(n_block) # base pose n_bone_bind_scale, n_bone_bind_rot, n_bone_bind_trans = math.decompose_srt( bone_bm) self.import_keyframe_controller(n_kfc, b_obj, bone_name, n_bone_bind_scale, n_bone_bind_rot.inverted(), n_bone_bind_trans) # object-level animation else: self.create_action(b_obj, b_obj.name + "-Anim") self.import_keyframe_controller(n_kfc, b_obj)
def export_transforms(self, parent_block, b_obj, b_action, bone=None): """ If bone == None, object level animation is exported. If a bone is given, skeletal animation is exported. """ # b_action may be None, then nothing is done. if not b_action: return # blender object must exist assert b_obj # if a bone is given, b_obj must be an armature if bone: assert type(b_obj.data) == bpy.types.Armature # just for more detailed error reporting later on bonestr = "" # skeletal animation - with bone correction & coordinate corrections if bone and bone.name in b_action.groups: # get bind matrix for bone or object bind_matrix = math.get_object_bind(bone) exp_fcurves = b_action.groups[bone.name].channels # just for more detailed error reporting later on bonestr = " in bone " + bone.name target_name = block_store.get_full_name(bone) priority = bone.niftools.priority # object level animation - no coordinate corrections elif not bone: # raise error on any objects parented to bones if b_obj.parent and b_obj.parent_type == "BONE": raise io_scene_niftools.utils.logging.NifError( "{} is parented to a bone AND has animations. The nif format does not support this!" .format(b_obj.name)) target_name = block_store.get_full_name(b_obj) priority = 0 # we have either a root object (Scene Root), in which case we take the coordinates without modification # or a generic object parented to an empty = node # objects may have an offset from their parent that is not apparent in the user input (ie. UI values and keyframes) # we want to export matrix_local, and the keyframes are in matrix_basis, so do: # matrix_local = matrix_parent_inverse * matrix_basis bind_matrix = b_obj.matrix_parent_inverse exp_fcurves = [ fcu for fcu in b_action.fcurves if fcu.data_path in ("rotation_quaternion", "rotation_euler", "location", "scale") ] else: # bone isn't keyframed in this action, nothing to do here return # decompose the bind matrix bind_scale, bind_rot, bind_trans = math.decompose_srt(bind_matrix) n_kfc, n_kfi = self.create_controller(parent_block, target_name, priority) # fill in the non-trivial values start_frame, stop_frame = b_action.frame_range self.set_flags_and_timing(n_kfc, exp_fcurves, start_frame, stop_frame) # get the desired fcurves for each data type from exp_fcurves quaternions = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("quaternion") ] translations = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("location") ] eulers = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("euler") ] scales = [ fcu for fcu in exp_fcurves if fcu.data_path.endswith("scale") ] # ensure that those groups that are present have all their fcurves for fcus, num_fcus in ((quaternions, 4), (eulers, 3), (translations, 3), (scales, 3)): if fcus and len(fcus) != num_fcus: raise io_scene_niftools.utils.logging.NifError( "Incomplete key set {} for action {}. Ensure that if a bone is keyframed for a property, all channels are keyframed." .format(bonestr, b_action.name)) # go over all fcurves collected above and transform and store all their keys quat_curve = [] euler_curve = [] trans_curve = [] scale_curve = [] for frame, quat in self.iter_frame_key(quaternions, mathutils.Quaternion): quat = math.export_keymat(bind_rot, quat.to_matrix().to_4x4(), bone).to_quaternion() quat_curve.append((frame, quat)) for frame, euler in self.iter_frame_key(eulers, mathutils.Euler): keymat = math.export_keymat(bind_rot, euler.to_matrix().to_4x4(), bone) euler = keymat.to_euler("XYZ", euler) euler_curve.append((frame, euler)) for frame, trans in self.iter_frame_key(translations, mathutils.Vector): keymat = math.export_keymat(bind_rot, mathutils.Matrix.Translation(trans), bone) trans = keymat.to_translation() + bind_trans trans_curve.append((frame, trans)) for frame, scale in self.iter_frame_key(scales, mathutils.Vector): # just use the first scale curve and assume even scale over all curves scale_curve.append((frame, scale[0])) if n_kfi: if max( len(c) for c in (quat_curve, euler_curve, trans_curve, scale_curve)) > 1: # number of frames is > 1, so add transform data n_kfd = block_store.create_block("NiTransformData", exp_fcurves) n_kfi.data = n_kfd else: # only add data if number of keys is > 1 # (see importer comments with import_kf_root: a single frame # keyframe denotes an interpolator without further data) # insufficient keys, so set the data and we're done! if trans_curve: trans = trans_curve[0][1] n_kfi.translation.x, n_kfi.translation.y, n_kfi.translation.z = trans if quat_curve: quat = quat_curve[0][1] elif euler_curve: quat = euler_curve[0][1].to_quaternion() if quat_curve or euler_curve: n_kfi.rotation.x = quat.x n_kfi.rotation.y = quat.y n_kfi.rotation.z = quat.z n_kfi.rotation.w = quat.w # ignore scale for now... n_kfi.scale = 1.0 # no need to add any keys, done return else: # add the keyframe data n_kfd = block_store.create_block("NiKeyframeData", exp_fcurves) n_kfc.data = n_kfd # TODO [animation] support other interpolation modes, get interpolation from blender? # probably requires additional data like tangents and stuff # finally we can export the data calculated above if euler_curve: n_kfd.rotation_type = NifFormat.KeyType.XYZ_ROTATION_KEY n_kfd.num_rotation_keys = 1 # *NOT* len(frames) this crashes the engine! for i, coord in enumerate(n_kfd.xyz_rotations): coord.num_keys = len(euler_curve) coord.interpolation = NifFormat.KeyType.LINEAR_KEY coord.keys.update_size() for key, (frame, euler) in zip(coord.keys, euler_curve): key.time = frame / self.fps key.value = euler[i] elif quat_curve: n_kfd.rotation_type = NifFormat.KeyType.QUADRATIC_KEY n_kfd.num_rotation_keys = len(quat_curve) n_kfd.quaternion_keys.update_size() for key, (frame, quat) in zip(n_kfd.quaternion_keys, quat_curve): key.time = frame / self.fps key.value.w = quat.w key.value.x = quat.x key.value.y = quat.y key.value.z = quat.z n_kfd.translations.interpolation = NifFormat.KeyType.LINEAR_KEY n_kfd.translations.num_keys = len(trans_curve) n_kfd.translations.keys.update_size() for key, (frame, trans) in zip(n_kfd.translations.keys, trans_curve): key.time = frame / self.fps key.value.x, key.value.y, key.value.z = trans n_kfd.scales.interpolation = NifFormat.KeyType.LINEAR_KEY n_kfd.scales.num_keys = len(scale_curve) n_kfd.scales.keys.update_size() for key, (frame, scale) in zip(n_kfd.scales.keys, scale_curve): key.time = frame / self.fps key.value = scale