Skip to content

Commit

Permalink
Merge pull request #469 from Candoran2/develop
Browse files Browse the repository at this point in the history
Shader flags and BSLightingShaderProperty updates, and minor fixes.
  • Loading branch information
HENDRIX-ZT2 authored Nov 3, 2021
2 parents 6874f52 + 91502d9 commit 92509dc
Show file tree
Hide file tree
Showing 17 changed files with 127 additions and 544 deletions.
4 changes: 2 additions & 2 deletions io_scene_niftools/addon_updater_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def draw(self, context):
return

# use a "failed flag"? it shows this label if the case failed.
if self.error is not "":
if self.error != "":
col = layout.column()
col.scale_y = 0.7
col.label(text="There was an issue trying to auto-install", icon="ERROR")
Expand Down Expand Up @@ -1102,7 +1102,7 @@ def update_settings_ui(self, context, element=None):
last_check = updater.json["last_check"]
if updater.error is not None and updater.error_msg is not None:
row.label(text=updater.error_msg)
elif last_check is not "" and last_check is not None:
elif last_check != "" and last_check is not None:
last_check = last_check[0: last_check.index(".")]
row.label(text="Last update check: " + last_check)
else:
Expand Down
18 changes: 13 additions & 5 deletions io_scene_niftools/modules/nif_export/armature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from io_scene_niftools.utils import math
from io_scene_niftools.utils.singleton import NifOp
from io_scene_niftools.utils.logging import NifLog
from pyffi.formats.nif import NifFormat


def get_bind_data(b_armature):
Expand All @@ -68,11 +69,14 @@ def export_bones(self, b_obj, n_root_node):
self.b_action = self.transform_anim.get_active_action(b_obj)
# the armature b_obj was already exported as a NiNode ("Scene Root") n_root_node
# export the bones as NiNodes, starting from root bones
old_position = b_obj.data.pose_position
b_obj.data.pose_position = 'POSE'
for b_bone in b_obj.data.bones.values():
if not b_bone.parent:
self.export_bone(b_obj, b_bone, n_root_node)
self.export_bone(b_obj, b_bone, n_root_node, n_root_node)
b_obj.data.pose_position = old_position

def export_bone(self, b_obj, b_bone, n_parent_node):
def export_bone(self, b_obj, b_bone, n_parent_node, n_root_node):
"""Exports a bone and all of its children."""
# create a new nif block for this b_bone
n_node = types.create_ninode(b_bone)
Expand All @@ -81,14 +85,18 @@ def export_bone(self, b_obj, b_bone, n_parent_node):
n_parent_node.add_child(n_node)

self.export_bone_flags(b_bone, n_node)
# rest pose
math.set_object_matrix(b_bone, n_node)
# set the pose on the nodes
nif_matrix = NifFormat.Matrix44()
nif_matrix.set_rows(*math.blender_bind_to_nif_bind(b_obj.pose.bones[b_bone.name].matrix).transposed())
# make the transform relative to the parent, rather than the armature
nif_matrix *= n_parent_node.get_transform(n_root_node).get_inverse(fast=False)
n_node.set_transform(nif_matrix)

# per-bone animation
self.transform_anim.export_transforms(n_node, b_obj, self.b_action, b_bone)
# continue down the bone tree
for b_child in b_bone.children:
self.export_bone(b_obj, b_child, n_node)
self.export_bone(b_obj, b_child, n_node, n_root_node)

def export_bone_flags(self, b_bone, n_node):
"""Exports or sets the flags according to the custom data in b_bone or the game version if none was set"""
Expand Down
40 changes: 26 additions & 14 deletions io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
mesh_hasnormals = False
if b_mat is not None:
mesh_hasnormals = True # for proper lighting
if (game == 'SKYRIM') and (b_mat.niftools_shader.bslsp_shaderobjtype == 'Skin Tint'):
if (game == 'SKYRIM') and (b_mat.niftools_shader.bslsp_shaderobjtype in ('Skin Tint', 'Face Tint')):
mesh_hasnormals = False # for proper lighting

# create a trishape block
Expand Down Expand Up @@ -452,8 +452,8 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):

# update bind position skinning data
# trishape.update_bind_position()
# override pyffi ttrishape.update_bind_position with custom one that is relative to the nif root
self.update_bind_position(trishape, n_root)
# override pyffi trishape.update_bind_position with custom one that is relative to the nif root
self.update_bind_position(trishape, n_root, b_obj_armature)

# calculate center and radius for each skin bone data block
trishape.update_skin_center_radius()
Expand Down Expand Up @@ -501,12 +501,12 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None):
self.morph_anim.export_morph(b_mesh, trishape, vertex_map)
return trishape

def update_bind_position(self, n_geom, n_root):
"""Make current position of the bones the bind position for this geometry.
def update_bind_position(self, n_geom, n_root, b_obj_armature):
"""Transfer the Blender bind position to the nif bind position.
Sets the NiSkinData overall transform to the inverse of the geometry transform
relative to the skeleton root, and sets the NiSkinData of each bone to
the geometry transform relative to the skeleton root times the inverse of the bone
transform relative to the skeleton root."""
the inverse of the transpose of the bone transform relative to the skeleton root, corrected
for the overall transform."""
if not n_geom.is_skin():
return

Expand All @@ -516,23 +516,32 @@ def update_bind_position(self, n_geom, n_root):
skindata = skininst.data
skelroot = skininst.skeleton_root

# calculate overall offset
geomtransform = n_geom.get_transform(skelroot)
skindata.set_transform(geomtransform.get_inverse())
# calculate overall offset (including the skeleton root transform) and use its inverse
geomtransform = (n_geom.get_transform(skelroot) * skelroot.get_transform()).get_inverse(fast=False)
skindata.set_transform(geomtransform)

# for some nifs, somehow n_root is not set properly?!
if not n_root:
NifLog.warn(f"n_root was not set, bug")
n_root = skelroot

old_position = b_obj_armature.data.pose_position
b_obj_armature.data.pose_position = 'POSE'

# calculate bone offsets
for i, bone in enumerate(skininst.bones):
bone_name = block_store.block_to_obj[bone].name
pose_bone = b_obj_armature.pose.bones[bone_name]
n_bind = NifFormat.Matrix44()
n_bind.set_rows(*math.blender_bind_to_nif_bind(pose_bone.matrix).transposed())
# todo [armature] figure out the correct transform that works universally
# inverse skin bind in nif armature space, relative to root / geom??
skindata.bone_list[i].set_transform(geomtransform * bone.get_transform(n_root).get_inverse())
skindata.bone_list[i].set_transform((n_bind * geomtransform).get_inverse(fast=False))
# this seems to be correct for skyrim heads, but breaks stuff like ZT2 elephant
# skindata.bone_list[i].set_transform(bone.get_transform(n_root).get_inverse())

b_obj_armature.data.pose_position = old_position

def get_bone_block(self, b_bone):
"""For a blender bone, return the corresponding nif node from the blocks that have already been exported"""
for n_block, b_obj in block_store.block_to_obj.items():
Expand Down Expand Up @@ -720,16 +729,19 @@ def get_triangulated_mesh(self, b_obj):
# get the armature influencing this mesh, if it exists
b_armature_obj = b_obj.find_armature()
if b_armature_obj:
for pbone in b_armature_obj.pose.bones:
pbone.matrix_basis = mathutils.Matrix()
old_position = b_armature_obj.data.pose_position
b_armature_obj.data.pose_position = 'REST'

# make sure the model has a triangulation modifier
self.ensure_tri_modifier(b_obj)

# make a copy with all modifiers applied
dg = bpy.context.evaluated_depsgraph_get()
eval_obj = b_obj.evaluated_get(dg)
return eval_obj.to_mesh(preserve_all_data_layers=True, depsgraph=dg)
eval_mesh = eval_obj.to_mesh(preserve_all_data_layers=True, depsgraph=dg)
if b_armature_obj:
b_armature_obj.data.pose_position = old_position
return eval_mesh

def ensure_tri_modifier(self, b_obj):
"""Makes sure that ob has a triangulation modifier in its stack."""
Expand Down
9 changes: 6 additions & 3 deletions io_scene_niftools/modules/nif_export/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ def set_node_flags(self, b_obj, n_node):
n_node.flags = 0x000E
elif game in ('SID_MEIER_S_RAILROADS', 'CIVILIZATION_IV'):
n_node.flags = 0x0010
elif game is 'EMPIRE_EARTH_II':
elif game == 'EMPIRE_EARTH_II':
n_node.flags = 0x0002
elif game is 'DIVINITY_2':
elif game == 'DIVINITY_2':
n_node.flags = 0x0310
else:
n_node.flags = 0x000C # morrowind
Expand Down Expand Up @@ -177,7 +177,10 @@ def export_node(self, b_obj, n_parent):

# If this has children or animations or more than one material it gets wrapped in a purpose made NiNode.
if not (b_action or b_obj.children or is_multimaterial or has_track):
return self.mesh_helper.export_tri_shapes(b_obj, n_parent, self.n_root, b_obj.name)
mesh = self.mesh_helper.export_tri_shapes(b_obj, n_parent, self.n_root, b_obj.name)
if not self.n_root:
self.n_root = mesh
return mesh

# set transform on trishapes rather than NiNodes for skinned meshes to fix an issue with clothing slots
if b_obj.parent and b_obj.parent.type == 'ARMATURE' and b_action:
Expand Down
28 changes: 14 additions & 14 deletions io_scene_niftools/modules/nif_export/property/shader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ def export_bs_effect_shader_property(self, b_mat):
# bsshader.alpha = (1 - b_mat.alpha)

# Emissive
bsshader.emissive_color.r = b_mat.niftools.emissive_color.r
bsshader.emissive_color.g = b_mat.niftools.emissive_color.g
bsshader.emissive_color.b = b_mat.niftools.emissive_color.b
BSShaderProperty.set_color3_property(bsshader.emissive_color, b_mat.niftools.emissive_color)
bsshader.emissive_color.a = b_mat.niftools.emissive_alpha.v
# TODO [shader] Expose a emission multiplier value
# bsshader.emissive_multiple = b_mat.emit
Expand All @@ -101,31 +99,27 @@ def export_bs_lighting_shader_property(self, b_mat):

# Diffuse color
d = b_mat.diffuse_color
bsshader.skin_tint_color.r = d[0]
bsshader.skin_tint_color.g = d[1]
bsshader.skin_tint_color.b = d[2]

if b_s_type == NifFormat.BSLightingShaderPropertyShaderType["Skin Tint"]:
BSShaderProperty.set_color3_property(bsshader.skin_tint_color, d)
elif b_s_type == NifFormat.BSLightingShaderPropertyShaderType["Hair Tint"]:
BSShaderProperty.set_color3_property(bsshader.hair_tint_color, d)
# TODO [shader] expose intensity value
# b_mat.diffuse_intensity = 1.0

bsshader.lighting_effect_1 = b_mat.niftools.lightingeffect1
bsshader.lighting_effect_2 = b_mat.niftools.lightingeffect2

# Emissive
e = b_mat.niftools.emissive_color
bsshader.emissive_color.r = e[0]
bsshader.emissive_color.g = e[1]
bsshader.emissive_color.b = e[2]
BSShaderProperty.set_color3_property(bsshader.emissive_color, b_mat.niftools.emissive_color)
# TODO [shader] Expose a emission multiplier value
# bsshader.emissive_multiple = b_mat.emit

# gloss
bsshader.glossiness = 1/b_mat.roughness - 1 if b_mat.roughness != 0 else FLOAT_MAX

# Specular color
s = b_mat.specular_color
bsshader.specular_color.r = s[0]
bsshader.specular_color.g = s[1]
bsshader.specular_color.b = s[2]
BSShaderProperty.set_color3_property(bsshader.specular_color, b_mat.specular_color)
bsshader.specular_strength = b_mat.specular_intensity

# Alpha
Expand Down Expand Up @@ -175,3 +169,9 @@ def process_flags(b_mat, flags):
if b_flag:
sf_flag_index = flags._names.index(sf_flag)
flags._items[sf_flag_index]._value = 1

@staticmethod
def set_color3_property(n_property, b_color):
n_property.r = b_color[0]
n_property.g = b_color[1]
n_property.b = b_color[2]
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def export_bs_lighting_shader_prop_textures(self, bsshader):
texset.num_textures = 9
texset.textures.update_size()

if self.slots[TEX_SLOTS.DETAIL]:
texset.textures[6] = TextureWriter.export_texture_filename(self.slots[TEX_SLOTS.DETAIL])
if self.slots[TEX_SLOTS.DECAL_0]:
texset.textures[6] = TextureWriter.export_texture_filename(self.slots[TEX_SLOTS.DECAL_0])

if self.slots[TEX_SLOTS.GLOSS]:
texset.textures[7] = TextureWriter.export_texture_filename(self.slots[TEX_SLOTS.GLOSS])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def export_texture_filename(b_texture_node):

# try and find a DDS alternative, force it if required
ddsfilename = f"{(filename[:-4])}.dds"
if os.path.exists(ddsfilename) or NifOp.props.force_dds:
if os.path.exists(bpy.path.abspath(ddsfilename)) or NifOp.props.force_dds:
filename = ddsfilename

# sanitize file path
Expand All @@ -136,6 +136,8 @@ def export_texture_filename(b_texture_node):
idx = filename.find("textures")
if idx >= 0:
filename = filename[idx:]
elif not os.path.exists(bpy.path.abspath(filename)):
pass
else:
NifLog.warn(f"{filename} does not reside in a 'Textures' folder; texture path will be stripped and textures may not display in-game")
filename = os.path.basename(filename)
Expand Down
8 changes: 3 additions & 5 deletions io_scene_niftools/modules/nif_import/armature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,9 @@ def import_bone_bind(self, n_block, b_armature_data, n_armature, b_parent_bone=N
# get transformation in blender's coordinate space
b_bind = math.nif_bind_to_blender_bind(n_bind)

# the following is a workaround because blender can no longer set matrices to bones directly
tail, roll = bpy.types.Bone.AxisRollFromMatrix(b_bind.to_3x3())
b_edit_bone.head = b_bind.to_translation()
b_edit_bone.tail = tail + b_edit_bone.head
b_edit_bone.roll = roll
# set the bone matrix - but set the tail first to prevent issues with zero-length bone
b_edit_bone.tail = mathutils.Vector([0, 0, 1])
b_edit_bone.matrix = b_bind
# link to parent
if b_parent_bone:
b_edit_bone.parent = b_parent_bone
Expand Down
2 changes: 1 addition & 1 deletion io_scene_niftools/modules/nif_import/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __init__(self):
def create_b_obj(n_block, b_obj_data, name=""):
"""Helper function to create a b_obj from b_obj_data, link it to the current scene, make it active and select it."""
# get the actual nif name
n_name = n_block.name.decode() if n_block else ""
n_name = block_store.import_name(n_block)
if name:
n_name = name
# let blender choose a name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,13 @@ def link_normal_node(self, b_texture_node):
group_node = nodes.new('ShaderNodeGroup')
group_node.node_tree = node_group
links.new(group_node.inputs[0], b_texture_node.outputs[0])
# create tangent normal map converter and link to it
tangent_converter = nodes.new("ShaderNodeNormalMap")
self.tree.links.new(tangent_converter.inputs[1], group_node.outputs[0])
# link to the diffuse shader
self.tree.links.new(self.diffuse_shader.inputs[2], tangent_converter.outputs[0])
if self.b_mat.niftools_shader.slsf_1_model_space_normals:
self.tree.links.new(self.diffuse_shader.inputs[2], group_node.outputs[0])
else:
# create tangent normal map converter and link to it
tangent_converter = nodes.new("ShaderNodeNormalMap")
self.tree.links.new(tangent_converter.inputs[1], group_node.outputs[0])
self.tree.links.new(self.diffuse_shader.inputs[2], tangent_converter.outputs[0])
# # Influence mapping
# b_texture_node.texture.use_normal_map = True # causes artifacts otherwise.
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,9 @@ def import_bs_lighting_shader_property(self, bs_shader_property):
self._nodes_wrapper.global_uv_offset_scale(x_scale, y_scale, x_offset, y_offset, clamp_x, clamp_y)

# Diffuse color
if bs_shader_property.skin_tint_color:
if shader_type == NifFormat.BSLightingShaderPropertyShaderType["Skin Tint"]:
Material.import_material_diffuse(self._b_mat, bs_shader_property.skin_tint_color)

if (self._b_mat.diffuse_color[0] + self._b_mat.diffuse_color[1] + self._b_mat.diffuse_color[2]) == 0:
elif shader_type == NifFormat.BSLightingShaderPropertyShaderType["Hair Tint"]:
Material.import_material_diffuse(self._b_mat, bs_shader_property.hair_tint_color)

# TODO [material][b_shader][property] Handle nialphaproperty node lookup
Expand Down
13 changes: 12 additions & 1 deletion io_scene_niftools/modules/nif_import/property/texture/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ def import_external_source(self, source):

# go through all texture search paths
for texdir in search_path_list:
if texdir[0:2] == "//":
# Blender-specific directory, slows down resolve_ncase:
relative = True
texdir = texdir[2:]
else:
relative = False
texdir = texdir.replace('\\', os.sep)
texdir = texdir.replace('/', os.sep)
# go through all possible file names, try alternate extensions too; for linux, also try lower case versions of filenames
Expand All @@ -281,10 +287,15 @@ def import_external_source(self, source):
tex = os.path.join(texdir, texfn)

# "ignore case" on linuxW
if relative:
tex = bpy.path.abspath("//" + tex)
tex = bpy.path.resolve_ncase(tex)
NifLog.debug(f"Searching {tex}")
if os.path.exists(tex):
return self.load_image(tex)
if relative:
return self.load_image(bpy.path.relpath(tex))
else:
return self.load_image(tex)

else:
tex = fn
Expand Down
8 changes: 2 additions & 6 deletions io_scene_niftools/nif_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,7 @@ def execute(self):
return {'FINISHED'}

for b_obj in self.exportable_objects:
# armatures should not be in rest position
if b_obj.type == 'ARMATURE':
b_obj.data.pose_position = 'POSE'

elif b_obj.type == 'MESH':
if b_obj.type == 'MESH':
if b_obj.parent and b_obj.parent.type == 'ARMATURE':
for b_mod in b_obj.modifiers:
if b_mod.type == 'ARMATURE' and b_mod.use_bone_envelopes:
Expand All @@ -113,7 +109,7 @@ def execute(self):
f"convert their envelopes to vertex weights, and turn off envelopes.")

# check for non-uniform transforms
scale = b_obj.matrix_local.to_scale()
scale = b_obj.scale
if abs(scale.x - scale.y) > NifOp.props.epsilon or abs(scale.y - scale.z) > NifOp.props.epsilon:
NifLog.warn(f"Non-uniform scaling not supported.\n"
f"Workaround: apply size and rotation (CTRL-A) on '{b_obj.name}'.")
Expand Down
1 change: 0 additions & 1 deletion io_scene_niftools/properties/collision.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ class CollisionProperty(PropertyGroup):
name='Collision layer',
description='Collision layer string (game-specific)',
items=game_specific_col_layer_items,
default=0
)

deactivator_type: EnumProperty(
Expand Down
Loading

0 comments on commit 92509dc

Please sign in to comment.