From 3fb9f57471488bc649767fa3d8d8b78b948b87c7 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Wed, 20 Oct 2021 17:43:20 +0200 Subject: [PATCH 01/18] Fixed name error of Facegen Detail Map property, causing it not to be exported. Added condition to not export normals when using a face tint (as done in face meshes). --- .../modules/nif_export/geometry/mesh/__init__.py | 2 +- io_scene_niftools/properties/shader.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 83b73357f..ff3a57706 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -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 diff --git a/io_scene_niftools/properties/shader.py b/io_scene_niftools/properties/shader.py index c7f08e711..77fccefce 100644 --- a/io_scene_niftools/properties/shader.py +++ b/io_scene_niftools/properties/shader.py @@ -245,8 +245,8 @@ class ShaderProps(PropertyGroup): name='Cast Shadows' ) - slsf_1_facegen_detail: BoolProperty( - name='Facegen Detail' + slsf_1_facegen_detail_map: BoolProperty( + name='Facegen Detail Map' ) slsf_1_Parallax: BoolProperty( From 33b3b04eb0403c2baac90a608231f9fbafccc826 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Wed, 20 Oct 2021 19:27:09 +0200 Subject: [PATCH 02/18] Added proper import/export of skin/hair tint --- .../modules/nif_export/property/shader/__init__.py | 12 +++++++++--- .../nif_import/property/shader/bsshaderproperty.py | 5 ++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/property/shader/__init__.py b/io_scene_niftools/modules/nif_export/property/shader/__init__.py index 195e67500..d7676feb3 100644 --- a/io_scene_niftools/modules/nif_export/property/shader/__init__.py +++ b/io_scene_niftools/modules/nif_export/property/shader/__init__.py @@ -101,9 +101,15 @@ 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"]: + bsshader.skin_tint_color.r = d[0] + bsshader.skin_tint_color.g = d[1] + bsshader.skin_tint_color.b = d[2] + elif b_s_type == NifFormat.BSLightingShaderPropertyShaderType["Hair Tint"]: + bsshader.hair_tint_color.r = d[0] + bsshader.hair_tint_color.g = d[1] + bsshader.hair_tint_color.b = d[2] # TODO [shader] expose intensity value # b_mat.diffuse_intensity = 1.0 diff --git a/io_scene_niftools/modules/nif_import/property/shader/bsshaderproperty.py b/io_scene_niftools/modules/nif_import/property/shader/bsshaderproperty.py index 1fb5fe53f..d91374283 100644 --- a/io_scene_niftools/modules/nif_import/property/shader/bsshaderproperty.py +++ b/io_scene_niftools/modules/nif_import/property/shader/bsshaderproperty.py @@ -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 From 61059562b026ccd919ed8fd8ee32286df2561aa3 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Thu, 21 Oct 2021 13:46:05 +0200 Subject: [PATCH 03/18] Fixed issue where scales of different sign, but same size were not detected. Fixed issue where finding texture paths would take a long time (~20s). --- .../modules/nif_import/property/texture/loader.py | 13 ++++++++++++- io_scene_niftools/nif_export.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/texture/loader.py b/io_scene_niftools/modules/nif_import/property/texture/loader.py index 3c4a6a8cd..1edb99850 100644 --- a/io_scene_niftools/modules/nif_import/property/texture/loader.py +++ b/io_scene_niftools/modules/nif_import/property/texture/loader.py @@ -262,6 +262,12 @@ def import_external_source(self, source): # go through all texture search paths for texdir in search_path_list: + # Blender-specific directory, slows down resolve_ncase: + if texdir[0:2] == "//": + 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 @@ -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 diff --git a/io_scene_niftools/nif_export.py b/io_scene_niftools/nif_export.py index b5a12d06c..b39b2fea2 100644 --- a/io_scene_niftools/nif_export.py +++ b/io_scene_niftools/nif_export.py @@ -113,7 +113,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}'.") From 30840d47a991ba42d7d53bfc001dda6b42112539 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Thu, 21 Oct 2021 14:57:29 +0200 Subject: [PATCH 04/18] Changed bslightingshaderproperty slot 6 export to agree with import. Changed texture export to handle relative file paths, and ignore missing texture folder if no file found (i.e. path is likely directly from import). --- .../nif_export/property/texture/types/bsshadertexture.py | 4 ++-- .../modules/nif_export/property/texture/writer.py | 4 +++- .../modules/nif_import/property/texture/loader.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/property/texture/types/bsshadertexture.py b/io_scene_niftools/modules/nif_export/property/texture/types/bsshadertexture.py index 04b394e84..4e61c96bc 100644 --- a/io_scene_niftools/modules/nif_export/property/texture/types/bsshadertexture.py +++ b/io_scene_niftools/modules/nif_export/property/texture/types/bsshadertexture.py @@ -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]) diff --git a/io_scene_niftools/modules/nif_export/property/texture/writer.py b/io_scene_niftools/modules/nif_export/property/texture/writer.py index 8e46277f6..81d033930 100644 --- a/io_scene_niftools/modules/nif_export/property/texture/writer.py +++ b/io_scene_niftools/modules/nif_export/property/texture/writer.py @@ -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 @@ -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) diff --git a/io_scene_niftools/modules/nif_import/property/texture/loader.py b/io_scene_niftools/modules/nif_import/property/texture/loader.py index 1edb99850..f395e8708 100644 --- a/io_scene_niftools/modules/nif_import/property/texture/loader.py +++ b/io_scene_niftools/modules/nif_import/property/texture/loader.py @@ -262,8 +262,8 @@ def import_external_source(self, source): # go through all texture search paths for texdir in search_path_list: - # Blender-specific directory, slows down resolve_ncase: if texdir[0:2] == "//": + # Blender-specific directory, slows down resolve_ncase: relative = True texdir = texdir[2:] else: From e35f417358bdc2dfdc7849eabc14d02f8a7de493 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Thu, 21 Oct 2021 19:04:49 +0200 Subject: [PATCH 05/18] Fixed issue where export would error if imported with an empty name on a root node due to mismatch between object and skeleton root name generation. --- io_scene_niftools/modules/nif_import/object/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_import/object/__init__.py b/io_scene_niftools/modules/nif_import/object/__init__.py index d42bcbef3..d929bfa4a 100644 --- a/io_scene_niftools/modules/nif_import/object/__init__.py +++ b/io_scene_niftools/modules/nif_import/object/__init__.py @@ -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 From 80e15e825b42ae0aec88fce99e8bd6e39d9dd1ec Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Fri, 22 Oct 2021 20:17:12 +0200 Subject: [PATCH 06/18] Fixed issue where there would be no root object when exporting with mesh as root. --- io_scene_niftools/modules/nif_export/object/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 81f181fde..eed78125e 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -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: From d5897b8f01d43cbd95270935070715c54ae2f6e6 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Fri, 22 Oct 2021 21:16:48 +0200 Subject: [PATCH 07/18] Made shader property flags interace dynamic, to avoid spelling mistakes and mismatches on format update. --- io_scene_niftools/properties/shader.py | 401 ++----------------------- io_scene_niftools/ui/shader.py | 104 +------ 2 files changed, 27 insertions(+), 478 deletions(-) diff --git a/io_scene_niftools/properties/shader.py b/io_scene_niftools/properties/shader.py index 77fccefce..274df5f5f 100644 --- a/io_scene_niftools/properties/shader.py +++ b/io_scene_niftools/properties/shader.py @@ -77,389 +77,26 @@ class ShaderProps(PropertyGroup): # default = 'SHADER_DEFAULT' ) - sf_specular: BoolProperty( - name='Specular' - ) - - sf_skinned: BoolProperty( - name='Skinned' - ) - - sf_low_detail: BoolProperty( - name='Low Detail' - ) - - sf_vertex_alpha: BoolProperty( - name='Vertex Alpha' - ) - - sf_unknown_1: BoolProperty( - name='Unknown 1' - ) - - sf_single_pass: BoolProperty( - name='Single Pass' - ) - - sf_empty: BoolProperty( - name='Empty' - ) - - sf_environment_mapping: BoolProperty( - name='Environment Mapping' - ) - - sf_alpha_texture: BoolProperty( - name='Alpha Texture' - ) - - sf_unknown_2: BoolProperty( - name='Unknown 2' - ) - - sf_face_gen: BoolProperty( - name='Face Gen' - ) - - sf_parallax_shader_index_15: BoolProperty( - name='Parallax Shader Index' - ) - - sf_unknown_3: BoolProperty( - name='Unknown 3' - ) - - sf_non_projective_shadows: BoolProperty( - name='Non-Projective Shadows' - ) - - sf_unknown_4: BoolProperty( - name='Unknown 4' - ) - - sf_refraction: BoolProperty( - name='Refraction' - ) - - sf_fire_refraction: BoolProperty( - name='Fire Refraction' - ) - - sf_eye_environment_mapping: BoolProperty( - name='Eye Environment Mapping' - ) - - sf_hair: BoolProperty( - name='Hair' - ) - - sf_dynamic_alpha: BoolProperty( - name='Dynamic Alpha' - ) - - sf_localmap_hide_secret: BoolProperty( - name='Local Map Hide Secret' - ) - - sf_window_environment_mapping: BoolProperty( - name='Window Environment Mapping' - ) - - sf_tree_billboard: BoolProperty( - name='Tree Billboard' - ) - - sf_shadow_frustum: BoolProperty( - name='Shadow Frustum' - ) - - sf_multiple_textures: BoolProperty( - name='Multiple Textures' - ) - - sf_remappable_textures: BoolProperty( - name='Remappable Textures' - ) - - sf_decal_single_pass: BoolProperty( - name='Decal Single Pass' - ) - - sf_dynamic_decal_single_pass: BoolProperty( - name='Dynamic Decal Single Pass' - ) - - sf_parallax_occulsion: BoolProperty( - name='Parallax Occlusion' - ) - - sf_external_emittance: BoolProperty( - name='External Emittance' - ) - - sf_shadow_map: BoolProperty( - name='Shadow Map' - ) - - sf_z_buffer_test: BoolProperty( - name='Z Buffer Test' - ) - - slsf_1_specular: BoolProperty( - name='Specular' - ) - - slsf_1_skinned: BoolProperty( - name='Skinned' - ) - - slsf_1_temp_refraction: BoolProperty( - name='Temp Refraction' - ) - - slsf_1_vertex_alpha: BoolProperty( - name='Vertex Alpha' - ) - - slsf_1_greyscale_to_palettecolor: BoolProperty( - name='Greyscale to Palette Color' - ) - - slsf_1_greyscale_to_palettealpha: BoolProperty( - name='Greyscale to Palette Alpha' - ) - - slsf_1_use_falloff: BoolProperty( - name='Use Falloff' - ) - - slsf_1_environment_mapping: BoolProperty( - name='Environment Mapping' - ) - - slsf_1_recieve_shadows: BoolProperty( - name='Receive Shadows' - ) - - slsf_1_cast_shadows: BoolProperty( - name='Cast Shadows' - ) - - slsf_1_facegen_detail_map: BoolProperty( - name='Facegen Detail Map' - ) - - slsf_1_Parallax: BoolProperty( - name='Parallax' - ) - - slsf_1_model_space_normals: BoolProperty( - name='Model Space Normals' - ) - - slsf_1_non_projective_shadows: BoolProperty( - name='Non Projective Shadows' - ) - - slsf_1_Landscape: BoolProperty( - name='Landscape' - ) - - slsf_1_refraction: BoolProperty( - name='Refraction' - ) - slsf_1_fire_refraction: BoolProperty( - name='Fire Refraction' - ) - - slsf_1_eye_environment_mapping: BoolProperty( - name='Eye Environment Mapping' - ) - - slsf_1_hair_soft_lighting: BoolProperty( - name='Hair Soft Lighting' - ) - - slsf_1_screendoor_alpha_fade: BoolProperty( - name='Screendoor Alpha Fade' - ) - - slsf_1_localmap_hide_secret: BoolProperty( - name='Localmap Hide Secret' - ) - - slsf_1_facegen_rgb_tint: BoolProperty( - name='Facegen RGB Tint' - ) - - slsf_1_own_emit: BoolProperty( - name='Own Emit' - ) - - slsf_1_projected_uv: BoolProperty( - name='Projected UV' - ) - - slsf_1_multiple_textures: BoolProperty( - name='Multiple Textures' - ) - - slsf_1_remappable_textures: BoolProperty( - name='Remappable Textures' - ) - - slsf_1_decal: BoolProperty( - name='Decal' - ) - - slsf_1_dynamic_decal: BoolProperty( - name='Dynamic Decal' - ) - - slsf_1_parallax_occlusion: BoolProperty( - name='Parallax Occlusion' - ) - - slsf_1_external_emittance: BoolProperty( - name='External Emittance' - ) - - slsf_1_soft_effect: BoolProperty( - name='Soft Effect' - ) - - slsf_1_z_buffer_test: BoolProperty( - name='ZBuffer Test' - ) - - slsf_2_z_buffer_write: BoolProperty( - name='ZBuffer Write' - ) - - slsf_2_lod_landscape: BoolProperty( - name='LOD Landscape' - ) - - slsf_2_lod_objects: BoolProperty( - name='LOD Objects' - ) - - slsf_2_no_fade: BoolProperty( - name='No Fade' - ) - - slsf_2_double_sided: BoolProperty( - name='Double Sided' - ) - - slsf_2_vertex_colors: BoolProperty( - name='Vertex Colors' - ) - - slsf_2_glow_map: BoolProperty( - name='Glow Map' - ) - - slsf_2_assume_shadowmask: BoolProperty( - name='Assume Shadowmask' - ) - - slsf_2_packed_tangent: BoolProperty( - name='Packed Tangent' - ) - - slsf_2_multi_index_snow: BoolProperty( - name='Multi Index Snow' - ) - - slsf_2_vertex_lighting: BoolProperty( - name='Vertex Lighting' - ) - - slsf_2_uniform_scale: BoolProperty( - name='Uniform Scale' - ) - - slsf_2_fit_slope: BoolProperty( - name='Fit Slope' - ) - - slsf_2_billboard: BoolProperty( - name='Billboard' - ) - - slsf_2_no_lod_land_blend: BoolProperty( - name='No LOD Land Blend' - ) - - slsf_2_env_map_light_fade: BoolProperty( - name='Envmap Light Fade' - ) - - slsf_2_wireframe: BoolProperty( - name='Wireframe' - ) - - slsf_2_weapon_blood: BoolProperty( - name='Weapon Blood' - ) - - slsf_2_hide_on_local_map: BoolProperty( - name='Hide On Local Map' - ) - - slsf_2_premult_alpha: BoolProperty( - name='Premult Alpha' - ) - - slsf_2_cloud_lod: BoolProperty( - name='Cloud Lod' - ) - - slsf_2_anisotropic_lighting: BoolProperty( - name='Anisotropic Lighting' - ) - - slsf_2_no_transparency_multisampling: BoolProperty( - name='No Transparency Multisampling' - ) - - slsf_2_unused01: BoolProperty( - name='Unused01' - ) - - slsf_2_multi_layer_parallax: BoolProperty( - name='Multi Layer Parallax' - ) - - slsf_2_soft_lighting: BoolProperty( - name='Soft Lighting' - ) - - slsf_2_rim_lighting: BoolProperty( - name='Rim Lighting' - ) - - slsf_2_back_lighting: BoolProperty( - name='Back Lighting' - ) - - slsf_2_unused02: BoolProperty( - name='Unused02' - ) - - slsf_2_tree_anim: BoolProperty( - name='Tree Anim' - ) - - slsf_2_effect_lighting: BoolProperty( - name='Effect Lighting' - ) - - slsf_2_hd_lod_objects: BoolProperty( - name='HD LOD Objects' - ) +def prettify_prop_name(property_name): + replacers = [('Hd', 'HD'), ('Lod', 'LOD')] + prettified = ' '.join([word.capitalize() for word in property_name.split('_')]) + for original, replacement in replacers: + prettified = prettified.replace(original, replacement) + return prettified + + +annotations_dict = ShaderProps.__dict__.get('__annotations__', None) +if annotations_dict: + for property_name in NifFormat.BSShaderFlags._names: + if property_name not in annotations_dict: + annotations_dict[property_name] = BoolProperty(name=prettify_prop_name(property_name[3:])) + for property_name in NifFormat.SkyrimShaderPropertyFlags1._names: + if property_name not in annotations_dict: + annotations_dict[property_name] = BoolProperty(name=prettify_prop_name(property_name[7:])) + for property_name in NifFormat.SkyrimShaderPropertyFlags2._names: + if property_name not in annotations_dict: + annotations_dict[property_name] = BoolProperty(name=prettify_prop_name(property_name[7:])) CLASSES = [ diff --git a/io_scene_niftools/ui/shader.py b/io_scene_niftools/ui/shader.py index 2fb1475a7..17c757028 100644 --- a/io_scene_niftools/ui/shader.py +++ b/io_scene_niftools/ui/shader.py @@ -39,6 +39,8 @@ from bpy.types import Panel +from pyffi.formats.nif import NifFormat + from io_scene_niftools.utils.decorators import register_classes, unregister_classes @@ -67,106 +69,16 @@ def draw(self, context): if nif_obj_props.bs_shadertype == 'BSShaderPPLightingProperty': row.prop(nif_obj_props, "bsspplp_shaderobjtype") - row.prop(nif_obj_props, "sf_alpha_texture") - row.prop(nif_obj_props, "sf_decal_single_pass") - row.prop(nif_obj_props, "sf_dynamic_alpha") - row.prop(nif_obj_props, "sf_dynamic_decal_single_pass") - row.prop(nif_obj_props, "sf_empty") - row.prop(nif_obj_props, "sf_environment_mapping") - row.prop(nif_obj_props, "sf_external_emittance") - row.prop(nif_obj_props, "sf_eye_environment_mapping") - row.prop(nif_obj_props, "sf_face_gen") - row.prop(nif_obj_props, "sf_fire_refraction") - row.prop(nif_obj_props, "sf_hair") - row.prop(nif_obj_props, "sf_localmap_hide_secret") - row.prop(nif_obj_props, "sf_low_detail") - row.prop(nif_obj_props, "sf_multiple_textures") - row.prop(nif_obj_props, "sf_non_projective_shadows") - row.prop(nif_obj_props, "sf_parallax_occulsion") - row.prop(nif_obj_props, "sf_parallax_shader_index_15") - row.prop(nif_obj_props, "sf_refraction") - row.prop(nif_obj_props, "sf_remappable_textures") - row.prop(nif_obj_props, "sf_shadow_frustum") - row.prop(nif_obj_props, "sf_shadow_map") - row.prop(nif_obj_props, "sf_single_pass") - row.prop(nif_obj_props, "sf_skinned") - row.prop(nif_obj_props, "sf_specular") - row.prop(nif_obj_props, "sf_tree_billboard") - row.prop(nif_obj_props, "sf_unknown_1") - row.prop(nif_obj_props, "sf_unknown_2") - row.prop(nif_obj_props, "sf_unknown_3") - row.prop(nif_obj_props, "sf_unknown_4") - row.prop(nif_obj_props, "sf_vertex_alpha") - row.prop(nif_obj_props, "sf_window_environment_mapping") - row.prop(nif_obj_props, "sf_z_buffer_test") + for property_name in sorted(NifFormat.BSShaderFlags._names): + row.prop(nif_obj_props, property_name) elif nif_obj_props.bs_shadertype in ('BSLightingShaderProperty', 'BSEffectShaderProperty'): row.prop(nif_obj_props, "bslsp_shaderobjtype") - row.prop(nif_obj_props, "slsf_1_cast_shadows") - row.prop(nif_obj_props, "slsf_1_decal") - row.prop(nif_obj_props, "slsf_1_dynamic_decal") - row.prop(nif_obj_props, "slsf_1_environment_mapping") - row.prop(nif_obj_props, "slsf_1_external_emittance") - row.prop(nif_obj_props, "slsf_1_eye_environment_mapping") - row.prop(nif_obj_props, "slsf_1_facegen_detail") - row.prop(nif_obj_props, "slsf_1_facegen_rgb_tint") - row.prop(nif_obj_props, "slsf_1_fire_refraction") - row.prop(nif_obj_props, "slsf_1_greyscale_to_palettealpha") - row.prop(nif_obj_props, "slsf_1_greyscale_to_palettecolor") - row.prop(nif_obj_props, "slsf_1_hair_soft_lighting") - row.prop(nif_obj_props, "slsf_1_Landscape") - row.prop(nif_obj_props, "slsf_1_localmap_hide_secret") - row.prop(nif_obj_props, "slsf_1_model_space_normals") - row.prop(nif_obj_props, "slsf_1_multiple_textures") - row.prop(nif_obj_props, "slsf_1_non_projective_shadows") - row.prop(nif_obj_props, "slsf_1_own_emit") - row.prop(nif_obj_props, "slsf_1_parallax_occlusion") - row.prop(nif_obj_props, "slsf_1_Parallax") - row.prop(nif_obj_props, "slsf_1_projected_uv") - row.prop(nif_obj_props, "slsf_1_recieve_shadows") - row.prop(nif_obj_props, "slsf_1_refraction") - row.prop(nif_obj_props, "slsf_1_remappable_textures") - row.prop(nif_obj_props, "slsf_1_screendoor_alpha_fade") - row.prop(nif_obj_props, "slsf_1_skinned") - row.prop(nif_obj_props, "slsf_1_soft_effect") - row.prop(nif_obj_props, "slsf_1_specular") - row.prop(nif_obj_props, "slsf_1_temp_refraction") - row.prop(nif_obj_props, "slsf_1_use_falloff") - row.prop(nif_obj_props, "slsf_1_vertex_alpha") - row.prop(nif_obj_props, "slsf_1_z_buffer_test") - row.prop(nif_obj_props, "slsf_2_anisotropic_lighting") - row.prop(nif_obj_props, "slsf_2_assume_shadowmask") - row.prop(nif_obj_props, "slsf_2_back_lighting") - row.prop(nif_obj_props, "slsf_2_billboard") - row.prop(nif_obj_props, "slsf_2_cloud_lod") - row.prop(nif_obj_props, "slsf_2_double_sided") - row.prop(nif_obj_props, "slsf_2_effect_lighting") - row.prop(nif_obj_props, "slsf_2_env_map_light_fade") - row.prop(nif_obj_props, "slsf_2_fit_slope") - row.prop(nif_obj_props, "slsf_2_glow_map") - row.prop(nif_obj_props, "slsf_2_hd_lod_objects") - row.prop(nif_obj_props, "slsf_2_hide_on_local_map") - row.prop(nif_obj_props, "slsf_2_lod_landscape") - row.prop(nif_obj_props, "slsf_2_lod_objects") - row.prop(nif_obj_props, "slsf_2_multi_index_snow") - row.prop(nif_obj_props, "slsf_2_multi_layer_parallax") - row.prop(nif_obj_props, "slsf_2_no_fade") - row.prop(nif_obj_props, "slsf_2_no_lod_land_blend") - row.prop(nif_obj_props, "slsf_2_no_transparency_multisampling") - row.prop(nif_obj_props, "slsf_2_packed_tangent") - row.prop(nif_obj_props, "slsf_2_premult_alpha") - row.prop(nif_obj_props, "slsf_2_rim_lighting") - row.prop(nif_obj_props, "slsf_2_soft_lighting") - row.prop(nif_obj_props, "slsf_2_tree_anim") - row.prop(nif_obj_props, "slsf_2_uniform_scale") - row.prop(nif_obj_props, "slsf_2_unused01") - row.prop(nif_obj_props, "slsf_2_unused02") - row.prop(nif_obj_props, "slsf_2_vertex_colors") - row.prop(nif_obj_props, "slsf_2_vertex_lighting") - row.prop(nif_obj_props, "slsf_2_weapon_blood") - row.prop(nif_obj_props, "slsf_2_wireframe") - row.prop(nif_obj_props, "slsf_2_z_buffer_write") + for property_name in sorted(NifFormat.SkyrimShaderPropertyFlags1._names): + row.prop(nif_obj_props, property_name) + for property_name in sorted(NifFormat.SkyrimShaderPropertyFlags2._names): + row.prop(nif_obj_props, property_name) classes = [ From d87fad90b0dadf176f034b9b02d4d6bf2d47b556 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Sat, 23 Oct 2021 00:15:47 +0200 Subject: [PATCH 08/18] Converted checks with string literals using 'is' to using == and != --- io_scene_niftools/addon_updater_ops.py | 4 ++-- io_scene_niftools/modules/nif_export/object/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/io_scene_niftools/addon_updater_ops.py b/io_scene_niftools/addon_updater_ops.py index e254e1cb0..efba015c6 100644 --- a/io_scene_niftools/addon_updater_ops.py +++ b/io_scene_niftools/addon_updater_ops.py @@ -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") @@ -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: diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index eed78125e..06c586691 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -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 From 614e8cf4efee060c284ddd024f69d219287cde2e Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Sat, 23 Oct 2021 17:25:32 +0200 Subject: [PATCH 09/18] Pulled out method for setting colors as per PR review. --- .../nif_export/property/shader/__init__.py | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/property/shader/__init__.py b/io_scene_niftools/modules/nif_export/property/shader/__init__.py index d7676feb3..39cd78941 100644 --- a/io_scene_niftools/modules/nif_export/property/shader/__init__.py +++ b/io_scene_niftools/modules/nif_export/property/shader/__init__.py @@ -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 @@ -103,13 +101,9 @@ def export_bs_lighting_shader_property(self, b_mat): d = b_mat.diffuse_color if b_s_type == NifFormat.BSLightingShaderPropertyShaderType["Skin Tint"]: - bsshader.skin_tint_color.r = d[0] - bsshader.skin_tint_color.g = d[1] - bsshader.skin_tint_color.b = d[2] + BSShaderProperty.set_color3_property(bsshader.skin_tint_color, d) elif b_s_type == NifFormat.BSLightingShaderPropertyShaderType["Hair Tint"]: - bsshader.hair_tint_color.r = d[0] - bsshader.hair_tint_color.g = d[1] - bsshader.hair_tint_color.b = d[2] + BSShaderProperty.set_color3_property(bsshader.hair_tint_color, d) # TODO [shader] expose intensity value # b_mat.diffuse_intensity = 1.0 @@ -117,10 +111,7 @@ def export_bs_lighting_shader_property(self, b_mat): 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 @@ -128,10 +119,7 @@ def export_bs_lighting_shader_property(self, b_mat): 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 @@ -181,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] From b108437da027228ade9e6a30fc3c605dd538c914 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Mon, 25 Oct 2021 21:37:23 +0200 Subject: [PATCH 10/18] Removed 'default' argument from collision_layer EnumProperty declaration. It wasn't doing anything and errored the plugin in Blender 2.8. --- io_scene_niftools/properties/collision.py | 1 - 1 file changed, 1 deletion(-) diff --git a/io_scene_niftools/properties/collision.py b/io_scene_niftools/properties/collision.py index f5a860dfb..877a9550a 100644 --- a/io_scene_niftools/properties/collision.py +++ b/io_scene_niftools/properties/collision.py @@ -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( From a28c067a7ae39dba41c162015ea32cf7355581e2 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:06:24 +0200 Subject: [PATCH 11/18] Apply bind pose to copied mesh without clearing the pose entirely. --- .../modules/nif_export/geometry/mesh/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index ff3a57706..2e072d65d 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -720,8 +720,7 @@ 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() + b_armature_obj.data.pose_position = 'REST' # make sure the model has a triangulation modifier self.ensure_tri_modifier(b_obj) @@ -729,7 +728,10 @@ def get_triangulated_mesh(self, 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 = 'POSE' + return eval_mesh def ensure_tri_modifier(self, b_obj): """Makes sure that ob has a triangulation modifier in its stack.""" From 56576b962f5cc8eec381a0da99dc0eb8e1148cbd Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:32:12 +0100 Subject: [PATCH 12/18] Change import of bone bind matrix to directly set matrix. --- io_scene_niftools/modules/nif_import/armature/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/armature/__init__.py b/io_scene_niftools/modules/nif_import/armature/__init__.py index 02bb61df0..552103aad 100644 --- a/io_scene_niftools/modules/nif_import/armature/__init__.py +++ b/io_scene_niftools/modules/nif_import/armature/__init__.py @@ -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.Vector3([0,0,1]) + b_edit_bone.matrix = b_bind # link to parent if b_parent_bone: b_edit_bone.parent = b_parent_bone From fa31d1e8bd3166713e63a32ebbc6a0f38454f0ca Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:36:40 +0100 Subject: [PATCH 13/18] Changed armature export so that the pose position (pose or rest) is preserved. --- .../modules/nif_export/geometry/mesh/__init__.py | 5 +++-- io_scene_niftools/nif_export.py | 6 +----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 2e072d65d..606786bd2 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -452,7 +452,7 @@ 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 + # override pyffi trishape.update_bind_position with custom one that is relative to the nif root self.update_bind_position(trishape, n_root) # calculate center and radius for each skin bone data block @@ -720,6 +720,7 @@ 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: + old_position = b_armature_obj.data.pose_position b_armature_obj.data.pose_position = 'REST' # make sure the model has a triangulation modifier @@ -730,7 +731,7 @@ def get_triangulated_mesh(self, b_obj): eval_obj = b_obj.evaluated_get(dg) eval_mesh = eval_obj.to_mesh(preserve_all_data_layers=True, depsgraph=dg) if b_armature_obj: - b_armature_obj.data.pose_position = 'POSE' + b_armature_obj.data.pose_position = old_position return eval_mesh def ensure_tri_modifier(self, b_obj): diff --git a/io_scene_niftools/nif_export.py b/io_scene_niftools/nif_export.py index b39b2fea2..df1054c2e 100644 --- a/io_scene_niftools/nif_export.py +++ b/io_scene_niftools/nif_export.py @@ -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: From e85c7640b10345a1ebf7a67bc9d3cbffd2a670cf Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Mon, 1 Nov 2021 21:42:03 +0100 Subject: [PATCH 14/18] Fixed wrong use of Vector3 (which doesn't exist) instead of Vector on mathutils. --- io_scene_niftools/modules/nif_import/armature/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_import/armature/__init__.py b/io_scene_niftools/modules/nif_import/armature/__init__.py index 552103aad..6387c00a8 100644 --- a/io_scene_niftools/modules/nif_import/armature/__init__.py +++ b/io_scene_niftools/modules/nif_import/armature/__init__.py @@ -244,7 +244,7 @@ def import_bone_bind(self, n_block, b_armature_data, n_armature, b_parent_bone=N b_bind = math.nif_bind_to_blender_bind(n_bind) # set the bone matrix - but set the tail first to prevent issues with zero-length bone - b_edit_bone.tail = mathutils.Vector3([0,0,1]) + b_edit_bone.tail = mathutils.Vector([0,0,1]) b_edit_bone.matrix = b_bind # link to parent if b_parent_bone: From 72e9cbd33e12a23a5ebc52192f9e9069aa94eef6 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Tue, 2 Nov 2021 10:22:37 +0100 Subject: [PATCH 15/18] Removed matrix multiplication that resolves to the identity matrix. --- io_scene_niftools/utils/math.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_scene_niftools/utils/math.py b/io_scene_niftools/utils/math.py index f2dd5f0b2..fa2d3344f 100644 --- a/io_scene_niftools/utils/math.py +++ b/io_scene_niftools/utils/math.py @@ -82,15 +82,15 @@ def export_keymat(rest_rot, key_matrix, bone): def get_bind_matrix(bone): """Get a nif armature-space matrix from a blender bone. """ - bind = correction @ correction_inv @ bone.matrix_local @ correction + bind = bone.matrix_local @ correction if bone.parent: - p_bind_restored = correction @ correction_inv @ bone.parent.matrix_local @ correction + p_bind_restored = bone.parent.matrix_local @ correction bind = p_bind_restored.inverted() @ bind return bind def nif_bind_to_blender_bind(nif_armature_space_matrix): - return correction_inv @ correction @ nif_armature_space_matrix @ correction_inv + return nif_armature_space_matrix @ correction_inv def import_matrix(n_block, relative_to=None): From 497a900b4b5c053926b2a315835b715c2a02b7d4 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:33:23 +0100 Subject: [PATCH 16/18] Added check to only insert tangent space converter when not using model space normals in nif. --- .../nif_import/property/nodes_wrapper/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py index 11d921d6c..4178f5153 100644 --- a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py @@ -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. # From 721ad3bd6f2bd1e5f73e97d1559a6263fe52be28 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Tue, 2 Nov 2021 20:31:16 +0100 Subject: [PATCH 17/18] Added export of Blender pose. --- .../modules/nif_export/armature/__init__.py | 18 +++++++++---- .../nif_export/geometry/mesh/__init__.py | 27 ++++++++++++------- io_scene_niftools/utils/math.py | 4 +++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/armature/__init__.py b/io_scene_niftools/modules/nif_export/armature/__init__.py index 7c8311015..c847ba8f9 100644 --- a/io_scene_niftools/modules/nif_export/armature/__init__.py +++ b/io_scene_niftools/modules/nif_export/armature/__init__.py @@ -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): @@ -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) @@ -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""" diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 606786bd2..6e8956ca6 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -453,7 +453,7 @@ 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 trishape.update_bind_position with custom one that is relative to the nif root - self.update_bind_position(trishape, n_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() @@ -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 @@ -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(): diff --git a/io_scene_niftools/utils/math.py b/io_scene_niftools/utils/math.py index fa2d3344f..7194c106c 100644 --- a/io_scene_niftools/utils/math.py +++ b/io_scene_niftools/utils/math.py @@ -89,6 +89,10 @@ def get_bind_matrix(bone): return bind +def blender_bind_to_nif_bind(blender_armature_space_matrix): + return blender_armature_space_matrix @ correction + + def nif_bind_to_blender_bind(nif_armature_space_matrix): return nif_armature_space_matrix @ correction_inv From 91502d9ff5b3c09c45055b1f1e9b6065125bab5b Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Tue, 2 Nov 2021 20:33:51 +0100 Subject: [PATCH 18/18] Added spaces after commas. --- io_scene_niftools/modules/nif_import/armature/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_import/armature/__init__.py b/io_scene_niftools/modules/nif_import/armature/__init__.py index 6387c00a8..936af2f08 100644 --- a/io_scene_niftools/modules/nif_import/armature/__init__.py +++ b/io_scene_niftools/modules/nif_import/armature/__init__.py @@ -244,7 +244,7 @@ def import_bone_bind(self, n_block, b_armature_data, n_armature, b_parent_bone=N b_bind = math.nif_bind_to_blender_bind(n_bind) # 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.tail = mathutils.Vector([0, 0, 1]) b_edit_bone.matrix = b_bind # link to parent if b_parent_bone: