Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speedup anim import & various other fixes #506

Merged
merged 55 commits into from
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from 46 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


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
2 changes: 1 addition & 1 deletion io_scene_niftools/modules/nif_export/collision/havok.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def export_bhk_rigid_body(self, b_obj, n_col_obj):
n_r_body.restitution = b_r_body.restitution
n_r_body.max_linear_velocity = b_obj.nifcollision.max_linear_velocity
n_r_body.max_angular_velocity = b_obj.nifcollision.max_angular_velocity
n_r_body.penetration_depth = b_obj.collision.permeability
n_r_body.penetration_depth = b_obj.nifcollision.penetration_depth
n_r_body.motion_system = b_obj.nifcollision.motion_system
n_r_body.deactivator_type = b_obj.nifcollision.deactivator_type
n_r_body.solver_deactivation = b_obj.nifcollision.solver_deactivation
Expand Down
78 changes: 43 additions & 35 deletions io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,22 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):

assert (b_obj.type == 'MESH')

# get mesh from b_obj
b_mesh = self.get_triangulated_mesh(b_obj)
b_mesh.calc_normals_split()
# get mesh from b_obj, and evaluate the mesh with modifiers applied, too
b_mesh = b_obj.data
eval_mesh = self.get_triangulated_mesh(b_obj)
eval_mesh.calc_normals_split()

# getVertsFromGroup fails if the mesh has no vertices
# (this happens when checking for fallout 3 body parts)
# so quickly catch this (rare!) case
if not b_mesh.vertices:
if not eval_mesh.vertices:
# do not export anything
NifLog.warn(f"{b_obj} has no vertices, skipped.")
return

# get the mesh's materials, this updates the mesh material list
if not isinstance(n_parent, NifFormat.RootCollisionNode):
mesh_materials = b_mesh.materials
mesh_materials = eval_mesh.materials
else:
# ignore materials on collision trishapes
mesh_materials = []
Expand All @@ -102,9 +103,9 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
mesh_materials = [None]

# vertex color check
mesh_hasvcol = b_mesh.vertex_colors
mesh_hasvcol = eval_mesh.vertex_colors
# list of body part (name, index, vertices) in this mesh
polygon_parts = self.get_polygon_parts(b_obj, b_mesh)
polygon_parts = self.get_polygon_parts(b_obj, eval_mesh)
game = bpy.context.scene.niftools_scene.game

# Non-textured materials, vertex colors are used to color the mesh
Expand Down Expand Up @@ -188,9 +189,9 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
# The following algorithm extracts all unique quads(vert, uv-vert, normal, vcol),
# produce lists of vertices, uv-vertices, normals, vertex colors, and face indices.

mesh_uv_layers = b_mesh.uv_layers
mesh_uv_layers = eval_mesh.uv_layers
vertquad_list = [] # (vertex, uv coordinate, normal, vertex color) list
vertex_map = [None for _ in range(len(b_mesh.vertices))] # blender vertex -> nif vertices
vertex_map = [None for _ in range(len(eval_mesh.vertices))] # blender vertex -> nif vertices
vertex_positions = []
normals = []
vertex_colors = []
Expand All @@ -200,21 +201,21 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
bodypartfacemap = []
polygons_without_bodypart = []

if b_mesh.polygons:
if eval_mesh.polygons:
if mesh_uv_layers:
# if we have uv coordinates double check that we have uv data
if not b_mesh.uv_layer_stencil:
NifLog.warn(f"No UV map for texture associated with selected mesh '{b_mesh.name}'.")
if not eval_mesh.uv_layer_stencil:
NifLog.warn(f"No UV map for texture associated with selected mesh '{eval_mesh.name}'.")

use_tangents = False
if mesh_uv_layers and mesh_hasnormals:
if game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM') or (game in self.texture_helper.USED_EXTRA_SHADER_TEXTURES):
use_tangents = True
b_mesh.calc_tangents(uvmap=mesh_uv_layers[0].name)
eval_mesh.calc_tangents(uvmap=mesh_uv_layers[0].name)
tangents = []
bitangent_signs = []

for poly in b_mesh.polygons:
for poly in eval_mesh.polygons:

# does the face belong to this trishape?
if b_mat is not None and poly.material_index != materialIndex:
Expand All @@ -230,25 +231,25 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
f_index = [-1] * f_numverts
for i, loop_index in enumerate(poly.loop_indices):

fv_index = b_mesh.loops[loop_index].vertex_index
vertex = b_mesh.vertices[fv_index]
fv_index = eval_mesh.loops[loop_index].vertex_index
vertex = eval_mesh.vertices[fv_index]
vertex_index = vertex.index
fv = vertex.co

# smooth = vertex normal, non-smooth = face normal)
if mesh_hasnormals:
if poly.use_smooth:
fn = b_mesh.loops[loop_index].normal
fn = eval_mesh.loops[loop_index].normal
else:
fn = poly.normal
else:
fn = None

fuv = [uv_layer.data[loop_index].uv for uv_layer in b_mesh.uv_layers]
fuv = [uv_layer.data[loop_index].uv for uv_layer in eval_mesh.uv_layers]

# TODO [geomotry][mesh] Need to map b_verts -> n_verts
if mesh_hasvcol:
f_col = list(b_mesh.vertex_colors[0].data[loop_index].color)
f_col = list(eval_mesh.vertex_colors[0].data[loop_index].color)
else:
f_col = None

Expand Down Expand Up @@ -282,8 +283,8 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
if mesh_hasnormals:
normals.append(vertquad[2])
if use_tangents:
tangents.append(b_mesh.loops[loop_index].tangent)
bitangent_signs.append([b_mesh.loops[loop_index].bitangent_sign])
tangents.append(eval_mesh.loops[loop_index].tangent)
bitangent_signs.append([eval_mesh.loops[loop_index].bitangent_sign])
if mesh_hasvcol:
vertex_colors.append(vertquad[3])
if mesh_uv_layers:
Expand Down Expand Up @@ -312,7 +313,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):

# check that there are no missing body part polygons
if polygons_without_bodypart:
self.select_unassigned_polygons(b_mesh, b_obj, polygons_without_bodypart)
self.select_unassigned_polygons(eval_mesh, b_obj, polygons_without_bodypart)

if len(triangles) > 65535:
raise NifError("Too many polygons. Decimate your mesh and try again.")
Expand Down Expand Up @@ -402,7 +403,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
b_list_weight = []
b_vert_group = b_obj.vertex_groups[bone_group]

for b_vert in b_mesh.vertices:
for b_vert in eval_mesh.vertices:
if len(b_vert.groups) == 0: # check vert has weight_groups
unweighted_vertices.append(b_vert.index)
continue
Expand Down Expand Up @@ -460,6 +461,24 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):

if NifData.data.version >= 0x04020100 and NifOp.props.skin_partition:
NifLog.info("Creating skin partition")

# warn on bad config settings
if game == 'OBLIVION':
if NifOp.props.pad_bones:
NifLog.warn("Using padbones on Oblivion export. Disable the pad bones option to get higher quality skin partitions.")
if game in ('OBLIVION', 'FALLOUT_3'):
if NifOp.props.max_bones_per_partition < 18:
NifLog.warn("Using less than 18 bones per partition on Oblivion/Fallout 3 export."
"Set it to 18 to get higher quality skin partitions.")
elif NifOp.props.max_bones_per_partition > 18:
NifLog.warn("Using more than 18 bones per partition on Oblivion/Fallout 3 export."
"This may cause issues in-game.")
if game == 'SKYRIM':
if NifOp.props.max_bones_per_partition < 24:
NifLog.warn("Using less than 24 bones per partition on Skyrim export."
"Set it to 24 to get higher quality skin partitions.")
# Skyrim Special Edition has a limit of 80 bones per partition, but export is not yet supported

part_order = [getattr(NifFormat.BSDismemberBodyPartType, face_map.name, None) for face_map in b_obj.face_maps]
part_order = [body_part for body_part in part_order if body_part is not None]
# override pyffi trishape.update_skin_partition with custom one (that allows ordering)
Expand All @@ -475,18 +494,6 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
maximize_bone_sharing=(game in ('FALLOUT_3', 'SKYRIM')),
part_sort_order=part_order)

# warn on bad config settings
if game == 'OBLIVION':
if NifOp.props.pad_bones:
NifLog.warn("Using padbones on Oblivion export. Disable the pad bones option to get higher quality skin partitions.")
if game in ('OBLIVION', 'FALLOUT_3'):
if NifOp.props.max_bones_per_partition < 18:
NifLog.warn("Using less than 18 bones per partition on Oblivion/Fallout 3 export."
"Set it to 18 to get higher quality skin partitions.")
if game == 'SKYRIM':
if NifOp.props.max_bones_per_partition < 24:
NifLog.warn("Using less than 24 bones per partition on Skyrim export."
"Set it to 24 to get higher quality skin partitions.")
if lostweight > NifOp.props.epsilon:
NifLog.warn(f"Lost {lostweight:f} in vertex weights while creating a skin partition for Blender object '{b_obj.name}' (nif block '{trishape.name}')")

Expand All @@ -498,6 +505,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
tridata.consistency_flags = b_obj.niftools.consistency_flags

# export EGM or NiGeomMorpherController animation
# shape keys are only present on the raw, unevaluated mesh
self.morph_anim.export_morph(b_mesh, trishape, vertex_map)
return trishape

Expand Down
58 changes: 23 additions & 35 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 @@ -213,26 +205,22 @@ def export_node(self, b_obj, n_parent):
# 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 bone
if b_child.parent_bone:
b_obj_bone = b_obj.data.bones[b_child.parent_bone]
self.export_node(b_child, block_store.block_to_obj[b_obj_bone])
# 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 +247,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 +261,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
Loading