diff --git a/io_scene_niftools/modules/nif_export/animation/__init__.py b/io_scene_niftools/modules/nif_export/animation/__init__.py index 93e37d1e9..5f45d2071 100644 --- a/io_scene_niftools/modules/nif_export/animation/__init__.py +++ b/io_scene_niftools/modules/nif_export/animation/__init__.py @@ -146,6 +146,10 @@ def create_controller(parent_block, target_name, priority=0): elif isinstance(parent_block, NifFormat.NiControllerSequence): controlled_block = parent_block.add_controlled_block() controlled_block.priority = priority + # todo - pyffi adds the names to the NiStringPalette, but it creates one per controller link... + # also the currently used pyffi version doesn't store target_name for ZT2 style KFs in + # controlled_block.set_node_name(target_name) + # the following code handles both issues and should probably be ported to pyffi if NifData.data.version < 0x0A020000: # older versions need the actual controller blocks controlled_block.target_name = target_name @@ -157,6 +161,15 @@ def create_controller(parent_block, target_name, priority=0): controlled_block.interpolator = n_kfi controlled_block.node_name = target_name controlled_block.controller_type = "NiTransformController" + # get the parent's string palette + if not parent_block.string_palette: + parent_block.string_palette = NifFormat.NiStringPalette() + # assign string palette to controller + controlled_block.string_palette = parent_block.string_palette + # add the strings and store their offsets + palette = controlled_block.string_palette.palette + controlled_block.node_name_offset = palette.add_string(controlled_block.node_name) + controlled_block.controller_type_offset = palette.add_string(controlled_block.controller_type) else: raise io_scene_niftools.utils.logging.NifError("Unsupported KeyframeController parent!") diff --git a/io_scene_niftools/modules/nif_export/animation/transform.py b/io_scene_niftools/modules/nif_export/animation/transform.py index 4c535c769..4cc75d2f9 100644 --- a/io_scene_niftools/modules/nif_export/animation/transform.py +++ b/io_scene_niftools/modules/nif_export/animation/transform.py @@ -135,7 +135,6 @@ def export_kf_root(self, b_armature=None): kf_root.stop_time = scene.frame_end / self.fps kf_root.target_name = targetname - kf_root.string_palette = NifFormat.NiStringPalette() else: raise NifError( f"Keyframe export for '{bpy.context.scene.niftools_scene.game}' is not supported.") @@ -239,31 +238,16 @@ def export_transforms(self, parent_block, b_obj, b_action, bone=None): 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 + # set the default transforms of the interpolator as the bone's bind pose + n_kfi.translation.x, n_kfi.translation.y, n_kfi.translation.z = bind_trans + n_kfi.rotation.w, n_kfi.rotation.x, n_kfi.rotation.y, n_kfi.rotation.z = bind_rot.to_quaternion() + n_kfi.scale = bind_scale + + if max(len(c) for c in (quat_curve, euler_curve, trans_curve, scale_curve)) > 0: + # number of frames is > 0, 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 diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 5d9ae7f33..d80a74912 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -42,13 +42,12 @@ from io_scene_niftools.utils.logging import NifLog -FPS = 30 - class Animation: def __init__(self): self.show_pose_markers() + self.fps = 30 @staticmethod def show_pose_markers(): @@ -141,7 +140,7 @@ def add_key(self, fcurves, t, key, interp): """ Add a key (len=n) to a set of fcurves (len=n) at the given frame. Set the key's interpolation to interp. """ - frame = round(t * animation.FPS) + frame = round(t * self.fps) for fcurve, k in zip(fcurves, key): fcurve.keyframe_points.insert(frame, k).interpolation = interp @@ -159,13 +158,12 @@ def import_text_key_extra_data(self, txk, b_action): if txk and b_action: for key in txk.text_keys: newkey = key.value.decode().replace('\r\n', '/').rstrip('/') - frame = round(key.time * animation.FPS) + frame = round(key.time * self.fps) marker = b_action.pose_markers.new(newkey) marker.frame = frame - @staticmethod - def set_frames_per_second(roots): - """Scan all blocks and set a reasonable number for FPS to this class and the scene.""" + def set_frames_per_second(self, roots): + """Scan all blocks and set a reasonable number for fps to this class and the scene.""" # find all key times key_times = [] for root in roots: @@ -194,9 +192,9 @@ def set_frames_per_second(roots): if not key_times: return - # calculate FPS + # calculate fps key_times = sorted(set(key_times)) - fps = animation.FPS + fps = self.fps lowest_diff = sum(abs(int(time * fps + 0.5) - (time * fps)) for time in key_times) # for test_fps in range(1,120): #disabled, used for testing @@ -206,6 +204,6 @@ def set_frames_per_second(roots): lowest_diff = diff fps = test_fps NifLog.info(f"Animation estimated at {fps} frames per second.") - animation.FPS = fps + self.fps = fps bpy.context.scene.render.fps = fps bpy.context.scene.frame_set(0) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 8b7102ad7..c93d3bea6 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -108,7 +108,7 @@ def import_sequence_stream_helper(self, kf_root, b_armature_obj, bind_data): def import_controller_sequence(self, kf_root, b_armature_obj, bind_data): NifLog.debug('Importing NiControllerSequence...') - b_action = self.create_action(b_armature_obj, kf_root.name.decode()) + b_action = self.create_action(b_armature_obj, kf_root.name.decode(), retrieve=False) # import text keys self.import_text_keys(kf_root, b_action) diff --git a/io_scene_niftools/modules/nif_import/armature/__init__.py b/io_scene_niftools/modules/nif_import/armature/__init__.py index 5573ddd45..b2c2cbfc1 100644 --- a/io_scene_niftools/modules/nif_import/armature/__init__.py +++ b/io_scene_niftools/modules/nif_import/armature/__init__.py @@ -235,6 +235,11 @@ def guess_orientation(self, n_armature): # return string identifiers return ids[forward_ind], ids[up_ind] + @staticmethod + def argmax(values): + """Return the index of the max value in values""" + return max(zip(values, range(len(values))))[1] + def get_forward_axis(self, n_bone, axis_indices): """Helper function to get the forward axis of a bone""" # check that n_block is indeed a bone @@ -242,8 +247,8 @@ def get_forward_axis(self, n_bone, axis_indices): return None trans = n_bone.translation.as_tuple() trans_abs = tuple(abs(v) for v in trans) - # do argmax - max_coord_ind = max(zip(trans_abs, range(len(trans_abs))))[1] + # get the index of the coordinate with the biggest absolute value + max_coord_ind = self.argmax(trans_abs) # now check the sign actual_value = trans[max_coord_ind] # handle sign accordingly so negative indices map to the negative identifiers in list