Skip to content

Commit

Permalink
Merge pull request #467 from niftools/release/v0.0.10
Browse files Browse the repository at this point in the history
Release/v0.0.10
  • Loading branch information
neomonkeus authored Oct 11, 2021
2 parents 89cfac0 + 2937be0 commit 7d7b350
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 84 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
Version v0.0.10
==============

- #465 Remove trailing dots for descriptions and UI items
- Fixes #246 - UI Registration : RNA_def_property_ui_text description ends in '.'

- #463 Fixes for the animation system
- Fixes #432 - Oblivion animation exported, NifSkope error: "<empty>"
- Fixes #458 - Store rest transform on NiTransformInterpolator
- Fixes #464 - Animation Import Fails

Version v0.0.9
==============

Expand Down
2 changes: 1 addition & 1 deletion io_scene_niftools/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.0.9
v0.0.10
2 changes: 1 addition & 1 deletion io_scene_niftools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"description": "Import and export files in the NetImmerse/Gamebryo formats (.nif, .kf, .egm)",
"author": "Niftools team",
"blender": (2, 82, 0),
"version": (0, 0, 9), # can't read from VERSION, blender wants it hardcoded
"version": (0, 0, 10), # can't read from VERSION, blender wants it hardcoded
"api": 39257,
"location": "File > Import-Export",
"warning": "Partially functional port from 2.49 series still in progress",
Expand Down
13 changes: 13 additions & 0 deletions io_scene_niftools/modules/nif_export/animation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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!")

Expand Down
30 changes: 7 additions & 23 deletions io_scene_niftools/modules/nif_export/animation/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down Expand Up @@ -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

Expand Down
18 changes: 8 additions & 10 deletions io_scene_niftools/modules/nif_import/animation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 7 additions & 2 deletions io_scene_niftools/modules/nif_import/armature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,15 +235,20 @@ 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
if not self.is_bone(n_bone):
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
Expand Down
20 changes: 10 additions & 10 deletions io_scene_niftools/operators/common_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,31 @@ class CommonDevOperator:
"""Abstract base class for import and export user interface."""

error_level_map = (
("DEBUG", "Debug", "Show all messages (only useful for debugging).", 10),
("INFO", "Info", "Show some informative messages, warnings, and errors.", 20),
("WARNING", "Warning", "Only show warnings and errors.", 30),
("ERROR", "Error", "Only show errors.", 40),
("CRITICAL", "Critical", "Only show extremely critical errors.", 50),
("DEBUG", "Debug", "Show all messages (only useful for debugging)", 10),
("INFO", "Info", "Show some informative messages, warnings, and errors", 20),
("WARNING", "Warning", "Only show warnings and errors", 30),
("ERROR", "Error", "Only show errors", 40),
("CRITICAL", "Critical", "Only show extremely critical errors", 50),
)

# Level of verbosity on the console.
plugin_log_level: bpy.props.EnumProperty(
items=error_level_map,
name="Plugin Log Level",
description="Blender Nif Plugin log level of verbosity on the console.",
description="Blender Nif Plugin log level of verbosity on the console",
default="INFO")

# Level of verbosity on the console.
pyffi_log_level: bpy.props.EnumProperty(
items=error_level_map,
name="Pyffi Log Level",
description="Pyffi log level of verbosity on the console.",
description="Pyffi log level of verbosity on the console",
default="INFO")

# Name of file where Python profiler dumps the profile.
profile_path: bpy.props.StringProperty(
name="Profile Path",
description="File where Python profiler dumps the profile. Set to empty string to turn off profiling.",
description="File where Python profiler dumps the profile. Set to empty string to turn off profiling",
maxlen=1024,
default="",
subtype="FILE_PATH",
Expand All @@ -76,7 +76,7 @@ class CommonDevOperator:
# Used for checking equality between floats.
epsilon: bpy.props.FloatProperty(
name="Epsilon",
description="Used for checking equality between floats.",
description="Used for checking equality between floats",
default=0.0005,
min=0.0, max=1.0, precision=5,
options={'HIDDEN'})
Expand All @@ -93,7 +93,7 @@ def set_import_scale(self, scale):
# Number of nif units per blender unit.
scale_correction: bpy.props.FloatProperty(
name="Scale Correction",
description="Changes size of mesh to fit onto Blender's default grid.",
description="Changes size of mesh to fit onto Blender's default grid",
get=get_import_scale,
set=set_import_scale,
default=0.1,
Expand Down
2 changes: 1 addition & 1 deletion io_scene_niftools/operators/kf_export_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class KfExportOperator(Operator, ExportHelper, CommonDevOperator, CommonScale, C
# Use BSAnimationNode (for Morrowind).
bs_animation_node: bpy.props.BoolProperty(
name="Use NiBSAnimationNode",
description="Use NiBSAnimationNode (for Morrowind).",
description="Use NiBSAnimationNode (for Morrowind)",
default=False)

def execute(self, context):
Expand Down
24 changes: 12 additions & 12 deletions io_scene_niftools/operators/nif_export_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,68 +58,68 @@ class NifExportOperator(Operator, ExportHelper, CommonDevOperator, CommonNif, Co
# How to export animation.
animation: bpy.props.EnumProperty(
items=[
('ALL_NIF', "All (nif)", "Geometry and animation to a single nif."),
('GEOM_NIF', "Geometry only (nif)", "Only geometry to a single nif."),
('ALL_NIF', "All (nif)", "Geometry and animation to a single nif"),
('GEOM_NIF', "Geometry only (nif)", "Only geometry to a single nif"),
],
name="Animation export",
description="Selects which parts of the blender file to export.",
description="Selects which parts of the blender file to export",
default='ALL_NIF')

# Use BSAnimationNode (for Morrowind).
bs_animation_node: bpy.props.BoolProperty(
name="Use NiBSAnimationNode",
description="Use NiBSAnimationNode (for Morrowind).",
description="Use NiBSAnimationNode (for Morrowind)",
default=False)

# Stripify geometries. Deprecate? (Strips are slower than triangle shapes.)
stripify: bpy.props.BoolProperty(
name="Stripify Geometries",
description="Stripify geometries.",
description="Stripify geometries",
default=False,
options={'HIDDEN'})

# Stitch strips. Deprecate? (Strips are slower than triangle shapes.)
stitch_strips: bpy.props.BoolProperty(
name="Stitch Strips",
description="Stitch strips.",
description="Stitch strips",
default=True,
options={'HIDDEN'})

# Flatten skin.
flatten_skin: bpy.props.BoolProperty(
name="Flatten Skin",
description="Flatten skin.",
description="Flatten skin",
default=False)

# Export skin partition.
skin_partition: bpy.props.BoolProperty(
name="Skin Partition",
description="Export skin partition.",
description="Export skin partition",
default=True)

# Pad and sort bones.
pad_bones: bpy.props.BoolProperty(
name="Pad & Sort Bones",
description="Pad and sort bones.",
description="Pad and sort bones",
default=False)

# Maximum number of bones per skin partition.
max_bones_per_partition: bpy.props.IntProperty(
name="Max Partition Bones",
description="Maximum number of bones per skin partition.",
description="Maximum number of bones per skin partition",
default=18, min=4, max=63)

# Maximum number of bones per vertex in skin partitions.
max_bones_per_vertex: bpy.props.IntProperty(
name="Max Vertex Bones",
description="Maximum number of bones per vertex in skin partitions.",
description="Maximum number of bones per vertex in skin partitions",
default=4, min=1,
)

# Pad and sort bones.
force_dds: bpy.props.BoolProperty(
name="Force DDS",
description="Force texture .dds extension.",
description="Force texture .dds extension",
default=True)

# Whether or not to remove duplicate materials
Expand Down
Loading

0 comments on commit 7d7b350

Please sign in to comment.