Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
814fd8c
Added extra warning for too many bones per partition and move warning…
Candoran2 Jan 5, 2022
a68aee7
Fixed issue #496 by allowing for multiple triangles with the same ver…
Candoran2 Jan 5, 2022
1b57dd2
Merge pull request #497 from Candoran2/develop
neomonkeus Jan 13, 2022
0aa5ba3
Import animation by default
HENDRIX-ZT2 Mar 28, 2022
6c43b75
Merge pull request #505 from HENDRIX-ZT2/develop
neomonkeus Mar 28, 2022
3dbe8a7
Add timing debug for keys
HENDRIX-ZT2 Mar 28, 2022
ebb59e7
Speed up keys import
HENDRIX-ZT2 Mar 28, 2022
35b1b79
Speed up keys import
HENDRIX-ZT2 Mar 28, 2022
62472b7
Merge branch 'niftools:develop' into develop
HENDRIX-ZT2 Mar 28, 2022
9997f62
Refactor keys import
HENDRIX-ZT2 Mar 30, 2022
14daae8
Remove removed attribute for blender 3.1
HENDRIX-ZT2 Mar 30, 2022
d67e66b
Fix anim import
HENDRIX-ZT2 Mar 30, 2022
1671410
Fix anim import
HENDRIX-ZT2 Mar 30, 2022
fd08980
Improve comments
HENDRIX-ZT2 Mar 30, 2022
56d208d
Improve parameters
HENDRIX-ZT2 Mar 30, 2022
5508e6e
Refactor and join code paths for keys import
HENDRIX-ZT2 Mar 30, 2022
c696772
Add todo for permeability
HENDRIX-ZT2 Mar 30, 2022
936f780
Cleanup
HENDRIX-ZT2 Mar 30, 2022
d00f471
Move key lut, share data type constants
HENDRIX-ZT2 Mar 30, 2022
b5d02a8
Refactor + fix import_visibility
HENDRIX-ZT2 Mar 30, 2022
d057831
Refactor morph + vis ctrl import
HENDRIX-ZT2 Mar 30, 2022
fd9eadf
Refactor mat anim import
HENDRIX-ZT2 Mar 30, 2022
dcc661c
Upgrade UV offset import
HENDRIX-ZT2 Mar 30, 2022
b30338f
Upgrade UV offset import
HENDRIX-ZT2 Mar 30, 2022
f3bc4fe
Upgrade UV offset import
HENDRIX-ZT2 Mar 30, 2022
133a9e5
Upgrade UV offset import
HENDRIX-ZT2 Mar 30, 2022
af4a25f
Remove uv anim comments from BFB plugin
HENDRIX-ZT2 Mar 31, 2022
0f5f263
Fix decoding handling - closes #500
HENDRIX-ZT2 Mar 31, 2022
16fd32c
Fix shape keys export, add doc - closes #180
HENDRIX-ZT2 Mar 31, 2022
d20ca21
Refactor export_children()
HENDRIX-ZT2 Apr 2, 2022
425f9da
Move root node type to scene
HENDRIX-ZT2 Apr 2, 2022
33d0a35
Refactor root node
HENDRIX-ZT2 Apr 2, 2022
7e679b4
Move BS Inv marker properties and UI to scene
HENDRIX-ZT2 Apr 2, 2022
bb9c5bf
Refactor BS Inv marker
HENDRIX-ZT2 Apr 2, 2022
cff4796
Refactor import_root_collision, import_empty
HENDRIX-ZT2 Apr 2, 2022
30d4e7b
Fixed error that occured when trying to qhull without any vertices.
Candoran2 Apr 29, 2022
085b06a
Temporarily moved penetration depth from collision modifier permeabil…
Candoran2 Apr 29, 2022
5eaeb32
Merge pull request #512 from Candoran2/develop
HENDRIX-ZT2 May 21, 2022
4b32bfa
Merge branch 'develop' into develop
HENDRIX-ZT2 May 21, 2022
fbdce87
Increase generated image name range for embedded textures - closes #510
HENDRIX-ZT2 May 21, 2022
ee5ea9f
Refactor UV nodes names, add _ after TexCoordIndex
HENDRIX-ZT2 May 22, 2022
58f618b
External textures fixes - closes #510, closes #517
HENDRIX-ZT2 May 26, 2022
6a74b8d
get_controller_data more intelligently for anim import
HENDRIX-ZT2 May 26, 2022
b93a623
Cleanup embedded tex
HENDRIX-ZT2 May 26, 2022
e95efd9
Move BSInvMarker back to object
HENDRIX-ZT2 May 26, 2022
9d8b2cb
Preliminary support for texture transform import - #514
HENDRIX-ZT2 May 26, 2022
9c7eba0
Naminc conventions for MorphAnimation as per review
HENDRIX-ZT2 May 27, 2022
e31eaa6
Fix frozen import with multiple tex transform controllers
HENDRIX-ZT2 May 27, 2022
a8d6e09
Cleanup & pull out skin partition code
HENDRIX-ZT2 May 27, 2022
519c413
Cleanup trishape export
HENDRIX-ZT2 May 27, 2022
e11ac76
Cleanup trishape export
HENDRIX-ZT2 May 27, 2022
1fd4486
Cleanup trishape export
HENDRIX-ZT2 May 27, 2022
56af76a
Cleanup add_defined_tangents
HENDRIX-ZT2 May 27, 2022
615ce6b
Merge branch 'develop' into develop
HENDRIX-ZT2 May 27, 2022
6aaae94
Revert TestTriShape
HENDRIX-ZT2 Jun 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/user/features/geometry/geometry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,15 @@ Vertex Color & Alpha
* `This image should clarify per-face vertex colouring
<http://i211.photobucket.com/albums/bb189/NifTools/Blender/documentation/per_face_vertex_color.jpg>`_
* On export, the scripts will create extra vertices for different vertex colors per face.


.. _geometry-shapekeys:

Shape Key Animations
--------------------

**Example:**

#. :ref:`Create a mesh-object <geometry-mesh>`.
#. Add relative shape keys to your mesh.
#. Keyframe each shape key's value so that the key influences the shape of the mesh at the desired time.
5 changes: 3 additions & 2 deletions io_scene_niftools/modules/nif_export/animation/morph.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ def __init__(self):
EGMData.data = None

def export_morph(self, b_mesh, n_trishape, vertmap):
# shape b_key morphing
NifLog.debug(f"Checking {b_mesh.name} for shape keys")
# shape keys are only present on non-evaluated meshes!
b_key = b_mesh.shape_keys
if b_key and len(b_key.key_blocks) > 1:

NifLog.debug(f"{b_mesh.name} has shape keys")
# yes, there is a b_key object attached
# export as egm, or as morph_data?
if b_key.key_blocks[1].name.startswith("EGM"):
Expand Down
3 changes: 2 additions & 1 deletion io_scene_niftools/modules/nif_export/animation/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from io_scene_niftools.modules.nif_export.block_registry import block_store
from io_scene_niftools.utils import math, consts
from io_scene_niftools.utils.logging import NifError, NifLog
from io_scene_niftools.utils.consts import QUAT, EULER, LOC, SCALE
Comment thread
HENDRIX-ZT2 marked this conversation as resolved.


class TransformAnimation(Animation):
Expand Down Expand Up @@ -166,7 +167,7 @@ def export_transforms(self, parent_block, b_obj, b_action, bone=None):
# 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")]
fcu.data_path in (QUAT, EULER, LOC, SCALE)]

else:
# bone isn't keyframed in this action, nothing to do here
Expand Down
349 changes: 171 additions & 178 deletions io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py

Large diffs are not rendered by default.

63 changes: 27 additions & 36 deletions io_scene_niftools/modules/nif_export/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,35 +97,27 @@ def get_export_objects(self, only_selected=True):
def export_root_node(self, root_objects, filebase):
""" Exports a nif's root node; use blender root if there is only one, else create a meta root """
# TODO [collsion] detect root collision -> root collision node (can be mesh or empty)
# self.nif_export.collisionhelper.export_collision(b_obj, n_parent)
# self.export_collision(b_obj, n_parent)
# return None # done; stop here
self.n_root = None
node_type = bpy.context.scene.niftools_scene.rootnode
# there is only one root object so that will be our final root
if len(root_objects) == 1:
b_obj = root_objects[0]
self.export_node(b_obj, None)
self.export_node(b_obj, None, n_node_type=node_type)

# there is more than one root object so we create a meta root
else:
NifLog.info("Created meta root because blender scene had multiple root objects")
self.n_root = types.create_ninode()
NifLog.info(f"Created meta root because blender scene had {len(root_objects)} root objects")
self.n_root = types.create_ninode(n_node_type=node_type)
self.n_root.name = "Scene Root"
for b_obj in root_objects:
self.export_node(b_obj, self.n_root)

# TODO [object] How dow we know we are selecting the right node in the case of multi-root?
# making root block a fade node
root_type = b_obj.niftools.rootnode
if bpy.context.scene.niftools_scene.game in ('FALLOUT_3', 'SKYRIM') and root_type == 'BSFadeNode':
NifLog.info("Making root block a BSFadeNode")
fade_root_block = NifFormat.BSFadeNode().deepcopy(self.n_root)
fade_root_block.replace_global_node(self.n_root, fade_root_block)
self.n_root = fade_root_block

# various extra datas
object_property = ObjectDataProperty()
object_property.export_bsxflags_upb(self.n_root, root_objects)
object_property.export_inventory_marker(self.n_root, root_objects)
object_property.export_inventory_marker(self.n_root, b_obj)
object_property.export_weapon_location(self.n_root, b_obj)
types.export_furniture_marker(self.n_root, filebase)
return self.n_root
Expand All @@ -147,7 +139,7 @@ def set_node_flags(self, b_obj, n_node):
else:
n_node.flags = 0x000C # morrowind

def export_node(self, b_obj, n_parent):
def export_node(self, b_obj, n_parent, n_node_type=None):
"""Export a mesh/armature/empty object b_obj as child of n_parent.
Export also all children of b_obj.

Expand Down Expand Up @@ -190,7 +182,7 @@ def export_node(self, b_obj, n_parent):
b_action = False

# -> everything else (empty/armature) is a (more or less regular) node
node = types.create_ninode(b_obj)
node = types.create_ninode(b_obj, n_node_type=n_node_type)
# set parenting here so that it can be accessed
if not self.n_root:
self.n_root = node
Expand All @@ -207,32 +199,31 @@ def export_node(self, b_obj, n_parent):
# export object animation
self.transform_anim.export_transforms(node, b_obj, b_action)
self.object_anim.export_visibility(node, b_action)
# if it is a mesh, export the mesh as trishape children of this ninode
# if it is a mesh, export the mesh as n_geom children of this ninode
if b_obj.type == 'MESH':
return self.mesh_helper.export_tri_shapes(b_obj, node, self.n_root)
# if it is an armature, export the bones as ninode children of this ninode
elif b_obj.type == 'ARMATURE':
self.armaturehelper.export_bones(b_obj, node)

# export all children of this b_obj as children of this NiNode
self.export_children(b_obj, node)
# special case: objects parented to armature bones
for b_child in b_obj.children:
# find and attach to the right node
if b_child.parent_bone:
b_obj_bone = b_obj.data.bones[b_child.parent_bone]
# find the correct n_node
# todo [object] this is essentially the same as Mesh.get_bone_block()
n_node = [k for k, v in block_store.block_to_obj.items() if v == b_obj_bone][0]
self.export_node(b_child, n_node)
# just child of the armature itself, so attach to armature root
else:
self.export_node(b_child, node)
else:
# export all children of this empty object as children of this node
for b_child in b_obj.children:
self.export_node(b_child, node)

return node

def export_children(self, b_parent, n_parent):
"""Export all children of blender object b_parent as children of n_parent."""
# loop over all obj's children
for b_child in b_parent.children:
temp_parent = n_parent
# special case: objects parented to armature bones - find the nif parent bone
if b_parent.type == 'ARMATURE' and b_child.parent_bone != "":
parent_bone = b_parent.data.bones[b_child.parent_bone]
assert (parent_bone in block_store.block_to_obj.values())
for temp_parent, obj in block_store.block_to_obj.items():
if obj == parent_bone:
break
self.export_node(b_child, temp_parent)

def export_collision(self, b_obj, n_parent):
"""Main function for adding collision object b_obj to a node.
Returns True if this object is exported as a collision"""
Expand All @@ -259,7 +250,7 @@ def export_collision(self, b_obj, n_parent):
else:
# all nodes failed so add new one
node = types.create_ninode(b_obj)
node.name = 'collisiondummy{:d}'.format(n_parent.num_children)
node.name = f'collisiondummy{n_parent.num_children}'
if b_obj.niftools.flags != 0:
node_flag_hex = hex(b_obj.niftools.flags)
else:
Expand All @@ -273,4 +264,4 @@ def export_collision(self, b_obj, n_parent):
else:
NifLog.warn(f"Collisions not supported for game '{bpy.context.scene.niftools_scene.game}', skipped collision object '{b_obj.name}'")

return True
return True
28 changes: 13 additions & 15 deletions io_scene_niftools/modules/nif_export/property/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,21 +220,19 @@ def has_collision():

# TODO [object][property] Move to object property
@staticmethod
def export_inventory_marker(n_root, root_objects):
if bpy.context.scene.niftools_scene.game in ('SKYRIM',):
for root_object in root_objects:
if root_object.niftools_bs_invmarker:
for extra_item in n_root.extra_data_list:
if isinstance(extra_item, NifFormat.BSInvMarker):
raise NifError("Multiple Items have Inventory marker data only one item may contain this data")
else:
n_extra_list = NifFormat.BSInvMarker()
n_extra_list.name = root_object.niftools_bs_invmarker[0].name.encode()
n_extra_list.rotation_x = (-root_object.niftools_bs_invmarker[0].bs_inv_x % (2 * pi)) * 1000
n_extra_list.rotation_y = (-root_object.niftools_bs_invmarker[0].bs_inv_y % (2 * pi)) * 1000
n_extra_list.rotation_z = (-root_object.niftools_bs_invmarker[0].bs_inv_z % (2 * pi)) * 1000
n_extra_list.zoom = root_object.niftools_bs_invmarker[0].bs_inv_zoom
n_root.add_extra_data(n_extra_list)
def export_inventory_marker(n_root, b_obj):
"""Attaches a BSInvMarker to n_root if desired and fill in its values"""
niftools_scene = bpy.context.scene.niftools_scene
bs_inv_store = b_obj.niftools.bs_inv
if niftools_scene.game in ('SKYRIM',) and bs_inv_store:
bs_inv = bs_inv_store[0]
n_bs_inv_marker = NifFormat.BSInvMarker()
n_bs_inv_marker.name = bs_inv.name.encode()
n_bs_inv_marker.rotation_x = (-bs_inv.x % (2 * pi)) * 1000
n_bs_inv_marker.rotation_y = (-bs_inv.y % (2 * pi)) * 1000
n_bs_inv_marker.rotation_z = (-bs_inv.z % (2 * pi)) * 1000
n_bs_inv_marker.zoom = bs_inv.zoom
n_root.add_extra_data(n_bs_inv_marker)

# TODO [object][property] Move to new object type
def export_weapon_location(self, n_root, root_obj):
Expand Down
25 changes: 13 additions & 12 deletions io_scene_niftools/modules/nif_export/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,22 @@
from io_scene_niftools.utils.singleton import NifOp


def create_ninode(b_obj=None):
def create_ninode(b_obj=None, n_node_type=None):
"""Essentially a wrapper around create_block() that creates nodes of the right type"""
# when no b_obj is passed, it means we create a root node
# when no b_obj is passed, use the passed n_node_type
if not b_obj:
return block_store.create_block("NiNode")

if n_node_type is None:
n_node_type = "NiNode"
# get node type - some are stored as custom property of the b_obj
try:
n_node_type = b_obj["type"]
except KeyError:
n_node_type = "NiNode"

# ...others by presence of constraints
if has_track(b_obj):
n_node_type = "NiBillboardNode"
else:
try:
n_node_type = b_obj["type"]
except KeyError:
n_node_type = "NiNode"

# ...others by presence of constraints
if has_track(b_obj):
n_node_type = "NiBillboardNode"

# now create the node
n_node = block_store.create_block(n_node_type, b_obj)
Expand Down
62 changes: 51 additions & 11 deletions io_scene_niftools/modules/nif_import/animation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from pyffi.formats.nif import NifFormat

from io_scene_niftools.utils.logging import NifLog
from io_scene_niftools.utils.consts import QUAT, EULER, LOC, SCALE


class Animation:
Expand All @@ -54,6 +55,23 @@ def __init__(self):
# and still be able to access existing actions from this run
self.actions = {}

@staticmethod
def get_controller_data(ctrl):
"""Return data for ctrl, look in interpolator (for newer games) or directly on ctrl"""
if ctrl.interpolator:
data = ctrl.interpolator.data
else:
data = ctrl.data
# these have their data set as a KeyGroup on data
if isinstance(data, (NifFormat.NiBoolData, NifFormat.NiFloatData, NifFormat.NiPosData)):
Comment thread
HENDRIX-ZT2 marked this conversation as resolved.
return data.data
return data

@staticmethod
def get_keys_values(items):
"""Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes"""
return [key.time for key in items], [key.value for key in items]

@staticmethod
def show_pose_markers():
"""Helper function to ensure that pose markers are shown"""
Expand Down Expand Up @@ -89,21 +107,21 @@ def create_action(self, b_obj, action_name):
b_obj.animation_data.action = b_action
return b_action

def create_fcurves(self, action, dtype, drange, flags=None, bonename=None, keyname=None):
def create_fcurves(self, action, dtype, drange, flags, bone_name, key_name):
""" Create fcurves in action for desired conditions. """
# armature pose bone animation
if bonename:
if bone_name:
fcurves = [
action.fcurves.new(data_path=f'pose.bones["{bonename}"].{dtype}', index=i, action_group=bonename)
action.fcurves.new(data_path=f'pose.bones["{bone_name}"].{dtype}', index=i, action_group=bone_name)
for i in drange]
# shapekey pose bone animation
elif keyname:
elif key_name:
fcurves = [
action.fcurves.new(data_path=f'key_blocks["{keyname}"].{dtype}', index=0,)
action.fcurves.new(data_path=f'key_blocks["{key_name}"].{dtype}', index=0,)
]
else:
# Object animation (non-skeletal) is lumped into the "LocRotScale" action_group
if dtype in ("rotation_euler", "rotation_quaternion", "location", "scale"):
if dtype in (QUAT, EULER, LOC, SCALE):
action_group = "LocRotScale"
# Non-transformaing animations (eg. visibility or material anims) use no action groups
else:
Expand Down Expand Up @@ -141,13 +159,34 @@ def set_extrapolation(extend_type, fcurves):
for fcurve in fcurves:
fcurve.extrapolation = 'CONSTANT'

def add_key(self, fcurves, t, key, interp):
def add_keys(self, b_action, key_type, key_range, flags, times, keys, interp, bone_name=None, key_name=None):
"""
Add a key (len=n) to a set of fcurves (len=n) at the given frame. Set the key's interpolation to interp.
Create needed fcurves and add a list of keys to an action.
"""
frame = round(t * self.fps)
for fcurve, k in zip(fcurves, key):
fcurve.keyframe_points.insert(frame, k).interpolation = interp
samples = [round(t * self.fps) for t in times]
assert len(samples) == len(keys)
# get interpolation enum representation
ipo = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[interp].value
interpolations = [ipo for _ in range(len(samples))]
# import the keys
try:
fcurves = self.create_fcurves(b_action, key_type, key_range, flags, bone_name, key_name)
if len(key_range) == 1:
# flat key - make it zippable
key_per_fcurve = [keys]
else:
key_per_fcurve = zip(*keys)
for fcurve, fcu_keys in zip(fcurves, key_per_fcurve):
# add new points
fcurve.keyframe_points.add(count=len(fcu_keys))
# populate points with keys for this curve
fcurve.keyframe_points.foreach_set("co", [x for co in zip(samples, fcu_keys) for x in co])
fcurve.keyframe_points.foreach_set("interpolation", interpolations)
# update
fcurve.update()
except RuntimeError:
# blender throws F-Curve ... already exists in action ...
NifLog.warn(f"Could not add fcurve '{key_type}' to '{b_action.name}', already added before?")

# import animation groups
def import_text_keys(self, n_block, b_action):
Expand Down Expand Up @@ -212,3 +251,4 @@ def set_frames_per_second(self, roots):
self.fps = fps
bpy.context.scene.render.fps = fps
bpy.context.scene.frame_set(0)

Loading