From 0bf58270d1f2bb919c50faac21d2701fc3c42857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 13 Jun 2022 00:45:46 +0200 Subject: [PATCH 01/56] Support for 'tsnmapcalc' Flavor Small modification that adds support for 'tsnmapcalc' flavor mostly used in road templates models, where road texture use normal map. --- addon/io_scs_tools/properties/material.py | 60 ++++++++++++++++++++++- addon/io_scs_tools/shader_presets.txt | 27 +++++++++- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index b37db79..4fed34c 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -273,7 +273,8 @@ def get_texture_types(): """ return {'base', 'reflection', 'over', 'oclu', 'mask', 'mult', 'iamod', 'lightmap', 'paintjob', 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'layer0', 'layer1', - 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b'} + 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b', + 'nmap_over'} def get_id(self): """Gets unique ID for material within current Blend file. If ID does not exists yet it's calculated. @@ -420,6 +421,12 @@ def update_shader_texture_nmap_detail(self, context): def update_shader_texture_nmap_detail_settings(self, context): __update_shader_texture_tobj_file__(self, context, "nmap_detail") + def update_shader_texture_nmap_over(self, context): + __update_shader_texture__(self, context, "nmap_over") + + def update_shader_texture_nmap_over_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "nmap_over") + def update_shader_texture_oclu(self, context): __update_shader_texture__(self, context, "oclu") @@ -1380,6 +1387,57 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): options={'HIDDEN'}, ) + # TEXTURE: NMAP_OVER + shader_texture_nmap_over: StringProperty( + name="Texture NMap Over", + description="Texture Nmap Over for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_nmap_over, + ) + shader_texture_nmap_over_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_nmap_over_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_nmap_over_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_nmap_over_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_nmap_over_settings + ) + shader_texture_nmap_over_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_nmap_over_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_nmap_over_uv: CollectionProperty( + name="Texture Nmap Over UV Sets", + description="Texture Nmap Over UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + # TEXTURE: OCCLUSION shader_texture_oclu: StringProperty( name="Texture Oclu", diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index e50e68b..40c9445 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -904,7 +904,7 @@ Shader { Shader { PresetName: "dif.spec.weight" Effect: "eut2.dif.spec.weight" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1106,7 +1106,7 @@ Shader { Shader { PresetName: "dif.spec.weight.weight.dif.spec.weight" Effect: "eut2.dif.spec.weight.weight.dif.spec.weight" - Flavors: ( "SHADOW" "TEXGEN0" "TEXGEN1" "ALPHA" ) + Flavors: ( "NMAP_TS_CALC_OVER" "SHADOW" "TEXGEN0" "TEXGEN1" "ALPHA" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2002,6 +2002,29 @@ Flavor { TexCoord: ( 6 ) } } +Flavor { + Type: "NMAP_TS_CALC" + Name: "tsnmapcalc" + Texture { + Tag: "texture[X]:texture_nmap" + Value: "" + TexCoord: ( 0 ) + } +} +Flavor { + Type: "NMAP_TS_CALC_OVER" + Name: "tsnmapcalc" + Texture { + Tag: "texture[X]:texture_nmap" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_over" + Value: "" + TexCoord: ( 1 ) + } +} Flavor { Type: "NOCULL" Name: "nocull" From aeeecf8f3fadef27aef08bda0d4cb4452adcf015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 13 Jun 2022 02:01:22 +0200 Subject: [PATCH 02/56] Support for 'tsnmapcalc' Flavor Fast fix that add tsnmapcalc to Shader Editor --- addon/io_scs_tools/internals/shaders/shader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 9e1c713..2c69859 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -71,6 +71,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".tsnmap16") or ".tsnmap16." in effect: flavors["nmap"] = True + if effect.endswith(".tsnmapcalc") or ".tsnmapcalc." in effect: + flavors["nmap"] = True + if effect.endswith(".indenv") or ".indenv." in effect: flavors["indenv"] = True From 7b0780282768bab5658a9fcc12c2bef92643b37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sun, 22 Sep 2024 21:08:22 +0200 Subject: [PATCH 03/56] amod support, aliasing nmap, aux fix * Added support for "dif.spec.amod.dif.spec" shader. It's not perfect, but it should be enough to work with models. (Col_alpha: 0.25 = full Base, 0.0/0.5 = Full Over. Idk why it's divided that way.) * Added condition to allow aliasing materials with "tsnmap" flavor, like it is made in "/material/terrain/" dir. * Fixed aux[0] and aux[1] (changed from float2 to float4 - texgen_X_rot and unused/nmap rot? idk) --- .gitignore | 1 + .../internals/shaders/eut2/__init__.py | 4 + .../eut2/dif_spec_amod_dif_spec/__init__.py | 225 ++++++++++++++++++ addon/io_scs_tools/properties/material.py | 15 ++ addon/io_scs_tools/shader_presets.txt | 70 +++++- addon/io_scs_tools/supported_effects.bin | Bin 221908 -> 190106 bytes addon/io_scs_tools/ui/material.py | 3 + 7 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py diff --git a/.gitignore b/.gitignore index c668405..1779ade 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ var/ *.egg-info/ .installed.cfg *.egg +.vs/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 973e499..d883946 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -131,6 +131,10 @@ def get_shader(effect): elif effect.startswith("dif.spec.over.dif.opac"): from io_scs_tools.internals.shaders.eut2.dif_spec_over_dif_opac import DifSpecOverDifOpac as Shader + + elif effect.startswith("dif.spec.amod.dif.spec"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_amod_dif_spec import DifSpecAmodDifSpec as Shader elif effect.startswith("dif.spec.mult.dif.spec.iamod.dif.spec"): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py new file mode 100644 index 0000000..0fe1d09 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py @@ -0,0 +1,225 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2019: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.utils import material as _material_utils + +class DifSpecAmodDifSpec(DifSpec): + SEC_UVMAP_NODE = "SecondUVMap" + VCOLOR_SCALE_NODE = "VertexColorScale" + VCOLOR_SUBTRACT_NODE = "VertexColorSubtract" + VCOLOR_ABS_NODE = "VertexColorAbsolute" + VCOLOR_INVERT_NODE = "VertexColorInvert" + MASK_TEX_NODE = "MaskTex" + OVER_TEX_NODE = "OverTex" + MASK_COLOR_INVERT_NODE = "MaskColorInvert" + MASK_VCOLOR_MIX_NODE = "MaskVertexColorMix" + MASK_COLOR_MIX_NODE = "MaskColorMix" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + DifSpec.init(node_tree, disable_remap_alpha=False) + + base_tex_n = node_tree.nodes[DifSpec.BASE_TEX_NODE] + vcol_multi_n = node_tree.nodes[DifSpec.VCOLOR_MULT_NODE] + vcol_group_n = node_tree.nodes[DifSpec.VCOL_GROUP_NODE] + + # node creation + # - column 0 - + sec_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") + sec_uvmap_n.name = DifSpecAmodDifSpec.SEC_UVMAP_NODE + sec_uvmap_n.label = DifSpecAmodDifSpec.SEC_UVMAP_NODE + sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1000) + sec_uvmap_n.uv_map = _MESH_consts.none_uv + + # - column 2 - + vcol_scale_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_scale_n.name = DifSpecAmodDifSpec.VCOLOR_SCALE_NODE + vcol_scale_n.label = DifSpecAmodDifSpec.VCOLOR_SCALE_NODE + vcol_scale_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) + vcol_scale_n.operation = "MULTIPLY" + vcol_scale_n.inputs[1].default_value = (2,) * 3 + + mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_tex_n.name = DifSpecAmodDifSpec.MASK_TEX_NODE + mask_tex_n.label = DifSpecAmodDifSpec.MASK_TEX_NODE + mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1000) + mask_tex_n.width = 140 + + over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_tex_n.name = DifSpecAmodDifSpec.OVER_TEX_NODE + over_tex_n.label = DifSpecAmodDifSpec.OVER_TEX_NODE + over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 700) + over_tex_n.width = 140 + + # - column 3 - + vcol_subtract_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_subtract_n.name = DifSpecAmodDifSpec.VCOLOR_SUBTRACT_NODE + vcol_subtract_n.label = DifSpecAmodDifSpec.VCOLOR_SUBTRACT_NODE + vcol_subtract_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) + vcol_subtract_n.operation = "SUBTRACT" + vcol_subtract_n.inputs[1].default_value = (1,) * 3 + + mask_invert_n = node_tree.nodes.new("ShaderNodeInvert") + mask_invert_n.name = DifSpecAmodDifSpec.MASK_COLOR_INVERT_NODE + mask_invert_n.label = DifSpecAmodDifSpec.MASK_COLOR_INVERT_NODE + mask_invert_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1000) + mask_invert_n.inputs[0].default_value = 1 + + # - column 4 - + vcol_abs_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_abs_n.name = DifSpecAmodDifSpec.VCOLOR_ABS_NODE + vcol_abs_n.label = DifSpecAmodDifSpec.VCOLOR_ABS_NODE + vcol_abs_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1100) + vcol_abs_n.operation = "ABSOLUTE" + + # - column 5 - + vcol_invert_n = node_tree.nodes.new("ShaderNodeInvert") + vcol_invert_n.name = DifSpecAmodDifSpec.VCOLOR_INVERT_NODE + vcol_invert_n.label = DifSpecAmodDifSpec.VCOLOR_INVERT_NODE + vcol_invert_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1100) + vcol_invert_n.inputs[0].default_value = 1 + + # - column 6 - + mask_vcol_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + mask_vcol_mix_n.name = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE + mask_vcol_mix_n.label = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE + mask_vcol_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1100) + mask_vcol_mix_n.blend_type = "MIX" + mask_vcol_mix_n.inputs['Color2'].default_value = (1,) * 4 + + # - column 7 - + mask_color_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + mask_color_mix_n.name = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE + mask_color_mix_n.label = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE + mask_color_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1050) + mask_color_mix_n.blend_type = "MIX" + + + + # links creation + # - Column 0 - + node_tree.links.new(vcol_group_n.outputs[1], vcol_scale_n.inputs[0]) + node_tree.links.new(sec_uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) + node_tree.links.new(sec_uvmap_n.outputs['UV'], over_tex_n.inputs['Vector']) + + # - Column 2 - + node_tree.links.new(base_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color2']) + node_tree.links.new(vcol_scale_n.outputs['Vector'], vcol_subtract_n.inputs[0]) + node_tree.links.new(mask_tex_n.outputs['Color'], mask_invert_n.inputs['Color']) + node_tree.links.new(over_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color1']) + + # - Column 3 - + node_tree.links.new(vcol_subtract_n.outputs['Vector'], vcol_abs_n.inputs[0]) + node_tree.links.new(mask_invert_n.outputs['Color'], mask_vcol_mix_n.inputs['Color1']) + + # - Column 4 - + node_tree.links.new(vcol_abs_n.outputs['Vector'], vcol_invert_n.inputs['Color']) + + # - Column 5 - + node_tree.links.new(vcol_invert_n.outputs['Color'], mask_vcol_mix_n.inputs['Fac']) + + # - Column 6 - + node_tree.links.new(mask_vcol_mix_n.outputs['Color'], mask_color_mix_n.inputs['Fac']) + + # - Column 7 - + node_tree.links.new(mask_color_mix_n.outputs['Color'], vcol_multi_n.inputs[1]) + + + @staticmethod + def set_over_uv(node_tree, uv_layer): + """Set UV layer to overlying texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecAmodDifSpec.SEC_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_mask_texture(node_tree, image): + """Set mask texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to over texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE].image = image + + @staticmethod + def set_mask_texture_settings(node_tree, settings): + """Set mask texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + # Commented because of texture linear colorspace problems in render + # _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE], settings) + + @staticmethod + def set_over_texture(node_tree, image): + """Set over texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to mult texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecAmodDifSpec.OVER_TEX_NODE].image = image + + @staticmethod + def set_over_texture_settings(node_tree, settings): + """Set mask texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecAmodDifSpec.OVER_TEX_NODE], settings) + \ No newline at end of file diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 4fed34c..1a361a3 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -342,6 +342,9 @@ def update_shader_attribute_tint(self, context): def update_shader_attribute_tint_opacity(self, context): __update_shader_attribute__(self, context, "tint_opacity") + + def update_shader_attribute_amod_decal_blending_factors(self, context): + __update_shader_attribute__(self, context, "amod_decal_blending_factors") def update_shader_texture_base(self, context): __update_shader_texture__(self, context, "base") @@ -712,6 +715,18 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): options={'HIDDEN'}, update=update_shader_attribute_tint_opacity ) + + shader_attribute_amod_decal_blending_factors: FloatVectorProperty( + name="Amod Decal Blending Factors", + description="SCS shader 'Amod Blending' value", + default=(1.0, 1.0), + min=0, max=1, + step=1, precision=2, + options={'HIDDEN'}, + subtype='NONE', + size=2, + update=update_shader_attribute_amod_decal_blending_factors, + ) shader_attribute_queue_bias: IntProperty( name="Queue Bias", diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 40c9445..00ad8fc 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -858,7 +858,7 @@ Shader { Shader { PresetName: "dif.spec.over.dif.opac" Effect: "eut2.dif.spec.over.dif.opac" - Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD|ENVMAP" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1776,6 +1776,58 @@ Shader { TexCoord: ( 1 ) } } +Shader { + PresetName: "dif.spec.amod.dif.spec" + Effect: "eut2.dif.spec.amod.dif.spec" + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "ASAFEWA" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "amod_decal_blending_factors" + Value: ( 1.0 1.0 ) + Hide: "True" + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 20.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 1.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 1 ) + } +} Flavor { Type: "AIRBRUSH" Name: "airbrush" @@ -1803,6 +1855,10 @@ Flavor { Type: "ASAFEW" Name: "asafew" } +Flavor { + Type: "ASAFEWA" + Name: "asafew.a" +} Flavor { Type: "AWHITE" Name: "awhite" @@ -2098,19 +2154,19 @@ Flavor { Type: "TEXGEN0" Name: "tg0" Attribute { - Format: FLOAT2 + Format: FLOAT4 Tag: "aux[0]" - FriendlyTag: "TexGen0 Scale (X,Z)" - Value: ( 10.0 10.0 ) + FriendlyTag: "TexGen0 Scale (X,Z,ROT_BASE,ROT_NMAP)" + Value: ( 10.0 10.0 0.0 0.0 ) } } Flavor { Type: "TEXGEN1" Name: "tg1" Attribute { - Format: FLOAT2 + Format: FLOAT4 Tag: "aux[1]" - FriendlyTag: "TexGen1 Scale (X,Z)" - Value: ( 8.0 8.0 ) + FriendlyTag: "TexGen1 Scale (X,Z,ROT_BASE,ROT_NMAP)" + Value: ( 8.0 8.0 0.0 0.0 ) } } diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 2f3338b873814391f5710ed61e350a2e9b94d66f..50a04993855fe1007ce092ad987226e83de3cd07 100644 GIT binary patch literal 190106 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 i1<8pO4xijFRVwDD$??`Ayz~%xTlvpz Date: Mon, 23 Sep 2024 15:19:18 +0200 Subject: [PATCH 04/56] Support for custom company locators Added support for custom unloading/loading locators in "Spawn Point" -> "Custom" setting. Custom (un)loading settings allow you, to set parking difficulty, max trailer lenght and trailer type like "Company" tool in ME. --- addon/io_scs_tools/consts.py | 40 ++++++++++++++++ addon/io_scs_tools/exp/pip/exporter.py | 2 + addon/io_scs_tools/exp/pip/spawn_point.py | 21 ++++++++ addon/io_scs_tools/imp/pip.py | 20 ++++++-- addon/io_scs_tools/properties/object.py | 58 ++++++++++++++++++++++- addon/io_scs_tools/ui/object.py | 13 +++++ 6 files changed, 149 insertions(+), 5 deletions(-) diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index 0ee4826..cb732e8 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -444,6 +444,46 @@ class PIF: TYPE_END = 0x00020000 TYPE_CROSS_SHARP = 0x00040000 + class PSPCF: + """Constants represetning spawn point custom flags. + """ + # Difficulties + RAIL_MASK = 0x0000000F + RAIL_NONE = 0x00000000 + RAIL_EASY = 0x00000001 + RAIL_MEDIUM = 0x00000002 + RAIL_HARD = 0x00000003 + RAIL_RIGID = 0x00000004 # ?? idk + RAIL_LOAD = 0x00000005 + + # Lenght + LENGHT_MASK = 0x000000F0 + LENGHT_14 = 0x00000000 + LENGHT_15 = 0x00000010 + LENGHT_16 = 0x00000020 + LENGHT_17 = 0x00000030 + LENGHT_18 = 0x00000040 + LENGHT_19 = 0x00000050 + LENGHT_20 = 0x00000060 + LENGHT_21 = 0x00000070 + LENGHT_22 = 0x00000080 + LENGHT_23 = 0x00000090 + LENGHT_24 = 0x000000A0 + LENGHT_25 = 0x000000B0 + LENGHT_26 = 0x000000C0 + LENGHT_27 = 0x000000D0 + LENGHT_28 = 0x000000E0 + UNLIMITED = 0x000000F0 + + # Rule + TRAILER_MASK = 0x000F0000 + TRAILER_ANY = 0x00000000 + TRAILER_BOX = 0x00010000 + TRAILER_TANK = 0x00020000 + TRAILER_DUMP_BULK = 0x00030000 + TRAILER_PLATFORM_LOG_CONT = 0x00040000 + TRAILER_LIVESTOCK = 0x00050000 + TRAILER_LOG = 0x00060000 class Bones: init_scale_key = "scs_init_scale" diff --git a/addon/io_scs_tools/exp/pip/exporter.py b/addon/io_scs_tools/exp/pip/exporter.py index 8f5a785..e03ac9e 100644 --- a/addon/io_scs_tools/exp/pip/exporter.py +++ b/addon/io_scs_tools/exp/pip/exporter.py @@ -327,6 +327,8 @@ def execute(dirpath, filename, name_suffix, prefab_locator_list, offset_matrix, spawn_point.set_type(int(locator_scs_props.locator_prefab_spawn_type)) + spawn_point.set_flags(locator_scs_props) + pip_spawn_points.append(spawn_point) # semaphores creation diff --git a/addon/io_scs_tools/exp/pip/spawn_point.py b/addon/io_scs_tools/exp/pip/spawn_point.py index 095a675..b30cd81 100644 --- a/addon/io_scs_tools/exp/pip/spawn_point.py +++ b/addon/io_scs_tools/exp/pip/spawn_point.py @@ -44,6 +44,7 @@ def __init__(self, name): self.__position = (0.,) * 3 self.__rotation = (1.,) + (0.,) * 3 self.__type = 0 # NONE + self.__flags = 0 # NONE SpawnPoint.__global_spawn_point_counter += 1 @@ -74,6 +75,25 @@ def set_type(self, sp_type): """ self.__type = sp_type + def set_flags(self, sp_flags): + """Set flags of spawn point. + + NOTE: there is no safety check if value is valid, + make sure that prefab locator properties are synced with PSPCF_* consts. + + :param sp_flags: integer flags of spawn point + :type sp_flags: int + """ + + # parking difficulty + self.__flags |= int(sp_flags.locator_prefab_custom_parking_difficulty) + + # lenght + self.__flags |= int(sp_flags.locator_prefab_custom_lenght) + + # rule + self.__flags |= int(sp_flags.locator_prefab_custom_rule) + def get_as_section(self): """Get spawn point information represented with SectionData structure class. @@ -86,5 +106,6 @@ def get_as_section(self): section.props.append(("Position", ["&&", tuple(self.__position)])) section.props.append(("Rotation", ["&&", tuple(self.__rotation)])) section.props.append(("Type", self.__type)) + section.props.append(("Flags", self.__flags)) return section diff --git a/addon/io_scs_tools/imp/pip.py b/addon/io_scs_tools/imp/pip.py index 6da37c3..9efe5b0 100644 --- a/addon/io_scs_tools/imp/pip.py +++ b/addon/io_scs_tools/imp/pip.py @@ -207,7 +207,7 @@ def _get_sign_properties(section): def _get_spawn_properties(section): """Receives a Spawn section and returns its properties in its own variables. For any item that fails to be found, it returns None.""" - spawn_name = spawn_position = spawn_rotation = spawn_type = None + spawn_name = spawn_position = spawn_rotation = spawn_type = spawn_flags = None for prop in section.props: if prop[0] in ("", "#"): pass @@ -219,9 +219,11 @@ def _get_spawn_properties(section): spawn_rotation = prop[1] elif prop[0] == "Type": spawn_type = prop[1] + elif prop[0] == "Flags": + spawn_flags = prop[1] else: lprint('\nW Unknown property in "Spawn" data: "%s"!', prop[0]) - return spawn_name, spawn_position, spawn_rotation, spawn_type + return spawn_name, spawn_position, spawn_rotation, spawn_type, spawn_flags def _get_t_light_properties(section): @@ -450,12 +452,19 @@ def _create_spawn_locator( spawn_name, spawn_position, spawn_rotation, - spawn_type + spawn_type, + spawn_flags ): locator = _object_utils.create_locator_empty(spawn_name, spawn_position, spawn_rotation, (1, 1, 1), 0.1, 'Prefab') if locator: locator.scs_props.locator_prefab_type = 'Spawn Point' locator.scs_props.locator_prefab_spawn_type = str(spawn_type) + + # flags + locator.scs_props.locator_prefab_custom_parking_difficulty = str(spawn_flags & _PL_consts.PSPCF.RAIL_MASK) + locator.scs_props.locator_prefab_custom_lenght = str(spawn_flags & _PL_consts.PSPCF.LENGHT_MASK) + locator.scs_props.locator_prefab_custom_rule = str(spawn_flags & _PL_consts.PSPCF.TRAILER_MASK) + return locator @@ -732,7 +741,8 @@ def load(filepath, terrain_points_trans): (spawn_name, spawn_position, spawn_rotation, - spawn_type) = _get_spawn_properties(section) + spawn_type, + spawn_flags) = _get_spawn_properties(section) if spawn_name is None: spawn_name = str('Sign_Locator_' + str(spawn_index)) @@ -744,6 +754,7 @@ def load(filepath, terrain_points_trans): spawn_position, spawn_rotation, spawn_type, + spawn_flags, ) spawn_index += 1 elif section.type == 'Semaphore': # former "TrafficLight" @@ -915,6 +926,7 @@ def load(filepath, terrain_points_trans): spawn_points_data[name][1], spawn_points_data[name][2], spawn_points_data[name][3], + spawn_points_data[name][4], # sp_flags ) if loc: _print_locator_result(loc, "Spawn Point", name) diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 606c9d3..68a394c 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -850,7 +850,7 @@ def locator_prefab_type_update(self, context): (_PL_consts.PSP.CAMERA_POINT, (str(_PL_consts.PSP.CAMERA_POINT), "Camera Point", "")), (_PL_consts.PSP.COMPANY_POS, (str(_PL_consts.PSP.COMPANY_POS), "Company Point", "")), (_PL_consts.PSP.COMPANY_UNLOAD_POS, (str(_PL_consts.PSP.COMPANY_UNLOAD_POS), "Company Unload Point", "")), - # (_PL_consts.PSP.CUSTOM, (str(_PL_consts.PSP.CUSTOM), "Custom", "")), + (_PL_consts.PSP.CUSTOM, (str(_PL_consts.PSP.CUSTOM), "Custom", "")), (_PL_consts.PSP.GARAGE_POS, (str(_PL_consts.PSP.GARAGE_POS), "Garage Point", "")), (_PL_consts.PSP.GAS_POS, (str(_PL_consts.PSP.GAS_POS), "Gas Station", "")), # (_PL_consts.PSP.HOTEL, (str(_PL_consts.PSP.HOTEL), "Hotel", "")), @@ -878,6 +878,62 @@ def locator_prefab_type_update(self, context): items=enum_spawn_type_items.values(), default=str(_PL_consts.PSP.NONE), ) + enum_custom_parking_difficulty_items = OrderedDict([ + (_PL_consts.PSPCF.RAIL_NONE, (str(_PL_consts.PSPCF.RAIL_NONE), "None", "")), + (_PL_consts.PSPCF.RAIL_EASY, (str(_PL_consts.PSPCF.RAIL_EASY), "Unload (Easy)", "")), + (_PL_consts.PSPCF.RAIL_MEDIUM, (str(_PL_consts.PSPCF.RAIL_MEDIUM), "Unload (Medium)", "")), + (_PL_consts.PSPCF.RAIL_HARD, (str(_PL_consts.PSPCF.RAIL_HARD), "Unload (Hard)", "")), + (_PL_consts.PSPCF.RAIL_RIGID, (str(_PL_consts.PSPCF.RAIL_RIGID), "Unload (Rigid?)", "")), + (_PL_consts.PSPCF.RAIL_LOAD, (str(_PL_consts.PSPCF.RAIL_LOAD), "Load", "")), + ]) + # LOCATORS - PREFAB - SPAWN POINTS (CUSTOM) + locator_prefab_custom_parking_difficulty: EnumProperty( + name="Parking Difficulty", + description="Current parking difficulty", + items=enum_custom_parking_difficulty_items.values(), + default=str(_PL_consts.PSPCF.RAIL_NONE), + ) + enum_custom_lenght_items = OrderedDict([ + (_PL_consts.PSPCF.LENGHT_14, (str(_PL_consts.PSPCF.LENGHT_14), "14 m", "")), + (_PL_consts.PSPCF.LENGHT_15, (str(_PL_consts.PSPCF.LENGHT_15), "15 m", "")), + (_PL_consts.PSPCF.LENGHT_16, (str(_PL_consts.PSPCF.LENGHT_16), "16 m", "")), + (_PL_consts.PSPCF.LENGHT_17, (str(_PL_consts.PSPCF.LENGHT_17), "17 m", "")), + (_PL_consts.PSPCF.LENGHT_18, (str(_PL_consts.PSPCF.LENGHT_18), "18 m", "")), + (_PL_consts.PSPCF.LENGHT_19, (str(_PL_consts.PSPCF.LENGHT_19), "19 m", "")), + (_PL_consts.PSPCF.LENGHT_20, (str(_PL_consts.PSPCF.LENGHT_20), "20 m", "")), + (_PL_consts.PSPCF.LENGHT_21, (str(_PL_consts.PSPCF.LENGHT_21), "21 m", "")), + (_PL_consts.PSPCF.LENGHT_22, (str(_PL_consts.PSPCF.LENGHT_22), "22 m", "")), + (_PL_consts.PSPCF.LENGHT_23, (str(_PL_consts.PSPCF.LENGHT_23), "23 m", "")), + (_PL_consts.PSPCF.LENGHT_24, (str(_PL_consts.PSPCF.LENGHT_24), "24 m", "")), + (_PL_consts.PSPCF.LENGHT_25, (str(_PL_consts.PSPCF.LENGHT_25), "25 m", "")), + (_PL_consts.PSPCF.LENGHT_26, (str(_PL_consts.PSPCF.LENGHT_26), "26 m", "")), + (_PL_consts.PSPCF.LENGHT_27, (str(_PL_consts.PSPCF.LENGHT_27), "27 m", "")), + (_PL_consts.PSPCF.LENGHT_28, (str(_PL_consts.PSPCF.LENGHT_28), "28 m", "")), + (_PL_consts.PSPCF.UNLIMITED, (str(_PL_consts.PSPCF.UNLIMITED), "Unlimited", "")), + ]) + locator_prefab_custom_lenght: EnumProperty( + name="Trailer Lenght", + description="Max trailer lenght", + options={'HIDDEN'}, + items=enum_custom_lenght_items.values(), + default=str(_PL_consts.PSPCF.LENGHT_14), + ) + enum_custom_rule_items = OrderedDict([ + (_PL_consts.PSPCF.TRAILER_ANY, (str(_PL_consts.PSPCF.TRAILER_ANY), "Any Trailer", "")), + (_PL_consts.PSPCF.TRAILER_BOX, (str(_PL_consts.PSPCF.TRAILER_BOX), "Box Trailer", "")), + (_PL_consts.PSPCF.TRAILER_TANK, (str(_PL_consts.PSPCF.TRAILER_TANK), "Tank Trailer", "")), + (_PL_consts.PSPCF.TRAILER_DUMP_BULK, (str(_PL_consts.PSPCF.TRAILER_DUMP_BULK), "Dump & Bulk", "")), + (_PL_consts.PSPCF.TRAILER_PLATFORM_LOG_CONT, (str(_PL_consts.PSPCF.TRAILER_PLATFORM_LOG_CONT), "Platform, Log & Container", "")), + (_PL_consts.PSPCF.TRAILER_LIVESTOCK, (str(_PL_consts.PSPCF.TRAILER_LIVESTOCK), "Livestock", "")), + (_PL_consts.PSPCF.TRAILER_LOG, (str(_PL_consts.PSPCF.TRAILER_LOG), "Log Trailer", "")), + ]) + locator_prefab_custom_rule: EnumProperty( + name="Trailer Type", + description="Trailer type", + options={'HIDDEN'}, + items=enum_custom_rule_items.values(), + default=str(_PL_consts.PSPCF.TRAILER_ANY), + ) # LOCATORS - PREFAB - TRAFFIC LIGHTS (SEMAPHORES) enum_tsem_id_items = OrderedDict([(-1, ('-1', "None", ""))]) for i in range(_PL_consts.TSEM_COUNT_MAX): diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index 4294a55..209232a 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -366,8 +366,21 @@ def draw_prefab_spawn_point(layout, obj): layout.use_property_split = True layout.use_property_decorate = False + # spawn type layout.prop(obj.scs_props, 'locator_prefab_spawn_type') + # check if Spawn Type = Custom (9) + if obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.CUSTOM): + # parking difficulty + layout.prop(obj.scs_props, 'locator_prefab_custom_parking_difficulty') + + # lenght + layout.prop(obj.scs_props, 'locator_prefab_custom_lenght') + + # rule (trailer type) + layout.prop(obj.scs_props, 'locator_prefab_custom_rule') + + @staticmethod def draw_prefab_semaphore(layout, obj): layout.use_property_split = True From 0e076239a5a24f5b7cc9e15f2552c442bb613e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 23 Sep 2024 16:13:01 +0200 Subject: [PATCH 05/56] Fix error when import .pip without "Flags" * Fixed error when you try import old .pip file, without "Flags" attribute inside "SpawnPoint". --- addon/io_scs_tools/imp/pip.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addon/io_scs_tools/imp/pip.py b/addon/io_scs_tools/imp/pip.py index 9efe5b0..890eec4 100644 --- a/addon/io_scs_tools/imp/pip.py +++ b/addon/io_scs_tools/imp/pip.py @@ -749,6 +749,9 @@ def load(filepath, terrain_points_trans): else: spawn_name = _name_utils.get_unique(spawn_name, spawn_points_data.keys()) + if spawn_flags is None: + spawn_flags = int(0) + spawn_points_data[spawn_name] = ( spawn_index, spawn_position, From 2c737707f6948a960c641e4d975ca009be1d805f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 4 Oct 2024 20:20:18 +0200 Subject: [PATCH 06/56] Fixes with custom spawn point, bgl remove * Custom spawn point fixes (now unload/load is separeted from parking difficulty) * Added visual colored shape showing depot place, size and difficulty * BGL module was replaced by GPU in one place * Some blf.size fixes (removed unnecessary arg) --- addon/io_scs_tools/consts.py | 17 ++-- addon/io_scs_tools/exp/pip/spawn_point.py | 3 + addon/io_scs_tools/imp/pip.py | 12 ++- addon/io_scs_tools/internals/open_gl/core.py | 28 +++--- .../internals/open_gl/locators/prefab.py | 93 ++++++++++++++++++- addon/io_scs_tools/properties/object.py | 24 +++-- addon/io_scs_tools/ui/object.py | 5 +- 7 files changed, 146 insertions(+), 36 deletions(-) diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index cb732e8..cf01780 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -447,14 +447,17 @@ class PIF: class PSPCF: """Constants represetning spawn point custom flags. """ + # Depot Types + DEPOT_TYPE_MASK = 0x00000004 + DEPOT_TYPE_UNLOAD = 0x00000000 + DEPOT_TYPE_LOAD = 0x00000004 + # Difficulties - RAIL_MASK = 0x0000000F - RAIL_NONE = 0x00000000 - RAIL_EASY = 0x00000001 - RAIL_MEDIUM = 0x00000002 - RAIL_HARD = 0x00000003 - RAIL_RIGID = 0x00000004 # ?? idk - RAIL_LOAD = 0x00000005 + DIFFICULTY_MASK = 0x00000003 + DIFFICULTY_NONE = 0x00000000 + DIFFICULTY_EASY = 0x00000001 + DIFFICULTY_MEDIUM = 0x00000002 + DIFFICULTY_HARD = 0x00000003 # Lenght LENGHT_MASK = 0x000000F0 diff --git a/addon/io_scs_tools/exp/pip/spawn_point.py b/addon/io_scs_tools/exp/pip/spawn_point.py index b30cd81..b1f4dbf 100644 --- a/addon/io_scs_tools/exp/pip/spawn_point.py +++ b/addon/io_scs_tools/exp/pip/spawn_point.py @@ -85,6 +85,9 @@ def set_flags(self, sp_flags): :type sp_flags: int """ + # depot type + self.__flags |= int(sp_flags.locator_prefab_custom_depot_type) + # parking difficulty self.__flags |= int(sp_flags.locator_prefab_custom_parking_difficulty) diff --git a/addon/io_scs_tools/imp/pip.py b/addon/io_scs_tools/imp/pip.py index 890eec4..92c17f8 100644 --- a/addon/io_scs_tools/imp/pip.py +++ b/addon/io_scs_tools/imp/pip.py @@ -460,10 +460,14 @@ def _create_spawn_locator( locator.scs_props.locator_prefab_type = 'Spawn Point' locator.scs_props.locator_prefab_spawn_type = str(spawn_type) - # flags - locator.scs_props.locator_prefab_custom_parking_difficulty = str(spawn_flags & _PL_consts.PSPCF.RAIL_MASK) - locator.scs_props.locator_prefab_custom_lenght = str(spawn_flags & _PL_consts.PSPCF.LENGHT_MASK) - locator.scs_props.locator_prefab_custom_rule = str(spawn_flags & _PL_consts.PSPCF.TRAILER_MASK) + # flags for custom spawn type + + # check if Spawn Type = Custom (9) + if locator.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.CUSTOM): + locator.scs_props.locator_prefab_custom_depot_type = str(spawn_flags & _PL_consts.PSPCF.DEPOT_TYPE_MASK) + locator.scs_props.locator_prefab_custom_parking_difficulty = str(spawn_flags & _PL_consts.PSPCF.DIFFICULTY_MASK) + locator.scs_props.locator_prefab_custom_lenght = str(spawn_flags & _PL_consts.PSPCF.LENGHT_MASK) + locator.scs_props.locator_prefab_custom_rule = str(spawn_flags & _PL_consts.PSPCF.TRAILER_MASK) return locator diff --git a/addon/io_scs_tools/internals/open_gl/core.py b/addon/io_scs_tools/internals/open_gl/core.py index 5a46dc5..5d1655a 100644 --- a/addon/io_scs_tools/internals/open_gl/core.py +++ b/addon/io_scs_tools/internals/open_gl/core.py @@ -20,7 +20,7 @@ import bpy import blf -import bgl +import gpu from mathutils import Vector from gpu_extras.presets import draw_texture_2d from io_scs_tools.consts import Operators as _OP_consts @@ -139,15 +139,14 @@ def _draw_3dview_report(window, area, region): # draw BT banner (bindcode, width, height) = _Show3DViewReport.get_scs_banner_img_data(window) - bgl.glEnable(bgl.GL_BLEND) - bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) + gpu.state.blend_set("ALPHA") draw_texture_2d(bindcode, (pos_x - 5, pos_y), width, height) - bgl.glDisable(bgl.GL_BLEND) + gpu.state.blend_set("NONE") # draw control buttons, if controls are enabled if _Show3DViewReport.has_controls(window): - blf.size(0, 20, 72) + blf.size(0, 20) blf.color(0, .8, .8, .8, 1) # set x and y offsets to report operator, so that area calculations for buttons can be calculated properly @@ -187,7 +186,7 @@ def _draw_3dview_report(window, area, region): # draw scroll controls if _Show3DViewReport.is_scrolled() and _Show3DViewReport.is_shown(): - blf.size(0, 16, 72) + blf.size(0, 16) # draw scroll up button scroll_up_pos = ( @@ -243,7 +242,7 @@ def _draw_3dview_report(window, area, region): # draw version string pos_y -= 12 - blf.size(0, 11, 72) + blf.size(0, 11) blf.color(0, 0.909803921568627, .631372549019608, .0627450980392157, 1) blf.shadow(0, 0, 0, 0, 0, 1) blf.position(0, pos_x, pos_y, 0) @@ -253,7 +252,7 @@ def _draw_3dview_report(window, area, region): # draw actual operator title and message if shown if _Show3DViewReport.is_shown(): - blf.size(0, 12, 72) + blf.size(0, 12) blf.color(0, 1, 1, 1, 1) blf.shadow(0, 0, 0, 0, 0, 1) @@ -322,7 +321,7 @@ def _draw_3dview_immediate_report(region): ) # set size of the immidete text - blf.size(0, 18, 72) + blf.size(0, 18) blf.color(0, .952, .635, .062, 1) # draw on center of the region @@ -457,16 +456,15 @@ def draw_custom_3d_elements(mode): return if mode == "Normal": - bgl.glEnable(bgl.GL_DEPTH_TEST) - bgl.glEnable(bgl.GL_BLEND) - bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA) + gpu.state.depth_test_set("LESS") + gpu.state.blend_set("ALPHA") # draw buffers _primitive.draw_buffers(bpy.context.space_data) if mode == "Normal": - bgl.glDisable(bgl.GL_DEPTH_TEST) - bgl.glDisable(bgl.GL_BLEND) + gpu.state.depth_test_set("NONE") + gpu.state.blend_set("NONE") def draw_custom_2d_elements(): @@ -495,7 +493,7 @@ def draw_custom_2d_elements(): return font_id = 0 # default font - blf.size(font_id, 12, 72) + blf.size(font_id, 12) blf.color(font_id, scs_globals.info_text_color[0], scs_globals.info_text_color[1], scs_globals.info_text_color[2], 1.0) blf.word_wrap(font_id, 999) blf.enable(font_id, blf.WORD_WRAP) diff --git a/addon/io_scs_tools/internals/open_gl/locators/prefab.py b/addon/io_scs_tools/internals/open_gl/locators/prefab.py index 0b60ba7..08a8d4b 100644 --- a/addon/io_scs_tools/internals/open_gl/locators/prefab.py +++ b/addon/io_scs_tools/internals/open_gl/locators/prefab.py @@ -99,6 +99,84 @@ def draw_shape_spawn_point(mat, scs_globals): _primitive.append_line_vertex((mat @ Vector((0.0, 0.1299, 0.525))), color) _primitive.append_line_vertex((mat @ Vector((0.0, -0.1299, 0.675))), color) +def draw_shape_spawn_point_custom(mat, scs_globals, obj): + """ + Draws shape for "Custom" type "Locator" of "Spawn Point" type. + :param mat: + :param scs_globals: + :param obj: + :return: + """ + + color = ( + scs_globals.locator_prefab_wire_color.r, + scs_globals.locator_prefab_wire_color.g, + scs_globals.locator_prefab_wire_color.b, + 1.0 + ) + + # Load + if (int(obj.scs_props.locator_prefab_custom_depot_type) / 4) == 1: + match obj.scs_props.locator_prefab_custom_parking_difficulty: + case "1": # Easy + difficulty_color = (0.0, 1.0, 1.0, 1.0) + case "2": # Medium + difficulty_color = (0.0, 0.471, 1.0, 1.0) + case "3": # Hard + difficulty_color = (0.0, 0.0, 0.784, 1.0) + case _: + difficulty_color = (0.0, 1.0, 1.0, 1.0) + # Unload + else: + match obj.scs_props.locator_prefab_custom_parking_difficulty: + case "1": # Easy + difficulty_color = (1.0, 1.0, 0.0, 1.0) + case "2": # Medium + difficulty_color = (0.706, 0.471, 0.0, 1.0) + case "3": # Hard + difficulty_color = (1.0, 0.0, 0.0, 1.0) + case _: + difficulty_color = (1.0, 1.0, 0.0, 1.0) + + # Matrix without "Locator Size" + mat_orig = obj.matrix_world + + width = 3.4 + height = 0.05 # Height above ground to prevent z-fight + lenght = (int(obj.scs_props.locator_prefab_custom_lenght) / 16) + 14 + + # Set diffrent shape for "Unlimited" lenght (max size of last fixed lenght) + if lenght == 29.0: + lenght = 20.0 + + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), difficulty_color) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 1.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 8.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 10.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 8.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 1.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), difficulty_color) + + # Locator + _primitive.append_line_vertex((mat @ Vector((0.0, 0.0, scs_globals.locator_empty_size))), color) # , color + _primitive.append_line_vertex((mat @ Vector((0.0, 0.0, 0.75))), color) + _primitive.append_line_vertex((mat @ Vector((-0.1299, 0.0, 0.525))), color) + _primitive.append_line_vertex((mat @ Vector((0.1299, 0.0, 0.675))), color) + _primitive.append_line_vertex((mat @ Vector((0.1299, 0.0, 0.525))), color) + _primitive.append_line_vertex((mat @ Vector((-0.1299, 0.0, 0.675))), color) + _primitive.append_line_vertex((mat @ Vector((0.0, -0.1299, 0.525))), color) + _primitive.append_line_vertex((mat @ Vector((0.0, 0.1299, 0.675))), color) + _primitive.append_line_vertex((mat @ Vector((0.0, 0.1299, 0.525))), color) + _primitive.append_line_vertex((mat @ Vector((0.0, -0.1299, 0.675))), color) + + # Depot + _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), difficulty_color) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, 0.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 2.0, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght, height))), difficulty_color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), difficulty_color) + def draw_shape_traffic_light(mat, scs_globals): """ @@ -211,7 +289,10 @@ def draw_prefab_locator(obj, scs_globals): _primitive.draw_shape_y_axis(mat, empty_size) _primitive.draw_shape_z_axis_neg(mat, empty_size) if not obj.scs_props.locator_preview_model_present or not scs_globals.show_preview_models: - draw_shape_spawn_point(mat, scs_globals) + if obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.CUSTOM): + draw_shape_spawn_point_custom(mat, scs_globals, obj) + else: + draw_shape_spawn_point(mat, scs_globals) elif obj.scs_props.locator_prefab_type == 'Traffic Semaphore': _primitive.draw_shape_x_axis(mat, empty_size) @@ -259,6 +340,16 @@ def get_prefab_locator_comprehensive_info(obj): spawn_type_i = int(obj.scs_props.locator_prefab_spawn_type) textlines.append("Type: %s" % obj.scs_props.enum_spawn_type_items[spawn_type_i][1]) + if obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.CUSTOM): + depot_type_i = int(obj.scs_props.locator_prefab_custom_depot_type) + difficulty_i = int(obj.scs_props.locator_prefab_custom_parking_difficulty) + lenght_i = int(obj.scs_props.locator_prefab_custom_lenght) + trailer_i = int(obj.scs_props.locator_prefab_custom_rule) + + textlines.append("Difficulty: %s (%s)" % (obj.scs_props.enum_custom_depot_type_items[depot_type_i][1], + obj.scs_props.enum_custom_parking_difficulty_items[difficulty_i][1])) + textlines.append("Lenght: %s" % obj.scs_props.enum_custom_lenght_items[lenght_i][1]) + textlines.append("Trailer: %s" % obj.scs_props.enum_custom_rule_items[trailer_i][1]) elif obj.scs_props.locator_prefab_type == 'Traffic Semaphore': diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 68a394c..e87a4a1 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -878,20 +878,28 @@ def locator_prefab_type_update(self, context): items=enum_spawn_type_items.values(), default=str(_PL_consts.PSP.NONE), ) + # LOCATORS - PREFAB - SPAWN POINTS (CUSTOM) + enum_custom_depot_type_items = OrderedDict([ + (_PL_consts.PSPCF.DEPOT_TYPE_UNLOAD, (str(_PL_consts.PSPCF.DEPOT_TYPE_UNLOAD), "Unload", "")), + (_PL_consts.PSPCF.DEPOT_TYPE_LOAD, (str(_PL_consts.PSPCF.DEPOT_TYPE_LOAD), "Load", "")), + ]) + locator_prefab_custom_depot_type: EnumProperty( + name="Depot Type", + description="Depot Type", + items=enum_custom_depot_type_items.values(), + default=str(_PL_consts.PSPCF.DEPOT_TYPE_UNLOAD), + ) enum_custom_parking_difficulty_items = OrderedDict([ - (_PL_consts.PSPCF.RAIL_NONE, (str(_PL_consts.PSPCF.RAIL_NONE), "None", "")), - (_PL_consts.PSPCF.RAIL_EASY, (str(_PL_consts.PSPCF.RAIL_EASY), "Unload (Easy)", "")), - (_PL_consts.PSPCF.RAIL_MEDIUM, (str(_PL_consts.PSPCF.RAIL_MEDIUM), "Unload (Medium)", "")), - (_PL_consts.PSPCF.RAIL_HARD, (str(_PL_consts.PSPCF.RAIL_HARD), "Unload (Hard)", "")), - (_PL_consts.PSPCF.RAIL_RIGID, (str(_PL_consts.PSPCF.RAIL_RIGID), "Unload (Rigid?)", "")), - (_PL_consts.PSPCF.RAIL_LOAD, (str(_PL_consts.PSPCF.RAIL_LOAD), "Load", "")), + (_PL_consts.PSPCF.DIFFICULTY_NONE, (str(_PL_consts.PSPCF.DIFFICULTY_NONE), "None", "")), + (_PL_consts.PSPCF.DIFFICULTY_EASY, (str(_PL_consts.PSPCF.DIFFICULTY_EASY), "Easy", "")), + (_PL_consts.PSPCF.DIFFICULTY_MEDIUM, (str(_PL_consts.PSPCF.DIFFICULTY_MEDIUM), "Medium", "")), + (_PL_consts.PSPCF.DIFFICULTY_HARD, (str(_PL_consts.PSPCF.DIFFICULTY_HARD), "Hard", "")), ]) - # LOCATORS - PREFAB - SPAWN POINTS (CUSTOM) locator_prefab_custom_parking_difficulty: EnumProperty( name="Parking Difficulty", description="Current parking difficulty", items=enum_custom_parking_difficulty_items.values(), - default=str(_PL_consts.PSPCF.RAIL_NONE), + default=str(_PL_consts.PSPCF.DIFFICULTY_EASY), ) enum_custom_lenght_items = OrderedDict([ (_PL_consts.PSPCF.LENGHT_14, (str(_PL_consts.PSPCF.LENGHT_14), "14 m", "")), diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index 209232a..f4388d2 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -371,6 +371,10 @@ def draw_prefab_spawn_point(layout, obj): # check if Spawn Type = Custom (9) if obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.CUSTOM): + + # depot type (load/unload) + layout.prop(obj.scs_props, 'locator_prefab_custom_depot_type', expand=True, toggle=True) + # parking difficulty layout.prop(obj.scs_props, 'locator_prefab_custom_parking_difficulty') @@ -380,7 +384,6 @@ def draw_prefab_spawn_point(layout, obj): # rule (trailer type) layout.prop(obj.scs_props, 'locator_prefab_custom_rule') - @staticmethod def draw_prefab_semaphore(layout, obj): layout.use_property_split = True From 3075e459e000ab746ad112c164cb06d722c3d663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 10 Oct 2024 19:16:22 +0200 Subject: [PATCH 07/56] reflection fix, shaders update * Fixed upside-down reflection in env shaders * Updated .bin file with new shaders (eut2.interior) --- .../eut2/std_node_groups/refl_normal_ng.py | 2 +- addon/io_scs_tools/supported_effects.bin | Bin 190106 -> 190943 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py index 15215bb..5216457 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py @@ -61,7 +61,7 @@ def __create_refl_normal_group__(): view_vector_n = refl_normal_g.nodes.new("ShaderNodeVectorMath") view_vector_n.location = (185, 250) view_vector_n.operation = "MULTIPLY" - view_vector_n.inputs[1].default_value = (-1,) * 3 + view_vector_n.inputs[1].default_value = (-1,1,1) view_vector_norm_n = refl_normal_g.nodes.new("ShaderNodeVectorMath") view_vector_norm_n.location = (185 * 2, 250) diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 50a04993855fe1007ce092ad987226e83de3cd07..f6a56b1c0cf59e5c4de0b3af50086133ce96a066 100644 GIT binary patch literal 190943 zcmbTf>yl>4RV7G|+1P-jQmJ$U1T7>OXf?w`tE$x?XlNORwasIcMP@{11r-^QL}XS; z`ZW&_W`o%T4>@l!`?mIFxt}j0;ZLEAbKJM@z4qD{_v7FH{lEU(kALSs{Eq(L|MU2> zhu^=r`_0)`FK@5U-d+Cs?B?a!{reYZH@ClieE3h7@85lPc5(IU?EdZL%d^|BF7NDr z-M&44d3Jtradvt0)!Dn(KVP22->z;h;*qo4D|z<(@!`i8m#@yw-@d*6&Dr(U>o4BP z(6{f-&Tn6yy}G!%fA{!sW|O|ZxxRW=QoTCAxV-=3@=~7s`irY~muDB3FVC-4hR28B zPbsh8zllG*d4HWlrTqGjwVL}c&M$7iK6`t9b@NUap~C$%hw=r#yncW7^`(g!OL~0x ztIac5@4Ne(H|KB9VrbLMf1i)zNodAic>mR>e~p)19%$>&SzB-AFRm_=P?UQAFL#d* zf0(m`xVq?b*U$f+f3sIqm8Auj`2AOl31cZP&iPR5|1k%|5VD5(wBqIQ;nVqa$cLBC z&hO7(U4AXHx<&n+@9*AueE44evs&6#BS!mf;`i0<>oYa-XO9p6B_FlN#;hV}%<6MG zSbjd=m|%g~Tx=ack@p8WvpUV&yW4;M`I%1d?(_KY{S-?7uJAICBGoO!PV0XU|o9-082uJfbfAjvU&+cBoaG)BHjN%2KI=g|fNMp|bMhM;El+-!=$4yfLBuWM4 z<^vi#{&01GmyW{6hi{usUR+&YzqmcW6GtNOm`PGO=N5P#AO0Bgl(%*N{b4Fv0Sbf4 zQvT2UgCP^D1X=kyT0Ge=eW=Lkqf?KA&D8qCDW_=sx36TWj}PQ`K*55mlwGl1>>V~R zn^5fPk2x(OI6G2(L>cWzX%#h;FZ!KC6p`ie;V-A6R{6TR|7<=yO_uX5xkZBSZ|3_? z&~0ennCLI&n=>nM5j~2~gIG_5UJJ4AEQAm|NJ#ORtco%VXU5c`PN^5H@A61SwImM1 zr79#vAVf${Cjo_Bod2c;9;G0i-;}V+*_O40CIt%z<{p`e#AR2}f1OIlu=C-x($6j3 zdwd`Zj3o#9e0T~B+u`4T6)vlWczmE&8UUfH@}r0oPIxY*l641*A+=(;|4HO{d@z0= z8SDF-vzOPm?=L=EojR6?1pq=~-%<{8++^1YVZ7U=%S=Gnq-^b}at))AY@HEmkf`$4 zX2qigwWBu-HvY zBQpxi!y><5(x^jOEL;XL348$%_XEmO!VjpLI)C?0DzIPd=Cw4TukDxrd@mez`}NE7`w@T0JjS;Lgd250rR&V?qTG_RIN`upyah&G18c)|_kV z*5;IddVKiLY1NS>Ce+=K?l~SuyhULl!+Hq*x2lr@o=VS-1)HbxIh)7A7G&}{>@iA7 z#$#dY7}N=4Uu9!p&;SJScZvYcZ?4{)RgcA7IRZZk!Xt`)y4yWI{P|9gF>iMUDsV7v zuutbgs-Wn}l}|!&$~`6uLWP!3k|)gvb8%J~63b3HhG6-MwUnqf9foXAMutKtq#^rS zm;7z8p)|*$+6ry_Xqp8B2WFKZ?%$lBpWSG)>&5N+yR#S9mp2z@U&#M_eE29|esz5= z&Rk6Az`4G98_l_xtvuK2!SveRR(yB&{^ggxBDaP>>HO;M#ohb+FC+zdr#&eOgRw*i zpRa`;g@gaZB(0=b+i`C%FC<0rXF7W~{~YYy{=EY>Q!w?8Y>|4^*;vqpl1j@Yp*AKm zBnz5Gt*)QLK($$7>DFot=?ryhBDZQ;aZA%gfl|A=Q7>P-fBn_fy|r4mHFob9SP4Et z!pKK`^2M-V2xoZ(Dd8Gv$^Ee3C4p!{G*P-as~ z;*O^b0GkOZSs{fHqC&N3aor8vGVvpMv7$(D9U0eIXS&}MfVb4q2$y9)+n5)fW=$8vqV)!$GUAw|fh32lcd5m3q&BgcR8 zMgQMR8a8QTE^pUM(%nz&;{BdE>&5w7G0ogS%6y}C#ztjKVQ?tJazU(LBmdkmbwc;3 zM=NJMq?sHHRCAbJW9Ip0g8K2{cTv-cF3fbTiS?0u?{ljGmp|P_1p|VIhEKK6L8MRX2~u?D-({Us@yP0ErGpCwp&1iUu)vVi zLF)yIYskw0a#d{G$tX)d;$Y;olD%`VFF2%;HxSx?0;t;-@%inI9C?sX*mE6PUpP-P zf3~rR>!vC8lxMj9JwwX<)$5yu>$i^T$>m8L4ak!W7%h*GkUb zau#0;^p|u1DSX9-`9htCw%W*x28MfWSFR2hs?+}$8)cvFs5N$rD)F{3lbu$|oy1!r zWyZd4>n}j~SmwUHiid`txdBjz0%DEwmgeu9YSbFx8Xq1XJ~3na=@XaX@xdT#xt~sK zRp&SDvFpq8uP!B&oNnR44v6f5E=g39K~=&|oK=sO<>Zjevge{Gtiw)FOL}v?EcCRT zwbC5`?~sqaxWip--pegh-u#o&-nFY`Y4A*^ixoRrc`mmwiQzP?Ub)YKgH3K7%Y4pG zZlB8?f|wqfWPn8~G`lAr`rLfB6MmfAWsB&b1BiLKaH=5VHS=D3je;p-8RjTQ*<=c* z5ETFphfv-XKh_tdV^qy{%d=uaq~#HEJ-esM=kejE=?B*G6IcyaGU!pe8$Kn9$vK?8 zT#y&k$ z8X?~`tLso!R2aUkZSdtGlji9ZzN+8 z790itG^Y=PcE(ip8g{lY695TU`jK+q#ryN?&*fmNggNPzq4m9-a*eSLf#59Vy}-T4 z_5J3ICM_onqR=$gE2>wrl^C|ZxqbOwPc8q$gv|8s zrrRS9q23jMFjAh(xLdmT>3`Xz(=WNhXLfIx{B#OhrB)la?#_n%qbA&nb6xawu11=`|VM=;}JWM;3C$4K< zP*(JtA9VrdLOZd5Dm;BX zRnh^IODl{Ll%a%U3wjEa^f`f*qif0Z_;7I0`;>KpyvPkVP!I{#0Fn3LaEtfc%p1(m zspX6fc|O#gr`ZKZ!I0k{W}d!E6i_~OD{sPoms~M0m3d+^UULimkS|5GAOdMpC zo@$}c+4g@m6rhVjbO2nWSNW1Un^QlE-w^d@ik_m)hvX5DCC_Mi%Z8$Ob~{{b+uMky z&Qv8=j8TiDflt!^kXu!`Qfna^aPy^A zlVag0Yj9HwCeZ?jgiYv_pkSzY28l!I*TQsi zS|A94=5Hd8pyt#`d&yAVXnRUF3jd}gdcqRo`ie1`F|@Tf)y+H#$&*Wg20e*ZW4%!o z48)vBDBZ}l2PEF?yl_(J1nw}8_-FS{BYFp}iEh=7?WlkkaG8 z`eyu7gWv$sJ|$F+!uPF;Y~_1_CZTy6jNz;-z3dkeK*KhslzF~-&!P2M zMpNu3P{u1kq4||-T0(IkDa_v6filLJBPI8|4cOL*_kG4zkBS<-L|;NW5N}qjj+?EO zK9u$SxaEk~G)Pr}vDVVC0kV5`JVE))uB<3C%Sy^3HX^hm&j73b=?>*^EN6}Bt{c+) zF34(00x0vklqeFMJD6F4HVj`qd1ns#&7J{|{mYy_)6B_rJNS0rJ3JlD0Vmk6KZ)2CV#Rt6|_hZ|5t6A)Bf3bf{r|8 zW?lymG&1@7%LeKBrib?|vCAjI8>r9@iv)wZDagTrjs^vSI-V%}z;E4T#H3TbcsXht zN;&b@8XD{XY&sNt4?)Ql33=tcpVE7 z6D6qrkh`7gxN>{R?H$5;N}&@}lcRZ`p_33j*0=UIoL=h~iSk@CH`!#n3i1yn#E%sJ z3TidD#6rq|pa&|D3$f^RQQu`Q9HW#1x;ra zBD*Jw8*-~gJ%YJHDb$T;2NBnaFHS2DKC?LnWrN%Y9T6{Y#0a(rP&;oeue5|{*KIFW z6x5)1ow&|gy_Gh_o9v=42IpSo(v_2L5h^glnG_YENgn_t&7Oilr5TAWr_iPGza&Co za~-{3$7E?FhX8zfk7Ex4=+-*gd#4Y~w>^2{LOVq#>2w-B^O?wWd=|@f)(ehoT>IMr zgAU#gcQVS?VG-`Q0L^2**Y|QOheSpX|Kxr!J*~~3t+H8Tlh&l}mNQ*bIK(omS0q7< zI!>+n@vs_$dE<(t7i|c)nh#Wiz_LEQBkDgN8Gbo0dw7O7R2py$E^&2MOiYh5#76+K z4w>nr)~zpkCR^@k?qz>S|82nqU1EyZ`{%)+N(mIcb}z)^Ds-fZ^wQu_Ge!htfY_Q3 zwTl$b>pC0&gwrVmAmyvIAn9*WLOrC2flof7(_m+fbXtKUsL?L4p!P`$w64~Jj$~q# z(T9vZU!fWktMwFY1SN8{vECM;4dsNJcou`Ffe=$hbeaRi;F$Ay#*7 zR)Z)Wig;`Np^tL?4rVtl(Uxi-HySia){_}>?$p#c2}`b0i9MhCAX1#TjZ~lD+3NuZ z)yAnZtx}T+-Bk08GG%8&jkbh)a2dEMJV-_HMk%RAMj!|QG$Xog*{B`e>|Fx_IQdLv zcuju?ZsTC|yCX<9TPa1Mh#ZF93UwJWh8iQ^dypRBEPBog7$v9Lq?po^V?){9_ zZqanCZ}^8If4VJXy&rjAm_kv(RR&dg_ak$4VE6mpV71diO3@4TYf_~?HD3kQR{Zc z^&P87_zP8jNvpGihj7Mg7K#ylbmr=(s8>aGXAJX1o?)>h;unr;Cl1NlVj9yZ#Vgov z_52Vp4EeKN6=+yqzM`lg&@?tMJik2CRp_GAOY^)?ZV#!HrV!$-Q+V-Wskd` z@eKoR&Ut-zsh4HdMGg`bZB(0%(vzW586VDpq|xdiVqqva9o1?8cRY%8BV4AJMkq5| z%T11kQvWyIHtNjU)U8ijq&`IfvI0b%I@0E;LT&@4Ek@J9_VX!6J+OslB-9MruM-Hd zM&1YTMy?_&aXIw1ukfJLdc`dm7hzkULHaJFf;^s~K_dx#t>uw>XbzCM6qt}lq!yEW~rU#kP;hf|j0 z8$+;}dD~d#01HKJG#|`wvOhljU4qV38nZOc@Bi%5Ti?sVz_NjBKj`9cAqRsX;Wg2+ za$lr$-)AY9A|5Ucl2(co_l8ZD1Ug6U+9J429b?{LZU^`$p|84@WGQrYQB_iQb!)Dq z-bVQb=_WA}uf|IiOh-iNpGcXlFq*4aGeNtTZM35_?3$RCkK_)fXGXfv_>_o(aZ|We z8E_;C#`NTLys(M85?OnUIcj8naH!Rzsh9RAqg>jZ3?bqCla|5xFLyk#4ye|^U#F^wWYC&9Hl)kl!V^^{I1+0Nab2quhlE-P_%q|Qk`z#c5hXR^wj%(V*^B4Ys*>f7$WmF(}>Qhl3 zdArfa-XK0`4e8ELt%#H|^9U5)Z?4bJ!?gETda>eik>V+ZnaXg>uJaCK_$I4^=UDTPmjRE}>Cec=-}e{gEIX=y=5Bqpi%V?CnhF zP;~SbOiyuQ0#8f`*)B=PR^s>6aXOzDUp`-CpiH4)JHPnwcKnN}{;KHm+ zKMqf>#xnBEV|wNi3l-w+J>es)P%0)MH%~>lKlH5(WU9USWpN&ub>xwEL9DTS|3X)1 zUKj_@*D|1v?9C9i@kT!9(AJ2xe2TdR(Bc;OQbs|c9O4VJk4ZFv^;|PpHL^KX3w5s` z*SZoxtVW_tRWf;)fc15_Tcet8U-bEUQBTF^JS4*yP?EMjqp7~NJ&%tfZ6TRB5h zU@=Gdt@Q>k^N8yqSYnpY8~O06mif2`sM-yW50de{5`BYUf~o%O6Jj69{FuCOJ427uN@DCc349HQVkqgE$>;)-xFPm~>6K4Rt`_Td}0R(u;x z(QT*Gm9hPCk3TIJU=!!%JxfM7H3N}T-=dSbUmHP2*JuTc724xh-Sn0fzg)$WH4eXn zjoO5c#yI?M9Ts04vB#oaS7MZdx||+BWIrG_N?hEN?F}AUw1C#?+yIQdX?cF>qwyFB za8A|amQ(rjRY8roW!~In7Q_c{6z6(tKj`LUMv~TbZBaPfW+Y7&6kS?$D&i8@)B%*9 zmn_-Uo|(ciTxpd6$4HKg3VWIn$&AFun~_C87?ciQ#iHXs89?HH7g6dw6dN}4;1>dO zoV}q(coK8#;2%a@N&$uwBCe3zy~`@XK{cpOXb%7Y%>3{$rDuNWKDbZ}hE=mIy9@NP;AC#E*?#4)Nlto!%LF`+&P@SJwKl_x>C^8Vs^Q|js*XcXQ2=xUvE+cQx^ zvEse*xWjYzgYrG*5@qK8Ax+na3uQ#T-d{0gc`%)BKmoYF_GnU@sIIZFo$m-`!BS_Uxc0M zv!(6VBGU#X;}9ktG>oXX0K4?GF)%TnuL(Tm>Jm#l^axuVM=CaU ztd3krmgu@|C0uBrRc>VVaEt?gDFrS_ZDbI}i}MZ#(T;J|YJmfqfd!%;b&{=v6U-vD zEtY4zT=RBgT<*GCf0qwsDk}&)tSBX#9uy+(-&ZXKf#W2u7~0ol@ZG8FU`soX-Z zs}#AxNZ(I~tKkI~0lDH6A0vbm#-DU0;_%=t=u>p^{YlbOu51QWyG=+$(|^+~`TTyQ zuHB8#&k5CPK{wPhR3ubwJe+wgj#WaHCJq)+gKaOT^O>#dJC=9h#|Q17>gJoL9$fTU=aC6D# zZswk@827}JbOrj}!OtWnSAfI=Axf*xw3G@>F{j_4;#YiqSIMHCCbpl~o8i%vx!lzN zaSnq(fzB%}Y!GE8juD`UU^1Pq-;7{dZ+7iR_i@_JqBL>W)WDp~9g-9bIY2K0SvRIj zL^w#5MjhoAr>!=BC|r~vz2R1wSxmyIUEQ{#fhHOrnY@hN1c5xXhPA^5DGp9W<+>DF zhQp5(B46l_y}iCWmrE9j$0pTat|DI_gj$2X_|UJP6s$97X7Mh^VHgYW8Nj;Z`B?4f zu4>kIX;7t7244-F-u+%qFzj??EbQhdj7$?lpMS)Cn|7XCc#8j;XHn{B%9ge+w0_zd z7Y}qDi~N+q%5YFqdh1+^ooh+v!gu{Gxk>YZAwM>TtcwFtqT~cvL-#qtGY#rugQ3}= z`K^MR#_P6gy#?B?g1WwcAsdJlyU-ML1(d8ZJd}omk>17gtyZm62(+nB4<3-C&AguW&h^->YdzV zsW)>G7hn{h43$rlSSF@M%PA&J{hGuSX))Je`2wa*y1+8eSvD(b#L5C^?rEvjVqYtp zztpFK7-?x=t^70ad(!)|0~Ox)uRn_$EnZ!p-(AY*RyUG+zr4H=P`bw)e+#UQV%^cU zpP~9(e~}6jWK;jvyfKhytccDt8X^t2vqeTDJE%Ca9G|K$DHRw)`wL4| zyAcN+3cWz|{k`l7-RTWf@2Vw5d$k%pqjP^#e+jE0NOul_?&!b( zewI=K&gm})M6YfrkKfd5-*AS!;1Jk%PN3v?$j3Ho(2aW1N|zKNwjnZC(<4mwr7Zc~ z49%ECEZN}5eiz5-hVt;nzAjnD1!vd}m2Jx1TuSOMDgWJ>f59bL&)u373=>C?Ttezl zZtwz@V^l~PS*YD%>*k^@48jWrlZ~IbC#&$!uuKJWh;HvL?Jg)NA+Qx=EDS=}iVhCM z`zW5Cw}abQj?W6z+b(HcH)vUgUN`9L=q+S1HmL16*f3WV)g08fG8!4llGi%>tzD#s z_w3$A9^`@Byde8cFGo2o-x#+D*!b8(?rSZ66g1Lmi5sU8muCII?q<0|=~$$``U?hIJV4T!8v>4$Jz8mU-ibs>NU2oV7NpEE4P z4Dp{d`9LQJ7sFLqG528ub_m@|7NKgZvy5pE6)eIIx$Tfb%pIvgY$`n<#RLEjYkN?mF2^qjS+VX^VBNh0{CWDB-xNo|`u(|DAAmnfQcJtj`yl@?UwWR3n#T}pKr_uM zPI3YEk8CvZ?fY{bB|?m(ZA+#zj&L4-|4y7b zb%K?7oY)tt5u!XF>J?F#9ryO>zLaJ+VL)O6=+dojYK&6LHytPi>{i{w)}>&xs8gw!$|n#;OK!3fof@p4$9yI@so^L%B; zPviQ;eadyfb~4pAA+Nh!<%!v1C>NAv<8|UQB&tTgv==p%rPV<+pJ|VBc}~4Uq@mUy z9*f*2&?dF)LP>ZMFep1Ak;&g_T0gU2e0q*n8E6!#R1qICZ!)MZjk;5tTzei7Lyc$b z$FC<1Rd2QZKT_Tw9ZYZH3%cZ zSA(D({L3yE9CF!TCTyB(($E4sA-wx{m*;Q3x*HohyMi$?vHh*8krrbwQ=KNWKdjwD zFoFCafT%sm;0a4{7SEfht53?d)vX`?b~Ywn-OyK-5=XqMi>fPmipJWvSW!f2+tC|S z4lIo?P>qLt3U6329&*WDiylXwQB{qcqPRD3YUIYhiz)8v;5l@FjPLt!2Ei*nAq(?$ zQh8|+$5LQYfc=5_H(*G4>UrIzYgD@gqTC(Pq|J6m{(P5i*=|+Ow9q9ga5~;(PT(O;0Xb5?2s0-SpU$^f zO+rP>rK9$7q4fJ+o=JQ3{$C{UO@9_$jiZRIgtFtm9vmEWfg z`zP)aEk0$rn?)?F-<5?#Le(2zdEqx&koe(t7K#_UImgzunQWgdL56Zor>w&yz)@@> z59Zuq%2w&ypyOP1-B0%0yUPnXX8~^aZkTHgc!(=^D6a0-j5mTG0pQJ`Pb5qjNR136 zZ!b$Qmvw6}^;RFNF|J%{bSaVxp;D3!GYm&I8&(8KQAiadXIXm;4!xg_=AJH0=|KF& zjcB}(Ukl;S6=0;$qx>Oe4PCdaYvyfZ7B!lD;#7rfeHA3BHb79U;-fa&naycU&#{7| z27qI!S5jSK$q|QMhP2sFrpkHfyYIk9a=(8(?C7(rf>G8`j3XojH@N1=?=5Pmw(M3W zyf`Kjqg9+IXg`SzKwskJzVp)tb*UE4j za>vl3KRC+HAj)DVO&J)}XfbDAcC>8P59%{g9eVTu4|3E(pbH_s5-;QDB~+odC!DO6DuzaCcduUr(kY&ewTt%p zLTFn5lZr0shPTL0RGhAhQ?unt{n5H|3H#KR^-y0OU>Hc-`=$b~UtTD=i0gg1l0emJ zzgr$Zl~8%i$T$pkWVE5bfj#>`&Xywhkhv`xoydvNGn1inGzV+-D7-(C-OZPK{xZ3# zl1hv%$-(IrcF#F@QX$p-PMyIDuWI&iV=0o+C=Q{a(1l`w`-k0MGbLSOj@ow3Lst2o z_{bZ@MJ{Lgme7CBX;Du|38R6JRspPce2@p&@|Bo8XGCi=sS2aQOs-3ZH4!Yy(~&_j zcVO7UMpm>2`GXV^vXmd|*BAB%P0*gqk&8{9DAJ)&GsvQK{Ph`pAYm(gGSFtic0aLy zAV0wrzKYFi^AMlYv-#RT&d4*tIY$)M9Z7q-5XHfbziO846w6GUU$%8UJLvMm6`UjX zAC;Hv@WPJ0F0E6D8#;E+h$PMwMg_^hljtjLZV!*^9@GwwUaC4$+5R$`1!E5of6Bej z3Xev%`{0As_zV#h>ja{I{gmo6k?E@-z!^^Hn5Ty@4N@ClWzEw!MLg=B-VqU$2r$~c zG+JJpbXL(@cUr<&mE35xEm`6QM;1WAc-C}HD@pKh2-#djTNBn8K&0Fw0y;?rZj*%k zYv86M*jiJ0RK%`0qtu;f&j&joVUm(3o~9Eu%&XM&E2Mc_ZT4rawxUeI)Sgp>$D2tV z6m?uN{uZ4zI>9-dt6Zzu$Tx%PD_Y3}Z}cM|*(h`Kt%us?9b)*ho?FWf;X0k2IVg!Z zQ30dpiX{MhBl$y#uLBY;-5QvnVa(xc`BsMvzu7V0L?BA%eqHphJ9Hx@KbGO0I@>3(=3P9h`w@ z7oubZKN5hOU3`82*GzuGIPo90nG4=q!0L8^PWmi%LJjVeOGf|_hWSQ{AU;b8>=W|n zwh9bLRM?#B5rgFZgrk8w4{`HmI)P8mS?ou{~ zX%K*mDaXORR3$qov}v&rDvTuB+Lz$Im_-JxwpI{cVKlxR`D!^`GwN>RnV|}0T0ik) zRZnKMx33nl+B`(So@XpO@tT{~_OAZa0c}nZ+)L+!C-S_!Vs!>Rf^DvEB<&s!w%2*l zW`t~w*li=k50}4w`{w-D$hydubuN}ly-^qZ-aNgvr>>r$>m9gv)j#UWuJLjX4IWY1> zY6HdkTj1mK!2W9`znWov!VO9b7=`UhU*-`9!m*-dX>OR^7fi8e1t`^o5P^EEp(b}S zIx;;pYPj&n>cp3p+A(DaMJdXLZ($3frKzw`6j}q$osA>Uof<9LP)Z_qLP%=5mZJTd zk{G^e>-Wrj{${pCovS4*;vN+)|F(j}7`>{QINin#;_}kd8F$A?NgXHdPSyEV0ql z;Cu;AZ};qvOXq`palEZv`mmoKY03KbR9dVjG`nVAiu2wF#!RGt*?@!@xINqc;3 zmBZ?MH}X>@ln-KkMdjk{&;1~4xz@&$c21+S+0{);S|1G10=Ame^fn)v(Ihn z%nF;k?`S`<4lLN7M8Z(VuiPFzsQ83ADz$lXu)Se|xtmb#16?nEV2hM5b$1MqR|#eF zf6+Q6xlgN78J)H@HqkIkm6knOHbC=XSw~FiuDq6z>N0+gTjHYr1n#pLI6{eP`h!mB z3GsAteth`f;_S*3qcnr{_dQUJI%hOK1x2=rhE`ll5)yzhUUrX`765720qZM&jtObW zC@H9B3CLhBthWrmq{etOjP7EYUEKBL?@J*PPaXDe)ic)$aXl9!e^4i> z13OU4x>iLhbyx8)YcPe$9OEO)P>|;@-h1F!`CHr;o+H#)a!UByuR+vD+bQ!otZ}1u zi*c0{NIo4^1YyXBC_s24#(mq_hiXF1$*-ixQ0BIHme1f0OW>HRgK=gThJptvzS0{Y z#uu>{3N!ec+$R={?1h3F;7AHCnq;rq*rq+z|1h_3`CaEaqHDXxJA(EdPPR2#XnDfZ2!3Q4vW;Bb1Pi zq5Y+UivC$aLF8$PlqK{OblM!?ShvVNYxKuKc+s(so*Nx89PnnZ5^m~m&!_WVtkGl zrt~$Xt*CW)u*oKBr!w^kJ4ISr(a_Xjp1a3#(3VjhJW6JzOwwkB#a;E?d3F4lnLC6W zjFvoN@TS-kzG`YG`Zx#?8H_C4({5hUk~=>ca?DP^A7Au@_A(Z@A4)W+W~?PAexe`( zs$Y>*2ly=7N{8TNha_60%V%}j*Qxg6sU;1XffAOCd#rygSn56UTAjJ75_ZOAdu`_@ z7Eb~-6b6Ab&rTwzq_BWzm|p!I%d|W_qG&i)48)s*L`#fH#^!L1ZG7n2qt`*Q?P>7c z?KePfn^F$-fY`^|1}fhd0|b6+XEqkjeJlwo;S-(>pY^8CU+@4b)j2x;dRQ>T)}3^*I38q18v z-Nm+3vH|7)Mh(Yh#E2-AtH5TF`>V_UmxW)$@>e;m>9r8pzD zjX3XG`ZK|H*$B(%WATH&6*63gh~nXB1<*E^LOvHIq+;!+tIQKT zBZ!ohnhk_WS3Y8vnUUhouu`0ivgzo#F;b6Xl#Mh)fSl8_s>(4<+M}Ul;uHW746OEC z`Xsd0;#gF!zJ#{_72cG$kHn!F>Xe#XlY8|}iL`D!G@_t!fm2EQ0wSt>TLzh7S}6)ik_ko)rF4hL?>2P`x22(j zxG2r&96KjnG7hY)K<@7^Z630ECvZD}7#(>WG}x929V*VGNn50<=I!Molw?BmFiJ?I zC~~cN+{|jZ>Yc4@u|86onQ_Tn+OLc12OHe8%Z^d(MNytha>L@Ck~G8|Kv0EWHcgGi zD%pv(SX!Ak04T6GE2vSuteu&f*BkCY7B_(1S zH!Y>U^sJA&VIAwOW)7v%CAKmhPd@g%Q@2_$mT7sCwz;BBvza9}Z{@PIE>IZffezZe zZUl-OQQ~^sprS!v;9gC&x{UXTOCTXkF_ebHQDHVST7}s@r&gspBQ4t4Nhdy$iK@iu zTx+R4OZtPZP~jPCNI!G^QN1&;myd!(56h#PrsP#EcuSxozH^f|sqer1&Ds6C^Sk>q zYzZyL6&5>K*l|D}>`825sT_t02aC}}3v@He zwDf$d!QZg*ESNNqY#44oUH55MAr(M%A+kLRL?1pS>!bz%R#xBu2E&i!70+_xEM9LI z)o+Nn9uGH|ea!L{=Z;Vwfun`hQZ!a(;X-c-gOoBR61eZqgwm0B5RcpI15n;Up;T~_ z6I%|Joj}s*-k5Dl(J3_#;k=#}JU)CAts%>v<_QudmWbNcC|#m~*z}33bYOMJ)WEq^ z8hCbu%#KWojCkJ1jttV%izSiw{=n7Ui@W#tUsO(yA859!&z9F2iP#WpScovDu9U-a z?|m6827$&sgOKjT2vf8LHYnIv5gqv_qEkxg@bs$CAsD}jjf)+qjb|Xtn7eIv_I#Yr zBFlO#`jhg;I=lk*bT{~f_OxKzz`~+>@o4?lM?VU_eDPi`KbFeo?DDJ6@6K;t%ayZq z@z;}nGF(%fCaa5zt+ps{KGba+x-tA#uDSgRxDNNS1c!>5RRgKy`m!Y+%GS{{!^Mza z*)Gys0#i&Fbw+Oy)t-&6I6G#UP@D{Ix%{QpZgvY1MY*&y5KBgEE+_Yzxd@XLbUCIy9j6Xr*_>F?A(79#Dns$IYXOwNiR{Ux=9{t% z^+e~IK9@OdI=4gQZWZ^+;Y}Z(Sg6l-to>>(MO3H(5tZ^gNHLXhh^(EKVSwtM@*a z91&Pn1Tla#S(LjJ^;H8^&`dC-M^I{>hL9LJFiQ-B`G9O=A=2VV`eK;VbA>eyLgt!a z%W7TmOL9A;*Cy2RHQ@9**TfGk?$%`-zxeep&g5fzV>%6Tjs}mzEqQFs-*r2}e}3pZ_FQ$Hp+ zK<`M6H%%rONG8GX&OB7KBmTQ@6M;?K#lHXF&iZ$*f4*UQ@HP7Qr>MR?Y`*owIZqfO z`vl1|pyHX=s#wd(E=V_&mu4`E^7o3Q{m5iq>ZmyIN2rqxDI$L>8V(bOe^A*rmFG5V zPDkFG)p}GW1`R4~hJetwJ$SNX0(B-K8Ff(IHpZUL@{bds`_*ES zwCbFV1cZ+8RqupPN@iI0Jyhh{{z+K{D?$?Fp7?9|WzL>>lo=p`+^M95uRmSl z>TX#F^(}2~nKRoo2dJxQ>|O2T;5F==qK(wPa*`^{2Bkxr9^f6l(h<^b z(7U@U=>uI~%I$?B)wy~io3nb$){!ahh(e2^2fUx3#SMc&pKSZ0F{KPn{xej-M33x?QlAl5Fdvw(`f&;u z-$U;}-v@etp!_SUtvd9Udy~GNICHlYK6dS=o~}J??xa3_AhorV&Ozl;GGVP6ON=zW zog`;t!C4u#c&}CE1=;-c(OItFbq(I|Q+fR?rnxebptf&&GGs!G6yKG*G=;O~K7d$o zc*j*+*#X@OAv+9Q#5}F0WRgtsZL80-FTNbmYb)^C;iMiL(oyqh%_59p7sEJhwjaS% z+2U+5ZE%I3tT;vYmuOl?s!e*lmJ{19y{CdLJcolPgb|aE;#XH_fx!}AC`PC|F=~J4 zdW&0$Is(5~K|4lPM!82ZBK3wW)(u&_TY|6=rWpBlml1x=)T?1E` zE4a8KMOA)KZAH~nTlCtppb`oLh#>GNSLBk>Lx};tj)Eocz_T1#D2?+D6E!{RJOh}g zbh9`(f-)7IJN)Si7rup{6B+5$)K0yG}!!`JsOZr|U$y!0qA zf9p}I6NoT?k9^`*AvT2MbS+%LrYQ6Ouq4<@ANTh_#=f?u=4zP>&=hvV6eZyz(-}=SVvD>X&?`f<`FJHq`c<9PFgu{YcSJYq6 zNYjU!KeZ6*4VuH9#hDbR?}y7d2B=?s05)q9yyu3+SWn9Bmp((=eeA+HmH*pT~dw8GnK`mx=cwu=Qi8 z7XX2kF(axv{%J=bVH{X-H1EFv$GmOqiLe9oug|Mf#bWvMmaX;8q%!@o!xm4wCbkQV zCtL>GGIEI0#(^PkC#W@1DK#2nE^srZgC40v2Ng~PCdX`z7y#*cG?CaI9=@978dD!R zW?OJ(4z8ZrAI?+e;rJAshCbT19F#yQ@D#Vn5b;#)eDKAH6lmEDoKP zx#x$GyIW#G%vC2OBc^&w(?i;C8LvH zrAe)VA)!XH(nCUXneW&LdY1C;_V&%^UrA!?|I~J*(Vfej=JY)7MA@gZ$_<*KVu&*Y__IeoYJcud0e9^S z6e-Zd&k+|fi5hyTVgQw7L+29LIu#{>w@}!g?dNzVC>&qVXUAeQLDvZ5LQ!W8gM=&! zkma<=5eGo|)HFQ>vR=3mg>H%`iDbSa%ry6&JdtpbsTVQ;pj{5%VaUUQ_*PpE3F?6~ z{2=B|q_puMgz&^gSBI&e^|#uIavdPjdtE{FV5{;nUhuh731Y0J7dKW~3Uef$G#3+?bj6egMcpsGUywQDajG84Uv%*(&qdZ;MPE z?fMINFWoaXx@q{WGo*!F;a19OCKI~MZvW}vTAGZhNlZvMR~8&`;Mb_vOjEwAqB?;( z?4PNu*gB#xZU~jo?O4<%8Jaqp-AD3X}b&cj8D~ev*m40FDbx>a%q} zL6tYfgLuj431~|HV2;wLE=%ZzLd5yzgRy$2lC9+fW;;c%SF!u?dGLBBIE{W~yd-G~ z+iaZ~ZRz3S14MVHMpy6wd*w@tk7s-6}FmWDsNExjFn z=sBI$Ws+uQ!q>wWQlm|qOC(-8T>td%%YQ3ySZD2fIf}tH2#VSgL_|46i^a)gZdr@R zaw)xb!G*;EvP>TEd)kuz_T+(lVSudq7*JF~c06Z}-SI{3+=lNe^TWp~tyaT4zh{_a ztK&Fc_i@TWZAS~73wBU_9LQF? z_<<&Rc}@{T6Dp&82y36OOnlHQP!3a>;W#2Br89GM6R6*)r7Y$P6mjqe{XG4}`O7cc z*XjOcDH+KPkjy!peEY`Vwa-NH zktpRNGh>3i0?j?MwoE**Aq2w&jaK<%7C7+3o5yPPrGYi``0tHvJh)@Z#-5D^lpRA=#pVq~x$6K{_m^^MU z92%o-lqO>#Nmz&I56f-tWVGSy{(!E&OcE}x&Qp7IL#c-3JkF^A&?r;7@PcoD4y@nw zx!@p(Dq}_SV+uo9@E}aj!p+n~Gke@OG8dKp)$a79=9jki{;8T>7DuiR(WzetV9EII zqsw_S{FG6@a0LfS@Dtmv2VDf7wmS#qD`vlw#l3uS64OU4vh8kApnFm`(L8M%8+yUQ zcug#Mq`j`VPxTu=%4-iS2^fZ3?E4N5cj!2P8>#u4oW@~pO4FN%#hjK;{+Hc)`dXHs ze4?Uv9}7S*@@>?q=fh}t+Uh3i2SIQCjOS{5EvcnGi(PhHomTY_h5N=JK63Lxl6WAX zU%27YFA)q9Z>+Yo05;kakyou@%#{Z$F3I_Yqw_09!A9M)JsL+_q_}zFDkWWhzo$bJ zkLZP8N@DEc>*%B7s85Kdh(szYa8t-)L79}C)VZi;E@G%@E+{VGW28wOJJLqu64Pn= z7Zo|Xm}ZfJT2~v>A~}|Z%rIAO5047wJDz5!l7TepNoY8-c4djUD{jMF!p&qR6VWwe zpsQgl7OdVGnJOs|zH~%TZv8`3QpRc4Cl@5ZU_wN3?6n<);)^S}-H?Adq|Rn9SK{TN z9!e;HrHKI2lAU+h*vXbf`uBCnD#D4qjDK5P^ z^LacDLujE$src3nY{{mne~*eSOq}$3m#gBO%0=!uX(C>LpZ!Rq;>+7NZ*T7}&nySI zx_N!}^6v7L{M`H}nf|M@IX&*XcIHL)bI4tT=AxQf5OT5^O6~>N6-_p^%fj*-wB63d zZkc7R$U11}0uv6UC`Olj+@Fx~#!>(p8a9Rl--s2HVymiI_{&4hYCi}$oiQAVQos2O z-W)O;|Lx8G*3Dirnpp>~Q8m#g-1ZENnA4}hi=*d0yu)Vt~A;=8r zLRQ*i+~ffxjYGiIMN$-Cdr)>G7%P%~etG6%6R>RG|f^ zoy>y?{A@@2axzS}yYD2lsbK15+j5bAJQx2-fDL2xPQ3-Jzqeq$PpM>KPkm}iL;%yo zl6wR8pi@;w^mlZX@%3MJ*YxZVweAyy0WP;akqx>_|Cawn=CV_88vSPPT_zlooiQ8J5dJe|r)V`sn9A2%;s znoW-_sH%f_SXG2h8K?|a!HQ(D?$BiT+OzsicBj+<024_uu6IxS^K=GcQOB*4Nd|vO+e78OXphNbUqZkO&DDRz%LkT*aj%fRMcPLQak!4V| zmfmY20~7i*AzZ-tK(>*<$Qz+Bm(GzQ3%|;b#mzE}b8 ztiCCu=)^uEZNC*7*NxdPg}T`pvF)fyPX-p0O!W@k7-y5mk3eXBtk9#Sgt?C~&NqlE zmfU$x>tlo&=Xd}4(NcQoFD=?GWlLFhEn4vT^LMi&rsN!HMER}qMwEgPb=_(N-Jt_Z z2H;Y=w#4EPiX2ldF?9?J;NZP*BbOx4hy?jX8=g)!4FQ2AW2r2L>ladL(V>qYFUf9R zvH4OG%x=nhx5uFBdi5Oe&DGuAt&|Pc#khZSetvdydG-1WsRr-PUR+<^T%5hRytsNV zS)!)^i-D!FnU;->N@B(qI?x66a>3=WjqCW4-6gEMy1`kYr)XX5eP9|Yd9FI(kpL*v zr2~Gl+z{}>2c!W)IZ$$4a7hTKUX{OU209weU9PwE)DwC5x{Xe_8@VC>WbtRe{_O1T z^^4D62oRpP8I=o#%Mf%YFXYNYYI7UIrvb=e6KFR_yuSVV2~A@|-2JKc;S|a-OLl`u zHW+*alnJzL?-dE!QOTUE3|)Ih;y>O;4aMQvsY`S1W!q;wYubtDyytHRHGRb>)_jW; z|K>cKCD61CbP*~`@H(?O);H%tVrMJ4df6=qp@UAIb-+<95K^E>1_Txgk$H3P7BPXG zdoDAbA@-QaiHC}1a0sBTNsszS`9#DQo$c`06XPsaX@?OcC#ZaPWJb`%X*^iyjzt}0 zE(J?n>aR?H|C^$1_l23(%7!g7dDrQ;?dUyZ1F4Vr7R{*i#J}D(|eRRpY!c{r>9p z&Dq5*>g7CLe*4?L%V6xVw9|tqB0r>#1(1wG4BFZL1`dd6HNKY?fz**fJ-MN;v`;Kh z)lND6=J|e(A^0G_O!BNPatgYVVdod=#udEWban+=eRAk=D64Cn^Q4bNNFOtpM{YR3 zc#Qy?r>65~1$1HYN5`b?S`hd^VdQ4&#!Cy?bzMvonmg$^q6(|}h+qg8mHLZ3J@hl= zI@nY~N=m1F91rIhX;Mo4|&x3L>ACJS7F(Zb=A5-C^8JKWjf)H{s{=93MwBd zt0D55F_?&GvOT4D*P{)*w0DOa=9*=vbEy*Iq7pyXGk#`|SSe(L|E2xI3Xycr%U8U; zR{)iSnzCdJ=izb0S6csc6}*Rpl-y-L?6M-?02JjY4Y>;j1p>VcA;WLjS#auxKlyF$ zGI~TtbqWievxL@v?rg{(F`~p?R>6jHK09dZg$(IRRaTo%hUUtG%LRg1RWTy8Xa4JY z`B-6RI}^Wsb@}e)7d=SZJS?-Fg2jQGmX|;&KJ39!nUYrUM#cT^Tbm3JOY()qg&G{B zO8A6LJQqPJl)9#T86Bq6e8YMpmdB$Znco&;Xb&W|N#aY|Z(MkD=jzMbIM#xMoprpj__q)2TsEGJd&h{U$LPIqalTxC8LB6way(<$ zl*R^P2N}C$>hcc#)-fxEk_s@8Gseh8O=DZ#imLp1Cv-PGWt2@>{*(>llHyWe`X zX;w01Lgz;)$F9k+-z68mN$21@DV%a~)AtSG#+?1B>Pm3l) z(lF|t4ddN3QvC0Dhw?2S89P6bK`~g?TL>&A3bPUu0261I@rNe>+8WTEMS2&4VzU0DrLx9^xtQav>!dO<+LANnd?P`Iqjdy$f*zUT#J3+E zE02YPQt?90QWhFI6`eSuj@k%IwU0H_fbf>(Vl_OERufRSf7uJ!s@;* z6sX!c^#Q3y-vLdpMB7IXpT3tZk;dC*2bp&_v{fUbs0Cu=rT|(%K@CR-$AlZNZ(rkH zL*>(K|4?}{@enz(s(NwM0m#l^jc*C=B3&bP$_Cu*X(*zJGsQNuRj)C>Cqe=Ez}Hi4jj-DO6q0s2^lD?v=lBz zWJ%cZxsVvUDe^Nz{V9}IdQu{PFj8a2d5?}%psGhd7T{T4I)2|E);t9{X6mG+!3Kt) zccFILwT2!z)bzJP{}MYFl8X+4g;Lwsf+W-+rM9TdD&B>yJ6UOLsZ&h_;YKh?Lu={W ze-W&1WelpKJR@+zXxi@6d7o>kJ@+OZ16Lxcfs{&W26)KgIHGQCj2d|ETv!cvDo>-2 z?p#S0=q+j=%)PWS!jTURl?&c&S%;rM!7Y;4P)UE^QfY;y-D~17M7t=_>aWYoqA`jg zZRF5|i%mR~BS53g59_Cjs+&sCV0Q^F*avm}tjRtn&AZ^p%6r&`ap>^&rf5k9isM>!Q(Y5qHQvGJ!Jq0!uyY_U#(7@k4e}E=!ylfvyA>LfvN|@Cj4~>xH zDOm26`LrP9go#W?c3%Bt zssv|hVfiMy1w17RZs>&uh)fQLvSFiFfaFgcF1 z)OG2hDd~*v8wjo!TWCm8*b*6oU#Kq3!JN?I=}b}yxd$Mm*a`i!qyGieIQlRz5w14w(&V?q8r;49o`5C>ZyAN+&T9Ps(e(7-0UqnS9`n@0Cf2j(>=w!w%ppv0=0 zqB@>iyPmBYawr8G+L=bs1c};%r1&rBLkR{V1!>YeV zTR+db8<*vIiCx+e({$$QTNp8iAckMBIZm%)BbT)&mPYK)RC`C+$%vF{x~;lH+57XG<{#!b)WsP|1Mrd7Hc7wr{z^iv ze2)A2?p%H&le)l=Wo?rLVqAB=-14g%*3C|l0 zK~5FVx|IM+523|(YWvV)!ukM}6tJ?E6vPz%ZfdnR8GJv*s3|&RDml{Q^xRqT_|l)1 zgQnYxR43zO%GmYlIo$GHbu1N@@K<3<@I>3=-sCKqvD8;p+_f_FOJh%`G3fryQJS^M zMn#%-42wK>fBse*@GytB3S`zG11jZq17Q8ATjQ1qM6U65WhFeyHw~+l5ljuYj3-Fs zfER-P#)eU~#!OrONb3cWTkm}?od5`QC!rdpID9#yJcmup?Ku%1W!uM4Z>((@nN<~g zd|^NPstCuJ;#WxRe@cmw#RX-XSgiQ%&L@agLfH6EI&S>$7(i%cR4}e73x-tcV)D(@ z5!J|GSU>YH2rU#r8Jd#wn#(p=kWO~?gNU*?sz|2+(fO>Tk9XR|%{6(SQ3i$+QN6ti90;g^U){L;I9*g?=CjY`y{9(l5t5A-#JAwzDYw z;8`DEMptIlXO|Wgp5#1k4o`di+n2%wE>V>em!q#ZZ2Nqxf5Z=r>K(*=~Y+^@e6duDr<=6PPdWPd2&aD1m6(ZO1T&_2$8M zvyLd&HAn6^)j|l*+|1pASZ|XghZlLzBsS?T2 z!$+K+9=uRKcqeIXK-=&~X94Eyb){EJ@NsNs-$!W~Rozq~|0s=xw{Ne1<5#iTO_lS8 zzZoVf8|?%#kK0%)ood9P`a~@Jh!TkLaH~!c8y_FOo3OR@E990gXpAydT?J}dPQgu4 zXZxzxzQ&5($_5o83(Of-b;qPR_KqoU9+DxaN2x}`3q>f15biWKe4g6o2(NhluNl~- z{FNJ-O$MSx#dSdpcA4DED79H_zhmQ}JfCUVej3lXqiOc=_)rP8xckx+*u2(?7a72* zUme|EU7Y>)`tn}3XQ)#EgSBwv5VbtS$*;W6R2;gBnA9W34r|N6764rR zhs}-XYEUSjVHuJKY{(Zv^C&Idi@ZTqwe`|>inlt5WJ7D@Ny^nRB}E_zbjo4W>)mBa zin^vHzuClrY!}#a1|z%0e3PGD{D}k1cc|-7k$s{%6FyyPB{#n3WmjkG4~m^#-}}$- z=|`hP=jU%;TwUsIbhmQzhDQ`Cc=hP{{TG*)dShL`F)i}m*k6v&cJt&fl?h^jhc{Y_mLDej4QG&w6>3bfhS#W}9GHZe^g;<<;2<{SMM< zeA-CEU;TLnhDfkyO?X@Trg@((jt51e zC1Q+_)nY|bo=Z)<7;hmk`!mlmnYfuoAx|nO>XRsuFsX~+14$}-Y$(wY?Y5-iWg_`2 z&7!idoflsL;RLIU(j^Vac6y@2oAOvcEqT?aBrT-{dMK#K{EyOwOHYiCDJC2_R^{|-eT!28UZJuW$ z$P?tj{(3+b4uL`?u}H9$#|J)S*n?zgaG8lv%c8DvTZsFE^L?Ja9mnqFm;UtpEkzCt zQKzeA~pGnO~RJ}G&` zsS%Tq6e9$?VcYavd5*9YBC7oyCySVnz?Y2Nk6Jw)IES4_pQ~UdlR_RYp6lenQkU&G zLNbn_$Yn1Mi+~7=<%Yft(oQCRF(QGQ9zOIFpUp-K1(l!h?Us_CUfo=t-<@6l`t71Y znh>7ge&B~y)1!>xzHODdC!La7EUv$gB`M^%V<;xCEZlHsWz2H|r`V>iT6(TC*9kpq zgr|TvjfU+RUo=G@^Q8{Ll93z(gTk{jraRX$5CIs+y_W;>^C=>P_!2eeG4yKo0w;*W znyA*$$bu>Y*Eh^`_Qhmtkwx|H#k=>qxumE|nAe{>Jy1ff4m&NB;}Pz}brDRw8rfN4 zqXkjb@sB*nA3yYTaN^X`q=U7=z>&K=J{&M>%Z2UHu8|OEXW|E z?rTV!8RPlW$Us6}-_M3)WsZUu>Wg5AA(GS1^6Wr#q1h9K=Zxzb?LT-Su4!mEAVbC% zOwu+|V(-aF*@hw_oG>V--EijaQHcDPOEEh?1sES!=5KHrX6yjm=veEk{_TLr8oS!m z)krN#2be%Tj7s|}Q)1XM;NUnElY0)gq~OX(d60E`N;?nK3bfkB>&ZU<6he^X>!N)p zJjDjydeQEJK><#gy!s%I4vHQDZU2|MWD{pA}5!j=EHah4Dgf5@i;0qpIYNACIt$P`0|iYgTME<4dnP(-3)`(dT|lv9EIv`ke&8dyr6+M^@;o1|eql z$orl>A$A8u1>q8X3sY5Kmv7y6?mdpY+9YxH* zLDyv>4)R5LmO#qFI6zAHtMC9jLRdJ+nC#PDA3i0+ANAmDbtpiyDKg)-qb1diHyEW> zmUjC_^$B6qH_^`)D6YVANdGRil6%+U+^JB8!)^tjlPd~<>10Yv_g~^S$(90U%EOj{ z54!-Aq71(+yzNlWkGei}?#G9J%7o8UeM+F3Bn<+V|C{HJ0u~G~kbwfXJ^=!cuiy8= zYgft2Za>Yo(4>GMx3-r43ai5v;qg>$Xxk!ftAAk;Kp&B@*bHeCLjjg`sF5e(kKx#} z?Ni^0Y7rj^Oy9&Yvo#(OEBP$cgo{;&W@FpSGf9F7M{qsm4tA#)n0ja{M5@&!DS6WMI>54>lNHaENVW=oIFmcL@DM zo8|iQ{Hx3RFt=t@dT39QF}t++-KG58@!k0~eos;^>l>ow*UQw!H3fu?npmfx^g0JRkY+-TPGjH%5+gD;9<&4pH! zPnHYfU|eVpp*}pEazu=*o>v=Upc47(@qS2;?x-|aw=PUe3Kt%VCotolhi2^ZR|LHN zlBtx!6qYqIs_A*?9uk?-n;t>-wFe%pqSXa*78@Uz*#dTbE{w6$e^9LNM^W}#haPOs zI9Z~k84Zwh5!%k0s!X@t|MIjkir*>W{RtuxNRRd&Rn=isN1(fzys`W?(8gh=sX`Ha zShkc(^A1Xvp>Rl#G7^3)#8nCBXZh#XZ@&z$?ef7%lq+`*&O{&5x)s zY%49Nwi%Y@)EpW4#geN43YfB`DRDf9nq&jdz3m2$^$5`}DUG?~7$(JOThW#xBopuW zTqIuw8F&%6k|)lknk$xCsF}N!sK7;LNf^a==1Ng)(7+I)ST3aYLYTBOjmXmgTIQRz zUlg*d-$uwar|}LDWf3v?zGvxwbl2Q5f64>XQ@;{x9ds5-^hoppw>S;zv=!&4e90oJ zuEV&J(sooiCsmQv|F$T_M*u1XV01x(f41cM@=!(E?uS#CLlZ1tfk>(M!^%q252b46 zX@HEhPsuxmJE8l5w2ngcDeo|`7vcgGW~g;lc?ZEYZ@(wfFQKPsH>4B_jM*HWr0u%R z$Z@e#A(T#Ridb;u=e_cYpFH@#HeUu2cuzvP9&tU@)j5~)h-2LW~#I6iSsAL&E@)Y^q)fjYu-93J}Izb_`m zC-g+BpciBgtq?wpQyQp(%1<@kYg+tn@cE9*3ZuF|=o^6?cknH4< zA6-G!E$ zSmUeWY_kx)=3ou$Zp7B@g%V@JrI-pml00VaiYVs42rK$Mz=t34cby#Ma1(36A=Z5) zhVTbFlZt<5s-`w<27Jyz;q1t(4n1uoFPpRiJ(-NrHMX;XKCeY2Lh1;HFtk;}lzw1w z@aG8A6jLBUc{09`kiRTG+DLaSHF{oKpRZ6L%B9GT*+)tsBOAkcWT;xfVB;%x^*9JB zn6(FT5mru6L6Djm;gcLR@agj*e~9g>gH@CcCjw>NH25%LisK7*CANNh{vGMpbC3u} zO@tie@M0ZWqDw(XcLqOtB|W+!&pu+A^Z7L2v!pSPFh||(d*GZR^; z?P{tTf*24$toXEd-z~p6f#5^_Ok$J+#PpVITdzCv>`Z-^PktR&6lqG++g01S_IsG6EvM#gBtR7LzPo>+wsL zM5={!9PU8uB$9%@wmIQ`{$a{;*iGBqQh49q5fuiB;O#D9z05A+kxs6ub7cbu{F6s#8~~goX}@nO_I965$a|Ejh_nW3hZn>>5S5 zYL_wz`fMqYM?-=*o?8|wi=-_JJlckgNH11G)#*8Kp$Z%lCW!okmNOC{M-$l_bEn70 zE1_vh$U>9+l8A}d98|Phf#=r+h84={n+S%U5ov8$wIuN39+fi1;PH;}L;zPEhdPrG zdN@wbSdP0E09WmIJuSx*$7*S5{-&nI^M5T2)< ziGEZ?CmW%G12-s@wnOs*HiKu+;4EmhqmynM0n|~04hPW=TDE@Xv-LsV%TXrM^bpFj zxK3(^44Ha?Gt!3t7}hH@q9`Jc*7QR&SOaWn*NjbRf&z)#Ra|lAb&p-#CSUXlhxT6#Plj+Dk^C*sD)WMIGIY4APjd( z(=vg4OlvETn6O-FD?4;!M}{Kj7zjQP`#XO=TJ5UK(T~i!lj7ENSS%bsfJyb2k8yvZ z4x6LxK8CSxnu+p;)p*#<<_>LW@>9~yAw@a|s#G8N{xG`gm~XOt`s;yIl)-JpP_D+>8FzuμLgRaCC&`OtbSh3=FPSv5~SU6x%02Lu5 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 Date: Fri, 11 Oct 2024 04:06:43 +0200 Subject: [PATCH 08/56] First "eut2.interior" shader implementation * Small fix of comments in amod shader * First implementation of "eut2.interior.lit" and "eut2.interior.curtain.lit" shaders (there is also "eut2.interior.spatial.lit", but SCS not use it anywhere now). It's without "interrior mapping" effect, but allow to create new models with that shaders. Because ConverterPIX not support new attributes from mat, after import from .scs, you MUST open .pit file, and replace all: interior_unit_room_dimensions --> aux[0] interior_atlas_dimensions --> aux[1] interior_glass_color --> aux[2] --- .../internals/shaders/eut2/__init__.py | 12 ++ .../eut2/dif_spec_amod_dif_spec/__init__.py | 28 +-- .../shaders/eut2/interior/__init__.py | 166 +++++++++++++++++ .../shaders/eut2/interior/curtain.py | 124 +++++++++++++ addon/io_scs_tools/shader_presets.txt | 173 ++++++++++++++++++ 5 files changed, 489 insertions(+), 14 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index d883946..4717ebb 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -40,6 +40,18 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.window.lit import WindowLit as Shader + elif effect == "interior.lit": + + from io_scs_tools.internals.shaders.eut2.interior import InteriorLit as Shader + + elif effect == "interior.curtain.lit": + + from io_scs_tools.internals.shaders.eut2.interior.curtain import InteriorCurtain as Shader + + # elif effect == "interior.spatial.lit": + # + # from io_scs_tools.internals.shaders.eut2.interior.spatial import InteriorSpatial as Shader + elif effect == "reflective": from io_scs_tools.internals.shaders.eut2.reflective import Reflective as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py index 0fe1d09..68a642a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py @@ -60,14 +60,14 @@ def init(node_tree): vcol_group_n = node_tree.nodes[DifSpec.VCOL_GROUP_NODE] # node creation - # - column 0 - + # - column -1 - sec_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uvmap_n.name = DifSpecAmodDifSpec.SEC_UVMAP_NODE sec_uvmap_n.label = DifSpecAmodDifSpec.SEC_UVMAP_NODE sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1000) sec_uvmap_n.uv_map = _MESH_consts.none_uv - # - column 2 - + # - column 1 - vcol_scale_n = node_tree.nodes.new("ShaderNodeVectorMath") vcol_scale_n.name = DifSpecAmodDifSpec.VCOLOR_SCALE_NODE vcol_scale_n.label = DifSpecAmodDifSpec.VCOLOR_SCALE_NODE @@ -87,7 +87,7 @@ def init(node_tree): over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 700) over_tex_n.width = 140 - # - column 3 - + # - column 2 - vcol_subtract_n = node_tree.nodes.new("ShaderNodeVectorMath") vcol_subtract_n.name = DifSpecAmodDifSpec.VCOLOR_SUBTRACT_NODE vcol_subtract_n.label = DifSpecAmodDifSpec.VCOLOR_SUBTRACT_NODE @@ -101,21 +101,21 @@ def init(node_tree): mask_invert_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1000) mask_invert_n.inputs[0].default_value = 1 - # - column 4 - + # - column 3 - vcol_abs_n = node_tree.nodes.new("ShaderNodeVectorMath") vcol_abs_n.name = DifSpecAmodDifSpec.VCOLOR_ABS_NODE vcol_abs_n.label = DifSpecAmodDifSpec.VCOLOR_ABS_NODE vcol_abs_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1100) vcol_abs_n.operation = "ABSOLUTE" - # - column 5 - + # - column 4 - vcol_invert_n = node_tree.nodes.new("ShaderNodeInvert") vcol_invert_n.name = DifSpecAmodDifSpec.VCOLOR_INVERT_NODE vcol_invert_n.label = DifSpecAmodDifSpec.VCOLOR_INVERT_NODE vcol_invert_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1100) vcol_invert_n.inputs[0].default_value = 1 - # - column 6 - + # - column 5 - mask_vcol_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") mask_vcol_mix_n.name = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE mask_vcol_mix_n.label = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE @@ -123,7 +123,7 @@ def init(node_tree): mask_vcol_mix_n.blend_type = "MIX" mask_vcol_mix_n.inputs['Color2'].default_value = (1,) * 4 - # - column 7 - + # - column 6 - mask_color_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") mask_color_mix_n.name = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE mask_color_mix_n.label = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE @@ -133,31 +133,31 @@ def init(node_tree): # links creation - # - Column 0 - + # - Column -1 - node_tree.links.new(vcol_group_n.outputs[1], vcol_scale_n.inputs[0]) node_tree.links.new(sec_uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) node_tree.links.new(sec_uvmap_n.outputs['UV'], over_tex_n.inputs['Vector']) - # - Column 2 - + # - Column 1 - node_tree.links.new(base_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color2']) node_tree.links.new(vcol_scale_n.outputs['Vector'], vcol_subtract_n.inputs[0]) node_tree.links.new(mask_tex_n.outputs['Color'], mask_invert_n.inputs['Color']) node_tree.links.new(over_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color1']) - # - Column 3 - + # - Column 2 - node_tree.links.new(vcol_subtract_n.outputs['Vector'], vcol_abs_n.inputs[0]) node_tree.links.new(mask_invert_n.outputs['Color'], mask_vcol_mix_n.inputs['Color1']) - # - Column 4 - + # - Column 3 - node_tree.links.new(vcol_abs_n.outputs['Vector'], vcol_invert_n.inputs['Color']) - # - Column 5 - + # - Column 4 - node_tree.links.new(vcol_invert_n.outputs['Color'], mask_vcol_mix_n.inputs['Fac']) - # - Column 6 - + # - Column 5 - node_tree.links.new(mask_vcol_mix_n.outputs['Color'], mask_color_mix_n.inputs['Fac']) - # - Column 7 - + # - Column 6 - node_tree.links.new(mask_color_mix_n.outputs['Color'], vcol_multi_n.inputs[1]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py new file mode 100644 index 0000000..74e6274 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py @@ -0,0 +1,166 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2024: SCS Software + +from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_window +from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv + +class InteriorLit(DifSpecAddEnv): + VGCOLOR_MULT_NODE = "VertexGlassColorMultiplier" + GLASS_COL_NODE = "GlassColor" + GLASS_COL_MIX_NODE = "GlassColorMix" + GLASS_COL_FAC_NODE = "GlassColorFactor" + + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + DifSpecAddEnv.init(node_tree) + + base_tex_n = node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE] + vcol_mult_n = node_tree.nodes[DifSpecAddEnv.VCOLOR_MULT_NODE] + diff_mult_n = node_tree.nodes[DifSpecAddEnv.DIFF_MULT_NODE] + + # set fresnel type to schlick + node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE].inputs['Fresnel Type'].default_value = 1.0 + + # delete existing + node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.OPACITY_NODE]) + + # move existing + diff_mult_n.location.x += pos_x_shift + vcol_mult_n.location.x += pos_x_shift + + # node creation + # - column 1 - + glass_col_n = node_tree.nodes.new("ShaderNodeRGB") + glass_col_n.name = glass_col_n.label = InteriorLit.GLASS_COL_NODE + glass_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) + + # - column 2 - + glass_col_fac_n = node_tree.nodes.new("ShaderNodeValue") + glass_col_fac_n.name = glass_col_fac_n.label = InteriorLit.GLASS_COL_FAC_NODE + glass_col_fac_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 800) + + # - column 3 - + vgcol_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + vgcol_mult_n.name = vgcol_mult_n.label = InteriorLit.VGCOLOR_MULT_NODE + vgcol_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1200) + vgcol_mult_n.operation = "MULTIPLY" + + # - column 4 - + glass_col_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + glass_col_mix_n.name = glass_col_mix_n.label = InteriorLit.GLASS_COL_MIX_NODE + glass_col_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1200) + + + # links creation + # - column 1 - + node_tree.links.new(base_tex_n.outputs['Color'], vgcol_mult_n.inputs[0]) + + node_tree.links.new(glass_col_n.outputs['Color'], vgcol_mult_n.inputs[1]) + node_tree.links.new(glass_col_n.outputs['Color'], glass_col_mix_n.inputs['Color2']) + + # - column 2 - + node_tree.links.new(glass_col_fac_n.outputs['Value'], glass_col_mix_n.inputs['Fac']) + + # - column 3 - + node_tree.links.new(vgcol_mult_n.outputs['Vector'], glass_col_mix_n.inputs['Color1']) + + # - column 4 - + node_tree.links.new(glass_col_mix_n.outputs['Color'], vcol_mult_n.inputs[1]) + + @staticmethod + def set_fresnel(node_tree, bias_scale): + """Set fresnel bias and scale value to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param bias_scale: bias and scale factors as tuple: (bias, scale) + :type bias_scale: (float, float) + """ + + bias_scale_window = get_fresnel_window(bias_scale[0], bias_scale[1]) + + DifSpecAddEnv.set_fresnel(node_tree, bias_scale_window) + + @staticmethod + def set_aux0(node_tree, aux_property): + """Set unit room dimension for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: TBA + :type aux_property: bpy.types.IDPropertyGroup + """ + pass # NOTE: TBA? + + @staticmethod + def set_aux1(node_tree, aux_property): + """Set atlas dimension for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: TBA + :type aux_property: bpy.types.IDPropertyGroup + """ + pass # NOTE: TBA? + + @staticmethod + def set_aux2(node_tree, aux_property): + """Set glass color for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: glass color + :type aux_property: bpy.types.IDPropertyGroup + """ + + color = (aux_property[0]["value"], aux_property[1]["value"], aux_property[2]["value"], 1.0) + node_tree.nodes[InteriorLit.GLASS_COL_NODE].outputs["Color"].default_value = color + + factor = aux_property[3]["value"] + node_tree.nodes[InteriorLit.GLASS_COL_FAC_NODE].outputs["Value"].default_value = factor + + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminance boost factor for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + pass # NOTE: as this variant doesn't use luminance effect we just ignore this factor diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py b/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py new file mode 100644 index 0000000..6a58289 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py @@ -0,0 +1,124 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2024: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.interior import InteriorLit +from io_scs_tools.utils import material as _material_utils + +class InteriorCurtain(InteriorLit): + SEC_UVMAP_NODE = "SecondUVMap" + OVER_TEX_NODE = "OverTex" + BASE_OVER_MIX_NODE = "BaseOverColorMix" + + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + InteriorLit.init(node_tree) + + base_tex_n = node_tree.nodes[InteriorLit.BASE_TEX_NODE] + vgcol_mult_n = node_tree.nodes[InteriorLit.VGCOLOR_MULT_NODE] + + # node creation + # - column -1 - + sec_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + sec_uv_n.name = sec_uv_n.label = InteriorCurtain.SEC_UVMAP_NODE + sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) + sec_uv_n.uv_map = _MESH_consts.none_uv + + # - column 1 - + over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_tex_n.name = over_tex_n.label = InteriorCurtain.OVER_TEX_NODE + over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) + over_tex_n.width = 140 + + # - column 2 - + base_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_mix_n.name = base_over_mix_n.label = InteriorCurtain.BASE_OVER_MIX_NODE + base_over_mix_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) + + + # links creation + # - column -1 - + node_tree.links.new(sec_uv_n.outputs['UV'], over_tex_n.inputs['Vector']) + + # - column 1 - + node_tree.links.new(base_tex_n.outputs['Color'], base_over_mix_n.inputs['Color1']) + node_tree.links.new(over_tex_n.outputs['Color'], base_over_mix_n.inputs['Color2']) + node_tree.links.new(over_tex_n.outputs['Alpha'], base_over_mix_n.inputs['Fac']) + + # - column 2 - + node_tree.links.new(base_over_mix_n.outputs['Color'], vgcol_mult_n.inputs[0]) + + + + @staticmethod + def set_over_texture(node_tree, image): + """Set overlying texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to over texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[InteriorCurtain.OVER_TEX_NODE].image = image + + @staticmethod + def set_over_texture_settings(node_tree, settings): + """Set overlying texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[InteriorCurtain.OVER_TEX_NODE], settings) + + @staticmethod + def set_over_uv(node_tree, uv_layer): + """Set UV layer to overlying texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[InteriorCurtain.SEC_UVMAP_NODE].uv_map = uv_layer diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 00ad8fc..835f63f 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1828,6 +1828,179 @@ Shader { TexCoord: ( 1 ) } } +Shader { + PresetName: "interior.lit" + Effect: "eut2.interior.lit" + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 65.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.200000003 0.8999999762 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 1700.0 17.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Atlas Dimensions (X,Z)" + Value: ( 8.0 4.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "Glass Color (R,G,B,Factor)" + Value: ( 1.0 1.0 1.0 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Room Dimensions (X,Z)" + Value: ( 3.5 3.5 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer0" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer1" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_detail" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } +} +Shader { + PresetName: "interior.curtain.lit" + Effect: "eut2.interior.curtain.lit" + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 65.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.200000003 0.8999999762 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 1700.0 17.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Atlas Dimensions (X,Z)" + Value: ( 8.0 4.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "Glass Color (R,G,B,Factor)" + Value: ( 1.0 1.0 1.0 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Room Dimensions (X,Z)" + Value: ( 3.5 3.5 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 2 ) + } + Texture { + Tag: "texture[X]:texture_layer0" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer1" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_detail" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } +} Flavor { Type: "AIRBRUSH" Name: "airbrush" From 8ebe28e8972062ee70a5d2020cdb0ccfbf480919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 30 Oct 2024 02:53:39 +0100 Subject: [PATCH 09/56] amod update, interior tweaks * Fixed "amod" shader - changed "decal_blending_factors" attribute to "aux" (and removed old properties) * Added support for "Decal blending factors" attributes in material tab * Updated "amod" nodes to better reflect that shader in blender * Corrected TexGen description in shader_presets.txt --- .../eut2/dif_spec_amod_dif_spec/__init__.py | 138 ++++++++-------- .../decal_blend_factor_ng.py | 155 ++++++++++++++++++ .../shaders/eut2/interior/__init__.py | 97 ++++++++++- .../shaders/eut2/shadowonly/__init__.py | 94 ++++++++++- addon/io_scs_tools/properties/material.py | 15 -- addon/io_scs_tools/shader_presets.txt | 19 ++- 6 files changed, 422 insertions(+), 96 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py index 68a642a..fde4cb8 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py @@ -20,19 +20,19 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.internals.shaders.eut2.dif_spec_amod_dif_spec import decal_blend_factor_ng from io_scs_tools.utils import material as _material_utils class DifSpecAmodDifSpec(DifSpec): SEC_UVMAP_NODE = "SecondUVMap" - VCOLOR_SCALE_NODE = "VertexColorScale" - VCOLOR_SUBTRACT_NODE = "VertexColorSubtract" - VCOLOR_ABS_NODE = "VertexColorAbsolute" - VCOLOR_INVERT_NODE = "VertexColorInvert" MASK_TEX_NODE = "MaskTex" OVER_TEX_NODE = "OverTex" - MASK_COLOR_INVERT_NODE = "MaskColorInvert" + BLENDING_FACTOR_1 = "BlendingFactor1" + BLENDING_FACTOR_2 = "BlendingFactor2" + DECAL_BLEND_FACTOR_NODE = "DecalBlendFactorGNode" MASK_VCOLOR_MIX_NODE = "MaskVertexColorMix" MASK_COLOR_MIX_NODE = "MaskColorMix" + @staticmethod def get_name(): @@ -58,22 +58,34 @@ def init(node_tree): base_tex_n = node_tree.nodes[DifSpec.BASE_TEX_NODE] vcol_multi_n = node_tree.nodes[DifSpec.VCOLOR_MULT_NODE] vcol_group_n = node_tree.nodes[DifSpec.VCOL_GROUP_NODE] + + # delete existing + node_tree.nodes.remove(node_tree.nodes[DifSpec.OPACITY_NODE]) # node creation # - column -1 - + blending_factor_1_n = node_tree.nodes.new("ShaderNodeValue") + blending_factor_1_n.name = DifSpecAmodDifSpec.BLENDING_FACTOR_1 + blending_factor_1_n.label = DifSpecAmodDifSpec.BLENDING_FACTOR_1 + blending_factor_1_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) + + blending_factor_2_n = node_tree.nodes.new("ShaderNodeValue") + blending_factor_2_n.name = DifSpecAmodDifSpec.BLENDING_FACTOR_2 + blending_factor_2_n.label = DifSpecAmodDifSpec.BLENDING_FACTOR_2 + blending_factor_2_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1000) + sec_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uvmap_n.name = DifSpecAmodDifSpec.SEC_UVMAP_NODE sec_uvmap_n.label = DifSpecAmodDifSpec.SEC_UVMAP_NODE - sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1000) + sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) sec_uvmap_n.uv_map = _MESH_consts.none_uv # - column 1 - - vcol_scale_n = node_tree.nodes.new("ShaderNodeVectorMath") - vcol_scale_n.name = DifSpecAmodDifSpec.VCOLOR_SCALE_NODE - vcol_scale_n.label = DifSpecAmodDifSpec.VCOLOR_SCALE_NODE - vcol_scale_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) - vcol_scale_n.operation = "MULTIPLY" - vcol_scale_n.inputs[1].default_value = (2,) * 3 + deacl_blend_fac_gn = node_tree.nodes.new("ShaderNodeGroup") + deacl_blend_fac_gn.name = DifSpecAmodDifSpec.DECAL_BLEND_FACTOR_NODE + deacl_blend_fac_gn.label = DifSpecAmodDifSpec.DECAL_BLEND_FACTOR_NODE + deacl_blend_fac_gn.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) + deacl_blend_fac_gn.node_tree = decal_blend_factor_ng.get_node_group() mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") mask_tex_n.name = DifSpecAmodDifSpec.MASK_TEX_NODE @@ -88,94 +100,56 @@ def init(node_tree): over_tex_n.width = 140 # - column 2 - - vcol_subtract_n = node_tree.nodes.new("ShaderNodeVectorMath") - vcol_subtract_n.name = DifSpecAmodDifSpec.VCOLOR_SUBTRACT_NODE - vcol_subtract_n.label = DifSpecAmodDifSpec.VCOLOR_SUBTRACT_NODE - vcol_subtract_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) - vcol_subtract_n.operation = "SUBTRACT" - vcol_subtract_n.inputs[1].default_value = (1,) * 3 - - mask_invert_n = node_tree.nodes.new("ShaderNodeInvert") - mask_invert_n.name = DifSpecAmodDifSpec.MASK_COLOR_INVERT_NODE - mask_invert_n.label = DifSpecAmodDifSpec.MASK_COLOR_INVERT_NODE - mask_invert_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1000) - mask_invert_n.inputs[0].default_value = 1 - - # - column 3 - - vcol_abs_n = node_tree.nodes.new("ShaderNodeVectorMath") - vcol_abs_n.name = DifSpecAmodDifSpec.VCOLOR_ABS_NODE - vcol_abs_n.label = DifSpecAmodDifSpec.VCOLOR_ABS_NODE - vcol_abs_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1100) - vcol_abs_n.operation = "ABSOLUTE" - - # - column 4 - - vcol_invert_n = node_tree.nodes.new("ShaderNodeInvert") - vcol_invert_n.name = DifSpecAmodDifSpec.VCOLOR_INVERT_NODE - vcol_invert_n.label = DifSpecAmodDifSpec.VCOLOR_INVERT_NODE - vcol_invert_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1100) - vcol_invert_n.inputs[0].default_value = 1 - - # - column 5 - mask_vcol_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") mask_vcol_mix_n.name = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE mask_vcol_mix_n.label = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE - mask_vcol_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1100) + mask_vcol_mix_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) mask_vcol_mix_n.blend_type = "MIX" - mask_vcol_mix_n.inputs['Color2'].default_value = (1,) * 4 + mask_vcol_mix_n.inputs['Color1'].default_value = (0,) * 3 + (1,) - # - column 6 - + # - column 3 - mask_color_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") mask_color_mix_n.name = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE mask_color_mix_n.label = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE - mask_color_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1050) + mask_color_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1200) mask_color_mix_n.blend_type = "MIX" # links creation # - Column -1 - - node_tree.links.new(vcol_group_n.outputs[1], vcol_scale_n.inputs[0]) + node_tree.links.new(vcol_group_n.outputs['Vertex Color Alpha'], deacl_blend_fac_gn.inputs['Vertex Alpha']) + node_tree.links.new(blending_factor_1_n.outputs['Value'], deacl_blend_fac_gn.inputs['Factor1']) + node_tree.links.new(blending_factor_2_n.outputs['Value'], deacl_blend_fac_gn.inputs['Factor2']) node_tree.links.new(sec_uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) node_tree.links.new(sec_uvmap_n.outputs['UV'], over_tex_n.inputs['Vector']) # - Column 1 - - node_tree.links.new(base_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color2']) - node_tree.links.new(vcol_scale_n.outputs['Vector'], vcol_subtract_n.inputs[0]) - node_tree.links.new(mask_tex_n.outputs['Color'], mask_invert_n.inputs['Color']) - node_tree.links.new(over_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color1']) + node_tree.links.new(base_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color1']) + node_tree.links.new(mask_tex_n.outputs['Color'], mask_vcol_mix_n.inputs['Color2']) + node_tree.links.new(over_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color2']) + node_tree.links.new(deacl_blend_fac_gn.outputs['Factor'], mask_vcol_mix_n.inputs['Fac']) # - Column 2 - - node_tree.links.new(vcol_subtract_n.outputs['Vector'], vcol_abs_n.inputs[0]) - node_tree.links.new(mask_invert_n.outputs['Color'], mask_vcol_mix_n.inputs['Color1']) - - # - Column 3 - - node_tree.links.new(vcol_abs_n.outputs['Vector'], vcol_invert_n.inputs['Color']) - - # - Column 4 - - node_tree.links.new(vcol_invert_n.outputs['Color'], mask_vcol_mix_n.inputs['Fac']) - - # - Column 5 - node_tree.links.new(mask_vcol_mix_n.outputs['Color'], mask_color_mix_n.inputs['Fac']) - # - Column 6 - + # - Column 3 - node_tree.links.new(mask_color_mix_n.outputs['Color'], vcol_multi_n.inputs[1]) @staticmethod - def set_over_uv(node_tree, uv_layer): - """Set UV layer to overlying texture in shader. + def set_aux0(node_tree, aux_property): + """Set decal blending factors to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param uv_layer: uv layer string used for over texture - :type uv_layer: str + :param aux_property: decal blending factors represented with property group + :type aux_property: bpy.types.IDPropertyGroup """ - if uv_layer is None or uv_layer == "": - uv_layer = _MESH_consts.none_uv - - node_tree.nodes[DifSpecAmodDifSpec.SEC_UVMAP_NODE].uv_map = uv_layer - + node_tree.nodes[DifSpecAmodDifSpec.BLENDING_FACTOR_1].outputs["Value"].default_value = aux_property[0]['value'] + node_tree.nodes[DifSpecAmodDifSpec.BLENDING_FACTOR_2].outputs["Value"].default_value = aux_property[1]['value'] + @staticmethod def set_mask_texture(node_tree, image): """Set mask texture to shader. @@ -197,9 +171,29 @@ def set_mask_texture_settings(node_tree, settings): :param settings: binary string of TOBJ settings gotten from tobj import :type settings: str """ - # Commented because of texture linear colorspace problems in render - # _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE], settings) - + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE], settings) + + # due the fact uvs get clamped in vertex shader, we have to manually switch repeat on, for effect to work correctly + node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE].extension = "REPEAT" + + # due the fact uvs colorspace linear, we have to manually switch to sRGB, for effect to work correctly + node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE].image.colorspace_settings.name = 'sRGB' + + @staticmethod + def set_over_uv(node_tree, uv_layer): + """Set UV layer to overlying texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecAmodDifSpec.SEC_UVMAP_NODE].uv_map = uv_layer + @staticmethod def set_over_texture(node_tree, image): """Set over texture to shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py new file mode 100644 index 0000000..a124be2 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py @@ -0,0 +1,155 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015: SCS Software + +from multiprocessing.spawn import import_main_path +import bpy +from io_scs_tools.consts import Material as _MAT_consts + +DECAL_BLEND_FACTOR_G = _MAT_consts.node_group_prefix + "DecalBlendFactor" + +_MAP_VCOLA_SHIFT_NODE = "MapVColAlphaShift" +_INVERT_NEG_NODE = "InvertNegatives" +_ALPHA_1_SCALE_NODE = "AlphaScale_1" +_ALPHA_2_SCALE_NODE = "AlphaScale_2" +_BLEND_1_ABS_NODE = "BlendingAbsolute_1" +_BLEND_2_ABS_NODE = "BlendingAbsolute_2" +_MULT_1_SCALE_NODE = "MultScale_1" +_MULT_2_SCALE_NODE = "MultScale_2" +_MULT_FINAL_NODE = "MultFinal" + +def get_node_group(): + """Gets node group for combining of Decal blending factors with mask texture. + + :return: node group which calculates change factor + :rtype: bpy.types.NodeGroup + """ + + if DECAL_BLEND_FACTOR_G not in bpy.data.node_groups: + __create_node_group__() + + return bpy.data.node_groups[DECAL_BLEND_FACTOR_G] + +def __create_node_group__(): + """Creates decal blending factor group. + + Inputs: Decal blending factor (1 & 2) and Vertex Color Alpha + Outputs: Factor + """ + + pos_x_shift = 185 + + dec_blend_fac_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=DECAL_BLEND_FACTOR_G) + + # inputs defining + dec_blend_fac_g.inputs.new("NodeSocketFloat", "Vertex Alpha") + dec_blend_fac_g.inputs.new("NodeSocketFloat", "Factor1") + dec_blend_fac_g.inputs.new("NodeSocketFloat", "Factor2") + + input_n = dec_blend_fac_g.nodes.new("NodeGroupInput") + input_n.location = (0, 0) + + # outputs defining + dec_blend_fac_g.outputs.new("NodeSocketColor", "Factor") + output_n = dec_blend_fac_g.nodes.new("NodeGroupOutput") + output_n.location = (pos_x_shift * 6, 0) + + + # nodes creation + # - column 1 - + map_vcol_a_shift_n = dec_blend_fac_g.nodes.new("ShaderNodeMapping") + map_vcol_a_shift_n.name = map_vcol_a_shift_n.label = _MAP_VCOLA_SHIFT_NODE + map_vcol_a_shift_n.location = (pos_x_shift, 100) + map_vcol_a_shift_n.vector_type = "POINT" + map_vcol_a_shift_n.inputs['Location'].default_value = (-0.5,) * 3 + + # - column 2 - + invert_neg_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + invert_neg_n.name = invert_neg_n.label = _INVERT_NEG_NODE + invert_neg_n.location = (pos_x_shift * 2, 250) + invert_neg_n.operation = "MULTIPLY" + invert_neg_n.inputs[1].default_value = (-1,) * 3 + + # - column 3 - + alpha_1_scale_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + alpha_1_scale_n.name = alpha_1_scale_n.label = _ALPHA_1_SCALE_NODE + alpha_1_scale_n.location = (pos_x_shift * 3, 250) + alpha_1_scale_n.operation = "MULTIPLY" + alpha_1_scale_n.inputs[1].default_value = (2,) * 3 + + blending_1_abs_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + blending_1_abs_n.name = blending_1_abs_n.label = _BLEND_1_ABS_NODE + blending_1_abs_n.location = (pos_x_shift * 3, 50) + blending_1_abs_n.operation = "ABSOLUTE" + + alpha_2_scale_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + alpha_2_scale_n.name = alpha_2_scale_n.label = _ALPHA_2_SCALE_NODE + alpha_2_scale_n.location = (pos_x_shift * 3, -100) + alpha_2_scale_n.operation = "MULTIPLY" + alpha_2_scale_n.inputs[1].default_value = (2,) * 3 + + blending_2_abs_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + blending_2_abs_n.name = blending_2_abs_n.label = _BLEND_2_ABS_NODE + blending_2_abs_n.location = (pos_x_shift * 3, -300) + blending_2_abs_n.operation = "ABSOLUTE" + + # - column 4 - + mult_1_scale_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + mult_1_scale_n.name = mult_1_scale_n.label = _MULT_1_SCALE_NODE + mult_1_scale_n.location = (pos_x_shift * 4, 100) + mult_1_scale_n.operation = "MULTIPLY" + + mult_2_scale_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + mult_2_scale_n.name = mult_2_scale_n.label = _MULT_2_SCALE_NODE + mult_2_scale_n.location = (pos_x_shift * 4, -200) + mult_2_scale_n.operation = "MULTIPLY" + + # - column 5 - + mult_final_n = dec_blend_fac_g.nodes.new("ShaderNodeVectorMath") + mult_final_n.name = mult_final_n.label = _MULT_FINAL_NODE + mult_final_n.location = (pos_x_shift * 5, 0) + mult_final_n.operation = "MAXIMUM" + + + # links creation + # - column 0 - + dec_blend_fac_g.links.new(input_n.outputs["Vertex Alpha"], map_vcol_a_shift_n.inputs["Vector"]) + dec_blend_fac_g.links.new(input_n.outputs["Factor1"], blending_1_abs_n.inputs["Vector"]) + dec_blend_fac_g.links.new(input_n.outputs["Factor2"], blending_2_abs_n.inputs["Vector"]) + + # - column 1 - + dec_blend_fac_g.links.new(map_vcol_a_shift_n.outputs["Vector"], invert_neg_n.inputs[0]) + dec_blend_fac_g.links.new(map_vcol_a_shift_n.outputs["Vector"], alpha_2_scale_n.inputs[0]) + + # - column 2 - + dec_blend_fac_g.links.new(invert_neg_n.outputs["Vector"], alpha_1_scale_n.inputs[0]) + + # - column 3 - + dec_blend_fac_g.links.new(alpha_1_scale_n.outputs["Vector"], mult_1_scale_n.inputs[0]) + dec_blend_fac_g.links.new(blending_1_abs_n.outputs["Vector"], mult_1_scale_n.inputs[1]) + + dec_blend_fac_g.links.new(alpha_2_scale_n.outputs["Vector"], mult_2_scale_n.inputs[0]) + dec_blend_fac_g.links.new(blending_2_abs_n.outputs["Vector"], mult_2_scale_n.inputs[1]) + + # - column 4 - + dec_blend_fac_g.links.new(mult_1_scale_n.outputs["Vector"], mult_final_n.inputs[0]) + dec_blend_fac_g.links.new(mult_2_scale_n.outputs["Vector"], mult_final_n.inputs[1]) + + # - column 5 - + dec_blend_fac_g.links.new(mult_final_n.outputs["Vector"], output_n.inputs["Factor"]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py index 74e6274..b6316bd 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py @@ -20,12 +20,19 @@ from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_window from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv +from io_scs_tools.utils import material as _material_utils class InteriorLit(DifSpecAddEnv): VGCOLOR_MULT_NODE = "VertexGlassColorMultiplier" GLASS_COL_NODE = "GlassColor" GLASS_COL_MIX_NODE = "GlassColorMix" GLASS_COL_FAC_NODE = "GlassColorFactor" + LAYER0_TEX_NODE = "Layer0Tex" + LAYER1_TEX_NODE = "Layer1Tex" + MASK_TEX_NODE = "MaskTex" + NMAP_TEX_NODE = "NmapTex" + ENV_SEP_XYZ_NODE = "EnvSepXYZ" + ENV_CHECK_XYZ_NODE = "EnvCheckXYZ" @staticmethod @@ -50,11 +57,16 @@ def init(node_tree): DifSpecAddEnv.init(node_tree) base_tex_n = node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE] + uvmap_n = node_tree.nodes[DifSpecAddEnv.UVMAP_NODE] vcol_mult_n = node_tree.nodes[DifSpecAddEnv.VCOLOR_MULT_NODE] diff_mult_n = node_tree.nodes[DifSpecAddEnv.DIFF_MULT_NODE] + add_env_group_n = node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE] # set fresnel type to schlick - node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE].inputs['Fresnel Type'].default_value = 1.0 + add_env_group_n.inputs['Fresnel Type'].default_value = 1.0 + + # Doesn't work... + # node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE].extension = 'REPEAT' # delete existing node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.OPACITY_NODE]) @@ -64,11 +76,42 @@ def init(node_tree): vcol_mult_n.location.x += pos_x_shift # node creation + # - column 0 - + env_sep_xyz_n = node_tree.nodes.new("ShaderNodeSeparateXYZ") + env_sep_xyz_n.name = env_sep_xyz_n.label = InteriorLit.ENV_SEP_XYZ_NODE + env_sep_xyz_n.location = (start_pos_x, start_pos_y + 1800) + + env_check_xyz_n = node_tree.nodes.new("ShaderNodeMath") + env_check_xyz_n.name = env_check_xyz_n.label = InteriorLit.ENV_CHECK_XYZ_NODE + env_check_xyz_n.location = (start_pos_x, start_pos_y + 2000) + env_check_xyz_n.operation = "LESS_THAN" + env_check_xyz_n.inputs[1].default_value = 1.0 + # - column 1 - glass_col_n = node_tree.nodes.new("ShaderNodeRGB") glass_col_n.name = glass_col_n.label = InteriorLit.GLASS_COL_NODE glass_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) + layer0_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + layer0_tex_n.name = layer0_tex_n.label = InteriorLit.LAYER0_TEX_NODE + layer0_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 700) + layer0_tex_n.width = 140 + + layer1_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + layer1_tex_n.name = layer1_tex_n.label = InteriorLit.LAYER1_TEX_NODE + layer1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 450) + layer1_tex_n.width = 140 + + mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_tex_n.name = mask_tex_n.label = InteriorLit.MASK_TEX_NODE + mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 200) + mask_tex_n.width = 140 + + nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + nmap_tex_n.name = nmap_tex_n.label = InteriorLit.NMAP_TEX_NODE + nmap_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y - 50) + nmap_tex_n.width = 140 + # - column 2 - glass_col_fac_n = node_tree.nodes.new("ShaderNodeValue") glass_col_fac_n.name = glass_col_fac_n.label = InteriorLit.GLASS_COL_FAC_NODE @@ -87,6 +130,13 @@ def init(node_tree): # links creation + # - column -1 - + node_tree.links.new(uvmap_n.outputs['UV'], env_sep_xyz_n.inputs['Vector']) + + # - column 0 - + node_tree.links.new(env_sep_xyz_n.outputs['Y'], env_check_xyz_n.inputs[0]) + node_tree.links.new(env_check_xyz_n.outputs['Value'], add_env_group_n.inputs['Weighted Color']) + # - column 1 - node_tree.links.new(base_tex_n.outputs['Color'], vgcol_mult_n.inputs[0]) @@ -102,6 +152,31 @@ def init(node_tree): # - column 4 - node_tree.links.new(glass_col_mix_n.outputs['Color'], vcol_mult_n.inputs[1]) + ### TEXTURES ### + @staticmethod + def set_nmap_texture(node_tree, image): + """Set nmap texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to nmap texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[InteriorLit.NMAP_TEX_NODE].image = image + + @staticmethod + def set_nmap_texture_settings(node_tree, settings): + """Set nmap texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[InteriorLit.NMAP_TEX_NODE], settings) + + ### ATTRIBUTES ### @staticmethod def set_fresnel(node_tree, bias_scale): """Set fresnel bias and scale value to shader. @@ -125,7 +200,8 @@ def set_aux0(node_tree, aux_property): :param aux_property: TBA :type aux_property: bpy.types.IDPropertyGroup """ - pass # NOTE: TBA? + # NOTE: TBA? + pass # NOTE: aux doesn't change anything in rendered material, so pass it @staticmethod def set_aux1(node_tree, aux_property): @@ -136,7 +212,8 @@ def set_aux1(node_tree, aux_property): :param aux_property: TBA :type aux_property: bpy.types.IDPropertyGroup """ - pass # NOTE: TBA? + # NOTE: TBA? + pass # NOTE: aux doesn't change anything in rendered material, so pass it @staticmethod def set_aux2(node_tree, aux_property): @@ -164,3 +241,17 @@ def set_aux5(node_tree, aux_property): :type aux_property: bpy.types.IDPropertyGroup """ pass # NOTE: as this variant doesn't use luminance effect we just ignore this factor + + + # def made to override base texture settings, because overwriting "extension" in init doesn't work + @staticmethod + def set_base_texture_settings(node_tree, settings): + """Set base texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + + node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE].extension = 'REPEAT' diff --git a/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py index 3a1799a..ace9ea2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py @@ -18,12 +18,19 @@ # Copyright (C) 2015-2019: SCS Software +from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.flavors import alpha_test +from io_scs_tools.internals.shaders.std_node_groups import output_shader_ng +from io_scs_tools.utils import material as _material_utils class Shadowonly(BaseShader): COL_NODE = "Color" OUTPUT_NODE = "Output" + UVMAP_NODE = "FirstUVs" + BASE_TEX_NODE = "BaseTex" + OUT_MAT_NODE = "OutMaterial" @staticmethod def get_name(): @@ -44,18 +51,37 @@ def init(node_tree): pos_x_shift = 185 # node creation + uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") + uvmap_n.name = uvmap_n.label = Shadowonly.UVMAP_NODE + uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y - 100) + uvmap_n.uv_map = _MESH_consts.none_uv + col_n = node_tree.nodes.new("ShaderNodeRGB") col_n.name = col_n.label = Shadowonly.COL_NODE col_n.location = (start_pos_x, start_pos_y) col_n.outputs['Color'].default_value = (0.01, 0, 0.01, 1.0) + base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + base_tex_n.name = base_tex_n.label = Shadowonly.BASE_TEX_NODE + base_tex_n.location = (start_pos_x, start_pos_y - 200) + base_tex_n.width = 140 + + out_mat_node = node_tree.nodes.new("ShaderNodeGroup") + out_mat_node.name = out_mat_node.label = Shadowonly.OUT_MAT_NODE + out_mat_node.location = (start_pos_x + pos_x_shift, start_pos_y) + out_mat_node.node_tree = output_shader_ng.get_node_group() + output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") output_n.name = output_n.label = Shadowonly.OUTPUT_NODE - output_n.location = (start_pos_x + pos_x_shift, start_pos_y) + output_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y) # links creation node_tree.links.new(output_n.inputs['Surface'], col_n.outputs['Color']) + node_tree.links.new(uvmap_n.outputs['UV'], base_tex_n.inputs['Vector']) + node_tree.links.new(col_n.outputs['Color'], out_mat_node.inputs['Color']) + node_tree.links.new(base_tex_n.outputs['Alpha'], out_mat_node.inputs['Alpha']) + @staticmethod def finalize(node_tree, material): """Finalize node tree and material settings. Should be called as last. @@ -69,6 +95,72 @@ def finalize(node_tree, material): material.use_backface_culling = True material.blend_method = "OPAQUE" + # set proper blend method and possible alpha test pass + if alpha_test.is_set(node_tree): + material.blend_method = "CLIP" + material.alpha_threshold = 0.05 + + # init parent + out_mat_node = node_tree.nodes[Shadowonly.OUT_MAT_NODE] + output_n = node_tree.nodes[Shadowonly.OUTPUT_NODE] + + # links creation + node_tree.links.new(out_mat_node.outputs['Shader'], output_n.inputs['Surface']) + + @staticmethod + def set_alpha_test_flavor(node_tree, switch_on): + """Set alpha test flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if alpha test should be switched on or off + :type switch_on: bool + """ + + if switch_on: + alpha_test.init(node_tree) + else: + alpha_test.delete(node_tree) + + @staticmethod + def set_base_texture(node_tree, image): + """Set base texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to base texture node + :type image: bpy.types.Image + """ + if alpha_test.is_set(node_tree): + node_tree.nodes[Shadowonly.BASE_TEX_NODE].image = image + + @staticmethod + def set_base_texture_settings(node_tree, settings): + """Set base texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + if alpha_test.is_set(node_tree): + _material_utils.set_texture_settings_to_node(node_tree.nodes[Shadowonly.BASE_TEX_NODE], settings) + + @staticmethod + def set_base_uv(node_tree, uv_layer): + """Set UV layer to base texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + if alpha_test.is_set(node_tree): + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[Shadowonly.UVMAP_NODE].uv_map = uv_layer + @staticmethod def set_shadow_bias(node_tree, value): """Set shadow bias attirbute for this shader. diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 1a361a3..4fed34c 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -342,9 +342,6 @@ def update_shader_attribute_tint(self, context): def update_shader_attribute_tint_opacity(self, context): __update_shader_attribute__(self, context, "tint_opacity") - - def update_shader_attribute_amod_decal_blending_factors(self, context): - __update_shader_attribute__(self, context, "amod_decal_blending_factors") def update_shader_texture_base(self, context): __update_shader_texture__(self, context, "base") @@ -715,18 +712,6 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): options={'HIDDEN'}, update=update_shader_attribute_tint_opacity ) - - shader_attribute_amod_decal_blending_factors: FloatVectorProperty( - name="Amod Decal Blending Factors", - description="SCS shader 'Amod Blending' value", - default=(1.0, 1.0), - min=0, max=1, - step=1, precision=2, - options={'HIDDEN'}, - subtype='NONE', - size=2, - update=update_shader_attribute_amod_decal_blending_factors, - ) shader_attribute_queue_bias: IntProperty( name="Queue Bias", diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 835f63f..f4950b3 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1473,7 +1473,7 @@ Shader { Shader { PresetName: "shadowonly" Effect: "eut2.shadowonly" - Flavors: ( "NOCULL" ) + Flavors: ( "NOCULL" "SHDW_ALPHA" ) Flags: 0 Attribute { Format: FLOAT @@ -1793,9 +1793,9 @@ Shader { } Attribute { Format: FLOAT2 - Tag: "amod_decal_blending_factors" + Tag: "aux[0]" + FriendlyTag: "Decal blending factors" Value: ( 1.0 1.0 ) - Hide: "True" } Attribute { Format: FLOAT @@ -2020,6 +2020,15 @@ Flavor { Type: "ALPHA" Name: "a" } +Flavor { + Type: "SHDW_ALPHA" + Name: "a" + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } +} Flavor { Type: "ALTUV" Name: "altuv" @@ -2329,7 +2338,7 @@ Flavor { Attribute { Format: FLOAT4 Tag: "aux[0]" - FriendlyTag: "TexGen0 Scale (X,Z,ROT_BASE,ROT_NMAP)" + FriendlyTag: "TexGen0 (ScaleX, ScaleZ, Rotation, Unk)" Value: ( 10.0 10.0 0.0 0.0 ) } } @@ -2339,7 +2348,7 @@ Flavor { Attribute { Format: FLOAT4 Tag: "aux[1]" - FriendlyTag: "TexGen1 Scale (X,Z,ROT_BASE,ROT_NMAP)" + FriendlyTag: "TexGen1 (ScaleX, ScaleZ, Rotation, Unk)" Value: ( 8.0 8.0 0.0 0.0 ) } } From 0e17894a4b85a4ae33354a5979df94c37b817c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 5 Nov 2024 18:58:51 +0100 Subject: [PATCH 10/56] support for old tg0 & tg1 float2 * Added support for old tg0 (aux[0]) and tg1 (aux[1]) float2 values * Changed tool version (added ".1" to version number) to distinguish my edits from official SCS version. Author is also added. --- addon/io_scs_tools/__init__.py | 4 ++-- .../io_scs_tools/internals/shaders/eut2/dif/__init__.py | 9 ++++++--- .../shaders/eut2/dif_spec_mult_dif_spec/__init__.py | 9 ++++++--- .../shaders/eut2/dif_spec_over_dif_opac/__init__.py | 9 ++++++--- .../shaders/eut2/dif_spec_weight_mult2/__init__.py | 9 ++++++--- .../eut2/dif_spec_weight_mult2_weight2/__init__.py | 9 ++++++--- .../dif_spec_weight_weight_dif_spec_weight/__init__.py | 9 ++++++--- .../internals/shaders/eut2/dif_weight_dif/__init__.py | 9 ++++++--- addon/io_scs_tools/internals/shaders/flavors/tg0.py | 7 +++++-- addon/io_scs_tools/internals/shaders/flavors/tg1.py | 7 +++++-- 10 files changed, 54 insertions(+), 27 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index e0df0a6..984e7bf 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -21,8 +21,8 @@ bl_info = { "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", - "author": "Simon Lusenc (50keda), Milos Zajic (4museman)", - "version": (2, 4, "aeadde03"), + "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", + "version": (2, 4, "aeadde03", 1), "blender": (3, 2, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py index 1056dcb..d8d7344 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py @@ -501,7 +501,7 @@ def set_tg0_flavor(node_tree, switch_on): @staticmethod def set_aux0(node_tree, aux_property): - """Set zero texture generation scale. + """Set zero texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -510,8 +510,11 @@ def set_aux0(node_tree, aux_property): """ if tg0.is_set(node_tree): - - tg0.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[0] + if (len(aux_property)) == 2: + tg0.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg0.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) @staticmethod def set_flat_flavor(node_tree, switch_on): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py index 1791006..57e3eff 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py @@ -167,7 +167,7 @@ def set_tg1_flavor(node_tree, switch_on): @staticmethod def set_aux1(node_tree, aux_property): - """Set second texture generation scale. + """Set second texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -176,5 +176,8 @@ def set_aux1(node_tree, aux_property): """ if tg1.is_set(node_tree): - - tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py index ef534de..9e66be4 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py @@ -87,7 +87,7 @@ def init(node_tree): @staticmethod def set_aux1(node_tree, aux_property): - """Set second texture generation scale. + """Set second texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -96,8 +96,11 @@ def set_aux1(node_tree, aux_property): """ if tg1.is_set(node_tree): - - tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) @staticmethod def set_reflection2(node_tree, value): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2/__init__.py index 3522eef..25642f3 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2/__init__.py @@ -191,7 +191,7 @@ def set_tg0_flavor(node_tree, switch_on): @staticmethod def set_aux0(node_tree, aux_property): - """Set zero texture generation scale. + """Set zero texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -200,5 +200,8 @@ def set_aux0(node_tree, aux_property): """ if tg0.is_set(node_tree): - - tg0.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[0] + if (len(aux_property)) == 2: + tg0.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg0.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py index d308976..24ca419 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py @@ -362,7 +362,7 @@ def set_tg1_flavor(node_tree, switch_on): @staticmethod def set_aux1(node_tree, aux_property): - """Set second texture generation scale. + """Set second texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -371,5 +371,8 @@ def set_aux1(node_tree, aux_property): """ if tg1.is_set(node_tree): - - tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py index 43c8bfa..11caea1 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py @@ -250,7 +250,7 @@ def set_tg1_flavor(node_tree, switch_on): @staticmethod def set_aux1(node_tree, aux_property): - """Set second texture generation scale. + """Set second texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -259,5 +259,8 @@ def set_aux1(node_tree, aux_property): """ if tg1.is_set(node_tree): - - tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py index 19b84e6..218ce38 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py @@ -222,7 +222,7 @@ def set_tg1_flavor(node_tree, switch_on): @staticmethod def set_aux1(node_tree, aux_property): - """Set second texture generation scale. + """Set second texture generation scale and rotation. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -231,5 +231,8 @@ def set_aux1(node_tree, aux_property): """ if tg1.is_set(node_tree): - - tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value']) + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/flavors/tg0.py b/addon/io_scs_tools/internals/shaders/flavors/tg0.py index c0c2f83..d60248b 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/tg0.py +++ b/addon/io_scs_tools/internals/shaders/flavors/tg0.py @@ -101,8 +101,8 @@ def is_set(node_tree): return FLAVOR_ID in node_tree.nodes -def set_scale(node_tree, scale_x, scale_y): - """Set scale of tex generation. +def set_scale(node_tree, scale_x, scale_y, rotation): + """Set scale and rotation of tex generation. :param node_tree: node tree which should be checked for existance of this flavor :type node_tree: bpy.types.NodeTree @@ -110,10 +110,13 @@ def set_scale(node_tree, scale_x, scale_y): :type scale_x: float :param scale_y: y coordinate scaling :type scale_y: float + :param rotation: base texture rotation + :type rotation: float """ vector_mapping_n = get_node(node_tree) if vector_mapping_n: + vector_mapping_n.inputs['Rotation'].default_value[2] = rotation / 57.3 vector_mapping_n.inputs['Scale'].default_value[0] = 1 / scale_x vector_mapping_n.inputs['Scale'].default_value[1] = 1 / scale_y diff --git a/addon/io_scs_tools/internals/shaders/flavors/tg1.py b/addon/io_scs_tools/internals/shaders/flavors/tg1.py index 2adae8d..3306c16 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/tg1.py +++ b/addon/io_scs_tools/internals/shaders/flavors/tg1.py @@ -101,8 +101,8 @@ def is_set(node_tree): return FLAVOR_ID in node_tree.nodes -def set_scale(node_tree, scale_x, scale_y): - """Set scale of tex generation. +def set_scale(node_tree, scale_x, scale_y, rotation): + """Set scale and rotation of tex generation. :param node_tree: node tree which should be checked for existance of this flavor :type node_tree: bpy.types.NodeTree @@ -110,10 +110,13 @@ def set_scale(node_tree, scale_x, scale_y): :type scale_x: float :param scale_y: y coordinate scaling :type scale_y: float + :param rotation: base texture rotation + :type rotation: float """ vector_mapping_n = get_node(node_tree) if vector_mapping_n: + vector_mapping_n.inputs['Rotation'].default_value[2] = rotation / 57.3 vector_mapping_n.inputs['Scale'].default_value[0] = 1 / scale_x vector_mapping_n.inputs['Scale'].default_value[1] = 1 / scale_y From 0295b756ddbb2ee3adf456c2e112979ccadbd1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 29 Nov 2024 04:23:41 +0100 Subject: [PATCH 11/56] fixes, new shader, new tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reverted "upside-down" envmap fix (caused problems elsewhere) * Fixed wrong order of “envmap” and “shadow” flavors in lamp shader * Fixed problems with exporting models with "interior" shader (missing UV1 in model data) by adding "Material Mapping" setting below attributes (need more testing) * Added "Interior Window Tool" in sidebar, which allows you to enable/disable glass reflection effect on interior shader (by changing UV tile). It's possible, that other shaders with "glass" reflections also support that. * Added support for "anim" flavor in "lamp" shader * Added support for "dif.spec.mult.dif.iamod.dif.add.env" shader --- addon/io_scs_tools/consts.py | 9 + addon/io_scs_tools/exp/pim/material.py | 20 ++ .../internals/shaders/eut2/__init__.py | 4 + .../__init__.py | 219 +++++++++++++++++ .../shaders/eut2/interior/__init__.py | 28 ++- .../eut2/std_node_groups/refl_normal_ng.py | 2 +- .../io_scs_tools/internals/shaders/shader.py | 36 ++- addon/io_scs_tools/operators/mesh.py | 75 ++++++ addon/io_scs_tools/properties/material.py | 223 +++++++++++++++++- addon/io_scs_tools/shader_presets.txt | 127 +++++++++- addon/io_scs_tools/supported_effects.bin | Bin 190943 -> 192085 bytes addon/io_scs_tools/ui/material.py | 99 +++++++- addon/io_scs_tools/ui/tool_shelf.py | 30 +++ addon/io_scs_tools/utils/material.py | 92 +++++++- 14 files changed, 946 insertions(+), 18 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index cf01780..acae933 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -227,6 +227,15 @@ class TrafficLightTypes(Enum): Yellow = 1 Green = 2 +class InteriorWindowTools: + """Constants related to interior window tools + """ + + class GlassReflection(Enum): + """Defined states of glass reflection. + """ + Enable = 0 + Disable = 1 class VertexColorTools: """Constants related to vertex color tools diff --git a/addon/io_scs_tools/exp/pim/material.py b/addon/io_scs_tools/exp/pim/material.py index 6cb9f68..3a7a1f0 100644 --- a/addon/io_scs_tools/exp/pim/material.py +++ b/addon/io_scs_tools/exp/pim/material.py @@ -101,6 +101,26 @@ def __init__(self, index, alias, effect, blend_mat): self.__used_textures_without_uv_count += 1 + if blend_mat and "scs_shader_attributes" in blend_mat and "mappings" in blend_mat["scs_shader_attributes"]: + for tex_entry in blend_mat["scs_shader_attributes"]["mappings"].values(): + self.__used_textures_count += 1 + if "Tag" in tex_entry: + tex_type = tex_entry["Tag"] + mappings = getattr(blend_mat.scs_props, "shader_mapping_" + tex_type, []) + + for uv_map_i, uv_map in enumerate(mappings): + if uv_map.value != "": # filter out none specified mappings + + tex_coord_map[uv_map.tex_coord] = uv_map.value + + elif uv_map.tex_coord != -1: # if tex coord is -1 texture doesn't use uvs + lprint("W Mapping type '%s' on material '%s' is missing UV mapping value, expect problems in game!", + (tex_type, blend_mat.name)) + + else: # if texture doesn't have mappings it means uv is not required for it + + self.__used_textures_without_uv_count += 1 + # create uv layer map with used tex_coord on it (this tex_coords now represents aliases for given uv layers) # It also uses ordered dictionary because order of keys now defines actually physical order for uvs in PIM file self.__uvs_map_by_name = OrderedDict() diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 4717ebb..c3e99f2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -152,6 +152,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_mult_dif_spec_iamod_dif_spec import DifSpecMultDifSpecIamodDifSpec as Shader + elif effect.startswith("dif.spec.mult.dif.iamod.dif.add.env"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_mult_dif_iamod_dif_add_env import DifSpecMultDifIamodDifAddEnv as Shader + elif effect.startswith("dif.spec.mult.dif.spec.add.env"): from io_scs_tools.internals.shaders.eut2.dif_spec_mult_dif_spec.add_env import DifSpecMultDifSpecAddEnv as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py new file mode 100644 index 0000000..277571a --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py @@ -0,0 +1,219 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2019: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.std_node_groups import alpha_remap_ng +from io_scs_tools.internals.shaders.eut2.dif_spec_mult_dif_spec import DifSpecMultDifSpec +from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv +from io_scs_tools.utils import material as _material_utils + +class DifSpecMultDifIamodDifAddEnv(DifSpecMultDifSpec, StdAddEnv): + THIRD_UVMAP_NODE = "ThirdUVMap" + IAMOD_TEX_NODE = "IamodTex" + IAMOD_SCALE_NODE = "IamodScaled" + IAMOD_MULTBASE_COL_MIX_NODE = "IamodMultBaseColorMix" + REMAP_ALPHA_GNODE = "RemapAlphaToWeight" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + DifSpecMultDifSpec.init(node_tree) + + # node creation + remap_alpha_n = node_tree.nodes.new("ShaderNodeGroup") + remap_alpha_n.name = remap_alpha_n.label = DifSpecMultDifSpec.REMAP_ALPHA_GNODE + remap_alpha_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1800) + remap_alpha_n.node_tree = alpha_remap_ng.get_node_group() + remap_alpha_n.inputs['Factor1'].default_value = 1.0 + remap_alpha_n.inputs['Factor2'].default_value = 0.0 + + StdAddEnv.add(node_tree, + DifSpecMultDifSpec.GEOM_NODE, + node_tree.nodes[DifSpecMultDifSpec.SPEC_COL_NODE].outputs['Color'], + node_tree.nodes[DifSpecMultDifSpec.REMAP_ALPHA_GNODE].outputs['Weighted Alpha'], + node_tree.nodes[DifSpecMultDifSpec.LIGHTING_EVAL_NODE].outputs['Normal'], + node_tree.nodes[DifSpecMultDifSpec.COMPOSE_LIGHTING_NODE].inputs['Env Color']) + + base_tex_n = node_tree.nodes[DifSpecMultDifSpec.BASE_TEX_NODE] + mult_tex_n = node_tree.nodes[DifSpecMultDifSpec.MULT_TEX_NODE] + add_env_gn = node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE] + + vcol_group_n = node_tree.nodes[DifSpecMultDifSpec.VCOL_GROUP_NODE] + mult_base_col_mix_n = node_tree.nodes[DifSpecMultDifSpec.MULT_BASE_COL_MIX_NODE] + vcol_scale_n = node_tree.nodes[DifSpecMultDifSpec.VCOLOR_SCALE_NODE] + vcol_mult_n = node_tree.nodes[DifSpecMultDifSpec.VCOLOR_MULT_NODE] + diff_mult_n = node_tree.nodes[DifSpecMultDifSpec.DIFF_MULT_NODE] + spec_mult_n = node_tree.nodes[DifSpecMultDifSpec.SPEC_MULT_NODE] + + # delete existing + node_tree.nodes.remove(node_tree.nodes[DifSpecMultDifIamodDifAddEnv.MULT_BASE_A_MIX_NODE]) + + # move existing + spec_mult_n.location.x += pos_x_shift + spec_mult_n.location.x -= pos_x_shift * 2 + + vcol_scale_n.location.y -= 200 + vcol_mult_n.location.y -= 200 + diff_mult_n.location.y -= 200 + mult_base_col_mix_n.location.y -= 200 + + # node creation + third_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") + third_uvmap_n.name = DifSpecMultDifIamodDifAddEnv.THIRD_UVMAP_NODE + third_uvmap_n.label = DifSpecMultDifIamodDifAddEnv.THIRD_UVMAP_NODE + third_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) + third_uvmap_n.uv_map = _MESH_consts.none_uv + + iamod_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + iamod_tex_n.name = DifSpecMultDifIamodDifAddEnv.IAMOD_TEX_NODE + iamod_tex_n.label = DifSpecMultDifIamodDifAddEnv.IAMOD_TEX_NODE + iamod_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) + iamod_tex_n.width = 140 + + iamod_scale_col_n = node_tree.nodes.new("ShaderNodeMixRGB") + iamod_scale_col_n.name = DifSpecMultDifIamodDifAddEnv.IAMOD_SCALE_NODE + iamod_scale_col_n.label = DifSpecMultDifIamodDifAddEnv.IAMOD_SCALE_NODE + iamod_scale_col_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1000) + iamod_scale_col_n.blend_type = "MIX" + iamod_scale_col_n.inputs['Color2'].default_value = (1,) * 4 + + iamod_multbase_col_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") + iamod_multbase_col_mix_n.name = DifSpecMultDifIamodDifAddEnv.IAMOD_MULTBASE_COL_MIX_NODE + iamod_multbase_col_mix_n.label = DifSpecMultDifIamodDifAddEnv.IAMOD_MULTBASE_COL_MIX_NODE + iamod_multbase_col_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1100) + iamod_multbase_col_mix_n.operation = "MULTIPLY" + + # links creation + node_tree.links.new(add_env_gn.inputs['Strength Multiplier'], mult_tex_n.outputs['Alpha']) + node_tree.links.new(remap_alpha_n.inputs['Alpha'], base_tex_n.outputs['Alpha']) + + node_tree.links.new(iamod_tex_n.inputs['Vector'], third_uvmap_n.outputs['UV']) + + node_tree.links.new(iamod_scale_col_n.inputs['Fac'], vcol_group_n.outputs['Vertex Color Alpha']) + node_tree.links.new(iamod_scale_col_n.inputs['Color1'], iamod_tex_n.outputs['Color']) + + node_tree.links.new(iamod_multbase_col_mix_n.inputs[0], mult_base_col_mix_n.outputs['Vector']) + node_tree.links.new(iamod_multbase_col_mix_n.inputs[1], iamod_scale_col_n.outputs['Color']) + + node_tree.links.new(vcol_mult_n.inputs[1], iamod_multbase_col_mix_n.outputs['Vector']) + + node_tree.links.new(spec_mult_n.inputs[1], remap_alpha_n.outputs['Weighted Alpha']) + + @staticmethod + def set_iamod_texture(node_tree, image): + """Set inverse alpha modulating texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to iamod texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecMultDifIamodDifAddEnv.IAMOD_TEX_NODE].image = image + + @staticmethod + def set_iamod_texture_settings(node_tree, settings): + """Set inverse alpha modulating texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecMultDifIamodDifAddEnv.IAMOD_TEX_NODE], settings) + + @staticmethod + def set_iamod_uv(node_tree, uv_layer): + """Set UV layer to inverse alpha modulating texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for iamod texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecMultDifIamodDifAddEnv.THIRD_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_alpha_test_flavor(node_tree, switch_on): + """Set alpha test flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if alpha test should be switched on or off + :type switch_on: bool + """ + + pass # NOTE: no support for this flavor; overriding with empty function + + @staticmethod + def set_blend_over_flavor(node_tree, switch_on): + """Set blend over flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if blend over should be switched on or off + :type switch_on: bool + """ + + pass # NOTE: no support for this flavor; overriding with empty function + + @staticmethod + def set_blend_add_flavor(node_tree, switch_on): + """Set blend add flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if blend add should be switched on or off + :type switch_on: bool + """ + + pass # NOTE: no support for this flavor; overriding with empty function + + @staticmethod + def set_blend_mult_flavor(node_tree, switch_on): + """Set blend mult flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if blend mult should be switched on or off + :type switch_on: bool + """ + + pass # NOTE: no support for this flavor; overriding with empty function diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py index b6316bd..6e6430d 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py @@ -18,6 +18,7 @@ # Copyright (C) 2015-2024: SCS Software +from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_window from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv from io_scs_tools.utils import material as _material_utils @@ -33,6 +34,7 @@ class InteriorLit(DifSpecAddEnv): NMAP_TEX_NODE = "NmapTex" ENV_SEP_XYZ_NODE = "EnvSepXYZ" ENV_CHECK_XYZ_NODE = "EnvCheckXYZ" + PERTURBATION_UVMAP_NODE = "PerturbationUVMap" @staticmethod @@ -65,9 +67,6 @@ def init(node_tree): # set fresnel type to schlick add_env_group_n.inputs['Fresnel Type'].default_value = 1.0 - # Doesn't work... - # node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE].extension = 'REPEAT' - # delete existing node_tree.nodes.remove(node_tree.nodes[DifSpecAddEnv.OPACITY_NODE]) @@ -76,6 +75,12 @@ def init(node_tree): vcol_mult_n.location.x += pos_x_shift # node creation + # - column -1 - + pert_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + pert_uv_n.name = pert_uv_n.label = InteriorLit.PERTURBATION_UVMAP_NODE + pert_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 950) + pert_uv_n.uv_map = _MESH_consts.none_uv + # - column 0 - env_sep_xyz_n = node_tree.nodes.new("ShaderNodeSeparateXYZ") env_sep_xyz_n.name = env_sep_xyz_n.label = InteriorLit.ENV_SEP_XYZ_NODE @@ -92,6 +97,7 @@ def init(node_tree): glass_col_n.name = glass_col_n.label = InteriorLit.GLASS_COL_NODE glass_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) + """ layer0_tex_n = node_tree.nodes.new("ShaderNodeTexImage") layer0_tex_n.name = layer0_tex_n.label = InteriorLit.LAYER0_TEX_NODE layer0_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 700) @@ -111,6 +117,7 @@ def init(node_tree): nmap_tex_n.name = nmap_tex_n.label = InteriorLit.NMAP_TEX_NODE nmap_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y - 50) nmap_tex_n.width = 140 + """ # - column 2 - glass_col_fac_n = node_tree.nodes.new("ShaderNodeValue") @@ -255,3 +262,18 @@ def set_base_texture_settings(node_tree, settings): """ node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE].extension = 'REPEAT' + + @staticmethod + def set_perturbation_mapping(node_tree, uv_layer): + """Set Perturbation UV layer to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[InteriorLit.PERTURBATION_UVMAP_NODE].uv_map = uv_layer diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py index 5216457..15215bb 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py @@ -61,7 +61,7 @@ def __create_refl_normal_group__(): view_vector_n = refl_normal_g.nodes.new("ShaderNodeVectorMath") view_vector_n.location = (185, 250) view_vector_n.operation = "MULTIPLY" - view_vector_n.inputs[1].default_value = (-1,1,1) + view_vector_n.inputs[1].default_value = (-1,) * 3 view_vector_norm_n = refl_normal_g.nodes.new("ShaderNodeVectorMath") view_vector_norm_n.location = (185 * 2, 250) diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 2c69859..08a7efc 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -110,7 +110,7 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".flipsheet") or ".flipsheet." in effect: flavors["flipsheet"] = True - __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, {}, flavors, recreate) + __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, {}, {}, flavors, recreate) def set_attribute(material, attr_type, attr_value): @@ -123,7 +123,7 @@ def set_attribute(material, attr_type, attr_value): :param attr_value: value which should be set to attribute in shader :type attr_value: object """ - __setup_nodes__(material, material.scs_props.mat_effect_name, {attr_type: attr_value}, {}, {}, {}, {}, False) + __setup_nodes__(material, material.scs_props.mat_effect_name, {attr_type: attr_value}, {}, {}, {}, {}, {}, False) def set_texture(material, tex_type, image): @@ -136,7 +136,7 @@ def set_texture(material, tex_type, image): :param image: blender texture image object :type image: bpy.types.Image """ - __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {tex_type: image}, {}, {}, {}, False) + __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {tex_type: image}, {}, {}, {}, {}, False) def set_texture_settings(material, tex_type, settings): @@ -149,7 +149,7 @@ def set_texture_settings(material, tex_type, settings): :param settings: binary string of TOBJ settings gotten from tobj import :type settings: str """ - __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {}, {tex_type: settings}, {}, {}, False) + __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {}, {tex_type: settings}, {}, {}, {}, False) def set_uv(material, tex_type, uv_layer, tex_coord): @@ -188,10 +188,24 @@ def set_uv(material, tex_type, uv_layer, tex_coord): is_valid_input = False if is_valid_input: - __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {}, {}, {tex_type: uv_layer}, {}, False) + __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {}, {}, {tex_type: uv_layer}, {}, {}, False) +def set_mapping(material, mapping_type, uv_layer, tex_coord): + """Set UV layer to given texture type in material. -def __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, uvs_dict, flavors_dict, recreate): + :param material: blender material + :type material: bpy.types.Material + :param mapping_type: type of SCS mapping to set + :type mapping_type: str + :param uv_layer: uv layer name which should be assigned to this texture + :type uv_layer: str + :param tex_coord: index of tex_coord this mapping uses + :type tex_coord: int + """ + + __setup_nodes__(material, material.scs_props.mat_effect_name, {}, {}, {}, {}, {mapping_type: uv_layer}, {}, False) + +def __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, uvs_dict, mapping_dict, flavors_dict, recreate): """Wrapping setup of nodes for given material in central function. It properly setup nodes for 3D view visualization in real time. @@ -207,6 +221,8 @@ def __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, uv :type tex_settings_dict: dict :param uvs_dict: shader uv layers which should be set on given material; entry: (texture_type: string of uv layer) :type uvs_dict: dict + :param mapping_dict: shader uv layers which should be set on given material; entry: (texture_type: string of uv layer) + :type mapping_dict: dict :param flavors_dict: shader flavors which should be set on given material; entry: (flavor_type: flavor_data) :type flavors_dict: dict :param recreate: flag indicating if shader nodes should be recreated. Should be triggered if effect name changes. @@ -265,6 +281,14 @@ def __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, uv else: lprint("D Unsupported set_uv with type %r called on shader %r", (tex_type, shader_module.get_name())) + # set mappings + for mapping_type in mapping_dict: + shader_set_mapping = getattr(shader_module, "set_" + mapping_type + "_mapping", None) + if shader_set_mapping: + shader_set_mapping(node_tree, mapping_dict[mapping_type]) + else: + lprint("D Unsupported set_mapping with type %r called on shader %r", (mapping_type, shader_module.get_name())) + # finalize shader on recreate (set material blending method, backface culling etc.) if recreate: shader_module.finalize(node_tree, material) diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index b6ec3cc..62b484f 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -26,6 +26,7 @@ from bpy.props import StringProperty, FloatProperty from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.consts import LampTools as _LT_consts +from io_scs_tools.consts import InteriorWindowTools as _IWT_consts from io_scs_tools.consts import VertexColorTools as _VCT_consts from io_scs_tools.utils import mesh as _mesh_utils from io_scs_tools.utils import view3d as _view3d_utils @@ -136,6 +137,78 @@ def execute(self, context): self.report({"INFO"}, "Lamp mask UV tool set %i faces to '%s'" % (polys_changed, changed_type)) return {'FINISHED'} +class InteriorWindowTool: + """ + Wrapper class for better navigation in file + """ + + class SCS_TOOLS_OT_SetGlassReflectionUV(bpy.types.Operator): + bl_label = "Set UV to glass" + bl_idname = "mesh.scs_tools_set_glassreflection_uv" + bl_description = "Sets offset for base interior UV according to given glass reflection state." + + glass_state: StringProperty( + description="", + default="", + options={'HIDDEN'}, + ) + + @classmethod + def poll(cls, context): + return context.object is not None and context.object.mode == "EDIT" + + def execute(self, context): + mesh = context.object.data + bm = bmesh.from_edit_mesh(mesh) # use bmesh module because we are working in edit mode + + # decide which offset to use depending on glass reflection state + offset_x = 0 + offset_y = 0 + if _IWT_consts.GlassReflection.Enable.name == self.glass_state: # glass reflection state checking + offset_y = 0 + elif _IWT_consts.GlassReflection.Disable.name == self.glass_state: + offset_y = 1 + else: + self.report({"ERROR"}, "Unsupported window glass state!") + return {"FINISHED"} + + polys_changed = 0 + for face in bm.faces: + + if face.select and len(context.object.material_slots) > 0: + material = context.object.material_slots[face.material_index].material + if material and len(material.scs_props.shader_texture_mask_uv) > 0: + + # use first mapping from mask texture + uv_lay_name = material.scs_props.shader_texture_mask_uv[0].value + + # if mask uv layer specified by current material doesn't exists + # move to next face + if uv_lay_name not in mesh.uv_layers: + self.report({"ERROR"}, "UV layer: '%s' not found in this object!" % uv_lay_name) + break + + uv_lay = bm.loops.layers.uv[uv_lay_name] + for loop in face.loops: + + uv = loop[uv_lay].uv + uv = (offset_x + (uv[0] - int(uv[0])), offset_y + (uv[1] - int(uv[1]))) + loop[uv_lay].uv = uv + + polys_changed += 1 + + # write data back if modified + if polys_changed > 0: + bmesh.update_edit_mesh(mesh) + + if self.glass_state != "": + changed_type = self.glass_state + else: + changed_type = "INVALID" + + self.report({"INFO"}, "Interior Window Tool set %i faces to '%s'" % (polys_changed, changed_type)) + return {'FINISHED'} + class VertexColorTools: """ @@ -658,6 +731,8 @@ def execute(self, context): classes = ( LampTool.SCS_TOOLS_OT_SetLampmaskUV, + InteriorWindowTool.SCS_TOOLS_OT_SetGlassReflectionUV, + VertexColorTools.SCS_TOOLS_OT_AddVertexColorsToActive, VertexColorTools.SCS_TOOLS_OT_AddVertexColorsToAll, VertexColorTools.SCS_TOOLS_OT_PrintVertexColorsStats, diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 4fed34c..dec2bcb 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -238,6 +238,42 @@ def update_value(self, context): tex_coord: IntProperty(default=-1) # used upon export for mapping uv to tex_coord field texture_type: StringProperty() # used in update function to be able to identify which texture should be updated + class UVAddMappingItem(bpy.types.PropertyGroup): + """Class for saving uv map reference for SCS mapping + """ + + def update_value(self, context): + __update_look__(self, context) + + material = _material_utils.get_material_from_context(context) + + if material: + _shader.set_mapping(material, self.mapping_type, self.value, self.tex_coord) + + # synchronize all scs mappings that uses the same tex_coord field in current material + if "scs_shader_attributes" in material and "mappings" in material["scs_shader_attributes"]: + for mapping_entry in material["scs_shader_attributes"]["mappings"].values(): + if "Tag" in mapping_entry: + curr_map_type = mapping_entry["Tag"] + if curr_map_type != self.mapping_type: # if different mapping from current + uv_mappings = getattr(material.scs_props, "shader_mapping_" + curr_map_type, []) + if uv_mappings and len(uv_mappings) > 0: + for uv_mapping in uv_mappings: + # if tex_coord props are the same and uv mapping differs then set current uv mapping value to it + if self.tex_coord != -1 and uv_mapping.tex_coord == self.tex_coord and uv_mapping.value != self.value: + uv_mapping.value = self.value + + value: StringProperty( + name="Mapping UV Set", + description="Mapping of UV Set to current mapping type", + default="", + options={'HIDDEN'}, + update=update_value + ) + + tex_coord: IntProperty(default=-1) # used upon export for mapping uv to tex_coord field + mapping_type: StringProperty() # used in update function to be able to identify which mapping should be updated + class TexCoordItem(bpy.types.PropertyGroup): """Property group holding data how to map uv maps to exported PIM file. It should be used only on material with imported shader. """ @@ -271,10 +307,9 @@ def get_texture_types(): :return: SCS Shader Texture Types :rtype: dict """ - return {'base', 'reflection', 'over', 'oclu', 'mask', 'mult', 'iamod', 'lightmap', 'paintjob', - 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'layer0', 'layer1', - 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b', - 'nmap_over'} + return {'base', 'reflection', 'over', 'oclu', 'mask' , 'mask_1' , 'mask_2', 'mask_3', 'mult', 'iamod', 'lightmap', 'paintjob', + 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'nmap_over' , 'layer0', 'layer1', + 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b'} def get_id(self): """Gets unique ID for material within current Blend file. If ID does not exists yet it's calculated. @@ -397,6 +432,24 @@ def update_shader_texture_mask(self, context): def update_shader_texture_mask_settings(self, context): __update_shader_texture_tobj_file__(self, context, "mask") + def update_shader_texture_mask_1(self, context): + __update_shader_texture__(self, context, "mask_1") + + def update_shader_texture_mask_1_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "mask_1") + + def update_shader_texture_mask_2(self, context): + __update_shader_texture__(self, context, "mask_2") + + def update_shader_texture_mask_2_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "mask_2") + + def update_shader_texture_mask_3(self, context): + __update_shader_texture__(self, context, "mask_3") + + def update_shader_texture_mask_3_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "mask_3") + def update_shader_texture_mult(self, context): __update_shader_texture__(self, context, "mult") @@ -1183,6 +1236,159 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): options={'HIDDEN'}, ) + # TEXTURE: MASK_1 + shader_texture_mask_1: StringProperty( + name="Texture Mask", + description="Texture Mask for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_mask_1, + ) + shader_texture_mask_1_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_mask_1_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_mask_1_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_mask_1_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_mask_1_settings + ) + shader_texture_mask_1_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_mask_1_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_mask_1_uv: CollectionProperty( + name="Texture Mask UV Sets", + description="Texture Mask UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: MASK_2 + shader_texture_mask_2: StringProperty( + name="Texture Mask", + description="Texture Mask for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_mask_2, + ) + shader_texture_mask_2_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_mask_2_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_mask_2_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_mask_2_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_mask_2_settings + ) + shader_texture_mask_2_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_mask_2_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_mask_2_uv: CollectionProperty( + name="Texture Mask UV Sets", + description="Texture Mask UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: MASK_3 + shader_texture_mask_3: StringProperty( + name="Texture Mask", + description="Texture Mask for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_mask_3, + ) + shader_texture_mask_3_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_mask_3_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_mask_3_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_mask_3_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_mask_3_settings + ) + shader_texture_mask_3_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_mask_3_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_mask_3_uv: CollectionProperty( + name="Texture Mask UV Sets", + description="Texture Mask UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + # TEXTURE: MULT shader_texture_mult: StringProperty( name="Texture Mult", @@ -1846,6 +2052,14 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): options={'HIDDEN'}, ) + # MAPPING: PERTURBATION + shader_mapping_perturbation: CollectionProperty( + name="Perturbation UV Sets", + description="Perturbation UV sets for active Material", + type=UVAddMappingItem, + options={'HIDDEN'}, + ) + # # Following String property is fed from MATERIAL SUBSTANCE data list, which is usually loaded from # # "//base/material/material.db" file and stored at "scs_inventories.matsubs". substance: StringProperty( @@ -1861,6 +2075,7 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): MaterialSCSTools.TexCoordItem, MaterialSCSTools.AuxiliaryItem, MaterialSCSTools.UVMappingItem, + MaterialSCSTools.UVAddMappingItem, MaterialSCSTools ) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index f4950b3..b134225 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -656,6 +656,67 @@ Shader { TexCoord: ( -1 ) } } +Shader { + PresetName: "dif.spec.mult.dif.iamod.dif.add.env" + Effect: "eut2.dif.spec.mult.dif.iamod.dif.add.env" + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 60.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.8000000119 0.8000000119 0.8000000119 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mult" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_iamod" + Value: "" + TexCoord: ( 2 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } +} Shader { PresetName: "dif.spec.mult.dif.spec.iamod.dif.spec" Effect: "eut2.dif.spec.mult.dif.spec.iamod.dif.spec" @@ -1321,7 +1382,7 @@ Shader { Shader { PresetName: "lamp" Effect: "eut2.lamp" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "ENVMAP" "SHADOW" ) + Flavors: ( "ANIM" "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "ENVMAP" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1911,6 +1972,10 @@ Shader { Value: "/material/environment/building_reflection/building_ref" TexCoord: ( -1 ) } + Mapping { + Tag: "perturbation" + TexCoord: ( 1 ) + } } Shader { PresetName: "interior.curtain.lit" @@ -2000,6 +2065,11 @@ Shader { Value: "/material/environment/building_reflection/building_ref" TexCoord: ( -1 ) } + Mapping { + Tag: "perturbation" + TexCoord: ( 1 ) + } +} } Flavor { Type: "AIRBRUSH" @@ -2118,6 +2188,25 @@ Flavor { TexCoord: ( -1 ) } } +Flavor { + Type: "ENVMAP_NOFAC" + Name: "add.env" + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/vehicle_reflection" + TexCoord: ( -1 ) + } +} Flavor { Type: "FADESHEET" Name: "fadesheet" @@ -2164,6 +2253,42 @@ Flavor { Hide: "True" } } +Flavor { + Type: "ANIM" + Name: "anim" + Attribute { + Format: FLOAT4 + Tag: "aux[0]" + FriendlyTag: "Sheet frame size (R, G)" + Value: ( 1.0 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[1]" + FriendlyTag: "Sheet frame size (B, A)" + Value: ( 1.0 1.0 1.0 1.0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask_1" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask_2" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask_3" + Value: "" + TexCoord: ( 1 ) + } +} Flavor { Type: "FLAT" Name: "flat" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index f6a56b1c0cf59e5c4de0b3af50086133ce96a066..f6cc9a7f7dde2cd277916909520484d0126fc1a0 100644 GIT binary patch literal 192085 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 UH%SQ7a7aCrcp#!6*dL$$A2WKHHUIzs literal 190943 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μLgRaCC&`OtbSh3=FPSv5~SU6x%02Lu5 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 0: + + layout_box_col = item_space.column(align=True) + + for mapping in uv_mappings: + + item_space_row = layout_box_col.row(align=True) + + if mapping.value and mapping.value != "" and mapping.value in bpy.context.active_object.data.uv_layers: + icon = 'GROUP_UVS' + else: + icon = 'ERROR' + + item_space_row.prop_search( + data=mapping, + property="value", + search_data=bpy.context.active_object.data, + search_property='uv_layers', + text="", + icon=icon, + ) + + else: + layout_box_col = item_space.column(align=True) + mapping_row.label(text="Unsupported Shader Mapping Type!", icon='ERROR') + + def draw(self, context): + mat = context.active_object.active_material + + # DESYNCED LOOK! + if self.draw_desynced_look(self.layout, context): + return + + col = self.layout.column(align=True) + for mapping_key in sorted(self.mappings_data.keys()): + self.draw_mapping_item(col, mat, _UI_SPLIT_PERC, self.mappings_data[mapping_key]) + + class SCS_TOOLS_PT_LooksOnMaterial(_shared.HeaderIconPanel, _MaterialPanelBlDefs, Panel): """Draws SCS Looks panel on object tab.""" @@ -798,6 +892,7 @@ def draw(self, context): classes = ( SCS_TOOLS_PT_Material, SCS_TOOLS_PT_MaterialAttributes, + SCS_TOOLS_PT_MaterialMappings, SCS_TOOLS_PT_MaterialTextures, SCS_TOOLS_PT_LooksOnMaterial, SCS_TOOLS_UL_MaterialCustomMappingSlot, diff --git a/addon/io_scs_tools/ui/tool_shelf.py b/addon/io_scs_tools/ui/tool_shelf.py index 754fa02..746aed9 100644 --- a/addon/io_scs_tools/ui/tool_shelf.py +++ b/addon/io_scs_tools/ui/tool_shelf.py @@ -22,6 +22,7 @@ from bpy.types import Panel from io_scs_tools.consts import Icons as _ICONS_consts from io_scs_tools.consts import LampTools as _LT_consts +from io_scs_tools.consts import InteriorWindowTools as _IWT_consts from io_scs_tools.consts import Operators as _OP_consts from io_scs_tools.consts import VertexColorTools as _VCT_consts from io_scs_tools.internals.icons import get_icon @@ -502,6 +503,33 @@ def draw(self, context): props.traffic_light_color = _LT_consts.TrafficLightTypes.Green.name props.vehicle_side = props.aux_color = "" +class SCS_TOOLS_PT_InteriorWindowTool(_ToolShelfBlDefs, Panel): + """ + Creates Interior Window Tool panel for SCS Tools tab. + """ + bl_label = "Interior Window Tool" + bl_context = "mesh_edit" + + @classmethod + def poll(cls, context): + return context.mode == "EDIT_MESH" + + def draw(self, context): + if not self.poll(context): + self.layout.label(text="Not in 'Edit Mode'!", icon="INFO") + return + + layout = self.layout + + body_col = layout.column(align=True) + body_row = body_col.row(align=True) + body_row.label(text="Glass Reflection", icon='MOD_LATTICE') + + body_row = body_col.row(align=True) + props = body_row.operator("mesh.scs_tools_set_glassreflection_uv", text="Enable") + props.glass_state = _IWT_consts.GlassReflection.Enable.name + props = body_row.operator("mesh.scs_tools_set_glassreflection_uv", text="Disable") + props.glass_state = _IWT_consts.GlassReflection.Disable.name class SCS_TOOLS_PT_VColoring(_ToolShelfBlDefs, Panel): bl_label = "VColoring" @@ -590,6 +618,7 @@ def draw(self, context): SCS_TOOLS_PT_DisplayMethods, SCS_TOOLS_PT_LampSwitcher, SCS_TOOLS_PT_LampTool, + SCS_TOOLS_PT_InteriorWindowTool, SCS_TOOLS_PT_VColoring, SCS_TOOLS_PT_VertexColorStatsTool, SCS_TOOLS_PT_VertexColorWrapTool, @@ -607,6 +636,7 @@ def register(): SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - Display Methods", SCS_TOOLS_PT_DisplayMethods.__name__) SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - Lamp Switcher", SCS_TOOLS_PT_LampSwitcher.__name__) SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - Lamp Tool", SCS_TOOLS_PT_LampTool.__name__) + SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - Interior Window Tool", SCS_TOOLS_PT_InteriorWindowTool.__name__) SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - VColoring", SCS_TOOLS_PT_VColoring.__name__) SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - VColor Stats", SCS_TOOLS_PT_VertexColorStatsTool.__name__) SCS_TOOLS_MT_MainMenu.append_sidebar_entry("Sidebar - VColor Wrap", SCS_TOOLS_PT_VertexColorWrapTool.__name__) diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index 34be39c..213a213 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -436,10 +436,13 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac attributes = {} textures = {} + mappings = {} attribute_i = 0 texture_i = 0 + mapping_i = 0 used_attribute_types = {} # attribute types used by current shader used_texture_types = {} # texture types used by current shader and should be overlooked during clearing of texture slots + used_mapping_types = {} # UV mapping types used by current shader (created especially for shaders that use UV without textures like 'interior' shader) for item in section.sections: if item.type == "Attribute": @@ -475,6 +478,20 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac textures[str(texture_i)] = texture_data texture_i += 1 + elif item.type == "Mapping": + uvmapping_data = {} + for prop in item.props: + # print(' prop: "%s"' % str(prop)) + uvmapping_data[prop[0]] = prop[1] + + # APPLY SECTION MAPPING VALUES + mapping_type = uvmapping_data['Tag'] + + used_mapping_types[mapping_type] = uvmapping_data + + mappings[str(mapping_i)] = uvmapping_data + mapping_i += 1 + scs_props_keys = set(material.scs_props.keys()) # if overriding back data also make sure to clear attribute values for current shader # to prevent storing of unused values from blend data block @@ -492,6 +509,11 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac if used_tex_type in key[14:]: is_key_used = True + if key.startswith("shader_mapping"): + for used_map_type in used_mapping_types: + if used_map_type in key[14:]: + is_key_used = True + # delete only unused shader keys everything else should stay in the place # as those keys might be used in some other way if not is_key_used and key.startswith("shader_"): @@ -677,12 +699,72 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac settings, map_type = _tobj_imp.get_settings_and_type(tobj_abs_path) created_tex_settings[tex_type] = settings + # collect old uv mappings per tex_coord values and delete them from material (they will be set back later) + old_uv_mappings = {} + for mapping_type in used_mapping_types: + + uv_mappings = getattr(material.scs_props, "shader_mapping_" + mapping_type, []) + if override_back_data: + while len(uv_mappings) > 0: + + # save only set uv mapping tex_cord:value pairs + if uv_mappings[0].value != "": + old_uv_mappings[uv_mappings[0].tex_coord] = uv_mappings[0].value + + uv_mappings.remove(0) + + # apply used mappings + created_uv_mappings = [] + for mapping_type in used_mapping_types.keys(): + + # skip unknown mapping type + if mapping_type not in ("perturbation"): + lprint("D Trying to apply unknown mapping type to SCS material: %r", (mapping_type,)) + continue + + uvmapping_data = used_mapping_types[mapping_type] + + uv_mappings = getattr(material.scs_props, "shader_mapping_" + mapping_type) + + # if there is an info about mapping in shader use it (in case of imported material this condition will fall!) + if "TexCoord" in uvmapping_data: + + for tex_coord_i, tex_coord in enumerate(uvmapping_data['TexCoord']): + tex_coord = int(tex_coord) + + if tex_coord != -1: + mapping = uv_mappings.add() + mapping['mapping_type'] = mapping_type + mapping['tex_coord'] = tex_coord + + # apply uv mappings either from imported data or from old mappings of previous shader + if "scs_tex_aliases" in material: # scs_tex_aliases are present only on import + + # if mesh is corrupted then tex aliases won't be filled in properly in material from PIM importer, + # so report error and skip creation of texture mapping for current tex_coord. + if str(tex_coord) not in material["scs_tex_aliases"]: + lprint("E Material %r is missing mapping coordinate aliases, some UV mappings in Material will remain empty!", + (material.name,)) + continue + + mapping['value'] = material["scs_tex_aliases"][str(tex_coord)] + created_uv_mappings.append((mapping_type, mapping.value, tex_coord)) + + elif tex_coord in old_uv_mappings: + + mapping['value'] = old_uv_mappings[tex_coord] + created_uv_mappings.append((mapping_type, mapping.value, tex_coord)) + + else: + lprint("D Trying to apply negative tex_coord in SCS Material Mappings: %r", (mapping_type,)) + # override shader data for identifying used attributes and textures in UI if override_back_data: shader_data = {'effect': preset_effect, 'attributes': attributes, - 'textures': textures} + 'textures': textures, + 'mappings' : mappings} material["scs_shader_attributes"] = shader_data # setup nodes for 3D view visualization @@ -696,6 +778,14 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac # data[2] = tex coord value _shader.set_uv(material, mapping_data[0], mapping_data[1], mapping_data[2]) + for mapping_data in created_uv_mappings: + + # data[0] = mapping type; + # data[1] = uv mapping value; + # data[2] = tex coord value + + _shader.set_mapping(material, mapping_data[0], mapping_data[1], mapping_data[2]) + def reload_tobj_settings(material, tex_type): """Relaods TOBJ settings on given texture type of material. From a2266f9d7b65e9012bfe4743b1995c872fb97f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 4 Dec 2024 00:49:27 +0100 Subject: [PATCH 12/56] aux5 fix, "baked" shader - Fixed "aux[5]" in "eut2.building.asafew.a.lvcol.day" shader. - Added support for "baked" shader (with flavors). - Changed "Specular Color" input in "add_env" node from required to optional (because "baked" shader don't use it) --- addon/io_scs_tools/__init__.py | 2 +- .../internals/shaders/eut2/__init__.py | 14 + .../internals/shaders/eut2/baked/__init__.py | 268 ++++++++++++++++++ .../internals/shaders/eut2/baked/add_env.py | 128 +++++++++ .../internals/shaders/eut2/baked/spec.py | 114 ++++++++ .../std_node_groups/spec_texture_calc_ng.py | 110 +++++++ .../shaders/eut2/std_passes/add_env.py | 9 +- addon/io_scs_tools/shader_presets.txt | 103 +++++-- 8 files changed, 722 insertions(+), 26 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/baked/spec.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 984e7bf..c9c82ad 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 1), + "version": (2, 4, "aeadde03", 2), "blender": (3, 2, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index c3e99f2..eff9207 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -244,6 +244,20 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif import Dif as Shader + elif effect.startswith("baked.spec"): + + if ".add.env" in effect: + + from io_scs_tools.internals.shaders.eut2.baked.add_env import BakedSpecAddEnv as Shader + + else: + + from io_scs_tools.internals.shaders.eut2.baked.spec import BakedSpec as Shader + + elif effect.startswith("baked"): + + from io_scs_tools.internals.shaders.eut2.baked import Baked as Shader + else: return None diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py new file mode 100644 index 0000000..e4664a5 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py @@ -0,0 +1,268 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2024: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.eut2.std_node_groups import compose_lighting_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import lighting_evaluator_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import vcolor_input_ng +from io_scs_tools.internals.shaders.flavors import nmap +from io_scs_tools.utils import convert as _convert_utils +from io_scs_tools.utils import material as _material_utils + + +class Baked(BaseShader): + GEOM_NODE = "Geometry" + UVMAP_NODE = "FirstUVs" + VCOL_GROUP_NODE = "VColorGroup" + BASE_TEX_NODE = "BaseTex" + VCOLOR_MULT_NODE = "VertexColorMultiplier" + VCOLOR_SCALE_NODE = "VertexColorScale" + LIGHTING_EVAL_NODE = "LightingEvaluator" + COMPOSE_LIGHTING_NODE = "ComposeLighting" + OUTPUT_NODE = "Output" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # node creation + vcol_group_n = node_tree.nodes.new("ShaderNodeGroup") + vcol_group_n.name = Baked.VCOL_GROUP_NODE + vcol_group_n.label = Baked.VCOL_GROUP_NODE + vcol_group_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1650) + vcol_group_n.node_tree = vcolor_input_ng.get_node_group() + + uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") + uvmap_n.name = Baked.UVMAP_NODE + uvmap_n.label = Baked.UVMAP_NODE + uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1500) + uvmap_n.uv_map = _MESH_consts.none_uv + + geometry_n = node_tree.nodes.new("ShaderNodeNewGeometry") + geometry_n.name = Baked.GEOM_NODE + geometry_n.label = Baked.GEOM_NODE + geometry_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1350) + + vcol_scale_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_scale_n.name = Baked.VCOLOR_SCALE_NODE + vcol_scale_n.label = Baked.VCOLOR_SCALE_NODE + vcol_scale_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1550) + vcol_scale_n.operation = "MULTIPLY" + vcol_scale_n.inputs[1].default_value = (2,) * 3 + + base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + base_tex_n.name = Baked.BASE_TEX_NODE + base_tex_n.label = Baked.BASE_TEX_NODE + base_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1500) + base_tex_n.width = 140 + + vcol_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_mult_n.name = Baked.VCOLOR_MULT_NODE + vcol_mult_n.label = Baked.VCOLOR_MULT_NODE + vcol_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) + vcol_mult_n.operation = "MULTIPLY" + + lighting_eval_n = node_tree.nodes.new("ShaderNodeGroup") + lighting_eval_n.name = Baked.LIGHTING_EVAL_NODE + lighting_eval_n.label = Baked.LIGHTING_EVAL_NODE + lighting_eval_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 1800) + lighting_eval_n.node_tree = lighting_evaluator_ng.get_node_group() + + compose_lighting_n = node_tree.nodes.new("ShaderNodeGroup") + compose_lighting_n.name = Baked.COMPOSE_LIGHTING_NODE + compose_lighting_n.label = Baked.COMPOSE_LIGHTING_NODE + compose_lighting_n.location = (start_pos_x + pos_x_shift * 8, start_pos_y + 2000) + compose_lighting_n.node_tree = compose_lighting_ng.get_node_group() + compose_lighting_n.inputs["Alpha"].default_value = 1.0 + + output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") + output_n.name = Baked.OUTPUT_NODE + output_n.label = Baked.OUTPUT_NODE + output_n.location = (start_pos_x + pos_x_shift * 9, start_pos_y + 1800) + + # links creation + node_tree.links.new(base_tex_n.inputs['Vector'], uvmap_n.outputs['UV']) + node_tree.links.new(vcol_scale_n.inputs[0], vcol_group_n.outputs['Vertex Color']) + + node_tree.links.new(vcol_mult_n.inputs[0], vcol_scale_n.outputs[0]) + node_tree.links.new(vcol_mult_n.inputs[1], base_tex_n.outputs['Color']) + + node_tree.links.new(lighting_eval_n.inputs['Normal Vector'], geometry_n.outputs['Normal']) + node_tree.links.new(lighting_eval_n.inputs['Incoming Vector'], geometry_n.outputs['Incoming']) + + node_tree.links.new(compose_lighting_n.inputs['Diffuse Color'], vcol_mult_n.outputs[0]) + node_tree.links.new(compose_lighting_n.inputs['Specular Lighting'], lighting_eval_n.outputs['Specular Lighting']) + node_tree.links.new(compose_lighting_n.inputs['Diffuse Lighting'], lighting_eval_n.outputs['Diffuse Lighting']) + + node_tree.links.new(output_n.inputs['Surface'], compose_lighting_n.outputs['Shader']) + + @staticmethod + def finalize(node_tree, material): + """Finalize node tree and material settings. Should be called as last. + + :param node_tree: node tree on which this shader should be finalized + :type node_tree: bpy.types.NodeTree + :param material: material used for this shader + :type material: bpy.types.Material + """ + + material.use_backface_culling = True + material.blend_method = "OPAQUE" + + if material.blend_method == "OPAQUE" and node_tree.nodes[Baked.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + node_tree.links.remove(node_tree.nodes[Baked.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) + + @staticmethod + def set_shadow_bias(node_tree, value): + """Set shadow bias attirbute for this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param value: shador bias factor + :type value: float + """ + + pass # NOTE: shadow bias won't be visualized as game uses it's own implementation + + @staticmethod + def set_queue_bias(node_tree, value): + """Set queue bias attirbute for this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param value: queue bias index + :type value: int + """ + + pass # NOTE: shadow bias won't be visualized as game uses it's own implementation + + @staticmethod + def set_base_texture(node_tree, image): + """Set base texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to base texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Baked.BASE_TEX_NODE].image = image + + @staticmethod + def set_base_texture_settings(node_tree, settings): + """Set base texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Baked.BASE_TEX_NODE], settings) + + @staticmethod + def set_base_uv(node_tree, uv_layer): + """Set UV layer to base texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[Baked.UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[Baked.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[Baked.GEOM_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal']) + else: + nmap.delete(node_tree) + + @staticmethod + def set_nmap_texture(node_tree, image): + """Set normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Image + """ + + nmap.set_texture(node_tree, image) + + @staticmethod + def set_nmap_texture_settings(node_tree, settings): + """Set normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + nmap.set_texture_settings(node_tree, settings) + + @staticmethod + def set_nmap_uv(node_tree, uv_layer): + """Set UV layer to normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + nmap.set_uv(node_tree, uv_layer) diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py new file mode 100644 index 0000000..1bba383 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py @@ -0,0 +1,128 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2024: SCS Software + +from io_scs_tools.internals.shaders.eut2.baked.spec import BakedSpec +from io_scs_tools.internals.shaders.eut2.std_node_groups import linear_to_srgb_ng +from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv +from io_scs_tools.utils import material as _material_utils + +class BakedSpecAddEnv(BakedSpec, StdAddEnv): + MASK_TEX_NODE = "MaskTex" + MASK_LIN_TO_SRGB_NODE = "MaskLinearToSRGB" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + BakedSpec.init(node_tree) + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + BakedSpec.init(node_tree) + + + StdAddEnv.add(node_tree, + BakedSpec.GEOM_NODE, + None, + None, + node_tree.nodes[BakedSpec.LIGHTING_EVAL_NODE].outputs['Normal'], + node_tree.nodes[BakedSpec.COMPOSE_LIGHTING_NODE].inputs['Env Color']) + + uvmap_n = node_tree.nodes[BakedSpec.UVMAP_NODE] + + add_env_gn = node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE] + + # overrides + # set strength multiplier to 2 for better visualization in Blender + node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE].inputs['Strength Multiplier'].default_value = 2.0 + + + # node creation + mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_tex_n.name = mask_tex_n.label = BakedSpecAddEnv.MASK_TEX_NODE + mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2200) + mask_tex_n.width = 140 + + mask_lin_to_srgb_n = node_tree.nodes.new("ShaderNodeGroup") + mask_lin_to_srgb_n.name = mask_lin_to_srgb_n.label = BakedSpecAddEnv.MASK_LIN_TO_SRGB_NODE + mask_lin_to_srgb_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 2100) + mask_lin_to_srgb_n.node_tree = linear_to_srgb_ng.get_node_group() + + + # links creation + node_tree.links.new(uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) + + node_tree.links.new(mask_tex_n.outputs['Color'], mask_lin_to_srgb_n.inputs['Value']) + + node_tree.links.new(mask_lin_to_srgb_n.outputs['Value'], add_env_gn.inputs['Env Factor Color']) + + + @staticmethod + def set_mask_texture(node_tree, image): + """Set mask texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to mask texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE].image = image + + @staticmethod + def set_mask_texture_settings(node_tree, settings): + """Set mask texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE], settings) + + # due the fact mask colorspace is linear, we have to manually switch to sRGB and then convert values by node, for effect to work correctly + node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE].image.colorspace_settings.name = 'sRGB' + + @staticmethod + def set_mask_uv(node_tree, uv_layer): + """Set UV layer to mask texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py b/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py new file mode 100644 index 0000000..528e836 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py @@ -0,0 +1,114 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2015-2024: SCS Software + +from io_scs_tools.internals.shaders.eut2.baked import Baked +from io_scs_tools.internals.shaders.eut2.std_node_groups import spec_texture_calc_ng +from io_scs_tools.utils import material as _material_utils + +class BakedSpec(Baked): + OVER_TEX_NODE = "OverTex" + SPEC_TEX_CALC = "SpecTexCalc" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + Baked.init(node_tree) + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + Baked.init(node_tree) + + uvmap_n = node_tree.nodes[Baked.UVMAP_NODE] + lighting_eval_n = node_tree.nodes[Baked.LIGHTING_EVAL_NODE] + compose_lighting_n = node_tree.nodes[Baked.COMPOSE_LIGHTING_NODE] + + # node creation + over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_tex_n.name = BakedSpec.OVER_TEX_NODE + over_tex_n.label = BakedSpec.OVER_TEX_NODE + over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1900) + over_tex_n.width = 140 + + over_tex_calc_ng = node_tree.nodes.new("ShaderNodeGroup") + over_tex_calc_ng.name = BakedSpec.SPEC_TEX_CALC + over_tex_calc_ng.label = BakedSpec.SPEC_TEX_CALC + over_tex_calc_ng.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1900) + over_tex_calc_ng.node_tree = spec_texture_calc_ng.get_node_group() + + + # links creation + node_tree.links.new(uvmap_n.outputs['UV'], over_tex_n.inputs['Vector']) + + node_tree.links.new(over_tex_n.outputs['Color'], over_tex_calc_ng.inputs['Color']) + + node_tree.links.new(over_tex_calc_ng.outputs['Shininess'], lighting_eval_n.inputs['Shininess']) + node_tree.links.new(over_tex_calc_ng.outputs['Specular'], compose_lighting_n.inputs['Specular Color']) + + + @staticmethod + def set_over_texture(node_tree, image): + """Set over texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to over texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[BakedSpec.OVER_TEX_NODE].image = image + + @staticmethod + def set_over_texture_settings(node_tree, settings): + """Set over texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[BakedSpec.OVER_TEX_NODE], settings) + + @staticmethod + def set_over_uv(node_tree, uv_layer): + """Set UV layer to over texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py new file mode 100644 index 0000000..0869ec2 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py @@ -0,0 +1,110 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2024: SCS Software + +import bpy +from io_scs_tools.consts import Material as _MAT_consts + +SPEC_TEXTURE_CALC_G = _MAT_consts.node_group_prefix + "SpecularTextureCalc" + +_COLOR_TO_RGB_NODE = "ColorToRGB" +_SHININESS_MULT = "ShininessMult" + + +def get_node_group(): + """Gets node group. + + :return: node group + :rtype: bpy.types.NodeGroup + """ + + if __group_needs_recreation__(): + __create_node_group__() + + return bpy.data.node_groups[SPEC_TEXTURE_CALC_G] + +def __group_needs_recreation__(): + """Tells if group needs recreation. + + :return: True group isn't up to date and has to be (re)created; False if group doesn't need to be (re)created + :rtype: bool + """ + # current checks: + # 1. group existence in blender data block + return SPEC_TEXTURE_CALC_G not in bpy.data.node_groups + +def __create_node_group__(): + """Creates group. + + Inputs: Color + Outputs: Shininess, Specular + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + if SPEC_TEXTURE_CALC_G not in bpy.data.node_groups: # creation + + spec_txt_calc_n = bpy.data.node_groups.new(type="ShaderNodeTree", name=SPEC_TEXTURE_CALC_G) + + else: # recreation + + spec_txt_calc_n = bpy.data.node_groups[SPEC_TEXTURE_CALC_G] + + # delete all inputs and outputs + spec_txt_calc_n.inputs.clear() + spec_txt_calc_n.outputs.clear() + + # delete all old nodes and links as they will be recreated now with actual version + spec_txt_calc_n.nodes.clear() + + # inputs defining + spec_txt_calc_n.inputs.new("NodeSocketColor", "Color") + + # outputs defining + spec_txt_calc_n.outputs.new("NodeSocketVector", "Shininess") + spec_txt_calc_n.outputs.new("NodeSocketVector", "Specular") + + # node creation + input_n = spec_txt_calc_n.nodes.new("NodeGroupInput") + input_n.location = (start_pos_x, start_pos_y) + + output_n = spec_txt_calc_n.nodes.new("NodeGroupOutput") + output_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y) + + color_to_rgb_n = spec_txt_calc_n.nodes.new("ShaderNodeSeparateColor") + color_to_rgb_n.name = color_to_rgb_n.label = _COLOR_TO_RGB_NODE + color_to_rgb_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y) + color_to_rgb_n.mode = "RGB" + + shininess_mult_n = spec_txt_calc_n.nodes.new("ShaderNodeVectorMath") + shininess_mult_n.name = shininess_mult_n.label = _SHININESS_MULT + shininess_mult_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 100) + shininess_mult_n.operation = "MULTIPLY" + shininess_mult_n.inputs[1].default_value = (255,) * 3 + + # links + spec_txt_calc_n.links.new(input_n.outputs['Color'], color_to_rgb_n.inputs['Color']) + + spec_txt_calc_n.links.new(color_to_rgb_n.outputs['Red'], shininess_mult_n.inputs[0]) + spec_txt_calc_n.links.new(color_to_rgb_n.outputs['Green'], output_n.inputs['Specular']) + + spec_txt_calc_n.links.new(shininess_mult_n.outputs['Vector'], output_n.inputs['Shininess']) \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py index 3834702..26bf272 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_passes/add_env.py @@ -42,8 +42,8 @@ def add(node_tree, geom_n_name, spec_col_socket, alpha_socket, final_normal_sock :type node_tree: bpy.types.NodeTree :param geom_n_name: name of geometry node from which normal and view vectors will be taken :type geom_n_name: str - :param spec_col_socket: specular color node socket from which specular color will be taken - :type spec_col_socket: bpy.type.NodeSocket + :param spec_col_socket: specular color node socket from which specular color will be taken (if None it won't be used) + :type spec_col_socket: bpy.type.NodeSocket | None :param alpha_socket: socket from which alpha will be taken (if None it won't be used) :type alpha_socket: bpy.type.NodeSocket | None :param final_normal_socket: socket of final normal, if not provided geometry normal is used @@ -105,7 +105,10 @@ def add(node_tree, geom_n_name, spec_col_socket, alpha_socket, final_normal_sock node_tree.links.new(add_env_n.inputs['Env Factor Color'], env_col_n.outputs['Color']) node_tree.links.new(add_env_n.inputs['Reflection Texture Color'], refl_tex_n.outputs['Color']) - node_tree.links.new(add_env_n.inputs['Specular Color'], spec_col_socket) + # fix for 'baked' shader that don't use specular + if spec_col_socket is not None: + node_tree.links.new(add_env_n.inputs['Specular Color'], spec_col_socket) + if add_env_n and alpha_socket: node_tree.links.new(add_env_n.inputs['Base Texture Alpha'], alpha_socket) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index b134225..5ed3123 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -190,10 +190,10 @@ Shader { Value: ( 0.0 ) } Attribute { - Format: FLOAT + Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Boost" - Value: ( 0.0 ) + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) } Texture { Tag: "texture[X]:texture_base" @@ -2070,6 +2070,66 @@ Shader { TexCoord: ( 1 ) } } +Shader { + PresetName: "baked" + Effect: "eut2.baked" + # Shader use also NIGHT flavor, but it's not used in shader (only "day") - probably game changing it depending on day time, like in other shaders with day/night in name + Flavors: ( "NMAP_TS" "SHADOW" "DAY" ) + Flags: 0 + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } +} +Shader { + PresetName: "baked.spec" + Effect: "eut2.baked.spec" + # Shader use also NIGHT flavor, but it's not used in shader (only "day") - probably game changing it depending on day time, like in other shaders with day/night in name + Flavors: ( "NMAP_TS" "SHADOW" "DAY" ) + Flags: 0 + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 0 ) + } +} +Shader { + PresetName: "baked.spec.add.env" + Effect: "eut2.baked.spec.add.env" + # Shader use also NIGHT flavor, but it's not used in shader (only "day") - probably game changing it depending on day time, like in other shaders with day/night in name + Flavors: ( "NMAP_TS" "PAINT" "SHADOW" "DAY" ) + Flags: 0 + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/vehicle_reflection" + TexCoord: ( -1 ) + } } Flavor { Type: "AIRBRUSH" @@ -2188,25 +2248,6 @@ Flavor { TexCoord: ( -1 ) } } -Flavor { - Type: "ENVMAP_NOFAC" - Name: "add.env" - Attribute { - Format: FLOAT2 - Tag: "fresnel" - Value: ( 0.2 0.9 ) - } - Texture { - Tag: "texture[X]:texture_mask" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/vehicle_reflection" - TexCoord: ( -1 ) - } -} Flavor { Type: "FADESHEET" Name: "fadesheet" @@ -2477,3 +2518,21 @@ Flavor { Value: ( 8.0 8.0 0.0 0.0 ) } } +Flavor { + Type: "DAY" + Name: "day" + Texture { + Tag: "texture[X]:texture_lightmap" + Value: "" + TexCoord: ( 0 ) + } +} +Flavor { + Type: "NIGHT" + Name: "night" + Texture { + Tag: "texture[X]:texture_lightmap" + Value: "" + TexCoord: ( 0 ) + } +} From e98dece922dd6d20bae44b9a2ad71e2944af9b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 4 Dec 2024 04:25:22 +0100 Subject: [PATCH 13/56] day/night replace, "building" fixes, visibility tools tweaks - Fixed problem with imported - Added parts starting with "_col" to collisions - Added automatic replace for outdated "window.day/night" shaders to "window.lit". - Added missing flavors in some "building" shaders - Added support for "building.add.env.lvcol.day" --- addon/io_scs_tools/imp/pit.py | 7 ++ .../internals/shaders/eut2/__init__.py | 16 ++-- addon/io_scs_tools/shader_presets.txt | 73 +++++++++++++++---- addon/io_scs_tools/utils/material.py | 2 +- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index b72cacf..6139217 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -189,6 +189,13 @@ def _get_look(section): mat_effect = mat_effect.replace(".night", ".day") lprint("W Night version of building shader detected in material %r, switching it to day!", (mat_alias,)) + # If day/night version of "window" shader is detected, switch it to "lit". + if mat_effect.startswith("eut2.window") and mat_effect.endswith((".day", ".night")): + + mat_effect = mat_effect.replace(".day", ".lit").replace(".night", ".lit") + + lprint("W Outdated Day or Night version of window shader detected in material %r, switching it to lit!", (mat_alias,)) + look_mat_settings[mat_alias] = (mat_effect, mat_flags, attributes, textures, sec) return look_name, look_mat_settings diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index eff9207..cd48395 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -168,15 +168,21 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_add_env.nofresnel import DifSpecAddEnvNoFresnel as Shader - elif effect.startswith("building.add.env.day"): + # Changed because of "lvcol.day" variant (all "buildings.add.env" use this same shader) + # + # elif effect.startswith("building.add.env.day"): + elif effect.startswith("building.add.env"): from io_scs_tools.internals.shaders.eut2.building.add_env_day import BuildingAddEnvDay as Shader - elif effect.startswith("building.lvcol.day"): - - from io_scs_tools.internals.shaders.eut2.building.lvcol_day import BuildingLvcolDay as Shader + # Changed because of "asafew.(...).day" variant (all non add.env "buildings" use this same shader) + # + # elif effect.startswith("building.lvcol.day"): + # + # from io_scs_tools.internals.shaders.eut2.building.lvcol_day import BuildingLvcolDay as Shader - elif effect.startswith("building.day"): + # elif effect.startswith("building.day"): + elif effect.startswith("building"): from io_scs_tools.internals.shaders.eut2.building.day import BuildingDay as Shader diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 5ed3123..49faa72 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -9,6 +9,58 @@ Header { Shader { PresetName: "building.add.env.day" Effect: "eut2.building.add.env.day" + Flavors: ( "SHADOW" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 0.9 0.9 0.9 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 60.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.9 0.9 0.9 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } +} +Shader { + PresetName: "building.add.env.lvcol.day" + Effect: "eut2.building.add.env.lvcol.day" Flags: 0 Attribute { Format: FLOAT3 @@ -60,6 +112,7 @@ Shader { Shader { PresetName: "building.day" Effect: "eut2.building.day" + Flavors: ( "SHADOW" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2073,8 +2126,7 @@ Shader { Shader { PresetName: "baked" Effect: "eut2.baked" - # Shader use also NIGHT flavor, but it's not used in shader (only "day") - probably game changing it depending on day time, like in other shaders with day/night in name - Flavors: ( "NMAP_TS" "SHADOW" "DAY" ) + Flavors: ( "NMAP_TS" "SHADOW" "DAY_TEX" ) Flags: 0 Texture { Tag: "texture[X]:texture_base" @@ -2085,8 +2137,7 @@ Shader { Shader { PresetName: "baked.spec" Effect: "eut2.baked.spec" - # Shader use also NIGHT flavor, but it's not used in shader (only "day") - probably game changing it depending on day time, like in other shaders with day/night in name - Flavors: ( "NMAP_TS" "SHADOW" "DAY" ) + Flavors: ( "NMAP_TS" "SHADOW" "DAY_TEX" ) Flags: 0 Texture { Tag: "texture[X]:texture_base" @@ -2102,8 +2153,7 @@ Shader { Shader { PresetName: "baked.spec.add.env" Effect: "eut2.baked.spec.add.env" - # Shader use also NIGHT flavor, but it's not used in shader (only "day") - probably game changing it depending on day time, like in other shaders with day/night in name - Flavors: ( "NMAP_TS" "PAINT" "SHADOW" "DAY" ) + Flavors: ( "NMAP_TS" "PAINT" "SHADOW" "DAY_TEX" ) Flags: 0 Attribute { Format: FLOAT2 @@ -2519,7 +2569,7 @@ Flavor { } } Flavor { - Type: "DAY" + Type: "DAY_TEX" Name: "day" Texture { Tag: "texture[X]:texture_lightmap" @@ -2527,12 +2577,3 @@ Flavor { TexCoord: ( 0 ) } } -Flavor { - Type: "NIGHT" - Name: "night" - Texture { - Tag: "texture[X]:texture_lightmap" - Value: "" - TexCoord: ( 0 ) - } -} diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index 213a213..7b871e6 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -393,7 +393,7 @@ def get_material_info(obj): if slot.material: - if obj.scs_props.scs_part.startswith("coll"): + if obj.scs_props.scs_part.startswith(("coll", "_col")): has_static_collision = True effect_name = slot.material.scs_props.mat_effect_name.lower() From 42d58658cbe0652cc42343473d5cd66bf2e36422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 4 Jan 2025 06:26:22 +0100 Subject: [PATCH 14/56] new effects (.bin) + "baked" fixes * Added new effects in supported_effects.bin. * Fixed "paint" flavor in "baked" shader (added mask_1 and preview of "Base Paint Color"). * Renamed some stuff in "interior" shader files. * Renamed some new flavors in "shader_presets.txt" to better reflect them. * Sorted flavor entries in "shader_presets.txt" alphabetically for better orientation. --- .../internals/shaders/eut2/baked/add_env.py | 124 +++++++++++- .../shaders/eut2/interior/__init__.py | 6 +- addon/io_scs_tools/shader_presets.txt | 191 +++++++++--------- addon/io_scs_tools/supported_effects.bin | Bin 192085 -> 194096 bytes 4 files changed, 224 insertions(+), 97 deletions(-) diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py index 1bba383..f52db34 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py @@ -19,14 +19,20 @@ # Copyright (C) 2015-2024: SCS Software from io_scs_tools.internals.shaders.eut2.baked.spec import BakedSpec +from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_truckpaint from io_scs_tools.internals.shaders.eut2.std_node_groups import linear_to_srgb_ng from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv +from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import material as _material_utils +from io_scs_tools.utils import get_scs_globals as _get_scs_globals class BakedSpecAddEnv(BakedSpec, StdAddEnv): MASK_TEX_NODE = "MaskTex" + MASK1_TEX_NODE = "Mask1Tex" MASK_LIN_TO_SRGB_NODE = "MaskLinearToSRGB" + BASE_PAINT_MULT_NODE = "BasePaintMult" + @staticmethod def get_name(): """Get name of this shader file with full modules path.""" @@ -88,6 +94,46 @@ def init(node_tree): node_tree.links.new(mask_lin_to_srgb_n.outputs['Value'], add_env_gn.inputs['Env Factor Color']) + @staticmethod + def init_paint(node_tree): + """Initialize extended node tree for colormask or airbrush flavors with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # make sure to skip execution if node exists + if BakedSpecAddEnv.MASK1_TEX_NODE not in node_tree.nodes: + + uvmap_n = node_tree.nodes[BakedSpec.UVMAP_NODE] + vcol_mult_n = node_tree.nodes[BakedSpec.VCOLOR_MULT_NODE] + compose_lighting_n = node_tree.nodes[BakedSpec.COMPOSE_LIGHTING_NODE] + + # node creation + mask1_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask1_tex_n.name = mask1_tex_n.label = BakedSpecAddEnv.MASK1_TEX_NODE + mask1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1150) + mask1_tex_n.width = 140 + + base_paint_mult_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_paint_mult_n.name = base_paint_mult_n.label = BakedSpecAddEnv.BASE_PAINT_MULT_NODE + base_paint_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1500) + base_paint_mult_n.blend_type = "MULTIPLY" + base_paint_mult_n.inputs['Fac'].default_value = 1 + base_paint_mult_n.inputs['Color2'].default_value = _convert_utils.to_node_color(_get_scs_globals().base_paint_color) + + # links creation + node_tree.links.new(uvmap_n.outputs['UV'], mask1_tex_n.inputs['Vector']) + node_tree.links.new(mask1_tex_n.outputs['Color'], base_paint_mult_n.inputs['Fac']) + node_tree.links.new(vcol_mult_n.outputs['Vector'], base_paint_mult_n.inputs['Color1']) + node_tree.links.new(base_paint_mult_n.outputs['Color'], compose_lighting_n.inputs['Diffuse Color']) + + @staticmethod def set_mask_texture(node_tree, image): @@ -112,7 +158,7 @@ def set_mask_texture_settings(node_tree, settings): """ _material_utils.set_texture_settings_to_node(node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE], settings) - # due the fact mask colorspace is linear, we have to manually switch to sRGB and then convert values by node, for effect to work correctly + # due the fact mask colorspace is linear, we have to manually switch to sRGB and then convert values by node, for effect to work correctly (blender don't like SCS format of linear DDS textures). node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE].image.colorspace_settings.name = 'sRGB' @staticmethod @@ -121,8 +167,80 @@ def set_mask_uv(node_tree, uv_layer): :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param uv_layer: uv layer string used for nmap texture + :param uv_layer: uv layer string used for mask texture + :type uv_layer: str + """ + + pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this + + @staticmethod + def set_mask_1_texture(node_tree, image): + """Set mask 1 texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to mask 1 texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[BakedSpecAddEnv.MASK1_TEX_NODE].image = image + + @staticmethod + def set_mask_1_texture_settings(node_tree, settings): + """Set mask 1 texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[BakedSpecAddEnv.MASK1_TEX_NODE], settings) + + # due the fact mask colorspace is linear, we have to manually switch to sRGB and then convert values by node, for effect to work correctly (blender don't like SCS format of linear DDS textures). + node_tree.nodes[BakedSpecAddEnv.MASK1_TEX_NODE].image.colorspace_settings.name = 'sRGB' + + @staticmethod + def set_mask_1_uv(node_tree, uv_layer): + """Set UV layer to mask 1 texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for mask 1 texture :type uv_layer: str """ - pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this \ No newline at end of file + pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this + + @staticmethod + def set_paint_flavor(node_tree, switch_on): + """Set paint flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + if switch_on: + BakedSpecAddEnv.init_paint(node_tree) + + @staticmethod + def set_fresnel(node_tree, bias_scale): + """Set fresnel bias and scale value to shader. + More tests are needed to determine if this is correct for paint flavor in baked shader, because without that, model is too glossy. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param bias_scale: bias and scale factors as tuple: (bias, scale) + :type bias_scale: (float, float) + """ + # skip ecxecution if node for "paint" flavor does not exist + if BakedSpecAddEnv.MASK1_TEX_NODE not in node_tree.nodes: + return + + bias_scale_truckpaint = get_fresnel_truckpaint(bias_scale[0], bias_scale[1]) + StdAddEnv.set_fresnel(node_tree, bias_scale_truckpaint) + + node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE].inputs['Fresnel Type'].default_value = 1 + + \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py index 6e6430d..c559882 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py @@ -34,7 +34,7 @@ class InteriorLit(DifSpecAddEnv): NMAP_TEX_NODE = "NmapTex" ENV_SEP_XYZ_NODE = "EnvSepXYZ" ENV_CHECK_XYZ_NODE = "EnvCheckXYZ" - PERTURBATION_UVMAP_NODE = "PerturbationUVMap" + PERT_UVMAP_NODE = "PerturbationUVMap" @staticmethod @@ -77,7 +77,7 @@ def init(node_tree): # node creation # - column -1 - pert_uv_n = node_tree.nodes.new("ShaderNodeUVMap") - pert_uv_n.name = pert_uv_n.label = InteriorLit.PERTURBATION_UVMAP_NODE + pert_uv_n.name = pert_uv_n.label = InteriorLit.PERT_UVMAP_NODE pert_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 950) pert_uv_n.uv_map = _MESH_consts.none_uv @@ -276,4 +276,4 @@ def set_perturbation_mapping(node_tree, uv_layer): if uv_layer is None or uv_layer == "": uv_layer = _MESH_consts.none_uv - node_tree.nodes[InteriorLit.PERTURBATION_UVMAP_NODE].uv_map = uv_layer + node_tree.nodes[InteriorLit.PERT_UVMAP_NODE].uv_map = uv_layer diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 49faa72..dae91b2 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1587,7 +1587,7 @@ Shader { Shader { PresetName: "shadowonly" Effect: "eut2.shadowonly" - Flavors: ( "NOCULL" "SHDW_ALPHA" ) + Flavors: ( "NOCULL" "ALPHA_MASK" ) Flags: 0 Attribute { Format: FLOAT @@ -1682,7 +1682,7 @@ Shader { Shader { PresetName: "truckpaint" Effect: "eut2.truckpaint" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "COLORMASK|AIRBRUSH" "FLIPFLAKE" "ALTUV" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "COLORMASK|AIRBRUSH" "FLIPFLAKE" "ALTUV" "ASAFEWA" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2126,7 +2126,7 @@ Shader { Shader { PresetName: "baked" Effect: "eut2.baked" - Flavors: ( "NMAP_TS" "SHADOW" "DAY_TEX" ) + Flavors: ( "NMAP_TS" "SHADOW" "DAY_MASK" ) Flags: 0 Texture { Tag: "texture[X]:texture_base" @@ -2137,7 +2137,7 @@ Shader { Shader { PresetName: "baked.spec" Effect: "eut2.baked.spec" - Flavors: ( "NMAP_TS" "SHADOW" "DAY_TEX" ) + Flavors: ( "NMAP_TS" "SHADOW" "DAY_MASK" ) Flags: 0 Texture { Tag: "texture[X]:texture_base" @@ -2153,7 +2153,7 @@ Shader { Shader { PresetName: "baked.spec.add.env" Effect: "eut2.baked.spec.add.env" - Flavors: ( "NMAP_TS" "PAINT" "SHADOW" "DAY_TEX" ) + Flavors: ( "NMAP_TS" "PAINT_MASK" "SHADOW" "DAY_MASK" ) Flags: 0 Attribute { Format: FLOAT2 @@ -2201,7 +2201,7 @@ Flavor { Name: "a" } Flavor { - Type: "SHDW_ALPHA" + Type: "ALPHA_MASK" Name: "a" Texture { Tag: "texture[X]:texture_base" @@ -2213,6 +2213,42 @@ Flavor { Type: "ALTUV" Name: "altuv" } +Flavor { + Type: "ANIM" + Name: "anim" + Attribute { + Format: FLOAT4 + Tag: "aux[0]" + FriendlyTag: "Sheet frame size (R, G)" + Value: ( 1.0 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[1]" + FriendlyTag: "Sheet frame size (B, A)" + Value: ( 1.0 1.0 1.0 1.0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask_1" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask_2" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask_3" + Value: "" + TexCoord: ( 1 ) + } +} Flavor { Type: "ASAFEW" Name: "asafew" @@ -2270,6 +2306,15 @@ Flavor { TexCoord: ( 1 2 ) } } +Flavor { + Type: "DAY_MASK" + Name: "day" + Texture { + Tag: "texture[X]:texture_lightmap" + Value: "" + TexCoord: ( 0 ) + } +} Flavor { Type: "DEPTH" Name: "decal" @@ -2344,42 +2389,6 @@ Flavor { Hide: "True" } } -Flavor { - Type: "ANIM" - Name: "anim" - Attribute { - Format: FLOAT4 - Tag: "aux[0]" - FriendlyTag: "Sheet frame size (R, G)" - Value: ( 1.0 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT4 - Tag: "aux[1]" - FriendlyTag: "Sheet frame size (B, A)" - Value: ( 1.0 1.0 1.0 1.0 ) - } - Texture { - Tag: "texture[X]:texture_mask" - Value: "" - TexCoord: ( 1 ) - } - Texture { - Tag: "texture[X]:texture_mask_1" - Value: "" - TexCoord: ( 1 ) - } - Texture { - Tag: "texture[X]:texture_mask_2" - Value: "" - TexCoord: ( 1 ) - } - Texture { - Tag: "texture[X]:texture_mask_3" - Value: "" - TexCoord: ( 1 ) - } -} Flavor { Type: "FLAT" Name: "flat" @@ -2393,17 +2402,22 @@ Flavor { Name: "lvcol" } Flavor { - Type: "NMAP_TS" + Type: "NMAP_DETAIL" Name: "tsnmap" Texture { Tag: "texture[X]:texture_nmap" Value: "" TexCoord: ( 0 ) } + Texture { + Tag: "texture[X]:texture_nmap_detail" + Value: "" + TexCoord: ( 0 ) + } } Flavor { - Type: "NMAP_TS_16" - Name: "tsnmap16" + Type: "NMAP_TS" + Name: "tsnmap" Texture { Tag: "texture[X]:texture_nmap" Value: "" @@ -2411,31 +2425,35 @@ Flavor { } } Flavor { - Type: "NMAP_DETAIL" - Name: "tsnmap" + Type: "NMAP_TS_16" + Name: "tsnmap16" Texture { Tag: "texture[X]:texture_nmap" Value: "" TexCoord: ( 0 ) } +} +Flavor { + Type: "NMAP_TS_CALC" + Name: "tsnmapcalc" Texture { - Tag: "texture[X]:texture_nmap_detail" + Tag: "texture[X]:texture_nmap" Value: "" TexCoord: ( 0 ) } } Flavor { - Type: "NMAP_UV_DETAIL" - Name: "tsnmapuv" + Type: "NMAP_TS_CALC_OVER" + Name: "tsnmapcalc" Texture { Tag: "texture[X]:texture_nmap" Value: "" - TexCoord: ( 6 ) + TexCoord: ( 0 ) } Texture { - Tag: "texture[X]:texture_nmap_detail" + Tag: "texture[X]:texture_nmap_over" Value: "" - TexCoord: ( 6 ) + TexCoord: ( 1 ) } } Flavor { @@ -2457,31 +2475,47 @@ Flavor { } } Flavor { - Type: "NMAP_TS_CALC" - Name: "tsnmapcalc" + Type: "NMAP_UV_DETAIL" + Name: "tsnmapuv" Texture { Tag: "texture[X]:texture_nmap" Value: "" - TexCoord: ( 0 ) + TexCoord: ( 6 ) } -} -Flavor { - Type: "NMAP_TS_CALC_OVER" - Name: "tsnmapcalc" Texture { - Tag: "texture[X]:texture_nmap" + Tag: "texture[X]:texture_nmap_detail" Value: "" - TexCoord: ( 0 ) + TexCoord: ( 6 ) } +} +Flavor { + Type: "NOCULL" + Name: "nocull" +} +Flavor { + Type: "PAINT" + Name: "paint" +} +Flavor { + Type: "PAINT_MASK" + Name: "paint" Texture { - Tag: "texture[X]:texture_nmap_over" + Tag: "texture[X]:texture_mask_1" Value: "" - TexCoord: ( 1 ) + TexCoord: ( 0 ) } } Flavor { - Type: "NOCULL" - Name: "nocull" + Type: "RETROREFLECTIVE_DECAL" + Name: "decal.over" +} +Flavor { + Type: "RETROREFLECTIVE_DIM_ALLDIR" + Name: "dim.alldir" +} +Flavor { + Type: "RETROREFLECTIVE_PIKO_ALLDIR" + Name: "piko.alldir" } Flavor { Type: "SHADOW" @@ -2532,22 +2566,6 @@ Flavor { Hide: "False" } } -Flavor { - Type: "PAINT" - Name: "paint" -} -Flavor { - Type: "RETROREFLECTIVE_DECAL" - Name: "decal.over" -} -Flavor { - Type: "RETROREFLECTIVE_DIM_ALLDIR" - Name: "dim.alldir" -} -Flavor { - Type: "RETROREFLECTIVE_PIKO_ALLDIR" - Name: "piko.alldir" -} Flavor { Type: "TEXGEN0" Name: "tg0" @@ -2568,12 +2586,3 @@ Flavor { Value: ( 8.0 8.0 0.0 0.0 ) } } -Flavor { - Type: "DAY_TEX" - Name: "day" - Texture { - Tag: "texture[X]:texture_lightmap" - Value: "" - TexCoord: ( 0 ) - } -} diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index f6cc9a7f7dde2cd277916909520484d0126fc1a0..b084828889c4f961d4a191d0940eac41e399ac0e 100644 GIT binary patch literal 194096 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 UH%SQ7a7aCrcp#!6*dL$$A2WKHHUIzs From f76e333c336500547aa78497afb6c8cb566b8076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 6 Jan 2025 01:19:47 +0100 Subject: [PATCH 15/56] bgl replaces * Some bgl module replaces --- addon/io_scs_tools/__init__.py | 2 +- .../internals/open_gl/primitive.py | 25 +++++++++---------- addon/io_scs_tools/operators/wm.py | 13 ++++++---- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index c9c82ad..f564d87 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 2), + "version": (2, 4, "aeadde03", 3), "blender": (3, 2, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/open_gl/primitive.py b/addon/io_scs_tools/internals/open_gl/primitive.py index a18f483..43b36ec 100644 --- a/addon/io_scs_tools/internals/open_gl/primitive.py +++ b/addon/io_scs_tools/internals/open_gl/primitive.py @@ -18,7 +18,6 @@ # Copyright (C) 2013-2019: SCS Software -import bgl import blf import gpu from array import array @@ -61,23 +60,23 @@ def __init__(self, buffer_type, draw_size, shader_type, attr_names): # depending on type setup callbacks executed before and after dispatching if buffer_type == _Buffer.Types.LINES: - self.__bgl_callback = bgl.glLineWidth - self.__bgl_callback_param_before = self.__draw_size - self.__bgl_callback_param_after = 1.0 + self.__gpu_callback = gpu.state.line_width_set + self.__gpu_callback_param_before = self.__draw_size + self.__gpu_callback_param_after = 1.0 self.__draw_type = 'LINES' elif buffer_type == _Buffer.Types.POINTS: - self.__bgl_callback = bgl.glPointSize - self.__bgl_callback_param_before = self.__draw_size - self.__bgl_callback_param_after = 1.0 + self.__gpu_callback = gpu.state.point_size_set + self.__gpu_callback_param_before = self.__draw_size + self.__gpu_callback_param_after = 1.0 self.__draw_type = 'POINTS' elif buffer_type == _Buffer.Types.TRIS: - self.__bgl_callback = lambda *args: None - self.__bgl_callback_param_before = None - self.__bgl_callback_param_after = None + self.__gpu_callback = lambda *args: None + self.__gpu_callback_param_before = None + self.__gpu_callback_param_after = None self.__draw_type = 'TRIS' def append_attr(self, attr_name, value): @@ -114,7 +113,7 @@ def draw(self, uniforms, space_3d): if self.__type == _Buffer.Types.TRIS and space_3d.shading.type == 'WIREFRAME': return - self.__bgl_callback(self.__bgl_callback_param_before) + self.__gpu_callback(self.__gpu_callback_param_before) # bind shader self.__shader.bind() @@ -133,7 +132,7 @@ def draw(self, uniforms, space_3d): batch = batch_for_shader(self.__shader, self.__draw_type, self.__data) batch.draw(self.__shader) - self.__bgl_callback(self.__bgl_callback_param_after) + self.__gpu_callback(self.__gpu_callback_param_after) def has_entries(self): """Checks if there is any antries in this buffer. @@ -902,7 +901,7 @@ def draw_rect_2d(positions, color): :param color: RGBA of rectangle :type color: tuple(float, float, float, float) """ - shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') + shader = gpu.shader.from_builtin('UNIFORM_COLOR') batch = batch_for_shader( shader, 'TRI_STRIP', { diff --git a/addon/io_scs_tools/operators/wm.py b/addon/io_scs_tools/operators/wm.py index 9a90d98..76f2f4e 100644 --- a/addon/io_scs_tools/operators/wm.py +++ b/addon/io_scs_tools/operators/wm.py @@ -19,6 +19,7 @@ # Copyright (C) 2013-2022: SCS Software +import gpu import bpy import os from bpy.props import StringProperty, BoolProperty, IntProperty @@ -136,13 +137,13 @@ def has_controls(window): @staticmethod def get_scs_banner_img_data(window): - """Loads image to blender data block, loads it to gl memory and gets bindcode address that can be used in - bgl module for image drawing. + """Loads image to blender data block, loads it to GPU memory and returns it's texture that can be used + for image drawing. :param window: window for which we should get banner image :type window: bpy.type.Window - :return: (bindcode of scs banner image, width of scs banner image, height of scs banner image - :rtype: (int, int, int) + :return: (texture, width of scs banner image, height of scs banner image + :rtype: (gpu.types.GPUTexture, int, int) """ if SCS_TOOLS_OT_Show3DViewReport.has_controls(window): @@ -161,12 +162,14 @@ def get_scs_banner_img_data(window): img = bpy.data.images[img_name] + texture = gpu.texture.from_image(img) + # ensure that image is loaded in GPU memory aka has proper bindcode, # we have to that each time because if operator is shown for long time blender might free it on it's own if img.bindcode == 0: img.gl_load() - return img.bindcode, img.size[0], img.size[1] + return texture, img.size[0], img.size[1] @staticmethod def get_lines(): From 99d11535441713e0627a657d69f81c54838502e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 6 Jan 2025 07:37:17 +0100 Subject: [PATCH 16/56] Fast truckpaint fix * Fixed unselectable tsnmap flavors in truckpaint. --- addon/io_scs_tools/supported_effects.bin | Bin 194096 -> 195778 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index b084828889c4f961d4a191d0940eac41e399ac0e..b38ef7bb64a8366efaf4c422154500a1b64bdc1c 100644 GIT binary patch literal 195778 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%0D2eju>IfK3xn}i(W~G4bc%a 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 z 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 Date: Wed, 8 Jan 2025 04:35:30 +0100 Subject: [PATCH 17/56] update to blender 4.1.1 A huge amount of edits needed to update BT to newer version (4.1.1 max) of Blender: * Updated camera settings (removed "cycles" to correspond to bpy.types.Camera changes) * Updated colorspace_settings ("Linear" (removed) -> "Linear Rec.709") * Updated node groups (replaced removed inputs/outputs with interface) * Some code tweaks in spec_texture_calc_ng.py * Removed forced sRGB color space instead Linear in amod and baked shaders (Blender 4.x now support more DDS dxgi formats, and it's not broken anymore) * Renamed "bindcode" to "texture" in open_gl/core.py to not mislead * Updated pim import (https://developer.blender.org/docs/release_notes/4.1/python_api/#mesh). More tests are required, cause I don't known if that changes are correctly made. --- addon/io_scs_tools/__init__.py | 4 +- addon/io_scs_tools/imp/pim.py | 16 ++-- addon/io_scs_tools/imp/pim_ef.py | 10 +-- addon/io_scs_tools/internals/open_gl/core.py | 4 +- .../internals/shaders/eut2/baked/add_env.py | 19 +---- .../eut2/dif_anim/anim_blend_factor_ng.py | 14 +++- .../eut2/dif_spec_amod_dif_spec/__init__.py | 3 - .../decal_blend_factor_ng.py | 25 +++++- .../dif_spec_fade_dif_spec/detail_setup_ng.py | 32 ++++++-- .../shaders/eut2/sky/uv_rescale_ng.py | 62 ++++++++++++--- .../eut2/std_node_groups/add_env_ng.py | 79 ++++++++++++++++--- .../eut2/std_node_groups/alpha_remap_ng.py | 26 +++++- .../std_node_groups/compose_lighting_ng.py | 62 ++++++++++++--- .../eut2/std_node_groups/fresnel_legacy_ng.py | 32 ++++++-- .../std_node_groups/fresnel_schlick_ng.py | 26 +++++- .../eut2/std_node_groups/lampmask_mixer_ng.py | 33 +++++--- .../std_node_groups/lighting_evaluator_ng.py | 49 ++++++++++-- .../eut2/std_node_groups/linear_to_srgb_ng.py | 13 ++- .../eut2/std_node_groups/mult2_mix_ng.py | 38 +++++++-- .../eut2/std_node_groups/refl_normal_ng.py | 20 ++++- .../std_node_groups/scs_uvs_combine_ng.py | 19 ++++- .../std_node_groups/scs_uvs_separate_ng.py | 20 ++++- .../std_node_groups/spec_texture_calc_ng.py | 68 ++++++---------- .../eut2/std_node_groups/vcolor_input_ng.py | 15 +++- .../shaders/eut2/water/mix_factor_ng.py | 32 ++++++-- .../shaders/eut2/water/water_stream_ng.py | 38 +++++++-- .../shaders/eut2/window/window_final_uv_ng.py | 20 ++++- .../eut2/window/window_offset_factor_ng.py | 20 ++++- .../eut2/window/window_uv_offset_ng.py | 26 +++++- .../flavors/fadesheet/fadesheet_compute_ng.py | 48 +++++++++-- .../flavors/flipsheet/flipsheet_compute_ng.py | 36 +++++++-- .../shaders/flavors/nmap/dds16_ng.py | 20 ++++- .../shaders/flavors/nmap/scale_ng.py | 25 +++++- .../shaders/std_node_groups/alpha_test_ng.py | 18 ++++- .../animsheet_frame_idx_to_col_row_ng.py | 24 +++++- .../std_node_groups/animsheet_frame_ng.py | 30 +++++-- .../animsheet_loop_frame_ng.py | 18 ++++- .../std_node_groups/animsheet_xfade_ng.py | 48 +++++++++-- .../shaders/std_node_groups/blend_add_ng.py | 12 ++- .../shaders/std_node_groups/blend_mult_ng.py | 12 ++- .../std_node_groups/output_shader_ng.py | 21 ++++- addon/io_scs_tools/utils/material.py | 14 ++-- 42 files changed, 890 insertions(+), 261 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index f564d87..29586e2 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,8 +22,8 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 3), - "blender": (3, 2, 0), + "version": (2, 4, "aeadde03", 4), + "blender": (4, 1, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", "tracker_url": "http://forum.scssoft.com/viewforum.php?f=163", diff --git a/addon/io_scs_tools/imp/pim.py b/addon/io_scs_tools/imp/pim.py index da3ed9c..999fc79 100644 --- a/addon/io_scs_tools/imp/pim.py +++ b/addon/io_scs_tools/imp/pim.py @@ -475,12 +475,11 @@ def _create_piece( # NORMALS - has to be applied after bmesh creation as they are set directly to mesh if _get_scs_globals().import_use_normals: - mesh.create_normals_split() - # first set normals directly to loops + clnors = [] for loop in mesh.loops: curr_n = _convert_utils.scs_to_blend_matrix() @ Vector(mesh_normals[loop.vertex_index]) - loop.normal[:] = curr_n + clnors.extend(curr_n) # then we have to go trough very important step they say, # as without validation we get wrong result for some normals @@ -490,13 +489,8 @@ def _create_piece( mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons)) # finally fill clnors from loops normals and apply them (taken from official Blenders scripts) - clnors = array.array('f', [0.0] * (len(mesh.loops) * 3)) - mesh.loops.foreach_get("normal", clnors) mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3))) - mesh.use_auto_smooth = True - mesh.auto_smooth_angle = 3.14 - mesh.free_normals_split() else: # set polygons to use smooth representation only mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons)) @@ -645,10 +639,10 @@ def visualise_normals(name, transformed_mesh_vertices, mesh_normals, import_scal :return: """ if mesh_normals: - transformed_mesh_normals = [io_utils.change_to_scs_xyz_coordinates(vec, import_scale) for vec in mesh_normals] + transformed_mesh_normals = [_convert_utils.change_to_scs_xyz_coordinates(vec, import_scale) for vec in mesh_normals] mesh_norm_vizu = bpy.data.meshes.new(str(name + "_norm_vizu")) object_norm_vizu = bpy.data.objects.new(str(name + "_norm_vizu"), mesh_norm_vizu) - bpy.context.scene.objects.link(object_norm_vizu) + bpy.context.collection.objects.link(object_norm_vizu) mesh_norm_vizu.vertices.add(len(transformed_mesh_vertices) * 2) mesh_norm_vizu.edges.add(len(transformed_mesh_vertices)) vert_i = edge_i = 0 @@ -670,7 +664,7 @@ def visualise_normals(name, transformed_mesh_vertices, mesh_normals, import_scal # mesh_norm_vizu.validate() mesh_norm_vizu.update() # object_norm_vizu.select = True - # bpy.context.scene.objects.active = object_norm_vizu + # bpy.context.collection.objects.active = object_norm_vizu else: print("WARNING! 'visualise_normals' - NO MESH NORMALS PROVIDED!") ''' diff --git a/addon/io_scs_tools/imp/pim_ef.py b/addon/io_scs_tools/imp/pim_ef.py index 758289f..b1143bb 100644 --- a/addon/io_scs_tools/imp/pim_ef.py +++ b/addon/io_scs_tools/imp/pim_ef.py @@ -316,15 +316,14 @@ def _create_piece( # NORMALS - has to be applied after bmesh creation as they are set directly to mesh if _get_scs_globals().import_use_normals: - mesh.create_normals_split() - # first set normals directly to loops + clnors = [] for poly_i, poly in enumerate(mesh.polygons): for poly_loop_i, loop_i in enumerate(poly.loop_indices): curr_n = _convert_utils.scs_to_blend_matrix() @ Vector(mesh_normals[poly_i][poly_loop_i]) - mesh.loops[loop_i].normal[:] = curr_n + clnors.extend(curr_n) # then we have to go trough very important step they say, # as without validation we get wrong result for some normals @@ -334,13 +333,8 @@ def _create_piece( mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons)) # finally fill clnors from loops normals and apply them (taken from official Blenders scripts) - clnors = array.array('f', [0.0] * (len(mesh.loops) * 3)) - mesh.loops.foreach_get("normal", clnors) mesh.normals_split_custom_set(tuple(zip(*(iter(clnors),) * 3))) - mesh.use_auto_smooth = True - mesh.auto_smooth_angle = 3.14 - mesh.free_normals_split() else: # set polygons to use smooth representation only mesh.polygons.foreach_set("use_smooth", [True] * len(mesh.polygons)) diff --git a/addon/io_scs_tools/internals/open_gl/core.py b/addon/io_scs_tools/internals/open_gl/core.py index 5d1655a..b77ed45 100644 --- a/addon/io_scs_tools/internals/open_gl/core.py +++ b/addon/io_scs_tools/internals/open_gl/core.py @@ -137,10 +137,10 @@ def _draw_3dview_report(window, area, region): break # draw BT banner - (bindcode, width, height) = _Show3DViewReport.get_scs_banner_img_data(window) + (texture, width, height) = _Show3DViewReport.get_scs_banner_img_data(window) gpu.state.blend_set("ALPHA") - draw_texture_2d(bindcode, (pos_x - 5, pos_y), width, height) + draw_texture_2d(texture, (pos_x - 5, pos_y), width, height) gpu.state.blend_set("NONE") # draw control buttons, if controls are enabled diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py index f52db34..6c51749 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py @@ -29,7 +29,6 @@ class BakedSpecAddEnv(BakedSpec, StdAddEnv): MASK_TEX_NODE = "MaskTex" MASK1_TEX_NODE = "Mask1Tex" - MASK_LIN_TO_SRGB_NODE = "MaskLinearToSRGB" BASE_PAINT_MULT_NODE = "BasePaintMult" @@ -74,25 +73,16 @@ def init(node_tree): # set strength multiplier to 2 for better visualization in Blender node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE].inputs['Strength Multiplier'].default_value = 2.0 - # node creation mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") mask_tex_n.name = mask_tex_n.label = BakedSpecAddEnv.MASK_TEX_NODE mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2200) mask_tex_n.width = 140 - mask_lin_to_srgb_n = node_tree.nodes.new("ShaderNodeGroup") - mask_lin_to_srgb_n.name = mask_lin_to_srgb_n.label = BakedSpecAddEnv.MASK_LIN_TO_SRGB_NODE - mask_lin_to_srgb_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 2100) - mask_lin_to_srgb_n.node_tree = linear_to_srgb_ng.get_node_group() - - # links creation node_tree.links.new(uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) - node_tree.links.new(mask_tex_n.outputs['Color'], mask_lin_to_srgb_n.inputs['Value']) - - node_tree.links.new(mask_lin_to_srgb_n.outputs['Value'], add_env_gn.inputs['Env Factor Color']) + node_tree.links.new(mask_tex_n.outputs['Color'], add_env_gn.inputs['Env Factor Color']) @staticmethod def init_paint(node_tree): @@ -132,7 +122,6 @@ def init_paint(node_tree): node_tree.links.new(mask1_tex_n.outputs['Color'], base_paint_mult_n.inputs['Fac']) node_tree.links.new(vcol_mult_n.outputs['Vector'], base_paint_mult_n.inputs['Color1']) node_tree.links.new(base_paint_mult_n.outputs['Color'], compose_lighting_n.inputs['Diffuse Color']) - @staticmethod @@ -158,9 +147,6 @@ def set_mask_texture_settings(node_tree, settings): """ _material_utils.set_texture_settings_to_node(node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE], settings) - # due the fact mask colorspace is linear, we have to manually switch to sRGB and then convert values by node, for effect to work correctly (blender don't like SCS format of linear DDS textures). - node_tree.nodes[BakedSpecAddEnv.MASK_TEX_NODE].image.colorspace_settings.name = 'sRGB' - @staticmethod def set_mask_uv(node_tree, uv_layer): """Set UV layer to mask texture in shader. @@ -196,9 +182,6 @@ def set_mask_1_texture_settings(node_tree, settings): """ _material_utils.set_texture_settings_to_node(node_tree.nodes[BakedSpecAddEnv.MASK1_TEX_NODE], settings) - # due the fact mask colorspace is linear, we have to manually switch to sRGB and then convert values by node, for effect to work correctly (blender don't like SCS format of linear DDS textures). - node_tree.nodes[BakedSpecAddEnv.MASK1_TEX_NODE].image.colorspace_settings.name = 'sRGB' - @staticmethod def set_mask_1_uv(node_tree, uv_layer): """Set UV layer to mask 1 texture in shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py index 8f02e4f..c36c8b9 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py @@ -69,12 +69,22 @@ def __create_node_group__(): blend_fac_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=BLEND_FACTOR_G) # inputs defining - blend_fac_g.inputs.new("NodeSocketFloat", "Speed") + blend_fac_g.interface.new_socket( + name = "Speed", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = blend_fac_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, start_pos_y) # outputs defining - blend_fac_g.outputs.new("NodeSocketColor", "Factor") + blend_fac_g.interface.new_socket( + name = "Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + output_n = blend_fac_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 9, start_pos_y) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py index fde4cb8..d79735b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py @@ -176,9 +176,6 @@ def set_mask_texture_settings(node_tree, settings): # due the fact uvs get clamped in vertex shader, we have to manually switch repeat on, for effect to work correctly node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE].extension = "REPEAT" - # due the fact uvs colorspace linear, we have to manually switch to sRGB, for effect to work correctly - node_tree.nodes[DifSpecAmodDifSpec.MASK_TEX_NODE].image.colorspace_settings.name = 'sRGB' - @staticmethod def set_over_uv(node_tree, uv_layer): """Set UV layer to overlying texture in shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py index a124be2..dfbd9aa 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py @@ -58,15 +58,32 @@ def __create_node_group__(): dec_blend_fac_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=DECAL_BLEND_FACTOR_G) # inputs defining - dec_blend_fac_g.inputs.new("NodeSocketFloat", "Vertex Alpha") - dec_blend_fac_g.inputs.new("NodeSocketFloat", "Factor1") - dec_blend_fac_g.inputs.new("NodeSocketFloat", "Factor2") + dec_blend_fac_g.interface.new_socket( + name = "Vertex Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + dec_blend_fac_g.interface.new_socket( + name = "Factor1", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + dec_blend_fac_g.interface.new_socket( + name = "Factor2", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) input_n = dec_blend_fac_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - dec_blend_fac_g.outputs.new("NodeSocketColor", "Factor") + dec_blend_fac_g.interface.new_socket( + name = "Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + output_n = dec_blend_fac_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py index 4c199d3..55295c5 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py @@ -55,15 +55,37 @@ def __create_group__(): detail_setup_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=DETAIL_SETUP_G) # inputs defining - detail_setup_g.inputs.new("NodeSocketFloat", "Fade From") - detail_setup_g.inputs.new("NodeSocketFloat", "Fade Range") - detail_setup_g.inputs.new("NodeSocketFloat", "Blend Bias") + detail_setup_g.interface.new_socket( + name = "Fade From", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + detail_setup_g.interface.new_socket( + name = "Fade Range", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + detail_setup_g.interface.new_socket( + name = "Blend Bias", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = detail_setup_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x, start_pos_y) # outputs defining - detail_setup_g.outputs.new("NodeSocketFloat", "Detail Strength") - detail_setup_g.outputs.new("NodeSocketFloat", "Blend Factor") + detail_setup_g.interface.new_socket( + name = "Detail Strength", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + detail_setup_g.interface.new_socket( + name = "Blend Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = detail_setup_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y) diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py b/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py index 34b1eed..d3397f0 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py @@ -58,20 +58,62 @@ def __create_node_group__(): rescale_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SKY_UV_RESCALE_G) # inputs defining - rescale_g.inputs.new("NodeSocketFloat", "Rescale Enabled") - rescale_g.inputs.new("NodeSocketFloat", "V Scale Base A") - rescale_g.inputs.new("NodeSocketFloat", "V Scale Base B") - rescale_g.inputs.new("NodeSocketFloat", "V Scale Over A") - rescale_g.inputs.new("NodeSocketFloat", "V Scale Over B") - rescale_g.inputs.new("NodeSocketVector", "UV") + rescale_g.interface.new_socket( + name = "Rescale Enabled", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + rescale_g.interface.new_socket( + name = "V Scale Base A", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + rescale_g.interface.new_socket( + name = "V Scale Base B", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + rescale_g.interface.new_socket( + name = "V Scale Over A", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + rescale_g.interface.new_socket( + name = "V Scale Over B", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + rescale_g.interface.new_socket( + name = "UV", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = rescale_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - rescale_g.outputs.new("NodeSocketVector", "UV Base A") - rescale_g.outputs.new("NodeSocketVector", "UV Base B") - rescale_g.outputs.new("NodeSocketVector", "UV Over A") - rescale_g.outputs.new("NodeSocketVector", "UV Over B") + rescale_g.interface.new_socket( + name = "UV Base A", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + rescale_g.interface.new_socket( + name = "UV Base B", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + rescale_g.interface.new_socket( + name = "UV Over A", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + rescale_g.interface.new_socket( + name = "UV Over B", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + output_n = rescale_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 9, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py index d46d202..6f7ca78 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py @@ -124,21 +124,74 @@ def __create_node_group__(): add_env_g.nodes.clear() # inputs defining - add_env_g.inputs.new("NodeSocketFloat", "Fresnel Type") - add_env_g.inputs.new("NodeSocketFloat", "Fresnel Scale") - add_env_g.inputs.new("NodeSocketFloat", "Fresnel Bias") - add_env_g.inputs.new("NodeSocketVector", "Normal Vector") - add_env_g.inputs.new("NodeSocketVector", "Reflection Normal Vector") - add_env_g.inputs.new("NodeSocketFloat", "Apply Fresnel") - add_env_g.inputs.new("NodeSocketColor", "Reflection Texture Color") - add_env_g.inputs.new("NodeSocketFloat", "Base Texture Alpha") - add_env_g.inputs.new("NodeSocketColor", "Env Factor Color") - add_env_g.inputs.new("NodeSocketColor", "Specular Color") - add_env_g.inputs.new("NodeSocketColor", "Weighted Color") - add_env_g.inputs.new("NodeSocketFloat", "Strength Multiplier") + add_env_g.interface.new_socket( + name = "Fresnel Type", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + add_env_g.interface.new_socket( + name = "Fresnel Scale", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + add_env_g.interface.new_socket( + name = "Fresnel Bias", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + add_env_g.interface.new_socket( + name = "Normal Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + add_env_g.interface.new_socket( + name = "Reflection Normal Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + add_env_g.interface.new_socket( + name = "Apply Fresnel", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + add_env_g.interface.new_socket( + name = "Reflection Texture Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + add_env_g.interface.new_socket( + name = "Base Texture Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + add_env_g.interface.new_socket( + name = "Env Factor Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + add_env_g.interface.new_socket( + name = "Specular Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + add_env_g.interface.new_socket( + name = "Weighted Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + add_env_g.interface.new_socket( + name = "Strength Multiplier", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + # outputs defining - add_env_g.outputs.new("NodeSocketColor", "Environment Addition Color") + add_env_g.interface.new_socket( + name = "Environment Addition Color", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) # node creation input_n = add_env_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py index 6c0b41e..4cff4c7 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py @@ -50,14 +50,32 @@ def __create_node_group__(): asafew_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=ASAFEW_G) # inputs defining - asafew_g.inputs.new("NodeSocketFloat", "Alpha") - asafew_g.inputs.new("NodeSocketFloat", "Factor1") - asafew_g.inputs.new("NodeSocketFloat", "Factor2") + asafew_g.interface.new_socket( + name = "Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + asafew_g.interface.new_socket( + name = "Factor1", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + asafew_g.interface.new_socket( + name = "Factor2", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = asafew_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - asafew_g.outputs.new("NodeSocketFloat", "Weighted Alpha") + asafew_g.interface.new_socket( + name = "Weighted Alpha", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = asafew_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 3, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py index 07966c7..0d7cf12 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py @@ -88,20 +88,62 @@ def __create_node_group__(): compose_light_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=COMPOSE_LIGHTING_G) # inputs defining - compose_light_g.inputs.new("NodeSocketFloat", "AddAmbient") - compose_light_g.inputs.new("NodeSocketColor", "Diffuse Color") - compose_light_g.inputs.new("NodeSocketColor", "Specular Color") - compose_light_g.inputs.new("NodeSocketColor", "Env Color") - compose_light_g.inputs.new("NodeSocketColor", "Diffuse Lighting") - compose_light_g.inputs.new("NodeSocketColor", "Specular Lighting") - compose_light_g.inputs.new("NodeSocketFloat", "Alpha") + compose_light_g.interface.new_socket( + name = "AddAmbient", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + compose_light_g.interface.new_socket( + name = "Diffuse Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + compose_light_g.interface.new_socket( + name = "Specular Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + compose_light_g.interface.new_socket( + name = "Env Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + compose_light_g.interface.new_socket( + name = "Diffuse Lighting", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + compose_light_g.interface.new_socket( + name = "Specular Lighting", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + compose_light_g.interface.new_socket( + name = "Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = compose_light_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, 0) # outputs defining - compose_light_g.outputs.new("NodeSocketShader", "Shader") - compose_light_g.outputs.new("NodeSocketColor", "Color") - compose_light_g.outputs.new("NodeSocketFloat", "Alpha") + compose_light_g.interface.new_socket( + name = "Shader", + in_out = "OUTPUT", + socket_type = "NodeSocketShader" + ) + compose_light_g.interface.new_socket( + name = "Color", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + compose_light_g.interface.new_socket( + name = "Alpha", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = compose_light_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 8, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py index 7abd9ea..8af2b36 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py @@ -47,15 +47,37 @@ def __create_fresnel_group__(): fresnel_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=FRESNEL_LEGACY_G) # inputs defining - fresnel_g.inputs.new("NodeSocketFloat", "Scale") - fresnel_g.inputs.new("NodeSocketFloat", "Bias") - fresnel_g.inputs.new("NodeSocketVector", "Normal Vector") - fresnel_g.inputs.new("NodeSocketVector", "Reflection Normal Vector") + fresnel_g.interface.new_socket( + name = "Scale", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fresnel_g.interface.new_socket( + name = "Bias", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fresnel_g.interface.new_socket( + name = "Normal Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + fresnel_g.interface.new_socket( + name = "Reflection Normal Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = fresnel_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - fresnel_g.outputs.new("NodeSocketFloat", "Fresnel Factor") + fresnel_g.interface.new_socket( + name = "Fresnel Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = fresnel_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 5, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py index fa5233c..eaece27 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py @@ -49,14 +49,32 @@ def __create_fresnel_group__(): pos_x_shift = 185 # inputs defining - fresnel_g.inputs.new("NodeSocketFloat", "Bias") - fresnel_g.inputs.new("NodeSocketVector", "Normal Vector") - fresnel_g.inputs.new("NodeSocketVector", "Reflection Normal Vector") + fresnel_g.interface.new_socket( + name = "Bias", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fresnel_g.interface.new_socket( + name = "Normal Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + fresnel_g.interface.new_socket( + name = "Reflection Normal Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = fresnel_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - fresnel_g.outputs.new("NodeSocketFloat", "Fresnel Factor") + fresnel_g.interface.new_socket( + name = "Fresnel Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = fresnel_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py index 15da572..ad7c9e3 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py @@ -66,14 +66,32 @@ def __create_node_group__(): lampmask_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=LAMPMASK_MIX_G) # inputs defining - lampmask_g.inputs.new("NodeSocketFloat", "Lampmask Tex Alpha") - lampmask_g.inputs.new("NodeSocketColor", "Lampmask Tex Color") - lampmask_g.inputs.new("NodeSocketVector", "UV Vector") + lampmask_g.interface.new_socket( + name = "Lampmask Tex Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + lampmask_g.interface.new_socket( + name = "Lampmask Tex Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + lampmask_g.interface.new_socket( + name = "UV Vector", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = lampmask_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - lampmask_g.outputs.new("NodeSocketColor", "Lampmask Addition Color") + lampmask_g.interface.new_socket( + name = "Lampmask Addition Color", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + output_n = lampmask_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 9, 0) @@ -283,7 +301,6 @@ def __init_vehicle_uv_bounding_nodes__(node_tree, vehicle_side, pos_x, pos_y): uv_in_bounds_n.name = vehicle_side.name + _IN_BOUNDS_SUFFIX uv_in_bounds_n.label = vehicle_side.name + _IN_BOUNDS_SUFFIX uv_in_bounds_n.location = (pos_x + 185, pos_y - 50) - uv_in_bounds_n.width_hidden = 100 uv_in_bounds_n.hide = True uv_in_bounds_n.operation = "LESS_THAN" @@ -321,7 +338,6 @@ def __init_traffic_light_uv_bounding_nodes__(node_tree, traffic_light_type, pos_ uv_x_in_bounds_n.name = traffic_light_type.name + "X" + _IN_BOUNDS_SUFFIX uv_x_in_bounds_n.label = traffic_light_type.name + "X" + _IN_BOUNDS_SUFFIX uv_x_in_bounds_n.location = (pos_x + 185, pos_y) - uv_x_in_bounds_n.width_hidden = 100 uv_x_in_bounds_n.hide = True uv_x_in_bounds_n.operation = "LESS_THAN" @@ -329,7 +345,6 @@ def __init_traffic_light_uv_bounding_nodes__(node_tree, traffic_light_type, pos_ uv_y_in_bounds_n.name = traffic_light_type.name + "Y" + _IN_BOUNDS_SUFFIX uv_y_in_bounds_n.label = traffic_light_type.name + "Y" + _IN_BOUNDS_SUFFIX uv_y_in_bounds_n.location = (pos_x + 185, pos_y - 50) - uv_y_in_bounds_n.width_hidden = 100 uv_y_in_bounds_n.hide = True uv_y_in_bounds_n.operation = "LESS_THAN" @@ -337,7 +352,6 @@ def __init_traffic_light_uv_bounding_nodes__(node_tree, traffic_light_type, pos_ uv_in_bounds_n.name = traffic_light_type.name + _IN_BOUNDS_SUFFIX uv_in_bounds_n.label = traffic_light_type.name + _IN_BOUNDS_SUFFIX uv_in_bounds_n.location = (pos_x + 185 * 2, pos_y) - uv_in_bounds_n.width_hidden = 100 uv_in_bounds_n.hide = True uv_in_bounds_n.operation = "MULTIPLY" @@ -386,7 +400,6 @@ def __init_vehicle_switch_nodes__(node_tree, a_output, r_output, g_output, b_out switch_n.name = lamp_type.name switch_n.label = lamp_type.name switch_n.location = (pos_x, pos_y) - switch_n.width_hidden = 100 switch_n.hide = True switch_n.operation = "MULTIPLY" switch_n.inputs[0].default_value = 0.0 @@ -498,7 +511,6 @@ def __init_aux_switch_nodes__(node_tree, a_output, r_output, g_output, lamp_type switch_n.name = lamp_type.name switch_n.label = lamp_type.name switch_n.location = (pos_x, pos_y) - switch_n.width_hidden = 100 switch_n.hide = True switch_n.operation = "MULTIPLY" switch_n.inputs[0].default_value = 0.0 @@ -553,7 +565,6 @@ def __init_traffic_light_switch_nodes__(node_tree, a_output, lamp_type, pos_x, p switch_n.name = lamp_type.name switch_n.label = lamp_type.name switch_n.location = (pos_x, pos_y) - switch_n.width_hidden = 100 switch_n.hide = True switch_n.operation = "MULTIPLY" switch_n.inputs[0].default_value = 0.0 diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py index 2008440..0f4b9d7 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py @@ -155,15 +155,50 @@ def __create_group__(): lighting_eval_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=LIGHTING_EVALUATOR_G) # inputs defining - lighting_eval_g.inputs.new("NodeSocketVector", "Normal Vector") # n_normal - lighting_eval_g.inputs.new("NodeSocketVector", "Incoming Vector") # n_eye - lighting_eval_g.inputs.new("NodeSocketFloat", "Shininess") # specular_exponent - lighting_eval_g.inputs.new("NodeSocketFloat", "Flat Lighting") # flat lighting switch, should be 0 or 1 + lighting_eval_g.interface.new_socket( + name = "Normal Vector", + description = "n_normal", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + lighting_eval_g.interface.new_socket( + name = "Incoming Vector", + description = "n_eye", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + lighting_eval_g.interface.new_socket( + name = "Shininess", + description = "specular_exponent", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + lighting_eval_g.interface.new_socket( + name = "Flat Lighting", + description = "Flat lighting switch, should be 0 or 1", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # outputs defining - lighting_eval_g.outputs.new("NodeSocketColor", "Diffuse Lighting") # final diffuse lighting - lighting_eval_g.outputs.new("NodeSocketColor", "Specular Lighting") # final specular lighting - lighting_eval_g.outputs.new("NodeSocketVector", "Normal") # bypassed normal, to have one access point to final normal + lighting_eval_g.interface.new_socket( + name = "Diffuse Lighting", + description = "Final Diffuse Lighting", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + lighting_eval_g.interface.new_socket( + name = "Specular Lighting", + description = "Final Specular Lighting", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + lighting_eval_g.interface.new_socket( + name = "Normal", + description = "Bypassed normal, to have one access point to final normal", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) else: # recreation diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py index 06b16f4..63a58f0 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py @@ -68,14 +68,21 @@ def __create_linear_to_srgb_group__(): start_pos_x = 0 pos_x_shift = 185 - # outputs defining # inputs defining - lin_to_srgb_g.inputs.new("NodeSocketFloat", "Value") + lin_to_srgb_g.interface.new_socket( + name = "Value", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) input_n = lin_to_srgb_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, 0) # outputs defining - lin_to_srgb_g.outputs.new("NodeSocketFloat", "Value") + lin_to_srgb_g.interface.new_socket( + name = "Value", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) output_n = lin_to_srgb_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 7, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py index a9dbb63..7863a22 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py @@ -58,16 +58,42 @@ def __create_node_group__(): mult2_mix_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=MULT2_MIX_G) # inputs defining - mult2_mix_g.inputs.new("NodeSocketFloat", "Base Alpha") - mult2_mix_g.inputs.new("NodeSocketColor", "Base Color") - mult2_mix_g.inputs.new("NodeSocketFloat", "Mult Alpha") - mult2_mix_g.inputs.new("NodeSocketColor", "Mult Color") + mult2_mix_g.interface.new_socket( + name = "Base Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + mult2_mix_g.interface.new_socket( + name = "Base Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + mult2_mix_g.interface.new_socket( + name = "Mult Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + mult2_mix_g.interface.new_socket( + name = "Mult Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + input_n = mult2_mix_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, start_pos_y) # outputs defining - mult2_mix_g.outputs.new("NodeSocketFloat", "Mix Alpha") - mult2_mix_g.outputs.new("NodeSocketColor", "Mix Color") + mult2_mix_g.interface.new_socket( + name = "Mix Alpha", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + mult2_mix_g.interface.new_socket( + name = "Mix Color", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + output_n = mult2_mix_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py index 15215bb..2c09a5f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py @@ -47,13 +47,27 @@ def __create_refl_normal_group__(): refl_normal_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=REFL_NORMAL_G) # inputs defining - refl_normal_g.inputs.new("NodeSocketVector", "Incoming") - refl_normal_g.inputs.new("NodeSocketVector", "Normal") + refl_normal_g.interface.new_socket( + name = "Incoming", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + refl_normal_g.interface.new_socket( + name = "Normal", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = refl_normal_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - refl_normal_g.outputs.new("NodeSocketVector", "Reflection Normal") + refl_normal_g.interface.new_socket( + name = "Reflection Normal", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + output_n = refl_normal_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py index d71f541..334a7bc 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py @@ -49,13 +49,26 @@ def __create_node_group__(): scs_uvs_combine_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SCS_UVS_COMBINE_G) # inputs defining - scs_uvs_combine_g.inputs.new("NodeSocketFloat", "U") - scs_uvs_combine_g.inputs.new("NodeSocketFloat", "V") + scs_uvs_combine_g.interface.new_socket( + name = "U", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + scs_uvs_combine_g.interface.new_socket( + name = "V", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = scs_uvs_combine_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - scs_uvs_combine_g.outputs.new("NodeSocketVector", "Vector") + scs_uvs_combine_g.interface.new_socket( + name = "Vector", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) output_n = scs_uvs_combine_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 4, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py index 0bc4f90..016fd97 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py @@ -49,13 +49,27 @@ def __create_node_group__(): scs_uvs_separate_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SCS_UVS_SEPARATE_G) # inputs defining - scs_uvs_separate_g.inputs.new("NodeSocketVector", "UV") + scs_uvs_separate_g.interface.new_socket( + name = "UV", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = scs_uvs_separate_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - scs_uvs_separate_g.outputs.new("NodeSocketFloat", "U") - scs_uvs_separate_g.outputs.new("NodeSocketFloat", "V") + scs_uvs_separate_g.interface.new_socket( + name = "U", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + scs_uvs_separate_g.interface.new_socket( + name = "V", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = scs_uvs_separate_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 4, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py index 0869ec2..dbcc63c 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py @@ -28,76 +28,60 @@ def get_node_group(): - """Gets node group. + """Gets node group for specular and shininess calculation. :return: node group :rtype: bpy.types.NodeGroup """ - if __group_needs_recreation__(): + if SPEC_TEXTURE_CALC_G not in bpy.data.node_groups: __create_node_group__() return bpy.data.node_groups[SPEC_TEXTURE_CALC_G] -def __group_needs_recreation__(): - """Tells if group needs recreation. - - :return: True group isn't up to date and has to be (re)created; False if group doesn't need to be (re)created - :rtype: bool - """ - # current checks: - # 1. group existence in blender data block - return SPEC_TEXTURE_CALC_G not in bpy.data.node_groups - def __create_node_group__(): - """Creates group. + """Creates specular and shininess group. Inputs: Color Outputs: Shininess, Specular """ - start_pos_x = 0 - start_pos_y = 0 - - pos_x_shift = 185 - - if SPEC_TEXTURE_CALC_G not in bpy.data.node_groups: # creation - - spec_txt_calc_n = bpy.data.node_groups.new(type="ShaderNodeTree", name=SPEC_TEXTURE_CALC_G) - - else: # recreation - - spec_txt_calc_n = bpy.data.node_groups[SPEC_TEXTURE_CALC_G] - - # delete all inputs and outputs - spec_txt_calc_n.inputs.clear() - spec_txt_calc_n.outputs.clear() - - # delete all old nodes and links as they will be recreated now with actual version - spec_txt_calc_n.nodes.clear() + spec_txt_calc_n = bpy.data.node_groups.new(type="ShaderNodeTree", name=SPEC_TEXTURE_CALC_G) # inputs defining - spec_txt_calc_n.inputs.new("NodeSocketColor", "Color") - - # outputs defining - spec_txt_calc_n.outputs.new("NodeSocketVector", "Shininess") - spec_txt_calc_n.outputs.new("NodeSocketVector", "Specular") + spec_txt_calc_n.interface.new_socket( + name = "Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) - # node creation input_n = spec_txt_calc_n.nodes.new("NodeGroupInput") - input_n.location = (start_pos_x, start_pos_y) + input_n.location = (0, 0) + + # outputs defining + spec_txt_calc_n.interface.new_socket( + name = "Shininess", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + spec_txt_calc_n.interface.new_socket( + name = "Specular", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) output_n = spec_txt_calc_n.nodes.new("NodeGroupOutput") - output_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y) + output_n.location = (185 * 3, 0) + # node creation color_to_rgb_n = spec_txt_calc_n.nodes.new("ShaderNodeSeparateColor") color_to_rgb_n.name = color_to_rgb_n.label = _COLOR_TO_RGB_NODE - color_to_rgb_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y) + color_to_rgb_n.location = (185, 0) color_to_rgb_n.mode = "RGB" shininess_mult_n = spec_txt_calc_n.nodes.new("ShaderNodeVectorMath") shininess_mult_n.name = shininess_mult_n.label = _SHININESS_MULT - shininess_mult_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 100) + shininess_mult_n.location = (185 * 2, 100) shininess_mult_n.operation = "MULTIPLY" shininess_mult_n.inputs[1].default_value = (255,) * 3 diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py index 2c333cd..f75a54e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py @@ -66,8 +66,19 @@ def __create_vcolor_group__(): vcol_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=VCOLOR_G) # outputs defining - vcol_g.outputs.new("NodeSocketColor", "Vertex Color") - vcol_g.outputs.new("NodeSocketFloat", "Vertex Color Alpha") + vcol_g.interface.new_socket( + name = "Vertex Color", + description = "Vertex color output", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + vcol_g.interface.new_socket( + name = "Vertex Color Alpha", + description = "Vertex color output", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = vcol_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 5, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py index 426ed10..507a900 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py @@ -55,15 +55,37 @@ def __create_group__(): detail_setup_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=MIX_FACTOR_G) # inputs defining - detail_setup_g.inputs.new("NodeSocketFloat", "Near Distance") - detail_setup_g.inputs.new("NodeSocketFloat", "Far Distance") - detail_setup_g.inputs.new("NodeSocketFloat", "Scramble Distance") + detail_setup_g.interface.new_socket( + name = "Near Distance", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + detail_setup_g.interface.new_socket( + name = "Far Distance", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + detail_setup_g.interface.new_socket( + name = "Scramble Distance", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = detail_setup_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x, start_pos_y) # outputs defining - detail_setup_g.outputs.new("NodeSocketFloat", "Mix Factor") - detail_setup_g.outputs.new("NodeSocketFloat", "Scramble Mix Factor") + detail_setup_g.interface.new_socket( + name = "Mix Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + detail_setup_g.interface.new_socket( + name = "Scramble Mix Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = detail_setup_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y) diff --git a/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py b/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py index 63041c2..6ab0d43 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py @@ -74,16 +74,42 @@ def __create_node_group__(): water_stream_ng = bpy.data.node_groups.new(type="ShaderNodeTree", name=WATER_STREAM_G) # inputs defining - water_stream_ng.inputs.new("NodeSocketVector", "Yaw0") - water_stream_ng.inputs.new("NodeSocketFloat", "Speed0") - water_stream_ng.inputs.new("NodeSocketVector", "Yaw1") - water_stream_ng.inputs.new("NodeSocketFloat", "Speed1") + water_stream_ng.interface.new_socket( + name = "Yaw0", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + water_stream_ng.interface.new_socket( + name = "Speed0", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + water_stream_ng.interface.new_socket( + name = "Yaw1", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + water_stream_ng.interface.new_socket( + name = "Speed1", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = water_stream_ng.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, start_pos_y) # outputs defining - water_stream_ng.outputs.new("NodeSocketVector", "Stream0") - water_stream_ng.outputs.new("NodeSocketVector", "Stream1") + water_stream_ng.interface.new_socket( + name = "Stream0", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + water_stream_ng.interface.new_socket( + name = "Stream1", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + output_n = water_stream_ng.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y) diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py b/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py index ae85fbd..09f1d75 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py @@ -50,13 +50,27 @@ def __create_node_group__(): final_uv_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=WINDOW_FINAL_UV_G) # inputs defining - final_uv_g.inputs.new("NodeSocketFloat", "UV") - final_uv_g.inputs.new("NodeSocketFloat", "Factor") + final_uv_g.interface.new_socket( + name = "UV", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + final_uv_g.interface.new_socket( + name = "Factor", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = final_uv_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - final_uv_g.outputs.new("NodeSocketFloat", "Final UV") + final_uv_g.interface.new_socket( + name = "Final UV", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = final_uv_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py index 1bd24c9..c35c67a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py @@ -50,13 +50,27 @@ def __create_node_group__(): offset_factor_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=WINDOW_OFFSET_FACTOR_G) # inputs defining - offset_factor_g.inputs.new("NodeSocketFloat", "WndToEye Up") - offset_factor_g.inputs.new("NodeSocketFloat", "WndToEye Direction") + offset_factor_g.interface.new_socket( + name = "WndToEye Up", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + offset_factor_g.interface.new_socket( + name = "WndToEye Direction", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + input_n = offset_factor_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - offset_factor_g.outputs.new("NodeSocketFloat", "Offset Factor") + offset_factor_g.interface.new_socket( + name = "Offset Factor", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + output_n = offset_factor_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py b/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py index 5bb8046..8b1595d 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py @@ -56,14 +56,32 @@ def __create_node_group__(): uv_offset_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=WINDOW_UV_OFFSET_G) # inputs defining - uv_offset_g.inputs.new("NodeSocketVector", "UV") - uv_offset_g.inputs.new("NodeSocketVector", "Normal") - uv_offset_g.inputs.new("NodeSocketVector", "Incoming") + uv_offset_g.interface.new_socket( + name = "UV", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + uv_offset_g.interface.new_socket( + name = "Normal", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + uv_offset_g.interface.new_socket( + name = "Incoming", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = uv_offset_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - uv_offset_g.outputs.new("NodeSocketVector", "UV Final") + uv_offset_g.interface.new_socket( + name = "UV Final", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + output_n = uv_offset_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 12, 0) diff --git a/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py b/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py index 868c66f..26e83f5 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py @@ -84,16 +84,48 @@ def __create_node_group__(): fadesheet_compute_g.nodes.clear() # inputs defining - fadesheet_compute_g.inputs.new("NodeSocketFloat", "FPS") - fadesheet_compute_g.inputs.new("NodeSocketFloat", "FramesRow") - fadesheet_compute_g.inputs.new("NodeSocketFloat", "FramesTotal") - fadesheet_compute_g.inputs.new("NodeSocketVector", "FrameSize") - fadesheet_compute_g.inputs.new("NodeSocketVector", "UV") + fadesheet_compute_g.interface.new_socket( + name = "FPS", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fadesheet_compute_g.interface.new_socket( + name = "FramesRow", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fadesheet_compute_g.interface.new_socket( + name = "FramesTotal", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fadesheet_compute_g.interface.new_socket( + name = "FrameSize", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + fadesheet_compute_g.interface.new_socket( + name = "UV", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) # outputs defining - fadesheet_compute_g.outputs.new("NodeSocketVector", "UV0") - fadesheet_compute_g.outputs.new("NodeSocketVector", "UV1") - fadesheet_compute_g.outputs.new("NodeSocketFloat", "FrameBlend") + fadesheet_compute_g.interface.new_socket( + name = "UV0", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + fadesheet_compute_g.interface.new_socket( + name = "UV1", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) + fadesheet_compute_g.interface.new_socket( + name = "FrameBlend", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) # node creation input_n = fadesheet_compute_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py b/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py index e029df7..21d079b 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py @@ -83,14 +83,38 @@ def __create_node_group__(): fadesheet_compute_g.nodes.clear() # inputs defining - fadesheet_compute_g.inputs.new("NodeSocketFloat", "FPS") - fadesheet_compute_g.inputs.new("NodeSocketFloat", "FramesRow") - fadesheet_compute_g.inputs.new("NodeSocketFloat", "FramesTotal") - fadesheet_compute_g.inputs.new("NodeSocketVector", "FrameSize") - fadesheet_compute_g.inputs.new("NodeSocketVector", "UV") + fadesheet_compute_g.interface.new_socket( + name = "FPS", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fadesheet_compute_g.interface.new_socket( + name = "FramesRow", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fadesheet_compute_g.interface.new_socket( + name = "FramesTotal", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + fadesheet_compute_g.interface.new_socket( + name = "FrameSize", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + fadesheet_compute_g.interface.new_socket( + name = "UV", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) # outputs defining - fadesheet_compute_g.outputs.new("NodeSocketVector", "UV") + fadesheet_compute_g.interface.new_socket( + name = "UV", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) # node creation input_n = fadesheet_compute_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py index 801e674..f6f5ae5 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py @@ -59,13 +59,27 @@ def __create_node_group__(): nmap_dds16_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=TSNMAP_DDS16_G) # inputs defining - nmap_dds16_g.inputs.new("NodeSocketColor", "Color") + nmap_dds16_g.interface.new_socket( + name = "Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + input_n = nmap_dds16_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - nmap_dds16_g.outputs.new("NodeSocketFloat", "Strength") - nmap_dds16_g.outputs.new("NodeSocketColor", "Color") + nmap_dds16_g.interface.new_socket( + name = "Strength", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + nmap_dds16_g.interface.new_socket( + name = "Color", + in_out = "OUTPUT", + socket_type = "NodeSocketColor" + ) + output_n = nmap_dds16_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py index 17520e2..c8b805a 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py @@ -72,14 +72,31 @@ def __create_nmap_scale_group__(): nmap_scale_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=TSNMAP_SCALE_G) # inputs defining - nmap_scale_g.inputs.new("NodeSocketColor", "NMap Tex Color") - nmap_scale_g.inputs.new("NodeSocketVector", "Original Normal") - nmap_scale_g.inputs.new("NodeSocketVector", "Modified Normal") + nmap_scale_g.interface.new_socket( + name = "NMap Tex Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + nmap_scale_g.interface.new_socket( + name = "Original Normal", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + nmap_scale_g.interface.new_socket( + name = "Modified Normal", + in_out = "INPUT", + socket_type = "NodeSocketVector" + ) + input_n = nmap_scale_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) # outputs defining - nmap_scale_g.outputs.new("NodeSocketVector", "Normal") + nmap_scale_g.interface.new_socket( + name = "Normal", + in_out = "OUTPUT", + socket_type = "NodeSocketVector" + ) output_n = nmap_scale_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py index cd7b95f..f10d1e7 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py @@ -81,11 +81,23 @@ def __create_node_group__(): alpha_test_g.nodes.clear() # inputs defining - alpha_test_g.inputs.new("NodeSocketShader", "Shader") - alpha_test_g.inputs.new("NodeSocketFloat", "Alpha") + alpha_test_g.interface.new_socket( + name = "Shader", + in_out = "INPUT", + socket_type = "NodeSocketShader" + ) + alpha_test_g.interface.new_socket( + name = "Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # outputs defining - alpha_test_g.outputs.new("NodeSocketShader", "Shader") + alpha_test_g.interface.new_socket( + name = "Shader", + in_out = "OUTPUT", + socket_type = "NodeSocketShader" + ) # node creation input_n = alpha_test_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py index 01ada43..4d376d9 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py @@ -84,12 +84,28 @@ def __create_node_group__(): animsheet_frame_idx_to_col_row_g.nodes.clear() # inputs defining - animsheet_frame_idx_to_col_row_g.inputs.new("NodeSocketFloat", "FrameIndex") - animsheet_frame_idx_to_col_row_g.inputs.new("NodeSocketFloat", "FramesRow") + animsheet_frame_idx_to_col_row_g.interface.new_socket( + name = "FrameIndex", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_frame_idx_to_col_row_g.interface.new_socket( + name = "FramesRow", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # outputs defining - animsheet_frame_idx_to_col_row_g.outputs.new("NodeSocketFloat", "ColIndex") - animsheet_frame_idx_to_col_row_g.outputs.new("NodeSocketFloat", "RowIndex") + animsheet_frame_idx_to_col_row_g.interface.new_socket( + name = "ColIndex", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_frame_idx_to_col_row_g.interface.new_socket( + name = "RowIndex", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) # node creation input_n = animsheet_frame_idx_to_col_row_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py index 8ede750..04d4ccf 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py @@ -84,13 +84,33 @@ def __create_node_group__(): animsheet_xfade_g.nodes.clear() # inputs defining - animsheet_xfade_g.inputs.new("NodeSocketFloat", "FPS") - animsheet_xfade_g.inputs.new("NodeSocketFloat", "FramesTotal") - animsheet_xfade_g.inputs.new("NodeSocketFloat", "FramesRow") + animsheet_xfade_g.interface.new_socket( + name = "FPS", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "FramesTotal", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "FramesRow", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # outputs defining - animsheet_xfade_g.outputs.new("NodeSocketFloat", "FrameX") - animsheet_xfade_g.outputs.new("NodeSocketFloat", "FrameY") + animsheet_xfade_g.interface.new_socket( + name = "FrameX", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "FrameY", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) # node creation input_n = animsheet_xfade_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py index 3af6197..1b97869 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py @@ -85,11 +85,23 @@ def __create_node_group__(): animsheet_loop_frame_g.nodes.clear() # inputs defining - animsheet_loop_frame_g.inputs.new("NodeSocketFloat", "FPS") - animsheet_loop_frame_g.inputs.new("NodeSocketFloat", "FramesTotal") + animsheet_loop_frame_g.interface.new_socket( + name = "FPS", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_loop_frame_g.interface.new_socket( + name = "FramesTotal", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # outputs defining - animsheet_loop_frame_g.outputs.new("NodeSocketFloat", "LoopFrame") + animsheet_loop_frame_g.interface.new_socket( + name = "LoopFrame", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) # node creation input_n = animsheet_loop_frame_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py index f52b241..ddd4eb4 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py @@ -89,16 +89,48 @@ def __create_node_group__(): animsheet_xfade_g.nodes.clear() # inputs defining - animsheet_xfade_g.inputs.new("NodeSocketFloat", "FPS") - animsheet_xfade_g.inputs.new("NodeSocketFloat", "FramesTotal") - animsheet_xfade_g.inputs.new("NodeSocketFloat", "FramesRow") + animsheet_xfade_g.interface.new_socket( + name = "FPS", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "FramesTotal", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "FramesRow", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # outputs defining - animsheet_xfade_g.outputs.new("NodeSocketFloat", "Frame0X") - animsheet_xfade_g.outputs.new("NodeSocketFloat", "Frame0Y") - animsheet_xfade_g.outputs.new("NodeSocketFloat", "Frame1X") - animsheet_xfade_g.outputs.new("NodeSocketFloat", "Frame1Y") - animsheet_xfade_g.outputs.new("NodeSocketFloat", "FrameBlend") + animsheet_xfade_g.interface.new_socket( + name = "Frame0X", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "Frame0Y", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "Frame1X", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "Frame1Y", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) + animsheet_xfade_g.interface.new_socket( + name = "FrameBlend", + in_out = "OUTPUT", + socket_type = "NodeSocketFloat" + ) # node creation input_n = animsheet_xfade_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py index b40ec0e..a17b2ec 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py @@ -81,10 +81,18 @@ def __create_node_group__(): blend_add_g.nodes.clear() # inputs defining - blend_add_g.inputs.new("NodeSocketShader", "Shader") + blend_add_g.interface.new_socket( + name = "Shader", + in_out = "INPUT", + socket_type = "NodeSocketShader" + ) # outputs defining - blend_add_g.outputs.new("NodeSocketShader", "Shader") + blend_add_g.interface.new_socket( + name = "Shader", + in_out = "OUTPUT", + socket_type = "NodeSocketShader" + ) # node creation input_n = blend_add_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py index 59cdd43..f01b0a5 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py @@ -81,10 +81,18 @@ def __create_node_group__(): blend_mult_g.nodes.clear() # inputs defining - blend_mult_g.inputs.new("NodeSocketShader", "Shader") + blend_mult_g.interface.new_socket( + name = "Shader", + in_out = "INPUT", + socket_type = "NodeSocketShader" + ) # outputs defining - blend_mult_g.outputs.new("NodeSocketShader", "Shader") + blend_mult_g.interface.new_socket( + name = "Shader", + in_out = "OUTPUT", + socket_type = "NodeSocketShader" + ) # node creation input_n = blend_mult_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py index c48af93..f13eb4e 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py @@ -85,15 +85,28 @@ def __create_node_group__(): output_shader_g.nodes.clear() # inputs defining - output_shader_g.inputs.new("NodeSocketColor", "Color") - output_shader_g.inputs.new("NodeSocketFloat", "Alpha") + output_shader_g.interface.new_socket( + name = "Color", + in_out = "INPUT", + socket_type = "NodeSocketColor" + ) + output_shader_g.interface.new_socket( + name = "Alpha", + in_out = "INPUT", + socket_type = "NodeSocketFloat" + ) # always set to full opaque by default since this behaviour is expected by shaders # since this behaviour is epxected by effects from before. - output_shader_g.inputs['Alpha'].default_value = 1 + output_shader_g.interface.items_tree['Alpha'].default_value = 1 + # outputs defining - output_shader_g.outputs.new("NodeSocketShader", "Shader") + output_shader_g.interface.new_socket( + name = "Shader", + in_out = "OUTPUT", + socket_type = "NodeSocketShader" + ) # node creation input_n = output_shader_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index 7b871e6..c5c09ba 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -308,11 +308,11 @@ def get_reflection_image(texture_path, report_invalid=False): camera.type = "PANO" camera.lens = 5 camera.sensor_width = 32 - camera.cycles.panorama_type = "EQUIRECTANGULAR" - camera.cycles.latitude_min = -pi * 0.5 - camera.cycles.latitude_max = pi * 0.5 - camera.cycles.longitude_min = pi - camera.cycles.longitude_max = -pi + camera.panorama_type = "EQUIRECTANGULAR" + camera.latitude_min = -pi * 0.5 + camera.latitude_max = pi * 0.5 + camera.longitude_min = pi + camera.longitude_max = -pi cam_obj = bpy.data.objects.new(camera.name, camera) cam_obj.location = (0,) * 3 @@ -896,7 +896,7 @@ def set_texture_settings_to_node(tex_node, settings): # linear colorspace if settings[0] == "1": - image.colorspace_settings.name = "Linear" + image.colorspace_settings.name = "Linear Rec.709" else: image.colorspace_settings.name = "sRGB" @@ -905,7 +905,7 @@ def set_texture_settings_to_node(tex_node, settings): if image.filepath[-4:] in (".tga", ".dds"): image.colorspace_settings.name = "Non-Color" elif image.filepath[-4:] == ".png" and image.is_float: - image.colorspace_settings.name = "Linear" + image.colorspace_settings.name = "Linear Rec.709" def has_valid_color_management(scene): From 272c2a44e6e14653f64fadc9d0425ac7af1db8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 11 Jan 2025 16:03:02 +0100 Subject: [PATCH 18/56] Old node replace, code tweaks * Replaced old ShaderNodeMixRGB with ShaderNodeMix * Some code tweaks (interface.new_socket entries was written in one line instead multi line) for better visualization * Added parts ended with "_c" to collisions (used e.g. in curves) * Added "_FACTOR" used in "piko.alldir" flavor to possible piece stream entries in pim files (not used in code for now) --- addon/io_scs_tools/exp/pim/piece_stream.py | 5 + .../internals/shaders/eut2/baked/add_env.py | 13 +-- .../shaders/eut2/dif_anim/__init__.py | 28 ++--- .../eut2/dif_anim/anim_blend_factor_ng.py | 19 ++-- .../eut2/dif_spec_amod_dif_spec/__init__.py | 38 +++---- .../decal_blend_factor_ng.py | 32 ++---- .../eut2/dif_spec_fade_dif_spec/__init__.py | 24 +++-- .../dif_spec_fade_dif_spec/detail_setup_ng.py | 48 +++------ .../__init__.py | 11 +- .../__init__.py | 22 ++-- .../eut2/dif_spec_over_dif_opac/__init__.py | 13 +-- .../dif_spec_weight_mult2_weight2/__init__.py | 37 ++++--- .../__init__.py | 49 +++++---- .../shaders/eut2/dif_weight_dif/__init__.py | 11 +- .../shaders/eut2/fakeshadow/__init__.py | 12 ++- .../shaders/eut2/interior/__init__.py | 23 ++-- .../shaders/eut2/interior/curtain.py | 12 ++- .../internals/shaders/eut2/sky/__init__.py | 94 ++++++++-------- .../shaders/eut2/sky/uv_rescale_ng.py | 67 +++--------- .../eut2/std_node_groups/add_env_ng.py | 102 +++++------------- .../eut2/std_node_groups/alpha_remap_ng.py | 30 ++---- .../std_node_groups/compose_lighting_ng.py | 67 +++--------- .../eut2/std_node_groups/fresnel_legacy_ng.py | 37 ++----- .../std_node_groups/fresnel_schlick_ng.py | 31 ++---- .../eut2/std_node_groups/lampmask_mixer_ng.py | 31 ++---- .../std_node_groups/lighting_evaluator_ng.py | 75 ++++--------- .../eut2/std_node_groups/linear_to_srgb_ng.py | 19 ++-- .../eut2/std_node_groups/mult2_mix_ng.py | 66 ++++-------- .../eut2/std_node_groups/refl_normal_ng.py | 25 ++--- .../std_node_groups/scs_uvs_combine_ng.py | 24 ++--- .../std_node_groups/scs_uvs_separate_ng.py | 25 ++--- .../std_node_groups/spec_texture_calc_ng.py | 25 ++--- .../eut2/std_node_groups/vcolor_input_ng.py | 17 +-- .../shaders/eut2/truckpaint/__init__.py | 84 ++++++++------- .../shaders/eut2/unlit_vcol_tex/__init__.py | 26 +++-- .../internals/shaders/eut2/water/__init__.py | 35 +++--- .../shaders/eut2/water/mix_factor_ng.py | 37 ++----- .../shaders/eut2/water/water_stream_ng.py | 43 ++------ .../shaders/eut2/window/window_final_uv_ng.py | 25 ++--- .../eut2/window/window_offset_factor_ng.py | 25 ++--- .../eut2/window/window_uv_offset_ng.py | 31 ++---- .../internals/shaders/flavors/awhite.py | 13 +-- .../flavors/fadesheet/fadesheet_compute_ng.py | 49 ++------- .../flavors/flipsheet/flipsheet_compute_ng.py | 36 ++----- .../shaders/flavors/nmap/dds16_ng.py | 25 ++--- .../shaders/flavors/nmap/scale_ng.py | 52 ++++----- .../shaders/std_node_groups/alpha_test_ng.py | 19 +--- .../animsheet_frame_idx_to_col_row_ng.py | 25 +---- .../std_node_groups/animsheet_frame_ng.py | 31 ++---- .../animsheet_loop_frame_ng.py | 19 +--- .../std_node_groups/animsheet_xfade_ng.py | 49 ++------- .../shaders/std_node_groups/blend_add_ng.py | 13 +-- .../shaders/std_node_groups/blend_mult_ng.py | 13 +-- .../std_node_groups/output_shader_ng.py | 20 +--- addon/io_scs_tools/utils/material.py | 4 +- 55 files changed, 614 insertions(+), 1192 deletions(-) diff --git a/addon/io_scs_tools/exp/pim/piece_stream.py b/addon/io_scs_tools/exp/pim/piece_stream.py index 3336a0b..8b779c5 100644 --- a/addon/io_scs_tools/exp/pim/piece_stream.py +++ b/addon/io_scs_tools/exp/pim/piece_stream.py @@ -33,6 +33,7 @@ class Types: RGBA = "_RGBA" UV = "_UV" # NOTE: there can be up to 9 uv streams TUV = "_TUV" # NOTE: there can be up to 9 tuv streams + FACTOR = "_FACTOR" # NOTE: used only in piko.alldir flavor __format = "" # defined by type of tag __tag = Types.POSITION @@ -66,6 +67,8 @@ def __init__(self, stream_type, index): self.__format = "FLOAT3" elif stream_type == Stream.Types.RGBA: self.__format = "FLOAT4" + elif stream_type == Stream.Types.FACTOR: + self.__format = "FLOAT4" elif stream_type == Stream.Types.UV: self.__tag_index = index self.__format = "FLOAT2" @@ -93,6 +96,8 @@ def add_entry(self, value): # return False # if self.__tag == Stream.Types.RGBA and len(value) != 4: # return False + # if self.__tag == Stream.Types.FACTOR and len(value) != 4: + # return False # if self.__tag == Stream.Types.UV and len(value) != 2: # return False diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py index 6c51749..b50dddc 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py @@ -110,18 +110,19 @@ def init_paint(node_tree): mask1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1150) mask1_tex_n.width = 140 - base_paint_mult_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_paint_mult_n = node_tree.nodes.new("ShaderNodeMix") base_paint_mult_n.name = base_paint_mult_n.label = BakedSpecAddEnv.BASE_PAINT_MULT_NODE base_paint_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1500) + base_paint_mult_n.data_type = "RGBA" base_paint_mult_n.blend_type = "MULTIPLY" - base_paint_mult_n.inputs['Fac'].default_value = 1 - base_paint_mult_n.inputs['Color2'].default_value = _convert_utils.to_node_color(_get_scs_globals().base_paint_color) + base_paint_mult_n.inputs['Factor'].default_value = 1 + base_paint_mult_n.inputs['B'].default_value = _convert_utils.to_node_color(_get_scs_globals().base_paint_color) # links creation node_tree.links.new(uvmap_n.outputs['UV'], mask1_tex_n.inputs['Vector']) - node_tree.links.new(mask1_tex_n.outputs['Color'], base_paint_mult_n.inputs['Fac']) - node_tree.links.new(vcol_mult_n.outputs['Vector'], base_paint_mult_n.inputs['Color1']) - node_tree.links.new(base_paint_mult_n.outputs['Color'], compose_lighting_n.inputs['Diffuse Color']) + node_tree.links.new(mask1_tex_n.outputs['Color'], base_paint_mult_n.inputs['Factor']) + node_tree.links.new(vcol_mult_n.outputs['Vector'], base_paint_mult_n.inputs['A']) + node_tree.links.new(base_paint_mult_n.outputs['Result'], compose_lighting_n.inputs['Diffuse Color']) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py index 4c81719..d70fcd4 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py @@ -84,14 +84,16 @@ def init(node_tree): over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1000) over_tex_n.width = 140 - base_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_mix_n = node_tree.nodes.new("ShaderNodeMix") base_over_mix_n.name = base_over_mix_n.label = DifAnim.BASE_OVER_MIX_NODE base_over_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + base_over_mix_n.data_type = "RGBA" base_over_mix_n.blend_type = "MIX" - base_over_a_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_a_mix_n = node_tree.nodes.new("ShaderNodeMix") base_over_a_mix_n.name = base_over_a_mix_n.label = DifAnim.BASE_OVER_AMIX_NODE - base_over_a_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1100) + base_over_a_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1050) + base_over_a_mix_n.data_type = "RGBA" base_over_a_mix_n.blend_type = "MIX" # links creation @@ -99,18 +101,18 @@ def init(node_tree): node_tree.links.new(over_tex_n.inputs['Vector'], sec_uvmap_n.outputs['UV']) # pass 1 - node_tree.links.new(base_over_mix_n.inputs['Fac'], blend_fac_gn.outputs['Factor']) - node_tree.links.new(base_over_mix_n.inputs['Color1'], base_tex_n.outputs['Color']) - node_tree.links.new(base_over_mix_n.inputs['Color2'], over_tex_n.outputs['Color']) + node_tree.links.new(base_over_mix_n.inputs['Factor'], blend_fac_gn.outputs['Factor']) + node_tree.links.new(base_over_mix_n.inputs['A'], base_tex_n.outputs['Color']) + node_tree.links.new(base_over_mix_n.inputs['B'], over_tex_n.outputs['Color']) - node_tree.links.new(base_over_a_mix_n.inputs['Fac'], blend_fac_gn.outputs['Factor']) - node_tree.links.new(base_over_a_mix_n.inputs['Color1'], base_tex_n.outputs['Alpha']) - node_tree.links.new(base_over_a_mix_n.inputs['Color2'], over_tex_n.outputs['Alpha']) + node_tree.links.new(base_over_a_mix_n.inputs['Factor'], blend_fac_gn.outputs['Factor']) + node_tree.links.new(base_over_a_mix_n.inputs['A'], base_tex_n.outputs['Alpha']) + node_tree.links.new(base_over_a_mix_n.inputs['B'], over_tex_n.outputs['Alpha']) # pass 2 - node_tree.links.new(vcol_mult_n.inputs[1], base_over_mix_n.outputs['Color']) + node_tree.links.new(vcol_mult_n.inputs[1], base_over_mix_n.outputs['Result']) - node_tree.links.new(opacity_n.inputs[0], base_over_a_mix_n.outputs['Color']) + node_tree.links.new(opacity_n.inputs[0], base_over_a_mix_n.outputs['Result']) @staticmethod def finalize(node_tree, material): @@ -300,8 +302,8 @@ def set_fadesheet_flavor(node_tree, switch_on): uvmap_n.outputs['UV'], base_tex_n.inputs[0], over_tex_n.inputs[0], - base_over_mix_n.inputs['Fac'], - base_over_amix_n.inputs['Fac']) + base_over_mix_n.inputs['Factor'], + base_over_amix_n.inputs['Factor']) else: fadesheet.delete(node_tree) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py index c36c8b9..1d7e83b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/anim_blend_factor_ng.py @@ -69,26 +69,19 @@ def __create_node_group__(): blend_fac_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=BLEND_FACTOR_G) # inputs defining - blend_fac_g.interface.new_socket( - name = "Speed", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + blend_fac_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Speed") + # outputs defining + blend_fac_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Factor") + + + # node creation input_n = blend_fac_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, start_pos_y) - # outputs defining - blend_fac_g.interface.new_socket( - name = "Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - output_n = blend_fac_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 9, start_pos_y) - # node creation anim_time_n = blend_fac_g.nodes.new("ShaderNodeValue") anim_time_n.name = anim_time_n.label = _ANIM_TIME_NODE anim_time_n.location = (start_pos_x, start_pos_y + 200) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py index d79735b..705a222 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py @@ -27,8 +27,6 @@ class DifSpecAmodDifSpec(DifSpec): SEC_UVMAP_NODE = "SecondUVMap" MASK_TEX_NODE = "MaskTex" OVER_TEX_NODE = "OverTex" - BLENDING_FACTOR_1 = "BlendingFactor1" - BLENDING_FACTOR_2 = "BlendingFactor2" DECAL_BLEND_FACTOR_NODE = "DecalBlendFactorGNode" MASK_VCOLOR_MIX_NODE = "MaskVertexColorMix" MASK_COLOR_MIX_NODE = "MaskColorMix" @@ -64,16 +62,6 @@ def init(node_tree): # node creation # - column -1 - - blending_factor_1_n = node_tree.nodes.new("ShaderNodeValue") - blending_factor_1_n.name = DifSpecAmodDifSpec.BLENDING_FACTOR_1 - blending_factor_1_n.label = DifSpecAmodDifSpec.BLENDING_FACTOR_1 - blending_factor_1_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) - - blending_factor_2_n = node_tree.nodes.new("ShaderNodeValue") - blending_factor_2_n.name = DifSpecAmodDifSpec.BLENDING_FACTOR_2 - blending_factor_2_n.label = DifSpecAmodDifSpec.BLENDING_FACTOR_2 - blending_factor_2_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1000) - sec_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uvmap_n.name = DifSpecAmodDifSpec.SEC_UVMAP_NODE sec_uvmap_n.label = DifSpecAmodDifSpec.SEC_UVMAP_NODE @@ -100,18 +88,20 @@ def init(node_tree): over_tex_n.width = 140 # - column 2 - - mask_vcol_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + mask_vcol_mix_n = node_tree.nodes.new("ShaderNodeMix") mask_vcol_mix_n.name = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE mask_vcol_mix_n.label = DifSpecAmodDifSpec.MASK_VCOLOR_MIX_NODE mask_vcol_mix_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) + mask_vcol_mix_n.data_type = "RGBA" mask_vcol_mix_n.blend_type = "MIX" - mask_vcol_mix_n.inputs['Color1'].default_value = (0,) * 3 + (1,) + mask_vcol_mix_n.inputs['A'].default_value = (0,) * 3 + (1,) # - column 3 - - mask_color_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + mask_color_mix_n = node_tree.nodes.new("ShaderNodeMix") mask_color_mix_n.name = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE mask_color_mix_n.label = DifSpecAmodDifSpec.MASK_COLOR_MIX_NODE mask_color_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1200) + mask_color_mix_n.data_type = "RGBA" mask_color_mix_n.blend_type = "MIX" @@ -119,22 +109,20 @@ def init(node_tree): # links creation # - Column -1 - node_tree.links.new(vcol_group_n.outputs['Vertex Color Alpha'], deacl_blend_fac_gn.inputs['Vertex Alpha']) - node_tree.links.new(blending_factor_1_n.outputs['Value'], deacl_blend_fac_gn.inputs['Factor1']) - node_tree.links.new(blending_factor_2_n.outputs['Value'], deacl_blend_fac_gn.inputs['Factor2']) node_tree.links.new(sec_uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) node_tree.links.new(sec_uvmap_n.outputs['UV'], over_tex_n.inputs['Vector']) # - Column 1 - - node_tree.links.new(base_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color1']) - node_tree.links.new(mask_tex_n.outputs['Color'], mask_vcol_mix_n.inputs['Color2']) - node_tree.links.new(over_tex_n.outputs['Color'], mask_color_mix_n.inputs['Color2']) - node_tree.links.new(deacl_blend_fac_gn.outputs['Factor'], mask_vcol_mix_n.inputs['Fac']) + node_tree.links.new(base_tex_n.outputs['Color'], mask_color_mix_n.inputs['A']) + node_tree.links.new(mask_tex_n.outputs['Color'], mask_vcol_mix_n.inputs['B']) + node_tree.links.new(over_tex_n.outputs['Color'], mask_color_mix_n.inputs['B']) + node_tree.links.new(deacl_blend_fac_gn.outputs['Factor'], mask_vcol_mix_n.inputs['Factor']) # - Column 2 - - node_tree.links.new(mask_vcol_mix_n.outputs['Color'], mask_color_mix_n.inputs['Fac']) + node_tree.links.new(mask_vcol_mix_n.outputs['Result'], mask_color_mix_n.inputs['Factor']) # - Column 3 - - node_tree.links.new(mask_color_mix_n.outputs['Color'], vcol_multi_n.inputs[1]) + node_tree.links.new(mask_color_mix_n.outputs['Result'], vcol_multi_n.inputs[1]) @staticmethod @@ -147,8 +135,8 @@ def set_aux0(node_tree, aux_property): :type aux_property: bpy.types.IDPropertyGroup """ - node_tree.nodes[DifSpecAmodDifSpec.BLENDING_FACTOR_1].outputs["Value"].default_value = aux_property[0]['value'] - node_tree.nodes[DifSpecAmodDifSpec.BLENDING_FACTOR_2].outputs["Value"].default_value = aux_property[1]['value'] + node_tree.nodes[DifSpecAmodDifSpec.DECAL_BLEND_FACTOR_NODE].inputs["Factor1"].default_value = aux_property[0]['value'] + node_tree.nodes[DifSpecAmodDifSpec.DECAL_BLEND_FACTOR_NODE].inputs["Factor2"].default_value = aux_property[1]['value'] @staticmethod def set_mask_texture(node_tree, image): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py index dfbd9aa..f39e74a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/decal_blend_factor_ng.py @@ -58,37 +58,21 @@ def __create_node_group__(): dec_blend_fac_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=DECAL_BLEND_FACTOR_G) # inputs defining - dec_blend_fac_g.interface.new_socket( - name = "Vertex Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - dec_blend_fac_g.interface.new_socket( - name = "Factor1", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - dec_blend_fac_g.interface.new_socket( - name = "Factor2", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + dec_blend_fac_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Vertex Alpha") + dec_blend_fac_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Factor1") + dec_blend_fac_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Factor2") + # outputs defining + dec_blend_fac_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Factor") + + + # nodes creation input_n = dec_blend_fac_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - dec_blend_fac_g.interface.new_socket( - name = "Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - output_n = dec_blend_fac_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) - - # nodes creation # - column 1 - map_vcol_a_shift_n = dec_blend_fac_g.nodes.new("ShaderNodeMapping") map_vcol_a_shift_n.name = map_vcol_a_shift_n.label = _MAP_VCOLA_SHIFT_NODE diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/__init__.py index 11e2592..960a196 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/__init__.py @@ -66,7 +66,7 @@ def init(node_tree): # node creation uv_scale_n = node_tree.nodes.new("ShaderNodeValue") uv_scale_n.name = uv_scale_n.label = DifSpecFadeDifSpec.UV_SCALE_NODE - uv_scale_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1200) + uv_scale_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) detail_uv_scaling_n = node_tree.nodes.new("ShaderNodeVectorMath") detail_uv_scaling_n.name = detail_uv_scaling_n.label = DifSpecFadeDifSpec.DETAIL_UV_SCALING_NODE @@ -83,14 +83,16 @@ def init(node_tree): detail_setup_group_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) detail_setup_group_n.node_tree = detail_setup_ng.get_node_group() - base_detail_mix_a_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_detail_mix_a_n = node_tree.nodes.new("ShaderNodeMix") base_detail_mix_a_n.name = base_detail_mix_a_n.label = DifSpecFadeDifSpec.BASE_DETAIL_MIX_A_NODE base_detail_mix_a_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1700) + base_detail_mix_a_n.data_type = "RGBA" base_detail_mix_a_n.blend_type = "MIX" - base_detail_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_detail_mix_n = node_tree.nodes.new("ShaderNodeMix") base_detail_mix_n.name = base_detail_mix_n.label = DifSpecFadeDifSpec.BASE_DETAIL_MIX_NODE base_detail_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1400) + base_detail_mix_n.data_type = "RGBA" base_detail_mix_n.blend_type = "MIX" # links creation @@ -101,19 +103,19 @@ def init(node_tree): node_tree.links.new(detail_tex_n.inputs['Vector'], detail_uv_scaling_n.outputs[0]) # pass 1 - node_tree.links.new(base_detail_mix_a_n.inputs['Fac'], detail_setup_group_n.outputs['Blend Factor']) - node_tree.links.new(base_detail_mix_a_n.inputs['Color1'], base_tex_n.outputs['Alpha']) - node_tree.links.new(base_detail_mix_a_n.inputs['Color2'], detail_tex_n.outputs['Alpha']) + node_tree.links.new(base_detail_mix_a_n.inputs['Factor'], detail_setup_group_n.outputs['Blend Factor']) + node_tree.links.new(base_detail_mix_a_n.inputs['A'], base_tex_n.outputs['Alpha']) + node_tree.links.new(base_detail_mix_a_n.inputs['B'], detail_tex_n.outputs['Alpha']) - node_tree.links.new(base_detail_mix_n.inputs['Fac'], detail_setup_group_n.outputs['Blend Factor']) - node_tree.links.new(base_detail_mix_n.inputs['Color1'], base_tex_n.outputs['Color']) - node_tree.links.new(base_detail_mix_n.inputs['Color2'], detail_tex_n.outputs['Color']) + node_tree.links.new(base_detail_mix_n.inputs['Factor'], detail_setup_group_n.outputs['Blend Factor']) + node_tree.links.new(base_detail_mix_n.inputs['A'], base_tex_n.outputs['Color']) + node_tree.links.new(base_detail_mix_n.inputs['B'], detail_tex_n.outputs['Color']) # pass 2 - node_tree.links.new(spec_mult_n.inputs[1], base_detail_mix_a_n.outputs['Color']) + node_tree.links.new(spec_mult_n.inputs[1], base_detail_mix_a_n.outputs['Result']) # pass 3 - node_tree.links.new(vcol_mult_n.inputs[1], base_detail_mix_n.outputs['Color']) + node_tree.links.new(vcol_mult_n.inputs[1], base_detail_mix_n.outputs['Result']) @staticmethod def set_detail_texture(node_tree, image): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py index 55295c5..a0f6df3 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_fade_dif_spec/detail_setup_ng.py @@ -55,41 +55,22 @@ def __create_group__(): detail_setup_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=DETAIL_SETUP_G) # inputs defining - detail_setup_g.interface.new_socket( - name = "Fade From", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - detail_setup_g.interface.new_socket( - name = "Fade Range", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - detail_setup_g.interface.new_socket( - name = "Blend Bias", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + detail_setup_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Fade From") + detail_setup_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Fade Range") + detail_setup_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Blend Bias") + # outputs defining + detail_setup_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Detail Strength") + detail_setup_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Blend Factor") + + + # group nodes input_n = detail_setup_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x, start_pos_y) - # outputs defining - detail_setup_g.interface.new_socket( - name = "Detail Strength", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - detail_setup_g.interface.new_socket( - name = "Blend Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = detail_setup_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y) - # group nodes camera_data_n = detail_setup_g.nodes.new("ShaderNodeCameraData") camera_data_n.location = (start_pos_x, start_pos_y + 100) @@ -131,14 +112,15 @@ def __create_group__(): detail_setup_g.links.new(output_n.inputs['Detail Strength'], equation_nodes[i].outputs[0]) - blend_factor_mix_n = detail_setup_g.nodes.new("ShaderNodeMixRGB") + blend_factor_mix_n = detail_setup_g.nodes.new("ShaderNodeMix") blend_factor_mix_n.name = blend_factor_mix_n.label = "BlendFactor" blend_factor_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y - 50) + blend_factor_mix_n.data_type = "RGBA" blend_factor_mix_n.blend_type = "MIX" - blend_factor_mix_n.inputs['Color2'].default_value = (0.0,) * 4 + blend_factor_mix_n.inputs['B'].default_value = (0.0,) * 4 # group links - detail_setup_g.links.new(blend_factor_mix_n.inputs['Fac'], input_n.outputs['Blend Bias']) - detail_setup_g.links.new(blend_factor_mix_n.inputs['Color1'], equation_nodes[-1].outputs[0]) + detail_setup_g.links.new(blend_factor_mix_n.inputs['Factor'], input_n.outputs['Blend Bias']) + detail_setup_g.links.new(blend_factor_mix_n.inputs['A'], equation_nodes[-1].outputs[0]) - detail_setup_g.links.new(output_n.inputs['Blend Factor'], blend_factor_mix_n.outputs['Color']) + detail_setup_g.links.new(output_n.inputs['Blend Factor'], blend_factor_mix_n.outputs['Result']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py index 277571a..7c46ece 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py @@ -103,12 +103,13 @@ def init(node_tree): iamod_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) iamod_tex_n.width = 140 - iamod_scale_col_n = node_tree.nodes.new("ShaderNodeMixRGB") + iamod_scale_col_n = node_tree.nodes.new("ShaderNodeMix") iamod_scale_col_n.name = DifSpecMultDifIamodDifAddEnv.IAMOD_SCALE_NODE iamod_scale_col_n.label = DifSpecMultDifIamodDifAddEnv.IAMOD_SCALE_NODE iamod_scale_col_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1000) + iamod_scale_col_n.data_type = "RGBA" iamod_scale_col_n.blend_type = "MIX" - iamod_scale_col_n.inputs['Color2'].default_value = (1,) * 4 + iamod_scale_col_n.inputs['B'].default_value = (1,) * 4 iamod_multbase_col_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") iamod_multbase_col_mix_n.name = DifSpecMultDifIamodDifAddEnv.IAMOD_MULTBASE_COL_MIX_NODE @@ -122,11 +123,11 @@ def init(node_tree): node_tree.links.new(iamod_tex_n.inputs['Vector'], third_uvmap_n.outputs['UV']) - node_tree.links.new(iamod_scale_col_n.inputs['Fac'], vcol_group_n.outputs['Vertex Color Alpha']) - node_tree.links.new(iamod_scale_col_n.inputs['Color1'], iamod_tex_n.outputs['Color']) + node_tree.links.new(iamod_scale_col_n.inputs['Factor'], vcol_group_n.outputs['Vertex Color Alpha']) + node_tree.links.new(iamod_scale_col_n.inputs['A'], iamod_tex_n.outputs['Color']) node_tree.links.new(iamod_multbase_col_mix_n.inputs[0], mult_base_col_mix_n.outputs['Vector']) - node_tree.links.new(iamod_multbase_col_mix_n.inputs[1], iamod_scale_col_n.outputs['Color']) + node_tree.links.new(iamod_multbase_col_mix_n.inputs[1], iamod_scale_col_n.outputs['Result']) node_tree.links.new(vcol_mult_n.inputs[1], iamod_multbase_col_mix_n.outputs['Vector']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py index 39aba7e..3a11eac 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py @@ -81,19 +81,21 @@ def init(node_tree): iamod_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) iamod_tex_n.width = 140 - iamod_scale_col_n = node_tree.nodes.new("ShaderNodeMixRGB") + iamod_scale_col_n = node_tree.nodes.new("ShaderNodeMix") iamod_scale_col_n.name = DifSpecMultDifSpecIamodDifSpec.IAMOD_SCALE_NODE iamod_scale_col_n.label = DifSpecMultDifSpecIamodDifSpec.IAMOD_SCALE_NODE iamod_scale_col_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1000) + iamod_scale_col_n.data_type = "RGBA" iamod_scale_col_n.blend_type = "MIX" - iamod_scale_col_n.inputs['Color2'].default_value = (1,) * 4 + iamod_scale_col_n.inputs['B'].default_value = (1,) * 4 - iamod_scale_a_n = node_tree.nodes.new("ShaderNodeMixRGB") + iamod_scale_a_n = node_tree.nodes.new("ShaderNodeMix") iamod_scale_a_n.name = DifSpecMultDifSpecIamodDifSpec.IAMOD_SCALE_A_NODE iamod_scale_a_n.label = DifSpecMultDifSpecIamodDifSpec.IAMOD_SCALE_A_NODE iamod_scale_a_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1500) + iamod_scale_a_n.data_type = "RGBA" iamod_scale_a_n.blend_type = "MIX" - iamod_scale_a_n.inputs['Color2'].default_value = (1,) * 4 + iamod_scale_a_n.inputs['B'].default_value = (1,) * 4 iamod_multbase_col_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") iamod_multbase_col_mix_n.name = DifSpecMultDifSpecIamodDifSpec.IAMOD_MULTBASE_COL_MIX_NODE @@ -110,17 +112,17 @@ def init(node_tree): # links creation node_tree.links.new(iamod_tex_n.inputs['Vector'], third_uvmap_n.outputs['UV']) - node_tree.links.new(iamod_scale_col_n.inputs['Fac'], vcol_group_n.outputs['Vertex Color Alpha']) - node_tree.links.new(iamod_scale_col_n.inputs['Color1'], iamod_tex_n.outputs['Color']) + node_tree.links.new(iamod_scale_col_n.inputs['Factor'], vcol_group_n.outputs['Vertex Color Alpha']) + node_tree.links.new(iamod_scale_col_n.inputs['A'], iamod_tex_n.outputs['Color']) - node_tree.links.new(iamod_scale_a_n.inputs['Fac'], vcol_group_n.outputs['Vertex Color Alpha']) - node_tree.links.new(iamod_scale_a_n.inputs['Color1'], iamod_tex_n.outputs['Alpha']) + node_tree.links.new(iamod_scale_a_n.inputs['Factor'], vcol_group_n.outputs['Vertex Color Alpha']) + node_tree.links.new(iamod_scale_a_n.inputs['A'], iamod_tex_n.outputs['Alpha']) node_tree.links.new(iamod_multbase_col_mix_n.inputs[0], mult_base_col_mix_n.outputs[0]) - node_tree.links.new(iamod_multbase_col_mix_n.inputs[1], iamod_scale_col_n.outputs['Color']) + node_tree.links.new(iamod_multbase_col_mix_n.inputs[1], iamod_scale_col_n.outputs['Result']) node_tree.links.new(iamod_multbase_a_mix_n.inputs[0], mult_base_a_mix_n.outputs['Value']) - node_tree.links.new(iamod_multbase_a_mix_n.inputs[1], iamod_scale_a_n.outputs['Color']) + node_tree.links.new(iamod_multbase_a_mix_n.inputs[1], iamod_scale_a_n.outputs['Result']) node_tree.links.new(vcol_mult_n.inputs[1], iamod_multbase_col_mix_n.outputs[0]) node_tree.links.new(spec_mult_n.inputs[1], iamod_multbase_a_mix_n.outputs['Value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py index 9e66be4..525ba9a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py @@ -70,20 +70,21 @@ def init(node_tree): over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) over_tex_n.width = 140 - over_mix_node = node_tree.nodes.new("ShaderNodeMixRGB") + over_mix_node = node_tree.nodes.new("ShaderNodeMix") over_mix_node.name = DifSpecOverDifOpac.OVER_MIX_NODE over_mix_node.label = DifSpecOverDifOpac.OVER_MIX_NODE - over_mix_node.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + over_mix_node.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1350) + over_mix_node.data_type = "RGBA" over_mix_node.blend_type = "MIX" # links creation node_tree.links.new(over_tex_n.inputs['Vector'], sec_uv_n.outputs['UV']) - node_tree.links.new(over_mix_node.inputs['Fac'], over_tex_n.outputs['Alpha']) - node_tree.links.new(over_mix_node.inputs['Color1'], base_tex_n.outputs['Color']) - node_tree.links.new(over_mix_node.inputs['Color2'], over_tex_n.outputs['Color']) + node_tree.links.new(over_mix_node.inputs['Factor'], over_tex_n.outputs['Alpha']) + node_tree.links.new(over_mix_node.inputs['A'], base_tex_n.outputs['Color']) + node_tree.links.new(over_mix_node.inputs['B'], over_tex_n.outputs['Color']) - node_tree.links.new(vcol_mult_n.inputs[1], over_mix_node.outputs['Color']) + node_tree.links.new(vcol_mult_n.inputs[1], over_mix_node.outputs['Result']) @staticmethod def set_aux1(node_tree, aux_property): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py index 24ca419..0abde0b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py @@ -107,9 +107,10 @@ def init(node_tree): mult_1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 600) mult_1_tex_n.width = 140 - spec_col_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + spec_col_mix_n = node_tree.nodes.new("ShaderNodeMix") spec_col_mix_n.name = spec_col_mix_n.label = DifSpecWeightMult2Weight2.SPEC_COLOR_MIX_NODE - spec_col_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2100) + spec_col_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2150) + spec_col_mix_n.data_type = "RGBA" spec_col_mix_n.blend_type = "MIX" sec_mult2_mix_n = node_tree.nodes.new("ShaderNodeGroup") @@ -117,14 +118,16 @@ def init(node_tree): sec_mult2_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 800) sec_mult2_mix_n.node_tree = mult2_mix_ng.get_node_group() - combined_a_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + combined_a_mix_n = node_tree.nodes.new("ShaderNodeMix") combined_a_mix_n.name = combined_a_mix_n.label = DifSpecWeightMult2Weight2.COMBINED_ALPHA_MIX_NODE - combined_a_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1200) + combined_a_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1250) + combined_a_mix_n.data_type = "RGBA" combined_a_mix_n.blend_type = "MIX" - combined_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + combined_mix_n = node_tree.nodes.new("ShaderNodeMix") combined_mix_n.name = combined_mix_n.label = DifSpecWeightMult2Weight2.COMBINED_MIX_NODE combined_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1000) + combined_mix_n.data_type = "RGBA" combined_mix_n.blend_type = "MIX" # links creation @@ -135,9 +138,9 @@ def init(node_tree): node_tree.links.new(base_1_tex_n.inputs["Vector"], sec_uv_n.outputs["UV"]) # pass 1 - node_tree.links.new(spec_col_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(spec_col_mix_n.inputs["Color1"], spec_col_n.outputs["Color"]) - node_tree.links.new(spec_col_mix_n.inputs["Color2"], sec_spec_col_n.outputs["Color"]) + node_tree.links.new(spec_col_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(spec_col_mix_n.inputs["A"], spec_col_n.outputs["Color"]) + node_tree.links.new(spec_col_mix_n.inputs["B"], sec_spec_col_n.outputs["Color"]) node_tree.links.new(sec_mult2_mix_n.inputs["Base Alpha"], base_1_tex_n.outputs["Alpha"]) node_tree.links.new(sec_mult2_mix_n.inputs["Base Color"], base_1_tex_n.outputs["Color"]) @@ -145,19 +148,19 @@ def init(node_tree): node_tree.links.new(sec_mult2_mix_n.inputs["Mult Color"], mult_1_tex_n.outputs["Color"]) # pass 2 - node_tree.links.new(combined_a_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(combined_a_mix_n.inputs["Color1"], mult2_mix_gn.outputs["Mix Alpha"]) - node_tree.links.new(combined_a_mix_n.inputs["Color2"], sec_mult2_mix_n.outputs["Mix Alpha"]) + node_tree.links.new(combined_a_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(combined_a_mix_n.inputs["A"], mult2_mix_gn.outputs["Mix Alpha"]) + node_tree.links.new(combined_a_mix_n.inputs["B"], sec_mult2_mix_n.outputs["Mix Alpha"]) - node_tree.links.new(combined_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(combined_mix_n.inputs["Color1"], mult2_mix_gn.outputs["Mix Color"]) - node_tree.links.new(combined_mix_n.inputs["Color2"], sec_mult2_mix_n.outputs["Mix Color"]) + node_tree.links.new(combined_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(combined_mix_n.inputs["A"], mult2_mix_gn.outputs["Mix Color"]) + node_tree.links.new(combined_mix_n.inputs["B"], sec_mult2_mix_n.outputs["Mix Color"]) # pass 3 - node_tree.links.new(spec_mult_n.inputs[0], spec_col_mix_n.outputs["Color"]) - node_tree.links.new(spec_mult_n.inputs[1], combined_a_mix_n.outputs["Color"]) + node_tree.links.new(spec_mult_n.inputs[0], spec_col_mix_n.outputs["Result"]) + node_tree.links.new(spec_mult_n.inputs[1], combined_a_mix_n.outputs["Result"]) - node_tree.links.new(vcol_mult_n.inputs[1], combined_mix_n.outputs["Color"]) + node_tree.links.new(vcol_mult_n.inputs[1], combined_mix_n.outputs["Result"]) @staticmethod def set_reflection2(node_tree, value): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py index 11caea1..400280a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py @@ -90,24 +90,29 @@ def init(node_tree): over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) over_tex_n.width = 140 - sec_spec_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + sec_spec_mix_n = node_tree.nodes.new("ShaderNodeMix") sec_spec_mix_n.name = sec_spec_mix_n.label = DifSpecWeightWeightDifSpecWeight.SEC_SPEC_MIX_NODE sec_spec_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2100) + sec_spec_mix_n.data_type = "RGBA" sec_spec_mix_n.blend_type = "MIX" - sec_shininess_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + sec_shininess_mix_n = node_tree.nodes.new("ShaderNodeMix") sec_shininess_mix_n.name = sec_shininess_mix_n.label = DifSpecWeightWeightDifSpecWeight.SEC_SHININESS_MIX_NODE - sec_shininess_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2300) + sec_shininess_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2350) + sec_shininess_mix_n.data_type = "RGBA" sec_shininess_mix_n.blend_type = "MIX" - base_over_a_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_a_mix_n = node_tree.nodes.new("ShaderNodeMix") base_over_a_mix_n.name = base_over_a_mix_n.label = DifSpecWeightWeightDifSpecWeight.BASE_OVER_A_MIX_NODE - base_over_a_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1900) + base_over_a_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1850) + base_over_a_mix_n.data_type = "RGBA" base_over_a_mix_n.blend_type = "MIX" - base_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_mix_n = node_tree.nodes.new("ShaderNodeMix") base_over_mix_n.name = base_over_mix_n.label = DifSpecWeightWeightDifSpecWeight.BASE_OVER_MIX_NODE base_over_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1350) + base_over_mix_n.data_type = "RGBA" + base_over_mix_n.blend_type = "MIX" vcol_spec_mul_n = node_tree.nodes.new("ShaderNodeVectorMath") vcol_spec_mul_n.name = vcol_spec_mul_n.label = DifSpecWeightWeightDifSpecWeight.VCOL_SPEC_MULT_NODE @@ -118,32 +123,32 @@ def init(node_tree): node_tree.links.new(over_tex_n.inputs["Vector"], sec_geom_n.outputs["UV"]) # pass 1 - node_tree.links.new(sec_shininess_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(sec_shininess_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(sec_spec_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(sec_spec_mix_n.inputs["Color1"], spec_col_n.outputs["Color"]) - node_tree.links.new(sec_spec_mix_n.inputs["Color2"], sec_spec_col_n.outputs["Color"]) + node_tree.links.new(sec_spec_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(sec_spec_mix_n.inputs["A"], spec_col_n.outputs["Color"]) + node_tree.links.new(sec_spec_mix_n.inputs["B"], sec_spec_col_n.outputs["Color"]) - node_tree.links.new(base_over_a_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(base_over_a_mix_n.inputs["Color1"], base_tex_n.outputs["Alpha"]) - node_tree.links.new(base_over_a_mix_n.inputs["Color2"], over_tex_n.outputs["Alpha"]) + node_tree.links.new(base_over_a_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(base_over_a_mix_n.inputs["A"], base_tex_n.outputs["Alpha"]) + node_tree.links.new(base_over_a_mix_n.inputs["B"], over_tex_n.outputs["Alpha"]) - node_tree.links.new(base_over_mix_n.inputs["Fac"], vcol_group_n.outputs["Vertex Color Alpha"]) - node_tree.links.new(base_over_mix_n.inputs["Color1"], base_tex_n.outputs["Color"]) - node_tree.links.new(base_over_mix_n.inputs["Color2"], over_tex_n.outputs["Color"]) + node_tree.links.new(base_over_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(base_over_mix_n.inputs["A"], base_tex_n.outputs["Color"]) + node_tree.links.new(base_over_mix_n.inputs["B"], over_tex_n.outputs["Color"]) # pass 2 - node_tree.links.new(spec_multi_n.inputs[0], sec_spec_mix_n.outputs["Color"]) - node_tree.links.new(spec_multi_n.inputs[1], base_over_a_mix_n.outputs["Color"]) + node_tree.links.new(spec_multi_n.inputs[0], sec_spec_mix_n.outputs["Result"]) + node_tree.links.new(spec_multi_n.inputs[1], base_over_a_mix_n.outputs["Result"]) - node_tree.links.new(vcol_multi_n.inputs[1], base_over_mix_n.outputs["Color"]) + node_tree.links.new(vcol_multi_n.inputs[1], base_over_mix_n.outputs["Result"]) # pass 3 node_tree.links.new(vcol_spec_mul_n.inputs[0], spec_multi_n.outputs[0]) node_tree.links.new(vcol_spec_mul_n.inputs[1], vcol_scale_n.outputs[0]) # pass 4 - node_tree.links.new(lighting_eval_n.inputs["Shininess"], sec_shininess_mix_n.outputs["Color"]) + node_tree.links.new(lighting_eval_n.inputs["Shininess"], sec_shininess_mix_n.outputs["Result"]) # pass 5 node_tree.links.new(compose_lighting_n.inputs["Specular Color"], vcol_spec_mul_n.outputs[0]) @@ -159,7 +164,7 @@ def set_shininess(node_tree, factor): :type factor: float """ - node_tree.nodes[DifSpecWeightWeightDifSpecWeight.SEC_SHININESS_MIX_NODE].inputs["Color1"].default_value = (factor,) * 4 + node_tree.nodes[DifSpecWeightWeightDifSpecWeight.SEC_SHININESS_MIX_NODE].inputs["A"].default_value = (factor,) * 4 @staticmethod def set_over_texture(node_tree, image): @@ -210,7 +215,7 @@ def set_aux3(node_tree, aux_property): node_tree.nodes[DifSpecWeightWeightDifSpecWeight.SEC_SPEC_COL_NODE].outputs["Color"].default_value = color factor = aux_property[3]["value"] - node_tree.nodes[DifSpecWeightWeightDifSpecWeight.SEC_SHININESS_MIX_NODE].inputs["Color2"].default_value = (factor,) * 4 + node_tree.nodes[DifSpecWeightWeightDifSpecWeight.SEC_SHININESS_MIX_NODE].inputs["B"].default_value = (factor,) * 4 @staticmethod def set_reflection2(node_tree, value): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py index 218ce38..b90ab7c 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py @@ -81,24 +81,25 @@ def init(node_tree): spec_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1900) spec_mult_n.operation = "MULTIPLY" - base_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_mix_n = node_tree.nodes.new("ShaderNodeMix") base_over_mix_n.name = base_over_mix_n.label = DifWeightDif.BASE_OVER_MIX_NODE base_over_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + base_over_mix_n.data_type = "RGBA" base_over_mix_n.blend_type = "MIX" # links creation node_tree.links.new(over_tex_n.inputs['Vector'], sec_uv_n.outputs['UV']) # pass 1 - node_tree.links.new(base_over_mix_n.inputs['Fac'], vcol_group_n.outputs['Vertex Color Alpha']) - node_tree.links.new(base_over_mix_n.inputs['Color1'], base_tex_n.outputs['Color']) - node_tree.links.new(base_over_mix_n.inputs['Color2'], over_tex_n.outputs['Color']) + node_tree.links.new(base_over_mix_n.inputs['Factor'], vcol_group_n.outputs['Vertex Color Alpha']) + node_tree.links.new(base_over_mix_n.inputs['A'], base_tex_n.outputs['Color']) + node_tree.links.new(base_over_mix_n.inputs['B'], over_tex_n.outputs['Color']) # pass 2 node_tree.links.new(spec_mult_n.inputs[0], spec_col_n.outputs[0]) node_tree.links.new(spec_mult_n.inputs[1], vcol_scale_n.outputs[0]) - node_tree.links.new(vcol_mult_n.inputs[1], base_over_mix_n.outputs['Color']) + node_tree.links.new(vcol_mult_n.inputs[1], base_over_mix_n.outputs['Result']) # pass to material node_tree.links.new(compose_lighting_n.inputs['Specular Color'], spec_mult_n.outputs[0]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py index 0049857..72f6844 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py @@ -51,20 +51,22 @@ def init(node_tree): wireframe_n.use_pixel_size = True wireframe_n.inputs['Size'].default_value = 2.0 - mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + mix_n = node_tree.nodes.new("ShaderNodeMix") mix_n.name = mix_n.label = Fakeshadow.MIX_NODE mix_n.location = (start_pos_x + pos_x_shift, start_pos_y) - mix_n.inputs['Color1'].default_value = (1, 1, 1, 1) # fakeshadow color - mix_n.inputs['Color2'].default_value = (0, 0, 0, 1) # wireframe color + mix_n.data_type = "RGBA" + mix_n.blend_type = "MIX" + mix_n.inputs['A'].default_value = (1, 1, 1, 1) # fakeshadow color + mix_n.inputs['B'].default_value = (0, 0, 0, 1) # wireframe color output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") output_n.name = output_n.label = Fakeshadow.OUTPUT_NODE output_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y) # links creation - node_tree.links.new(mix_n.inputs['Fac'], wireframe_n.outputs['Fac']) + node_tree.links.new(mix_n.inputs['Factor'], wireframe_n.outputs['Fac']) - node_tree.links.new(output_n.inputs['Surface'], mix_n.outputs['Color']) + node_tree.links.new(output_n.inputs['Surface'], mix_n.outputs['Result']) @staticmethod def set_shadow_bias(node_tree, value): diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py index c559882..e84a0d0 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py @@ -27,7 +27,6 @@ class InteriorLit(DifSpecAddEnv): VGCOLOR_MULT_NODE = "VertexGlassColorMultiplier" GLASS_COL_NODE = "GlassColor" GLASS_COL_MIX_NODE = "GlassColorMix" - GLASS_COL_FAC_NODE = "GlassColorFactor" LAYER0_TEX_NODE = "Layer0Tex" LAYER1_TEX_NODE = "Layer1Tex" MASK_TEX_NODE = "MaskTex" @@ -119,11 +118,6 @@ def init(node_tree): nmap_tex_n.width = 140 """ - # - column 2 - - glass_col_fac_n = node_tree.nodes.new("ShaderNodeValue") - glass_col_fac_n.name = glass_col_fac_n.label = InteriorLit.GLASS_COL_FAC_NODE - glass_col_fac_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 800) - # - column 3 - vgcol_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") vgcol_mult_n.name = vgcol_mult_n.label = InteriorLit.VGCOLOR_MULT_NODE @@ -131,9 +125,11 @@ def init(node_tree): vgcol_mult_n.operation = "MULTIPLY" # - column 4 - - glass_col_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + glass_col_mix_n = node_tree.nodes.new("ShaderNodeMix") glass_col_mix_n.name = glass_col_mix_n.label = InteriorLit.GLASS_COL_MIX_NODE glass_col_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1200) + glass_col_mix_n.data_type = "RGBA" + glass_col_mix_n.blend_type = "MIX" # links creation @@ -148,16 +144,13 @@ def init(node_tree): node_tree.links.new(base_tex_n.outputs['Color'], vgcol_mult_n.inputs[0]) node_tree.links.new(glass_col_n.outputs['Color'], vgcol_mult_n.inputs[1]) - node_tree.links.new(glass_col_n.outputs['Color'], glass_col_mix_n.inputs['Color2']) - - # - column 2 - - node_tree.links.new(glass_col_fac_n.outputs['Value'], glass_col_mix_n.inputs['Fac']) + node_tree.links.new(glass_col_n.outputs['Color'], glass_col_mix_n.inputs['B']) # - column 3 - - node_tree.links.new(vgcol_mult_n.outputs['Vector'], glass_col_mix_n.inputs['Color1']) + node_tree.links.new(vgcol_mult_n.outputs['Vector'], glass_col_mix_n.inputs['A']) # - column 4 - - node_tree.links.new(glass_col_mix_n.outputs['Color'], vcol_mult_n.inputs[1]) + node_tree.links.new(glass_col_mix_n.outputs['Result'], vcol_mult_n.inputs[1]) ### TEXTURES ### @staticmethod @@ -236,7 +229,9 @@ def set_aux2(node_tree, aux_property): node_tree.nodes[InteriorLit.GLASS_COL_NODE].outputs["Color"].default_value = color factor = aux_property[3]["value"] - node_tree.nodes[InteriorLit.GLASS_COL_FAC_NODE].outputs["Value"].default_value = factor + + + node_tree.nodes[InteriorLit.GLASS_COL_MIX_NODE].inputs['Factor'].default_value = factor @staticmethod def set_aux5(node_tree, aux_property): diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py b/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py index 6a58289..22ba32e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py @@ -66,9 +66,11 @@ def init(node_tree): over_tex_n.width = 140 # - column 2 - - base_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_over_mix_n = node_tree.nodes.new("ShaderNodeMix") base_over_mix_n.name = base_over_mix_n.label = InteriorCurtain.BASE_OVER_MIX_NODE base_over_mix_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) + base_over_mix_n.data_type = "RGBA" + base_over_mix_n.blend_type = "MIX" # links creation @@ -76,12 +78,12 @@ def init(node_tree): node_tree.links.new(sec_uv_n.outputs['UV'], over_tex_n.inputs['Vector']) # - column 1 - - node_tree.links.new(base_tex_n.outputs['Color'], base_over_mix_n.inputs['Color1']) - node_tree.links.new(over_tex_n.outputs['Color'], base_over_mix_n.inputs['Color2']) - node_tree.links.new(over_tex_n.outputs['Alpha'], base_over_mix_n.inputs['Fac']) + node_tree.links.new(base_tex_n.outputs['Color'], base_over_mix_n.inputs['A']) + node_tree.links.new(over_tex_n.outputs['Color'], base_over_mix_n.inputs['B']) + node_tree.links.new(over_tex_n.outputs['Alpha'], base_over_mix_n.inputs['Factor']) # - column 2 - - node_tree.links.new(base_over_mix_n.outputs['Color'], vgcol_mult_n.inputs[0]) + node_tree.links.new(base_over_mix_n.outputs['Result'], vgcol_mult_n.inputs[0]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py index 8b57386..7e4dc1e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py @@ -96,7 +96,7 @@ def init(node_tree): diff_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2000) for tex_type_i, tex_type in enumerate(texture_types.get()): - Sky.__create_texel_component_nodes__(node_tree, tex_type, (start_pos_x + pos_x_shift, start_pos_y + 1500 - tex_type_i * 450)) + Sky.__create_texel_component_nodes__(node_tree, tex_type, (start_pos_x + pos_x_shift, start_pos_y + 1500 - tex_type_i * 550)) base_a_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[0]] base_b_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[1]] @@ -119,34 +119,40 @@ def init(node_tree): diff_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 2000) diff_mult_n.operation = "MULTIPLY" - weather_base_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_base_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_base_mix_n.name = weather_base_mix_n.label = Sky.WEATHER_BASE_MIX_NODE - weather_base_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) + weather_base_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1400) + weather_base_mix_n.data_type = "RGBA" weather_base_mix_n.blend_type = "MIX" - weather_base_alpha_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_base_alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_base_alpha_mix_n.name = weather_base_alpha_mix_n.label = Sky.WEATHER_BASE_A_MIX_NODE - weather_base_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1300) + weather_base_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1150) + weather_base_alpha_mix_n.data_type = "RGBA" weather_base_alpha_mix_n.blend_type = "MIX" - weather_over_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_over_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_over_mix_n.name = weather_over_mix_n.label = Sky.WEATHER_OVER_MIX_NODE - weather_over_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 700) + weather_over_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 300) + weather_over_mix_n.data_type = "RGBA" weather_over_mix_n.blend_type = "MIX" - weather_over_alpha_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_over_alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_over_alpha_mix_n.name = weather_over_alpha_mix_n.label = Sky.WEATHER_OVER_A_MIX_NODE - weather_over_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 500) + weather_over_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 50) + weather_over_alpha_mix_n.data_type = "RGBA" weather_over_alpha_mix_n.blend_type = "MIX" - weather_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_mix_n.name = weather_mix_n.label = Sky.WEATHER_MIX_NODE - weather_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1100) + weather_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 850) + weather_mix_n.data_type = "RGBA" weather_mix_n.blend_type = "MIX" - weather_alpha_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + weather_alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_alpha_mix_n.name = weather_alpha_mix_n.label = Sky.WEATHER_A_MIX_NODE - weather_alpha_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 900) + weather_alpha_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 600) + weather_alpha_mix_n.data_type = "RGBA" weather_alpha_mix_n.blend_type = "MIX" weather_diff_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") @@ -156,7 +162,7 @@ def init(node_tree): opacity_stars_mix_n = node_tree.nodes.new("ShaderNodeVectorMath") opacity_stars_mix_n.name = opacity_stars_mix_n.label = Sky.OPACITY_STARS_MIX_NODE - opacity_stars_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 900) + opacity_stars_mix_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 700) opacity_stars_mix_n.operation = "MULTIPLY" opacity_stars_mix_n.inputs[1].default_value = (1.0,) * 3 @@ -184,30 +190,30 @@ def init(node_tree): node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs[0]) node_tree.links.new(diff_mult_n.inputs[1], vcol_mult_n.outputs[0]) - node_tree.links.new(weather_base_mix_n.inputs['Color1'], base_a_tex_final_n.outputs[0]) - node_tree.links.new(weather_base_mix_n.inputs['Color2'], base_b_tex_final_n.outputs[0]) + node_tree.links.new(weather_base_mix_n.inputs['A'], base_a_tex_final_n.outputs['Result']) + node_tree.links.new(weather_base_mix_n.inputs['B'], base_b_tex_final_n.outputs['Result']) - node_tree.links.new(weather_base_alpha_mix_n.inputs['Color1'], base_a_tex_final_alpha_n.outputs[0]) - node_tree.links.new(weather_base_alpha_mix_n.inputs['Color2'], base_b_tex_final_alpha_n.outputs[0]) + node_tree.links.new(weather_base_alpha_mix_n.inputs['A'], base_a_tex_final_alpha_n.outputs['Result']) + node_tree.links.new(weather_base_alpha_mix_n.inputs['B'], base_b_tex_final_alpha_n.outputs['Result']) - node_tree.links.new(weather_over_mix_n.inputs['Color1'], over_a_tex_final_n.outputs[0]) - node_tree.links.new(weather_over_mix_n.inputs['Color2'], over_b_tex_final_n.outputs[0]) + node_tree.links.new(weather_over_mix_n.inputs['A'], over_a_tex_final_n.outputs['Result']) + node_tree.links.new(weather_over_mix_n.inputs['B'], over_b_tex_final_n.outputs['Result']) - node_tree.links.new(weather_over_alpha_mix_n.inputs['Color1'], over_a_tex_final_alpha_n.outputs[0]) - node_tree.links.new(weather_over_alpha_mix_n.inputs['Color2'], over_b_tex_final_alpha_n.outputs[0]) + node_tree.links.new(weather_over_alpha_mix_n.inputs['A'], over_a_tex_final_alpha_n.outputs['Result']) + node_tree.links.new(weather_over_alpha_mix_n.inputs['B'], over_b_tex_final_alpha_n.outputs['Result']) # pass 5 - node_tree.links.new(weather_mix_n.inputs['Color1'], weather_base_mix_n.outputs[0]) - node_tree.links.new(weather_mix_n.inputs['Color2'], weather_over_mix_n.outputs[0]) + node_tree.links.new(weather_mix_n.inputs['A'], weather_base_mix_n.outputs['Result']) + node_tree.links.new(weather_mix_n.inputs['B'], weather_over_mix_n.outputs['Result']) - node_tree.links.new(weather_alpha_mix_n.inputs['Color1'], weather_base_alpha_mix_n.outputs[0]) - node_tree.links.new(weather_alpha_mix_n.inputs['Color2'], weather_over_alpha_mix_n.outputs[0]) + node_tree.links.new(weather_alpha_mix_n.inputs['A'], weather_base_alpha_mix_n.outputs['Result']) + node_tree.links.new(weather_alpha_mix_n.inputs['B'], weather_over_alpha_mix_n.outputs['Result']) # pass 6 node_tree.links.new(weather_diff_mult_n.inputs[0], diff_mult_n.outputs[0]) - node_tree.links.new(weather_diff_mult_n.inputs[1], weather_mix_n.outputs[0]) + node_tree.links.new(weather_diff_mult_n.inputs[1], weather_mix_n.outputs['Result']) - node_tree.links.new(opacity_stars_mix_n.inputs[0], weather_alpha_mix_n.outputs[0]) + node_tree.links.new(opacity_stars_mix_n.inputs[0], weather_alpha_mix_n.outputs['Result']) # pass 7 node_tree.links.new(out_shader_node.inputs['Color'], weather_diff_mult_n.outputs[0]) @@ -244,17 +250,19 @@ def __create_texel_component_nodes__(node_tree, tex_type, location): tex_n.location = (location[0], location[1]) tex_n.width = 140 - mix_col_n = node_tree.nodes.new("ShaderNodeMixRGB") + mix_col_n = node_tree.nodes.new("ShaderNodeMix") mix_col_n.name = mix_col_n.label = Sky.TEX_FINAL_MIX_NODE_PREFIX + tex_type - mix_col_n.location = (location[0] + pos_x_shift * 2, location[1] + 100) + mix_col_n.location = (location[0] + pos_x_shift * 2, location[1] + 150) + mix_col_n.data_type = "RGBA" mix_col_n.blend_type = "MIX" - mix_col_n.inputs['Color2'].default_value = (0.0,) * 4 + mix_col_n.inputs['B'].default_value = (0.0,) * 4 - mix_a_n = node_tree.nodes.new("ShaderNodeMixRGB") + mix_a_n = node_tree.nodes.new("ShaderNodeMix") mix_a_n.name = mix_a_n.label = Sky.TEX_FINAL_A_MIX_NODE_PREFIX + tex_type mix_a_n.location = (location[0] + pos_x_shift * 2, location[1] - 100) + mix_a_n.data_type = "RGBA" mix_a_n.blend_type = "MIX" - mix_a_n.inputs['Color2'].default_value = (0.0,) * 4 + mix_a_n.inputs['B'].default_value = (0.0,) * 4 @staticmethod def __create_texel_component_links__(node_tree, tex_type, uv_socket): @@ -279,10 +287,10 @@ def __create_texel_component_links__(node_tree, tex_type, uv_socket): node_tree.links.new(texel_obb_n.inputs[0], separate_uv_n.outputs['Y']) node_tree.links.new(tex_n.inputs[0], uv_socket) - node_tree.links.new(mix_col_n.inputs['Fac'], texel_obb_n.outputs[0]) - node_tree.links.new(mix_col_n.inputs['Color1'], tex_n.outputs['Color']) - node_tree.links.new(mix_a_n.inputs['Fac'], texel_obb_n.outputs[0]) - node_tree.links.new(mix_a_n.inputs['Color1'], tex_n.outputs['Alpha']) + node_tree.links.new(mix_col_n.inputs['Factor'], texel_obb_n.outputs[0]) + node_tree.links.new(mix_col_n.inputs['A'], tex_n.outputs['Color']) + node_tree.links.new(mix_a_n.inputs['Factor'], texel_obb_n.outputs[0]) + node_tree.links.new(mix_a_n.inputs['A'], tex_n.outputs['Alpha']) @staticmethod def finalize(node_tree, material): @@ -474,15 +482,15 @@ def set_aux0(node_tree, aux_property): profile_blend = _math_utils.clamp(aux_property[0]['value']) - node_tree.nodes[Sky.WEATHER_BASE_MIX_NODE].inputs['Fac'].default_value = profile_blend - node_tree.nodes[Sky.WEATHER_BASE_A_MIX_NODE].inputs['Fac'].default_value = profile_blend - node_tree.nodes[Sky.WEATHER_OVER_MIX_NODE].inputs['Fac'].default_value = profile_blend - node_tree.nodes[Sky.WEATHER_OVER_A_MIX_NODE].inputs['Fac'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_BASE_MIX_NODE].inputs['Factor'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_BASE_A_MIX_NODE].inputs['Factor'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_OVER_MIX_NODE].inputs['Factor'].default_value = profile_blend + node_tree.nodes[Sky.WEATHER_OVER_A_MIX_NODE].inputs['Factor'].default_value = profile_blend weather_blend = _math_utils.clamp(aux_property[1]['value']) - node_tree.nodes[Sky.WEATHER_MIX_NODE].inputs['Fac'].default_value = weather_blend - node_tree.nodes[Sky.WEATHER_A_MIX_NODE].inputs['Fac'].default_value = weather_blend + node_tree.nodes[Sky.WEATHER_MIX_NODE].inputs['Factor'].default_value = weather_blend + node_tree.nodes[Sky.WEATHER_A_MIX_NODE].inputs['Factor'].default_value = weather_blend if sky_stars.is_set(node_tree): stars_opacity = (_math_utils.clamp(aux_property[2]['value']),) * 3 diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py b/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py index d3397f0..6472ab2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/uv_rescale_ng.py @@ -58,66 +58,27 @@ def __create_node_group__(): rescale_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SKY_UV_RESCALE_G) # inputs defining - rescale_g.interface.new_socket( - name = "Rescale Enabled", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - rescale_g.interface.new_socket( - name = "V Scale Base A", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - rescale_g.interface.new_socket( - name = "V Scale Base B", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - rescale_g.interface.new_socket( - name = "V Scale Over A", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - rescale_g.interface.new_socket( - name = "V Scale Over B", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - rescale_g.interface.new_socket( - name = "UV", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + rescale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Rescale Enabled") + rescale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "V Scale Base A") + rescale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "V Scale Base B") + rescale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "V Scale Over A") + rescale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "V Scale Over B") + rescale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "UV") + # outputs defining + rescale_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV Base A") + rescale_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV Base B") + rescale_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV Over A") + rescale_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV Over B") + + + # create nodes input_n = rescale_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - rescale_g.interface.new_socket( - name = "UV Base A", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - rescale_g.interface.new_socket( - name = "UV Base B", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - rescale_g.interface.new_socket( - name = "UV Over A", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - rescale_g.interface.new_socket( - name = "UV Over B", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - output_n = rescale_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 9, 0) - # create nodes rescale_inv_n = rescale_g.nodes.new("ShaderNodeMath") rescale_inv_n.name = rescale_inv_n.label = _RESCALE_INV_NODE rescale_inv_n.location = (pos_x_shift * 2, 100) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py index 6f7ca78..742b608 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/add_env_ng.py @@ -124,74 +124,22 @@ def __create_node_group__(): add_env_g.nodes.clear() # inputs defining - add_env_g.interface.new_socket( - name = "Fresnel Type", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - add_env_g.interface.new_socket( - name = "Fresnel Scale", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - add_env_g.interface.new_socket( - name = "Fresnel Bias", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - add_env_g.interface.new_socket( - name = "Normal Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - add_env_g.interface.new_socket( - name = "Reflection Normal Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - add_env_g.interface.new_socket( - name = "Apply Fresnel", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - add_env_g.interface.new_socket( - name = "Reflection Texture Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - add_env_g.interface.new_socket( - name = "Base Texture Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - add_env_g.interface.new_socket( - name = "Env Factor Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - add_env_g.interface.new_socket( - name = "Specular Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - add_env_g.interface.new_socket( - name = "Weighted Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - add_env_g.interface.new_socket( - name = "Strength Multiplier", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Fresnel Type") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Fresnel Scale") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Fresnel Bias") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Normal Vector") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Reflection Normal Vector") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Apply Fresnel") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Reflection Texture Color") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Base Texture Alpha") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Env Factor Color") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Specular Color") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Weighted Color") + add_env_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Strength Multiplier") # outputs defining - add_env_g.interface.new_socket( - name = "Environment Addition Color", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) + add_env_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Environment Addition Color") + # node creation input_n = add_env_g.nodes.new("NodeGroupInput") @@ -228,9 +176,10 @@ def __create_node_group__(): refl_tex_mult_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y - 150) refl_tex_mult_n.operation = "MULTIPLY" - fresnel_type_mix_n = add_env_g.nodes.new("ShaderNodeMixRGB") + fresnel_type_mix_n = add_env_g.nodes.new("ShaderNodeMix") fresnel_type_mix_n.name = fresnel_type_mix_n.label = _FRESNEL_TYPE_MIX_NODE fresnel_type_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 200) + fresnel_type_mix_n.data_type = "RGBA" fresnel_type_mix_n.blend_type = "MIX" refl_tex_col_mult_n = add_env_g.nodes.new("ShaderNodeVectorMath") @@ -238,14 +187,15 @@ def __create_node_group__(): refl_tex_col_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y - 200) refl_tex_col_mult_n.operation = "MULTIPLY" - tex_fresnel_mult_n = add_env_g.nodes.new("ShaderNodeMixRGB") + tex_fresnel_mult_n = add_env_g.nodes.new("ShaderNodeMix") tex_fresnel_mult_n.name = tex_fresnel_mult_n.label = _TEX_FRESNEL_MULT_NODE tex_fresnel_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y) + tex_fresnel_mult_n.data_type = "RGBA" tex_fresnel_mult_n.blend_type = "MULTIPLY" global_env_factor_n = add_env_g.nodes.new("ShaderNodeValue") global_env_factor_n.name = global_env_factor_n.label = _GLOBAL_ENV_FACTOR_NODE - global_env_factor_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y - 200) + global_env_factor_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y - 250) global_env_mult_n = add_env_g.nodes.new("ShaderNodeVectorMath") global_env_mult_n.name = global_env_mult_n.label = _GLOBAL_ENV_MULT_NODE @@ -279,20 +229,20 @@ def __create_node_group__(): add_env_g.links.new(fresnel_schlick_n.inputs['Bias'], input_n.outputs['Fresnel Bias']) # pass 3 - add_env_g.links.new(fresnel_type_mix_n.inputs['Fac'], input_n.outputs['Fresnel Type']) - add_env_g.links.new(fresnel_type_mix_n.inputs['Color1'], fresnel_legacy_n.outputs['Fresnel Factor']) - add_env_g.links.new(fresnel_type_mix_n.inputs['Color2'], fresnel_schlick_n.outputs['Fresnel Factor']) + add_env_g.links.new(fresnel_type_mix_n.inputs['Factor'], input_n.outputs['Fresnel Type']) + add_env_g.links.new(fresnel_type_mix_n.inputs['A'], fresnel_legacy_n.outputs['Fresnel Factor']) + add_env_g.links.new(fresnel_type_mix_n.inputs['B'], fresnel_schlick_n.outputs['Fresnel Factor']) add_env_g.links.new(refl_tex_col_mult_n.inputs[0], refl_tex_mult_n.outputs[0]) add_env_g.links.new(refl_tex_col_mult_n.inputs[1], env_spec_mult_n.outputs[0]) # pass 4 - add_env_g.links.new(tex_fresnel_mult_n.inputs['Fac'], input_n.outputs['Apply Fresnel']) - add_env_g.links.new(tex_fresnel_mult_n.inputs['Color1'], refl_tex_col_mult_n.outputs[0]) - add_env_g.links.new(tex_fresnel_mult_n.inputs['Color2'], fresnel_type_mix_n.outputs['Color']) + add_env_g.links.new(tex_fresnel_mult_n.inputs['Factor'], input_n.outputs['Apply Fresnel']) + add_env_g.links.new(tex_fresnel_mult_n.inputs['A'], refl_tex_col_mult_n.outputs[0]) + add_env_g.links.new(tex_fresnel_mult_n.inputs['B'], fresnel_type_mix_n.outputs['Result']) # pass 5 - add_env_g.links.new(global_env_mult_n.inputs[0], tex_fresnel_mult_n.outputs['Color']) + add_env_g.links.new(global_env_mult_n.inputs[0], tex_fresnel_mult_n.outputs['Result']) add_env_g.links.new(global_env_mult_n.inputs[1], global_env_factor_n.outputs['Value']) # pass 6 diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py index 4cff4c7..3db1976 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/alpha_remap_ng.py @@ -50,32 +50,18 @@ def __create_node_group__(): asafew_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=ASAFEW_G) # inputs defining - asafew_g.interface.new_socket( - name = "Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - asafew_g.interface.new_socket( - name = "Factor1", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - asafew_g.interface.new_socket( - name = "Factor2", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + asafew_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha") + asafew_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Factor1") + asafew_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Factor2") + # outputs defining + asafew_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Weighted Alpha") + + + # node creation input_n = asafew_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - asafew_g.interface.new_socket( - name = "Weighted Alpha", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = asafew_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 3, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py index 0d7cf12..ffea647 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py @@ -88,66 +88,27 @@ def __create_node_group__(): compose_light_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=COMPOSE_LIGHTING_G) # inputs defining - compose_light_g.interface.new_socket( - name = "AddAmbient", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - compose_light_g.interface.new_socket( - name = "Diffuse Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - compose_light_g.interface.new_socket( - name = "Specular Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - compose_light_g.interface.new_socket( - name = "Env Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - compose_light_g.interface.new_socket( - name = "Diffuse Lighting", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - compose_light_g.interface.new_socket( - name = "Specular Lighting", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - compose_light_g.interface.new_socket( - name = "Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "AddAmbient") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Diffuse Color") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Specular Color") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Env Color") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Diffuse Lighting") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Specular Lighting") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha") + # outputs defining + compose_light_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") + compose_light_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Color") + compose_light_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Alpha") + + + # nodes creation input_n = compose_light_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, 0) - # outputs defining - compose_light_g.interface.new_socket( - name = "Shader", - in_out = "OUTPUT", - socket_type = "NodeSocketShader" - ) - compose_light_g.interface.new_socket( - name = "Color", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - compose_light_g.interface.new_socket( - name = "Alpha", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = compose_light_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 8, 0) - # nodes creation add_ambient_col_n = compose_light_g.nodes.new("ShaderNodeRGB") add_ambient_col_n.name = add_ambient_col_n.label = _ADD_AMBIENT_COL_NODE add_ambient_col_n.location = (start_pos_x + pos_x_shift * 1, 400) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py index 8af2b36..7a3db7e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_legacy_ng.py @@ -47,41 +47,22 @@ def __create_fresnel_group__(): fresnel_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=FRESNEL_LEGACY_G) # inputs defining - fresnel_g.interface.new_socket( - name = "Scale", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fresnel_g.interface.new_socket( - name = "Bias", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fresnel_g.interface.new_socket( - name = "Normal Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - fresnel_g.interface.new_socket( - name = "Reflection Normal Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Scale") + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Bias") + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Normal Vector") + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Reflection Normal Vector") + # outputs defining + fresnel_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Fresnel Factor") + + + # group nodes input_n = fresnel_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - fresnel_g.interface.new_socket( - name = "Fresnel Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = fresnel_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 5, 0) - # group nodes dot_n = fresnel_g.nodes.new("ShaderNodeVectorMath") dot_n.location = (185, 100) dot_n.operation = "DOT_PRODUCT" diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py index eaece27..88e71a0 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/fresnel_schlick_ng.py @@ -49,36 +49,21 @@ def __create_fresnel_group__(): pos_x_shift = 185 # inputs defining - fresnel_g.interface.new_socket( - name = "Bias", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fresnel_g.interface.new_socket( - name = "Normal Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - fresnel_g.interface.new_socket( - name = "Reflection Normal Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Bias") + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Normal Vector") + fresnel_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Reflection Normal Vector") + # outputs defining + fresnel_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Fresnel Factor") + + + # group nodes input_n = fresnel_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - fresnel_g.interface.new_socket( - name = "Fresnel Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = fresnel_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) - # group nodes dot_n = fresnel_g.nodes.new("ShaderNodeVectorMath") dot_n.location = (pos_x_shift, 100) dot_n.operation = "DOT_PRODUCT" diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py index ad7c9e3..93bb8ee 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py @@ -66,36 +66,21 @@ def __create_node_group__(): lampmask_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=LAMPMASK_MIX_G) # inputs defining - lampmask_g.interface.new_socket( - name = "Lampmask Tex Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - lampmask_g.interface.new_socket( - name = "Lampmask Tex Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - lampmask_g.interface.new_socket( - name = "UV Vector", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + lampmask_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Lampmask Tex Alpha") + lampmask_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Lampmask Tex Color") + lampmask_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "UV Vector") + # outputs defining + lampmask_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Lampmask Addition Color") + + + # nodes creation input_n = lampmask_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - lampmask_g.interface.new_socket( - name = "Lampmask Addition Color", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - output_n = lampmask_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 9, 0) - # nodes creation alpha_decode_n = lampmask_g.nodes.new("ShaderNodeMath") alpha_decode_n.name = alpha_decode_n.label = _TEX_COL_SEP_NODE alpha_decode_n.location = (pos_x_shift, 50) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py index 0f4b9d7..793500b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lighting_evaluator_ng.py @@ -41,7 +41,7 @@ _DIFF_FLAT_MULT_NODE = "DFlat=DiffuseLightColor*0.4" -_DIFF_FLAT_NODE = "FlatFilter" +_DIFF_FLAT_NODE = "DiffFlatFilter" _N_SUM_NODE = "NSum=IncomingVector+NLight" _N_HALF_NODE = "NHalf=normalize(NSum)" @@ -52,7 +52,7 @@ _SPEC_FACTOR_SMOOTH_NODE = "SpecFacSmooth=SpecFac*NDotLSpecCut" # clamp it! _SPEC_MULT_NODE = "S=SpecularLightColor*SpecFacSmooth" # goes to Specular Lighting output -_SPEC_FLAT_NODE = "FlatFilter" +_SPEC_FLAT_NODE = "SpecFlatFilter" def get_node_group(): @@ -155,50 +155,15 @@ def __create_group__(): lighting_eval_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=LIGHTING_EVALUATOR_G) # inputs defining - lighting_eval_g.interface.new_socket( - name = "Normal Vector", - description = "n_normal", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - lighting_eval_g.interface.new_socket( - name = "Incoming Vector", - description = "n_eye", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - lighting_eval_g.interface.new_socket( - name = "Shininess", - description = "specular_exponent", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - lighting_eval_g.interface.new_socket( - name = "Flat Lighting", - description = "Flat lighting switch, should be 0 or 1", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + lighting_eval_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Normal Vector", description = "n_normal") + lighting_eval_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Incoming Vector", description = "n_eye") + lighting_eval_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Shininess", description = "specular_exponent") + lighting_eval_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Flat Lighting", description = "Flat lighting switch, should be 0 or 1") # outputs defining - lighting_eval_g.interface.new_socket( - name = "Diffuse Lighting", - description = "Final Diffuse Lighting", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - lighting_eval_g.interface.new_socket( - name = "Specular Lighting", - description = "Final Specular Lighting", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - lighting_eval_g.interface.new_socket( - name = "Normal", - description = "Bypassed normal, to have one access point to final normal", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) + lighting_eval_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Diffuse Lighting", description = "Final Diffuse Lighting") + lighting_eval_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Specular Lighting", description = "Final Specular Lighting") + lighting_eval_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Normal", description = "Bypassed normal, to have one access point to final normal") else: # recreation @@ -266,9 +231,10 @@ def __create_group__(): diff_flat_mult_n.operation = "MULTIPLY" diff_flat_mult_n.inputs[1].default_value = (0.4,) * 3 - diff_flat_n = lighting_eval_g.nodes.new("ShaderNodeMixRGB") + diff_flat_n = lighting_eval_g.nodes.new("ShaderNodeMix") diff_flat_n.name = diff_flat_n.label = _DIFF_FLAT_NODE diff_flat_n.location = (start_pos_x + pos_x_shift * 5, 400) + diff_flat_n.data_type = "RGBA" diff_flat_n.blend_type = "MIX" diff_amb_add_n = lighting_eval_g.nodes.new("ShaderNodeVectorMath") @@ -320,11 +286,12 @@ def __create_group__(): spec_mult_n.location = (start_pos_x + pos_x_shift * 8, -200) spec_mult_n.operation = "MULTIPLY" - spec_flat_n = lighting_eval_g.nodes.new("ShaderNodeMixRGB") + spec_flat_n = lighting_eval_g.nodes.new("ShaderNodeMix") spec_flat_n.name = spec_flat_n.label = _SPEC_FLAT_NODE spec_flat_n.location = (start_pos_x + pos_x_shift * 9, -200) + spec_flat_n.data_type = "RGBA" spec_flat_n.blend_type = "MIX" - spec_flat_n.inputs["Color2"].default_value = (0.0,) * 4 + spec_flat_n.inputs["B"].default_value = (0.0,) * 4 # group links # pass #-2 @@ -355,15 +322,15 @@ def __create_group__(): lighting_eval_g.links.new(n_dot_h_n.inputs[1], input_n.outputs["Normal Vector"]) # pass #4 - lighting_eval_g.links.new(diff_flat_n.inputs["Fac"], input_n.outputs["Flat Lighting"]) - lighting_eval_g.links.new(diff_flat_n.inputs["Color1"], diff_mult_n.outputs[0]) - lighting_eval_g.links.new(diff_flat_n.inputs["Color2"], diff_flat_mult_n.outputs[0]) + lighting_eval_g.links.new(diff_flat_n.inputs["Factor"], input_n.outputs["Flat Lighting"]) + lighting_eval_g.links.new(diff_flat_n.inputs["A"], diff_mult_n.outputs[0]) + lighting_eval_g.links.new(diff_flat_n.inputs["B"], diff_flat_mult_n.outputs[0]) lighting_eval_g.links.new(n_dot_h_max_n.inputs[0], n_dot_h_n.outputs["Value"]) # pass #5 lighting_eval_g.links.new(diff_amb_add_n.inputs[0], light_amb_n.outputs["Color"]) - lighting_eval_g.links.new(diff_amb_add_n.inputs[1], diff_flat_n.outputs["Color"]) + lighting_eval_g.links.new(diff_amb_add_n.inputs[1], diff_flat_n.outputs["Result"]) lighting_eval_g.links.new(n_dot_l_spec_cut_n.inputs[0], n_dot_l_n.outputs["Value"]) @@ -379,12 +346,12 @@ def __create_group__(): lighting_eval_g.links.new(spec_mult_n.inputs[1], light_spec_n.outputs["Color"]) # pass #8 - lighting_eval_g.links.new(spec_flat_n.inputs["Fac"], input_n.outputs["Flat Lighting"]) - lighting_eval_g.links.new(spec_flat_n.inputs["Color1"], spec_mult_n.outputs[0]) + lighting_eval_g.links.new(spec_flat_n.inputs["Factor"], input_n.outputs["Flat Lighting"]) + lighting_eval_g.links.new(spec_flat_n.inputs["A"], spec_mult_n.outputs[0]) # pass: out lighting_eval_g.links.new(output_n.inputs["Diffuse Lighting"], diff_amb_add_n.outputs[0]) - lighting_eval_g.links.new(output_n.inputs["Specular Lighting"], spec_flat_n.outputs["Color"]) + lighting_eval_g.links.new(output_n.inputs["Specular Lighting"], spec_flat_n.outputs["Result"]) lighting_eval_g.links.new(output_n.inputs["Normal"], input_n.outputs["Normal Vector"]) # set default lighting diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py index 63a58f0..8bead63 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/linear_to_srgb_ng.py @@ -69,24 +69,19 @@ def __create_linear_to_srgb_group__(): pos_x_shift = 185 # inputs defining - lin_to_srgb_g.interface.new_socket( - name = "Value", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + lin_to_srgb_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Value") + + # outputs defining + lin_to_srgb_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Value") + + + # group nodes input_n = lin_to_srgb_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, 0) - # outputs defining - lin_to_srgb_g.interface.new_socket( - name = "Value", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) output_n = lin_to_srgb_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 7, 0) - # group nodes small_mult_n = lin_to_srgb_g.nodes.new("ShaderNodeMath") small_mult_n.name = small_mult_n.label = _SMALL_MULT_NODE small_mult_n.location = (start_pos_x + pos_x_shift * 3, 200) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py index 7863a22..83c5045 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py @@ -58,46 +58,23 @@ def __create_node_group__(): mult2_mix_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=MULT2_MIX_G) # inputs defining - mult2_mix_g.interface.new_socket( - name = "Base Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - mult2_mix_g.interface.new_socket( - name = "Base Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - mult2_mix_g.interface.new_socket( - name = "Mult Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - mult2_mix_g.interface.new_socket( - name = "Mult Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) + mult2_mix_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Base Alpha") + mult2_mix_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Base Color") + mult2_mix_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Mult Alpha") + mult2_mix_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Mult Color") + # outputs defining + mult2_mix_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Mix Alpha") + mult2_mix_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Mix Color") + + + # nodes creation input_n = mult2_mix_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, start_pos_y) - # outputs defining - mult2_mix_g.interface.new_socket( - name = "Mix Alpha", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - mult2_mix_g.interface.new_socket( - name = "Mix Color", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - output_n = mult2_mix_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y) - # nodes creation separate_mult_n = mult2_mix_g.nodes.new("ShaderNodeSeparateRGB") separate_mult_n.name = _SEPARATE_MULT_NODE separate_mult_n.label = _SEPARATE_MULT_NODE @@ -110,12 +87,13 @@ def __create_node_group__(): mult_green_scale_n.operation = "MULTIPLY" mult_green_scale_n.inputs[1].default_value = 2.0 - mult_green_mix_n = mult2_mix_g.nodes.new("ShaderNodeMixRGB") + mult_green_mix_n = mult2_mix_g.nodes.new("ShaderNodeMix") mult_green_mix_n.name = _MULT_GREEN_MIX_NODE mult_green_mix_n.label = _MULT_GREEN_MIX_NODE mult_green_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 200) + mult_green_mix_n.data_type = "RGBA" mult_green_mix_n.blend_type = "MIX" - mult_green_mix_n.inputs["Color2"].default_value = (1.0,) * 4 + mult_green_mix_n.inputs["B"].default_value = (1.0,) * 4 mult_base_mult_n = mult2_mix_g.nodes.new("ShaderNodeVectorMath") mult_base_mult_n.name = _MULT_BASE_MULT_NODE @@ -123,25 +101,27 @@ def __create_node_group__(): mult_base_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 400) mult_base_mult_n.operation = "MULTIPLY" - alpha_mix_n = mult2_mix_g.nodes.new("ShaderNodeMixRGB") + alpha_mix_n = mult2_mix_g.nodes.new("ShaderNodeMix") alpha_mix_n.name = _ALPHA_MIX_NODE alpha_mix_n.label = _ALPHA_MIX_NODE alpha_mix_n.location = (start_pos_x + pos_x_shift, start_pos_y - 200) + alpha_mix_n.data_type = "RGBA" + alpha_mix_n.blend_type = "MIX" # links creation mult2_mix_g.links.new(separate_mult_n.inputs["Image"], input_n.outputs["Mult Color"]) mult2_mix_g.links.new(mult_green_scale_n.inputs[0], separate_mult_n.outputs["G"]) - mult2_mix_g.links.new(mult_green_mix_n.inputs["Fac"], input_n.outputs["Base Alpha"]) - mult2_mix_g.links.new(mult_green_mix_n.inputs["Color1"], mult_green_scale_n.outputs["Value"]) + mult2_mix_g.links.new(mult_green_mix_n.inputs["Factor"], input_n.outputs["Base Alpha"]) + mult2_mix_g.links.new(mult_green_mix_n.inputs["A"], mult_green_scale_n.outputs["Value"]) mult2_mix_g.links.new(mult_base_mult_n.inputs[0], input_n.outputs["Base Color"]) - mult2_mix_g.links.new(mult_base_mult_n.inputs[1], mult_green_mix_n.outputs["Color"]) + mult2_mix_g.links.new(mult_base_mult_n.inputs[1], mult_green_mix_n.outputs["Result"]) - mult2_mix_g.links.new(alpha_mix_n.inputs["Fac"], input_n.outputs["Base Alpha"]) - mult2_mix_g.links.new(alpha_mix_n.inputs["Color1"], input_n.outputs["Mult Alpha"]) - mult2_mix_g.links.new(alpha_mix_n.inputs["Color2"], input_n.outputs["Base Alpha"]) + mult2_mix_g.links.new(alpha_mix_n.inputs["Factor"], input_n.outputs["Base Alpha"]) + mult2_mix_g.links.new(alpha_mix_n.inputs["A"], input_n.outputs["Mult Alpha"]) + mult2_mix_g.links.new(alpha_mix_n.inputs["B"], input_n.outputs["Base Alpha"]) mult2_mix_g.links.new(output_n.inputs["Mix Color"], mult_base_mult_n.outputs[0]) - mult2_mix_g.links.new(output_n.inputs["Mix Alpha"], alpha_mix_n.outputs["Color"]) + mult2_mix_g.links.new(output_n.inputs["Mix Alpha"], alpha_mix_n.outputs["Result"]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py index 2c09a5f..51ca520 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/refl_normal_ng.py @@ -47,31 +47,20 @@ def __create_refl_normal_group__(): refl_normal_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=REFL_NORMAL_G) # inputs defining - refl_normal_g.interface.new_socket( - name = "Incoming", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - refl_normal_g.interface.new_socket( - name = "Normal", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + refl_normal_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Incoming") + refl_normal_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Normal") + # outputs defining + refl_normal_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Reflection Normal") + + + # group nodes input_n = refl_normal_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - refl_normal_g.interface.new_socket( - name = "Reflection Normal", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - output_n = refl_normal_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) - # group nodes view_vector_n = refl_normal_g.nodes.new("ShaderNodeVectorMath") view_vector_n.location = (185, 250) view_vector_n.operation = "MULTIPLY" diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py index 334a7bc..6d997d2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_combine_ng.py @@ -49,30 +49,20 @@ def __create_node_group__(): scs_uvs_combine_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SCS_UVS_COMBINE_G) # inputs defining - scs_uvs_combine_g.interface.new_socket( - name = "U", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - scs_uvs_combine_g.interface.new_socket( - name = "V", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + scs_uvs_combine_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "U") + scs_uvs_combine_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "V") + # outputs defining + scs_uvs_combine_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Vector") + + + # group nodes input_n = scs_uvs_combine_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - scs_uvs_combine_g.interface.new_socket( - name = "Vector", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) output_n = scs_uvs_combine_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 4, 0) - # group nodes v_to_scs_inv_n = scs_uvs_combine_g.nodes.new("ShaderNodeMath") v_to_scs_inv_n.location = (pos_x_shift * 1, -100) v_to_scs_inv_n.operation = "MULTIPLY" diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py index 016fd97..9656836 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/scs_uvs_separate_ng.py @@ -49,31 +49,20 @@ def __create_node_group__(): scs_uvs_separate_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=SCS_UVS_SEPARATE_G) # inputs defining - scs_uvs_separate_g.interface.new_socket( - name = "UV", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + scs_uvs_separate_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "UV") + # outputs defining + scs_uvs_separate_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "U") + scs_uvs_separate_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "V") + + + # group nodes input_n = scs_uvs_separate_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - scs_uvs_separate_g.interface.new_socket( - name = "U", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - scs_uvs_separate_g.interface.new_socket( - name = "V", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = scs_uvs_separate_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 4, 0) - # group nodes separate_xyz_n = scs_uvs_separate_g.nodes.new("ShaderNodeSeparateXYZ") separate_xyz_n.location = (pos_x_shift * 1, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py index dbcc63c..b521da6 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/spec_texture_calc_ng.py @@ -49,31 +49,20 @@ def __create_node_group__(): spec_txt_calc_n = bpy.data.node_groups.new(type="ShaderNodeTree", name=SPEC_TEXTURE_CALC_G) # inputs defining - spec_txt_calc_n.interface.new_socket( - name = "Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) + spec_txt_calc_n.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Color") + # outputs defining + spec_txt_calc_n.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Shininess") + spec_txt_calc_n.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Specular") + + + # node creation input_n = spec_txt_calc_n.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - spec_txt_calc_n.interface.new_socket( - name = "Shininess", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - spec_txt_calc_n.interface.new_socket( - name = "Specular", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - output_n = spec_txt_calc_n.nodes.new("NodeGroupOutput") output_n.location = (185 * 3, 0) - # node creation color_to_rgb_n = spec_txt_calc_n.nodes.new("ShaderNodeSeparateColor") color_to_rgb_n.name = color_to_rgb_n.label = _COLOR_TO_RGB_NODE color_to_rgb_n.location = (185, 0) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py index f75a54e..10b26b2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py @@ -66,23 +66,14 @@ def __create_vcolor_group__(): vcol_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=VCOLOR_G) # outputs defining - vcol_g.interface.new_socket( - name = "Vertex Color", - description = "Vertex color output", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - vcol_g.interface.new_socket( - name = "Vertex Color Alpha", - description = "Vertex color output", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) + vcol_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Vertex Color", description = "Vertex color output") + vcol_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Vertex Color Alpha", description = "Vertex color output") + + # group nodes output_n = vcol_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 5, 0) - # group nodes vcol_n = vcol_g.nodes.new("ShaderNodeVertexColor") vcol_n.name = vcol_n.label = _VCOL_ATTRIBUTE_NODE vcol_n.location = (pos_x_shift, 200) diff --git a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py index 35e7ef5..3937031 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py @@ -115,26 +115,29 @@ def init(node_tree): opacity_vcol_n.operation = "MULTIPLY" # node creation - level 4 - paint_spec_mult_n = node_tree.nodes.new("ShaderNodeMixRGB") + paint_spec_mult_n = node_tree.nodes.new("ShaderNodeMix") paint_spec_mult_n.name = paint_spec_mult_n.label = Truckpaint.PAINT_SPECULAR_MULT_NODE paint_spec_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1950) + paint_spec_mult_n.data_type = "RGBA" paint_spec_mult_n.blend_type = "MULTIPLY" - paint_spec_mult_n.inputs['Fac'].default_value = 0 + paint_spec_mult_n.inputs['Factor'].default_value = 0 # node creation - level 5 - base_paint_mult_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_paint_mult_n = node_tree.nodes.new("ShaderNodeMix") base_paint_mult_n.name = base_paint_mult_n.label = Truckpaint.BASE_PAINT_MULT_NODE base_paint_mult_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1500) + base_paint_mult_n.data_type = "RGBA" base_paint_mult_n.blend_type = "MULTIPLY" - base_paint_mult_n.inputs['Fac'].default_value = 1 - base_paint_mult_n.inputs['Color2'].default_value = _convert_utils.to_node_color(_get_scs_globals().base_paint_color) + base_paint_mult_n.inputs['Factor'].default_value = 1 + base_paint_mult_n.inputs['B'].default_value = _convert_utils.to_node_color(_get_scs_globals().base_paint_color) # node creation - level 6 - paint_diff_mult_n = node_tree.nodes.new("ShaderNodeMixRGB") + paint_diff_mult_n = node_tree.nodes.new("ShaderNodeMix") paint_diff_mult_n.name = paint_diff_mult_n.label = Truckpaint.PAINT_DIFFUSE_MULT_NODE paint_diff_mult_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 1400) + paint_diff_mult_n.data_type = "RGBA" paint_diff_mult_n.blend_type = "MULTIPLY" - paint_diff_mult_n.inputs['Fac'].default_value = 0 + paint_diff_mult_n.inputs['Factor'].default_value = 0 # make links - level 2 node_tree.links.new(env_vcol_mix_n.inputs[0], env_color_n.outputs['Color']) @@ -147,23 +150,23 @@ def init(node_tree): node_tree.links.new(opacity_vcol_n.inputs[1], opacity_n.outputs[0]) # make links - level 3 - node_tree.links.new(paint_spec_mult_n.inputs['Color1'], spec_color_n.outputs['Color']) + node_tree.links.new(paint_spec_mult_n.inputs['A'], spec_color_n.outputs['Color']) # make links - level 4 - node_tree.links.new(spec_mult_n.inputs[0], paint_spec_mult_n.outputs['Color']) + node_tree.links.new(spec_mult_n.inputs[0], paint_spec_mult_n.outputs['Result']) node_tree.links.new(spec_mult_n.inputs[1], spec_vcol_mix_n.outputs[0]) - node_tree.links.new(base_paint_mult_n.inputs['Color1'], diff_mult_n.outputs[0]) + node_tree.links.new(base_paint_mult_n.inputs['A'], diff_mult_n.outputs[0]) # make links - level 5 node_tree.links.new(add_refl_gn.inputs['Env Factor Color'], env_vcol_mix_n.outputs[0]) - node_tree.links.new(add_refl_gn.inputs['Specular Color'], paint_spec_mult_n.outputs['Color']) + node_tree.links.new(add_refl_gn.inputs['Specular Color'], paint_spec_mult_n.outputs['Result']) - node_tree.links.new(paint_diff_mult_n.inputs['Color1'], base_paint_mult_n.outputs['Color']) + node_tree.links.new(paint_diff_mult_n.inputs['A'], base_paint_mult_n.outputs['Result']) # make links - output - # node_tree.links.new(compose_lighting_n.inputs['Specular Color'], paint_spec_mult_n.outputs['Color']) - node_tree.links.new(compose_lighting_n.inputs['Diffuse Color'], paint_diff_mult_n.outputs['Color']) + # node_tree.links.new(compose_lighting_n.inputs['Specular Color'], paint_spec_mult_n.outputs['Result']) + node_tree.links.new(compose_lighting_n.inputs['Diffuse Color'], paint_diff_mult_n.outputs['Result']) @staticmethod def init_colormask_or_airbrush(node_tree): @@ -180,7 +183,7 @@ def init_colormask_or_airbrush(node_tree): # disable base paint color if Truckpaint.BASE_PAINT_MULT_NODE in node_tree.nodes: - node_tree.nodes[Truckpaint.BASE_PAINT_MULT_NODE].inputs["Fac"].default_value = 0 + node_tree.nodes[Truckpaint.BASE_PAINT_MULT_NODE].inputs["Factor"].default_value = 0 paint_diff_mult_n = node_tree.nodes[Truckpaint.PAINT_DIFFUSE_MULT_NODE] paint_spec_mult_n = node_tree.nodes[Truckpaint.PAINT_SPECULAR_MULT_NODE] @@ -219,31 +222,36 @@ def init_colormask_or_airbrush(node_tree): paint_tex_sep.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 650) # node creation - level 3 - airbrush_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + airbrush_mix_n = node_tree.nodes.new("ShaderNodeMix") airbrush_mix_n.name = airbrush_mix_n.label = Truckpaint.AIRBRUSH_MIX_NODE airbrush_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1000) + airbrush_mix_n.data_type = "RGBA" airbrush_mix_n.blend_type = "MIX" - col_mask_b_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + col_mask_b_mix_n = node_tree.nodes.new("ShaderNodeMix") col_mask_b_mix_n.name = col_mask_b_mix_n.label = Truckpaint.COL_MASK_B_MIX_NODE col_mask_b_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 600) + col_mask_b_mix_n.data_type = "RGBA" col_mask_b_mix_n.blend_type = "MIX" - col_mask_g_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + col_mask_g_mix_n = node_tree.nodes.new("ShaderNodeMix") col_mask_g_mix_n.name = col_mask_g_mix_n.label = Truckpaint.COL_MASK_G_MIX_NODE col_mask_g_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 400) + col_mask_g_mix_n.data_type = "RGBA" col_mask_g_mix_n.blend_type = "MIX" - col_mask_r_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + col_mask_r_mix_n = node_tree.nodes.new("ShaderNodeMix") col_mask_r_mix_n.name = col_mask_r_mix_n.label = Truckpaint.COL_MASK_R_MIX_NODE col_mask_r_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 200) + col_mask_r_mix_n.data_type = "RGBA" col_mask_r_mix_n.blend_type = "MIX" # node creation - level 4 - blend_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + blend_mix_n = node_tree.nodes.new("ShaderNodeMix") blend_mix_n.name = blend_mix_n.label = Truckpaint.BLEND_MIX_NODE blend_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 800) - blend_mix_n.inputs['Fac'].default_value = 1.0 + blend_mix_n.inputs['Factor'].default_value = 1.0 + blend_mix_n.data_type = "RGBA" blend_mix_n.blend_type = "MIX" # make links - level 0 @@ -253,29 +261,29 @@ def init_colormask_or_airbrush(node_tree): node_tree.links.new(paint_tex_sep.inputs['Image'], paint_tex_n.outputs['Color']) # make links - level 2 - node_tree.links.new(airbrush_mix_n.inputs['Fac'], paint_tex_n.outputs['Alpha']) - node_tree.links.new(airbrush_mix_n.inputs['Color1'], paint_base_col_n.outputs['Color']) - node_tree.links.new(airbrush_mix_n.inputs['Color2'], paint_tex_n.outputs['Color']) + node_tree.links.new(airbrush_mix_n.inputs['Factor'], paint_tex_n.outputs['Alpha']) + node_tree.links.new(airbrush_mix_n.inputs['A'], paint_base_col_n.outputs['Color']) + node_tree.links.new(airbrush_mix_n.inputs['B'], paint_tex_n.outputs['Color']) - node_tree.links.new(col_mask_b_mix_n.inputs['Fac'], paint_tex_sep.outputs['B']) - node_tree.links.new(col_mask_b_mix_n.inputs['Color1'], paint_base_col_n.outputs['Color']) - node_tree.links.new(col_mask_b_mix_n.inputs['Color2'], paint_b_col_n.outputs['Color']) + node_tree.links.new(col_mask_b_mix_n.inputs['Factor'], paint_tex_sep.outputs['B']) + node_tree.links.new(col_mask_b_mix_n.inputs['A'], paint_base_col_n.outputs['Color']) + node_tree.links.new(col_mask_b_mix_n.inputs['B'], paint_b_col_n.outputs['Color']) - node_tree.links.new(col_mask_g_mix_n.inputs['Fac'], paint_tex_sep.outputs['G']) - node_tree.links.new(col_mask_g_mix_n.inputs['Color1'], col_mask_b_mix_n.outputs['Color']) - node_tree.links.new(col_mask_g_mix_n.inputs['Color2'], paint_g_col_n.outputs['Color']) + node_tree.links.new(col_mask_g_mix_n.inputs['Factor'], paint_tex_sep.outputs['G']) + node_tree.links.new(col_mask_g_mix_n.inputs['A'], col_mask_b_mix_n.outputs['Result']) + node_tree.links.new(col_mask_g_mix_n.inputs['B'], paint_g_col_n.outputs['Color']) - node_tree.links.new(col_mask_r_mix_n.inputs['Fac'], paint_tex_sep.outputs['R']) - node_tree.links.new(col_mask_r_mix_n.inputs['Color1'], col_mask_g_mix_n.outputs['Color']) - node_tree.links.new(col_mask_r_mix_n.inputs['Color2'], paint_r_col_n.outputs['Color']) + node_tree.links.new(col_mask_r_mix_n.inputs['Factor'], paint_tex_sep.outputs['R']) + node_tree.links.new(col_mask_r_mix_n.inputs['A'], col_mask_g_mix_n.outputs['Result']) + node_tree.links.new(col_mask_r_mix_n.inputs['B'], paint_r_col_n.outputs['Color']) # make links - level 3 - node_tree.links.new(blend_mix_n.inputs['Color1'], airbrush_mix_n.outputs['Color']) - node_tree.links.new(blend_mix_n.inputs['Color2'], col_mask_r_mix_n.outputs['Color']) + node_tree.links.new(blend_mix_n.inputs['A'], airbrush_mix_n.outputs['Result']) + node_tree.links.new(blend_mix_n.inputs['B'], col_mask_r_mix_n.outputs['Result']) # make links - level 5 - node_tree.links.new(paint_diff_mult_n.inputs['Color2'], blend_mix_n.outputs['Color']) - node_tree.links.new(paint_spec_mult_n.inputs['Color2'], paint_tex_n.outputs['Alpha']) + node_tree.links.new(paint_diff_mult_n.inputs['B'], blend_mix_n.outputs['Result']) + node_tree.links.new(paint_spec_mult_n.inputs['B'], paint_tex_n.outputs['Alpha']) @staticmethod def set_fresnel(node_tree, bias_scale): @@ -305,7 +313,7 @@ def set_base_paint_color(node_tree, color): return color = _convert_utils.to_node_color(color) - node_tree.nodes[Truckpaint.BASE_PAINT_MULT_NODE].inputs["Color2"].default_value = color + node_tree.nodes[Truckpaint.BASE_PAINT_MULT_NODE].inputs["B"].default_value = color @staticmethod def set_paintjob_texture(node_tree, image): diff --git a/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py index a1567f7..759bf5f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py @@ -463,24 +463,28 @@ def set_fadesheet_flavor(node_tree, switch_on): base1_tex_n.location = (base_tex_n.location.x, base_tex_n.location.y - 300) base1_tex_n.width = 140 - base_base1_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_base1_mix_n = node_tree.nodes.new("ShaderNodeMix") base_base1_mix_n.name = base_base1_mix_n.label = UnlitVcolTex.BASE_BASE1_MIX_NODE - base_base1_mix_n.location = (base_tex_n.location.x + 185 * 2, base_tex_n.location.y - 300) + base_base1_mix_n.location = (base_tex_n.location.x + 185 * 2, base_tex_n.location.y - 250) + base_base1_mix_n.data_type = "RGBA" + base_base1_mix_n.blend_type = "MIX" - base_base1_amix_n = node_tree.nodes.new("ShaderNodeMixRGB") + base_base1_amix_n = node_tree.nodes.new("ShaderNodeMix") base_base1_amix_n.name = base_base1_amix_n.label = UnlitVcolTex.BASE_BASE1_AMIX_NODE base_base1_amix_n.location = (base_tex_n.location.x + 185 * 2, base_tex_n.location.y - 500) + base_base1_amix_n.data_type = "RGBA" + base_base1_amix_n.blend_type = "MIX" # links - node_tree.links.new(base_base1_mix_n.inputs['Color1'], base_tex_n.outputs['Color']) - node_tree.links.new(base_base1_mix_n.inputs['Color2'], base1_tex_n.outputs['Color']) + node_tree.links.new(base_base1_mix_n.inputs['A'], base_tex_n.outputs['Color']) + node_tree.links.new(base_base1_mix_n.inputs['B'], base1_tex_n.outputs['Color']) - node_tree.links.new(base_base1_amix_n.inputs['Color1'], base_tex_n.outputs['Alpha']) - node_tree.links.new(base_base1_amix_n.inputs['Color2'], base1_tex_n.outputs['Alpha']) + node_tree.links.new(base_base1_amix_n.inputs['A'], base_tex_n.outputs['Alpha']) + node_tree.links.new(base_base1_amix_n.inputs['B'], base1_tex_n.outputs['Alpha']) - node_tree.links.new(tex_mult_n.inputs[1], base_base1_mix_n.outputs['Color']) + node_tree.links.new(tex_mult_n.inputs[1], base_base1_mix_n.outputs['Result']) - node_tree.links.new(opacity_n.inputs[0], base_base1_amix_n.outputs['Color']) + node_tree.links.new(opacity_n.inputs[0], base_base1_amix_n.outputs['Result']) # flavor creation uvmap_n.location.x -= 185 @@ -490,8 +494,8 @@ def set_fadesheet_flavor(node_tree, switch_on): uvmap_n.outputs['UV'], base_tex_n.inputs[0], base1_tex_n.inputs[0], - base_base1_mix_n.inputs['Fac'], - base_base1_amix_n.inputs['Fac']) + base_base1_mix_n.inputs['Factor'], + base_base1_amix_n.inputs['Factor']) else: fadesheet.delete(node_tree) diff --git a/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py index 7ea994b..abc30f5 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py @@ -129,22 +129,25 @@ def init(node_tree): normal_normalize_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 700) normal_normalize_n.operation = "NORMALIZE" - near_horizon_env_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + near_horizon_env_mix_n = node_tree.nodes.new("ShaderNodeMix") near_horizon_env_mix_n.name = near_horizon_env_mix_n.label = Water.NEAR_HORIZON_ENV_MIX_NODE near_horizon_env_mix_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 2100) + near_horizon_env_mix_n.data_type = "RGBA" near_horizon_env_mix_n.blend_type = "MIX" - near_horizon_env_mix_n.inputs['Color2'].default_value = (0.0,) * 4 # far horizon is without env, thus lerp to zero + near_horizon_env_mix_n.inputs['B'].default_value = (0.0,) * 4 # far horizon is without env, thus lerp to zero - near_horizon_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + near_horizon_mix_n = node_tree.nodes.new("ShaderNodeMix") near_horizon_mix_n.name = near_horizon_mix_n.label = Water.NEAR_HORIZON_MIX_NODE near_horizon_mix_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 1700) + near_horizon_mix_n.data_type = "RGBA" near_horizon_mix_n.blend_type = "MIX" - normal_scramble_n = node_tree.nodes.new("ShaderNodeMixRGB") + normal_scramble_n = node_tree.nodes.new("ShaderNodeMix") normal_scramble_n.name = normal_scramble_n.label = Water.LAY0_LAY1_NORMAL_SCRAMBLE_NODE normal_scramble_n.location = (start_pos_x + pos_x_shift * 8, start_pos_y + 1200) + normal_scramble_n.data_type = "RGBA" normal_scramble_n.blend_type = "MIX" - normal_scramble_n.inputs['Color1'].default_value = (0.0, 0.0, 1.0, 0.0) # WATER_V_NORMAL + normal_scramble_n.inputs['A'].default_value = (0.0, 0.0, 1.0, 0.0) # WATER_V_NORMAL # links creation # pass 2 @@ -158,23 +161,23 @@ def init(node_tree): node_tree.links.new(horizon_mix_n.inputs[0], vcol_mult_n.outputs[0]) node_tree.links.new(horizon_mix_n.inputs[1], horizon_col_n.outputs['Color']) - node_tree.links.new(normal_scramble_n.inputs['Fac'], mix_factor_n.outputs['Scramble Mix Factor']) - node_tree.links.new(normal_scramble_n.inputs['Color2'], normal_normalize_n.outputs['Vector']) + node_tree.links.new(normal_scramble_n.inputs['Factor'], mix_factor_n.outputs['Scramble Mix Factor']) + node_tree.links.new(normal_scramble_n.inputs['B'], normal_normalize_n.outputs['Vector']) # pass 5 - node_tree.links.new(near_horizon_env_mix_n.inputs['Fac'], mix_factor_n.outputs['Mix Factor']) - node_tree.links.new(near_horizon_env_mix_n.inputs['Color1'], near_mix_n.outputs[0]) + node_tree.links.new(near_horizon_env_mix_n.inputs['Factor'], mix_factor_n.outputs['Mix Factor']) + node_tree.links.new(near_horizon_env_mix_n.inputs['A'], near_mix_n.outputs[0]) - node_tree.links.new(near_horizon_mix_n.inputs['Fac'], mix_factor_n.outputs['Mix Factor']) - node_tree.links.new(near_horizon_mix_n.inputs['Color1'], near_mix_n.outputs[0]) - node_tree.links.new(near_horizon_mix_n.inputs['Color2'], horizon_mix_n.outputs[0]) + node_tree.links.new(near_horizon_mix_n.inputs['Factor'], mix_factor_n.outputs['Mix Factor']) + node_tree.links.new(near_horizon_mix_n.inputs['A'], near_mix_n.outputs[0]) + node_tree.links.new(near_horizon_mix_n.inputs['B'], horizon_mix_n.outputs[0]) # pass 6 - node_tree.links.new(lighting_eval_n.inputs['Normal Vector'], normal_scramble_n.outputs['Color']) + node_tree.links.new(lighting_eval_n.inputs['Normal Vector'], normal_scramble_n.outputs['Result']) # pass 7 - node_tree.links.new(compose_lighting_n.inputs['Env Color'], near_horizon_env_mix_n.outputs['Color']) - node_tree.links.new(compose_lighting_n.inputs['Diffuse Color'], near_horizon_mix_n.outputs['Color']) + node_tree.links.new(compose_lighting_n.inputs['Env Color'], near_horizon_env_mix_n.outputs['Result']) + node_tree.links.new(compose_lighting_n.inputs['Diffuse Color'], near_horizon_mix_n.outputs['Result']) # add environment pass and normal maps StdAddEnv.add(node_tree, @@ -182,7 +185,7 @@ def init(node_tree): node_tree.nodes[Dif.SPEC_COL_NODE].outputs['Color'], None, node_tree.nodes[Water.LIGHTING_EVAL_NODE].outputs['Normal'], - node_tree.nodes[Water.NEAR_HORIZON_ENV_MIX_NODE].inputs['Color1']) + node_tree.nodes[Water.NEAR_HORIZON_ENV_MIX_NODE].inputs['A']) node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE].inputs['Base Texture Alpha'].default_value = 1 # set full reflection strength diff --git a/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py index 507a900..86f641a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/water/mix_factor_ng.py @@ -55,41 +55,22 @@ def __create_group__(): detail_setup_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=MIX_FACTOR_G) # inputs defining - detail_setup_g.interface.new_socket( - name = "Near Distance", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - detail_setup_g.interface.new_socket( - name = "Far Distance", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - detail_setup_g.interface.new_socket( - name = "Scramble Distance", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + detail_setup_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Near Distance") + detail_setup_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Far Distance") + detail_setup_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Scramble Distance") + # outputs defining + detail_setup_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Mix Factor") + detail_setup_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Scramble Mix Factor") + + + # group nodes input_n = detail_setup_g.nodes.new("NodeGroupInput") input_n.location = (start_pos_x, start_pos_y) - # outputs defining - detail_setup_g.interface.new_socket( - name = "Mix Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - detail_setup_g.interface.new_socket( - name = "Scramble Mix Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = detail_setup_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y) - # group nodes camera_data_n = detail_setup_g.nodes.new("ShaderNodeCameraData") camera_data_n.location = (start_pos_x, start_pos_y + 100) diff --git a/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py b/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py index 6ab0d43..cb3e85d 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/water/water_stream_ng.py @@ -74,46 +74,23 @@ def __create_node_group__(): water_stream_ng = bpy.data.node_groups.new(type="ShaderNodeTree", name=WATER_STREAM_G) # inputs defining - water_stream_ng.interface.new_socket( - name = "Yaw0", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - water_stream_ng.interface.new_socket( - name = "Speed0", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - water_stream_ng.interface.new_socket( - name = "Yaw1", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - water_stream_ng.interface.new_socket( - name = "Speed1", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + water_stream_ng.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Yaw0") + water_stream_ng.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Speed0") + water_stream_ng.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Yaw1") + water_stream_ng.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Speed1") + # outputs defining + water_stream_ng.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Stream0") + water_stream_ng.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Stream1") + + + # node creation input_n = water_stream_ng.nodes.new("NodeGroupInput") input_n.location = (start_pos_x - pos_x_shift, start_pos_y) - # outputs defining - water_stream_ng.interface.new_socket( - name = "Stream0", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - water_stream_ng.interface.new_socket( - name = "Stream1", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - output_n = water_stream_ng.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y) - # node creation stream0_speed_mult_n = water_stream_ng.nodes.new("ShaderNodeVectorMath") stream0_speed_mult_n.name = stream0_speed_mult_n.label = _STREAM0_SPEED_MULT_NODE stream0_speed_mult_n.location = (start_pos_x, start_pos_y + 100) diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py b/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py index 09f1d75..9a5e864 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/window_final_uv_ng.py @@ -50,31 +50,20 @@ def __create_node_group__(): final_uv_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=WINDOW_FINAL_UV_G) # inputs defining - final_uv_g.interface.new_socket( - name = "UV", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - final_uv_g.interface.new_socket( - name = "Factor", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + final_uv_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "UV") + final_uv_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Factor") + # outputs defining + final_uv_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Final UV") + + + # group nodes input_n = final_uv_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - final_uv_g.interface.new_socket( - name = "Final UV", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = final_uv_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) - # group nodes # UV_STEPS = floor(uv * 0.5) half_scale_n = final_uv_g.nodes.new("ShaderNodeMath") half_scale_n.location = (pos_x_shift * 1, 150) diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py b/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py index c35c67a..f15ec36 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/window_offset_factor_ng.py @@ -50,31 +50,20 @@ def __create_node_group__(): offset_factor_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=WINDOW_OFFSET_FACTOR_G) # inputs defining - offset_factor_g.interface.new_socket( - name = "WndToEye Up", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - offset_factor_g.interface.new_socket( - name = "WndToEye Direction", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + offset_factor_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "WndToEye Up") + offset_factor_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "WndToEye Direction") + # outputs defining + offset_factor_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Offset Factor") + + + # group nodes input_n = offset_factor_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - offset_factor_g.interface.new_socket( - name = "Offset Factor", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - output_n = offset_factor_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 6, 0) - # group nodes # ANGLE_TO_ROTATION = saturate(acos(WndToEyeUp)) arcos_n = offset_factor_g.nodes.new("ShaderNodeMath") arcos_n.location = (pos_x_shift * 1, 200) diff --git a/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py b/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py index 8b1595d..04e1fd2 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/window/window_uv_offset_ng.py @@ -56,36 +56,21 @@ def __create_node_group__(): uv_offset_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=WINDOW_UV_OFFSET_G) # inputs defining - uv_offset_g.interface.new_socket( - name = "UV", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - uv_offset_g.interface.new_socket( - name = "Normal", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - uv_offset_g.interface.new_socket( - name = "Incoming", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + uv_offset_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "UV") + uv_offset_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Normal") + uv_offset_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Incoming") + # outputs defining + uv_offset_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV Final") + + + # group nodes input_n = uv_offset_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - uv_offset_g.interface.new_socket( - name = "UV Final", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - output_n = uv_offset_g.nodes.new("NodeGroupOutput") output_n.location = (pos_x_shift * 12, 0) - # group nodes # pass 1 es_world_up_n = uv_offset_g.nodes.new("ShaderNodeVectorTransform") es_world_up_n.name = es_world_up_n.label = "ESWorldUp" diff --git a/addon/io_scs_tools/internals/shaders/flavors/awhite.py b/addon/io_scs_tools/internals/shaders/flavors/awhite.py index 2ed9f80..a43eefd 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/awhite.py +++ b/addon/io_scs_tools/internals/shaders/flavors/awhite.py @@ -32,10 +32,11 @@ def __create_node__(node_tree): :return: alpha to white mixing node :rtype: bpy.types.Node """ - awhite_mix_n = node_tree.nodes.new("ShaderNodeMixRGB") + awhite_mix_n = node_tree.nodes.new("ShaderNodeMix") awhite_mix_n.name = awhite_mix_n.label = _AWHITE_MIX_NODE + awhite_mix_n.data_type = "RGBA" awhite_mix_n.blend_type = "MIX" - awhite_mix_n.inputs['Color1'].default_value = (1,) * 4 + awhite_mix_n.inputs['A'].default_value = (1,) * 4 return awhite_mix_n @@ -62,10 +63,10 @@ def init(node_tree, location, mix_factor_from, color_from, color_to): awhite_mix_n.location = location # links creation - node_tree.links.new(awhite_mix_n.inputs['Fac'], mix_factor_from) - node_tree.links.new(awhite_mix_n.inputs['Color2'], color_from) + node_tree.links.new(awhite_mix_n.inputs['Factor'], mix_factor_from) + node_tree.links.new(awhite_mix_n.inputs['B'], color_from) - node_tree.links.new(color_to, awhite_mix_n.outputs['Color']) + node_tree.links.new(color_to, awhite_mix_n.outputs['Result']) # FIXME: move to old system after: https://developer.blender.org/T68406 is resolved flavor_frame = node_tree.nodes.new(type="NodeFrame") @@ -106,6 +107,6 @@ def get_out_socket(node_tree): :rtype: bpy.types.NodeSocket | None """ if is_set(node_tree): - return node_tree.nodes[_AWHITE_MIX_NODE].outputs['Color'] + return node_tree.nodes[_AWHITE_MIX_NODE].outputs['Result'] return None diff --git a/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py b/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py index 26e83f5..d198bdd 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/fadesheet/fadesheet_compute_ng.py @@ -84,48 +84,17 @@ def __create_node_group__(): fadesheet_compute_g.nodes.clear() # inputs defining - fadesheet_compute_g.interface.new_socket( - name = "FPS", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fadesheet_compute_g.interface.new_socket( - name = "FramesRow", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fadesheet_compute_g.interface.new_socket( - name = "FramesTotal", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fadesheet_compute_g.interface.new_socket( - name = "FrameSize", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - fadesheet_compute_g.interface.new_socket( - name = "UV", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FPS") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesRow") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesTotal") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "FrameSize") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "UV") # outputs defining - fadesheet_compute_g.interface.new_socket( - name = "UV0", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - fadesheet_compute_g.interface.new_socket( - name = "UV1", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) - fadesheet_compute_g.interface.new_socket( - name = "FrameBlend", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) + fadesheet_compute_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV0") + fadesheet_compute_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV1") + fadesheet_compute_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "FrameBlend") + # node creation input_n = fadesheet_compute_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py b/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py index 21d079b..7593310 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/flipsheet/flipsheet_compute_ng.py @@ -83,38 +83,14 @@ def __create_node_group__(): fadesheet_compute_g.nodes.clear() # inputs defining - fadesheet_compute_g.interface.new_socket( - name = "FPS", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fadesheet_compute_g.interface.new_socket( - name = "FramesRow", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fadesheet_compute_g.interface.new_socket( - name = "FramesTotal", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - fadesheet_compute_g.interface.new_socket( - name = "FrameSize", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - fadesheet_compute_g.interface.new_socket( - name = "UV", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FPS") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesRow") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesTotal") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "FrameSize") + fadesheet_compute_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "UV") # outputs defining - fadesheet_compute_g.interface.new_socket( - name = "UV", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) + fadesheet_compute_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "UV") # node creation input_n = fadesheet_compute_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py index f6f5ae5..14b336c 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py @@ -59,31 +59,20 @@ def __create_node_group__(): nmap_dds16_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=TSNMAP_DDS16_G) # inputs defining - nmap_dds16_g.interface.new_socket( - name = "Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) + nmap_dds16_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Color") + # outputs defining + nmap_dds16_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Strength") + nmap_dds16_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketColor", name = "Color") + + + # group nodes input_n = nmap_dds16_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - nmap_dds16_g.interface.new_socket( - name = "Strength", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - nmap_dds16_g.interface.new_socket( - name = "Color", - in_out = "OUTPUT", - socket_type = "NodeSocketColor" - ) - output_n = nmap_dds16_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) - # group nodes separate_rgb_n = nmap_dds16_g.nodes.new("ShaderNodeSeparateRGB") separate_rgb_n.name = separate_rgb_n.label = _NMAP_TEX_SEP_NODE separate_rgb_n.location = (185 * 1, 100) diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py index c8b805a..ed24862 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py @@ -72,35 +72,21 @@ def __create_nmap_scale_group__(): nmap_scale_g = bpy.data.node_groups.new(type="ShaderNodeTree", name=TSNMAP_SCALE_G) # inputs defining - nmap_scale_g.interface.new_socket( - name = "NMap Tex Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - nmap_scale_g.interface.new_socket( - name = "Original Normal", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) - nmap_scale_g.interface.new_socket( - name = "Modified Normal", - in_out = "INPUT", - socket_type = "NodeSocketVector" - ) + nmap_scale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "NMap Tex Color") + nmap_scale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Original Normal") + nmap_scale_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketVector", name = "Modified Normal") + # outputs defining + nmap_scale_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketVector", name = "Normal") + + + # group nodes input_n = nmap_scale_g.nodes.new("NodeGroupInput") input_n.location = (0, 0) - # outputs defining - nmap_scale_g.interface.new_socket( - name = "Normal", - in_out = "OUTPUT", - socket_type = "NodeSocketVector" - ) output_n = nmap_scale_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) - # group nodes separate_rgb_n = nmap_scale_g.nodes.new("ShaderNodeSeparateRGB") separate_rgb_n.name = separate_rgb_n.label = _NMAP_TEX_SEP_NODE separate_rgb_n.location = (185 * 1, 400) @@ -141,14 +127,16 @@ def __create_nmap_scale_group__(): green_math_abs_n.operation = "ABSOLUTE" green_math_abs_n.use_clamp = True - red_mix_n = nmap_scale_g.nodes.new("ShaderNodeMixRGB") + red_mix_n = nmap_scale_g.nodes.new("ShaderNodeMix") red_mix_n.name = red_mix_n.label = _COMBINE_RED_NODE red_mix_n.location = (185 * 5, 100) + red_mix_n.data_type = "RGBA" red_mix_n.blend_type = "MIX" - green_mix_n = nmap_scale_g.nodes.new("ShaderNodeMixRGB") + green_mix_n = nmap_scale_g.nodes.new("ShaderNodeMix") green_mix_n.name = green_mix_n.label = _COMBINE_GREEN_NODE - green_mix_n.location = (185 * 6, -100) + green_mix_n.location = (185 * 6, 0) + green_mix_n.data_type = "RGBA" green_mix_n.blend_type = "MIX" # group links @@ -163,12 +151,12 @@ def __create_nmap_scale_group__(): nmap_scale_g.links.new(red_math_abs_n.inputs[0], red_math_mult_n.outputs[0]) nmap_scale_g.links.new(green_math_abs_n.inputs[0], green_math_mult_n.outputs[0]) - nmap_scale_g.links.new(red_mix_n.inputs['Fac'], red_math_abs_n.outputs[0]) - nmap_scale_g.links.new(red_mix_n.inputs['Color1'], input_n.outputs['Original Normal']) - nmap_scale_g.links.new(red_mix_n.inputs['Color2'], input_n.outputs['Modified Normal']) + nmap_scale_g.links.new(red_mix_n.inputs['Factor'], red_math_abs_n.outputs[0]) + nmap_scale_g.links.new(red_mix_n.inputs['A'], input_n.outputs['Original Normal']) + nmap_scale_g.links.new(red_mix_n.inputs['B'], input_n.outputs['Modified Normal']) - nmap_scale_g.links.new(green_mix_n.inputs['Fac'], green_math_abs_n.outputs[0]) - nmap_scale_g.links.new(green_mix_n.inputs['Color1'], red_mix_n.outputs['Color']) - nmap_scale_g.links.new(green_mix_n.inputs['Color2'], input_n.outputs['Modified Normal']) + nmap_scale_g.links.new(green_mix_n.inputs['Factor'], green_math_abs_n.outputs[0]) + nmap_scale_g.links.new(green_mix_n.inputs['A'], red_mix_n.outputs['Result']) + nmap_scale_g.links.new(green_mix_n.inputs['B'], input_n.outputs['Modified Normal']) - nmap_scale_g.links.new(output_n.inputs['Normal'], green_mix_n.outputs['Color']) + nmap_scale_g.links.new(output_n.inputs['Normal'], green_mix_n.outputs['Result']) diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py index f10d1e7..4ded226 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/alpha_test_ng.py @@ -81,23 +81,12 @@ def __create_node_group__(): alpha_test_g.nodes.clear() # inputs defining - alpha_test_g.interface.new_socket( - name = "Shader", - in_out = "INPUT", - socket_type = "NodeSocketShader" - ) - alpha_test_g.interface.new_socket( - name = "Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + alpha_test_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketShader", name = "Shader") + alpha_test_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha") # outputs defining - alpha_test_g.interface.new_socket( - name = "Shader", - in_out = "OUTPUT", - socket_type = "NodeSocketShader" - ) + alpha_test_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") + # node creation input_n = alpha_test_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py index 4d376d9..cea2ca4 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_idx_to_col_row_ng.py @@ -84,28 +84,13 @@ def __create_node_group__(): animsheet_frame_idx_to_col_row_g.nodes.clear() # inputs defining - animsheet_frame_idx_to_col_row_g.interface.new_socket( - name = "FrameIndex", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_frame_idx_to_col_row_g.interface.new_socket( - name = "FramesRow", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_frame_idx_to_col_row_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FrameIndex") + animsheet_frame_idx_to_col_row_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesRow") # outputs defining - animsheet_frame_idx_to_col_row_g.interface.new_socket( - name = "ColIndex", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_frame_idx_to_col_row_g.interface.new_socket( - name = "RowIndex", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_frame_idx_to_col_row_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "ColIndex") + animsheet_frame_idx_to_col_row_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "RowIndex") + # node creation input_n = animsheet_frame_idx_to_col_row_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py index 04d4ccf..165b77e 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_frame_ng.py @@ -84,33 +84,14 @@ def __create_node_group__(): animsheet_xfade_g.nodes.clear() # inputs defining - animsheet_xfade_g.interface.new_socket( - name = "FPS", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "FramesTotal", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "FramesRow", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_xfade_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FPS") + animsheet_xfade_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesTotal") + animsheet_xfade_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesRow") # outputs defining - animsheet_xfade_g.interface.new_socket( - name = "FrameX", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "FrameY", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "FrameX") + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "FrameY") + # node creation input_n = animsheet_xfade_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py index 1b97869..8e1cf53 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_loop_frame_ng.py @@ -85,23 +85,12 @@ def __create_node_group__(): animsheet_loop_frame_g.nodes.clear() # inputs defining - animsheet_loop_frame_g.interface.new_socket( - name = "FPS", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_loop_frame_g.interface.new_socket( - name = "FramesTotal", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_loop_frame_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FPS") + animsheet_loop_frame_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesTotal") # outputs defining - animsheet_loop_frame_g.interface.new_socket( - name = "LoopFrame", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_loop_frame_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "LoopFrame") + # node creation input_n = animsheet_loop_frame_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py index ddd4eb4..b0a0e29 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/animsheet_xfade_ng.py @@ -89,48 +89,17 @@ def __create_node_group__(): animsheet_xfade_g.nodes.clear() # inputs defining - animsheet_xfade_g.interface.new_socket( - name = "FPS", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "FramesTotal", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "FramesRow", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_xfade_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FPS") + animsheet_xfade_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesTotal") + animsheet_xfade_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "FramesRow") # outputs defining - animsheet_xfade_g.interface.new_socket( - name = "Frame0X", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "Frame0Y", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "Frame1X", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "Frame1Y", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) - animsheet_xfade_g.interface.new_socket( - name = "FrameBlend", - in_out = "OUTPUT", - socket_type = "NodeSocketFloat" - ) + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Frame0X") + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Frame0Y") + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Frame1X") + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "Frame1Y") + animsheet_xfade_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketFloat", name = "FrameBlend") + # node creation input_n = animsheet_xfade_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py index a17b2ec..19ab6ca 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_add_ng.py @@ -81,18 +81,11 @@ def __create_node_group__(): blend_add_g.nodes.clear() # inputs defining - blend_add_g.interface.new_socket( - name = "Shader", - in_out = "INPUT", - socket_type = "NodeSocketShader" - ) + blend_add_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketShader", name = "Shader") # outputs defining - blend_add_g.interface.new_socket( - name = "Shader", - in_out = "OUTPUT", - socket_type = "NodeSocketShader" - ) + blend_add_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") + # node creation input_n = blend_add_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py index f01b0a5..f6947b5 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/blend_mult_ng.py @@ -81,18 +81,11 @@ def __create_node_group__(): blend_mult_g.nodes.clear() # inputs defining - blend_mult_g.interface.new_socket( - name = "Shader", - in_out = "INPUT", - socket_type = "NodeSocketShader" - ) + blend_mult_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketShader", name = "Shader") # outputs defining - blend_mult_g.interface.new_socket( - name = "Shader", - in_out = "OUTPUT", - socket_type = "NodeSocketShader" - ) + blend_mult_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") + # node creation input_n = blend_mult_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py index f13eb4e..f832ac1 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py @@ -85,28 +85,16 @@ def __create_node_group__(): output_shader_g.nodes.clear() # inputs defining - output_shader_g.interface.new_socket( - name = "Color", - in_out = "INPUT", - socket_type = "NodeSocketColor" - ) - output_shader_g.interface.new_socket( - name = "Alpha", - in_out = "INPUT", - socket_type = "NodeSocketFloat" - ) + output_shader_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Color") + output_shader_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha") # always set to full opaque by default since this behaviour is expected by shaders # since this behaviour is epxected by effects from before. output_shader_g.interface.items_tree['Alpha'].default_value = 1 - # outputs defining - output_shader_g.interface.new_socket( - name = "Shader", - in_out = "OUTPUT", - socket_type = "NodeSocketShader" - ) + output_shader_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") + # node creation input_n = output_shader_g.nodes.new("NodeGroupInput") diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index c5c09ba..821f0c5 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -392,8 +392,8 @@ def get_material_info(obj): for slot in obj.material_slots: if slot.material: - - if obj.scs_props.scs_part.startswith(("coll", "_col")): + # "_col..." used in additional parts, "..._c" used in curve models + if obj.scs_props.scs_part.startswith(("coll", "_col")) or obj.scs_props.scs_part.endswith(("_c")): has_static_collision = True effect_name = slot.material.scs_props.mat_effect_name.lower() From 6a45a650427066c03a8ea01a16e0ca818763351c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 13 Jan 2025 01:56:46 +0100 Subject: [PATCH 19/56] export auto_smooth fix * Removed remnants of "use_auto_smooth", "auto_smooth_angle" and "calc_normals_split" removed in Blender 4.1. Btw. As they wrote: "The Mesh auto_smooth property has been replaced by a modifier node group asset." I completly don't know if due to this and other changes with Mesh, it should be removed as in this fix, or done in other way. Someone with more knowledge should look at this. --- addon/io_scs_tools/utils/mesh.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/addon/io_scs_tools/utils/mesh.py b/addon/io_scs_tools/utils/mesh.py index 26c4537..3167e21 100644 --- a/addon/io_scs_tools/utils/mesh.py +++ b/addon/io_scs_tools/utils/mesh.py @@ -311,11 +311,10 @@ def get_mesh_for_normals(mesh): new_mesh = mesh.copy() # if user is not using auto smooth, then apply it now just for the porpuse of proper normals split calculation. - if not new_mesh.use_auto_smooth: - new_mesh.use_auto_smooth = True - new_mesh.auto_smooth_angle = 3.14 - - new_mesh.calc_normals_split() + # NOTE: auto_smooth property has been replaced by a modifier node group asset in Blender 4.1, so now users should use modifier yourself? + # if not new_mesh.use_auto_smooth: + # new_mesh.use_auto_smooth = True + # new_mesh.auto_smooth_angle = 3.14 return new_mesh From cfc5d8308882708d7c0dc97cb7432d99d18e13e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 13 Jan 2025 02:23:47 +0100 Subject: [PATCH 20/56] fix 2 Change that I forget to do in last fix... --- addon/io_scs_tools/utils/mesh.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addon/io_scs_tools/utils/mesh.py b/addon/io_scs_tools/utils/mesh.py index 3167e21..4b57984 100644 --- a/addon/io_scs_tools/utils/mesh.py +++ b/addon/io_scs_tools/utils/mesh.py @@ -320,13 +320,12 @@ def get_mesh_for_normals(mesh): def cleanup_mesh(mesh): - """Frees normals split and removes mesh if possible. + """Removes mesh if possible. :param mesh: mesh to be cleaned :type mesh: bpy.types.Mesh """ - mesh.free_normals_split() if mesh.users == 0: bpy.data.meshes.remove(mesh, do_unlink=True) From d86aec684cf1d5427c4d914ebd0a5228ead0aa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 25 Jan 2025 22:19:20 +0100 Subject: [PATCH 21/56] truckpaint fix * Fixed not updated node inputs in airbrush/colormask flavors --- .../internals/shaders/eut2/truckpaint/airbrush.py | 6 +++--- .../internals/shaders/eut2/truckpaint/colormask.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/airbrush.py b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/airbrush.py index b8474e8..205fb96 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/airbrush.py +++ b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/airbrush.py @@ -40,10 +40,10 @@ def init(node_tree): Truckpaint.init_colormask_or_airbrush(node_tree) blend_mix_n = node_tree.nodes[Truckpaint.BLEND_MIX_NODE] - blend_mix_n.inputs['Fac'].default_value = 0.0 + blend_mix_n.inputs['Factor'].default_value = 0.0 paint_diff_mult_n = node_tree.nodes[Truckpaint.PAINT_DIFFUSE_MULT_NODE] - paint_diff_mult_n.inputs['Fac'].default_value = 1.0 + paint_diff_mult_n.inputs['Factor'].default_value = 1.0 paint_spec_mult_n = node_tree.nodes[Truckpaint.PAINT_SPECULAR_MULT_NODE] - paint_spec_mult_n.inputs['Fac'].default_value = 0.0 + paint_spec_mult_n.inputs['Factor'].default_value = 0.0 diff --git a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/colormask.py b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/colormask.py index 31f0aaa..c547a29 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/colormask.py +++ b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/colormask.py @@ -40,10 +40,10 @@ def init(node_tree): Truckpaint.init_colormask_or_airbrush(node_tree) blend_mix_n = node_tree.nodes[Truckpaint.BLEND_MIX_NODE] - blend_mix_n.inputs['Fac'].default_value = 1.0 + blend_mix_n.inputs['Factor'].default_value = 1.0 paint_diff_mult_n = node_tree.nodes[Truckpaint.PAINT_DIFFUSE_MULT_NODE] - paint_diff_mult_n.inputs['Fac'].default_value = 1.0 + paint_diff_mult_n.inputs['Factor'].default_value = 1.0 paint_spec_mult_n = node_tree.nodes[Truckpaint.PAINT_SPECULAR_MULT_NODE] - paint_spec_mult_n.inputs['Fac'].default_value = 1.0 + paint_spec_mult_n.inputs['Factor'].default_value = 1.0 From a9200285aeded91bb86d3d707b2f0c8a2faf40f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 31 Jan 2025 00:20:29 +0100 Subject: [PATCH 22/56] Import and TOBJ fixes - Fixed "Unexpected 'KeyError' accured during import" error, when importing models with empty parts. Mostly with models, where in pim file is unused "defaultpart" (like in /vehicle/truck/renault_magnum_2009/truck.pmg). - Fixed "Settings in TOBJ file not saved; content is malformed ..." error, when texture other than ".tobj" is used (caused by blender local dirs starting with "//" instead abs path) --- addon/io_scs_tools/imp/pix.py | 4 +++- addon/io_scs_tools/properties/material.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/addon/io_scs_tools/imp/pix.py b/addon/io_scs_tools/imp/pix.py index f9b2b33..e499026 100644 --- a/addon/io_scs_tools/imp/pix.py +++ b/addon/io_scs_tools/imp/pix.py @@ -233,7 +233,9 @@ def _create_scs_root_object(name, loaded_variants, loaded_looks, mats_info, obje part.include = True # cleanup generated terrain points vertex layers by variant - for obj in parts_dict[part.name]: + # Because "collect_parts_on_root" return ONLY dictionary of USED parts, parts_dict must include empty list for cases, + # where part.name is not existing in parts_dict (especially for "defaultpart" which is always included, and can be used also as empty part)) + for obj in parts_dict.get(part.name, []): if obj.type != "MESH": continue diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index dec2bcb..a36f03e 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -129,7 +129,9 @@ def __update_shader_texture_tobj_file__(self, context, tex_type): if texture_raw_path.endswith(".tobj"): texture_name = _path_utils.get_texture_path_from_tobj(tobj_file, raw_value=True) else: - texture_name = os.path.basename(texture_raw_path) + # path must be expanded to absolute path becasue basename doesn't support paths with "//" + abs_texture_path = bpy.path.abspath(texture_raw_path) + texture_name = os.path.basename(abs_texture_path) # update last tobj load time if export was successful otherwise report saving problems if _tobj_exp.export(tobj_file, texture_name, texture_settings): From b14424c8f91aff5de9124b8b0bb8355d94d13a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 31 Jan 2025 07:21:10 +0100 Subject: [PATCH 23/56] Updated to 4.2 Updated shaders to Blender 4.2 --- addon/io_scs_tools/__init__.py | 2 +- .../internals/shaders/eut2/baked/__init__.py | 5 +-- .../internals/shaders/eut2/dif/__init__.py | 20 +++++---- .../shaders/eut2/dif_lum/__init__.py | 19 ++++---- .../shaders/eut2/dif_lum_spec/__init__.py | 19 ++++---- .../shaders/eut2/dif_weight_dif/__init__.py | 18 +++++--- .../shaders/eut2/fakeshadow/__init__.py | 2 +- .../internals/shaders/eut2/glass/__init__.py | 3 +- .../shaders/eut2/light_tex/__init__.py | 3 +- .../internals/shaders/eut2/none/__init__.py | 3 +- .../shaders/eut2/reflective/__init__.py | 3 +- .../shaders/eut2/shadowmap/__init__.py | 3 +- .../shaders/eut2/shadowonly/__init__.py | 6 +-- .../internals/shaders/eut2/sky/__init__.py | 12 +++-- .../std_node_groups/compose_lighting_ng.py | 2 + .../shaders/eut2/unlit_tex/__init__.py | 20 +++++---- .../shaders/eut2/unlit_vcol_tex/__init__.py | 20 +++++---- .../std_node_groups/output_shader_ng.py | 45 ++++++++++++++++--- 18 files changed, 131 insertions(+), 74 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 29586e2..50bee18 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -23,7 +23,7 @@ "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", "version": (2, 4, "aeadde03", 4), - "blender": (4, 1, 0), + "blender": (4, 2, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", "tracker_url": "http://forum.scssoft.com/viewforum.php?f=163", diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py index e4664a5..2f4ea0f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/__init__.py @@ -139,10 +139,7 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" - - if material.blend_method == "OPAQUE" and node_tree.nodes[Baked.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: - node_tree.links.remove(node_tree.nodes[Baked.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) + material.surface_render_method = "DITHERED" @staticmethod def set_shadow_bias(node_tree, value): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py index d8d7344..e979c5e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py @@ -139,6 +139,7 @@ def init(node_tree): compose_lighting_n.location = (start_pos_x + pos_x_shift * 8, start_pos_y + 2000) compose_lighting_n.node_tree = compose_lighting_ng.get_node_group() compose_lighting_n.inputs["Alpha"].default_value = 1.0 + compose_lighting_n.inputs["Alpha Type"].default_value = -1.0 output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") output_n.name = Dif.OUTPUT_NODE @@ -179,16 +180,16 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" + + compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] # set proper blend method and possible alpha test pass if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 + compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): - compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage if compose_lighting_n.inputs['Alpha'].links: @@ -201,13 +202,16 @@ def finalize(node_tree, material): alpha_test.add_pass(node_tree, shader_from, alpha_from, shader_to) if blend_add.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" - if material.blend_method == "OPAQUE" and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py index 23732c3..3b13d7a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py @@ -66,19 +66,19 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" + + compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] # set proper blend method if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 + compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage - compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] if compose_lighting_n.inputs['Alpha'].links: node_tree.links.remove(compose_lighting_n.inputs['Alpha'].links[0]) if lum_out_shader_n.inputs['Alpha'].links: @@ -91,13 +91,16 @@ def finalize(node_tree, material): alpha_test.add_pass(node_tree, shader_from, alpha_from, shader_to) if blend_add.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" - if material.blend_method == "OPAQUE" and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py index e1ec804..0dd8eac 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py @@ -66,19 +66,19 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" + + compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] # set proper blend method if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 + compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage - compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] if compose_lighting_n.inputs['Alpha'].links: node_tree.links.remove(compose_lighting_n.inputs['Alpha'].links[0]) if lum_out_shader_n.inputs['Alpha'].links: @@ -91,13 +91,16 @@ def finalize(node_tree, material): alpha_test.add_pass(node_tree, shader_from, alpha_from, shader_to) if blend_add.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" - if material.blend_method == "OPAQUE" and node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py index b90ab7c..6d95913 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_weight_dif/__init__.py @@ -116,12 +116,13 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" + + compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] # set proper blend method if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 + compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): @@ -138,13 +139,16 @@ def finalize(node_tree, material): alpha_test.add_pass(node_tree, shader_from, alpha_from, shader_to) if blend_add.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): - material.blend_method = "BLEND" + compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" - if material.blend_method == "OPAQUE" and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py index 72f6844..7055593 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py @@ -91,4 +91,4 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" diff --git a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py index 214f48a..4f51f58 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py @@ -370,7 +370,8 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "BLEND" + material.surface_render_method = "BLENDED" + node_tree.nodes[Glass.COMPOSE_LIGHTING_NODE].inputs["Alpha Type"].default_value = 1.0 @staticmethod def set_add_ambient(node_tree, factor): diff --git a/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py index e92045a..bdb1122 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py @@ -87,7 +87,8 @@ def finalize(node_tree, material): Dif.finalize(node_tree, material) # in game it gets added to framebuffer, however we don't have access to frame buffer thus make approximation with alpha blending - material.blend_method = "BLEND" + material.surface_render_method = "BLENDED" + node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs["Alpha Type"].default_value = 1.0 @staticmethod def set_shininess(node_tree, factor): diff --git a/addon/io_scs_tools/internals/shaders/eut2/none/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/none/__init__.py index 88f41a7..9b06b2b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/none/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/none/__init__.py @@ -74,4 +74,5 @@ def finalize(node_tree, material): """ # make sure that alpha clip is enabled on that material - material.blend_method = "CLIP" + material.surface_render_method = "DITHERED" + node_tree.nodes[NNone.SHADER_NODE].inputs["Alpha Type"].default_value = 0.0 diff --git a/addon/io_scs_tools/internals/shaders/eut2/reflective/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/reflective/__init__.py index 7474f0d..d386f36 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/reflective/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/reflective/__init__.py @@ -137,7 +137,8 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "BLEND" + material.surface_render_method = "BLENDED" + node_tree.nodes[Reflective.COMPOSE_LIGHTING_NODE].inputs["Alpha Type"].default_value = 1.0 @staticmethod def set_base_texture(node_tree, image): diff --git a/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py index 5988727..1f7c59b 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/shadowmap/__init__.py @@ -87,7 +87,8 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "BLEND" + material.surface_render_method = "BLENDED" + node_tree.nodes[Shadowmap.OUT_SHADER_NODE].inputs["Alpha Type"].default_value = 1.0 @staticmethod def set_base_texture(node_tree, image): diff --git a/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py index ace9ea2..b3d5d9d 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py @@ -93,17 +93,17 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" # set proper blend method and possible alpha test pass if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 # init parent out_mat_node = node_tree.nodes[Shadowonly.OUT_MAT_NODE] output_n = node_tree.nodes[Shadowonly.OUTPUT_NODE] + out_mat_node.inputs["Alpha Type"].default_value = 0.0 + # links creation node_tree.links.new(out_mat_node.outputs['Shader'], output_n.inputs['Surface']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py index 7e4dc1e..a8365b6 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py @@ -303,14 +303,18 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "BLEND" + material.surface_render_method = "BLENDED" + + out_shader_node = node_tree.nodes[Sky.OUT_SHADER_NODE] if sky_stars.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_node.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if sky_back.is_set(node_tree): - material.blend_method = "OPAQUE" + out_shader_node.inputs["Alpha Type"].default_value = -1.0 + material.surface_render_method = "DITHERED" - if material.blend_method == "OPAQUE" and node_tree.nodes[Sky.OUT_SHADER_NODE].inputs['Alpha'].links: + if out_shader_node.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Sky.OUT_SHADER_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[Sky.OUT_SHADER_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py index ffea647..d690448 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/compose_lighting_ng.py @@ -95,6 +95,7 @@ def __create_node_group__(): compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Diffuse Lighting") compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Specular Lighting") compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha") + compose_light_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha Type", description = "-1 = OPAQUE\n0 = CLIP\n1 = BLEND") # outputs defining compose_light_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") @@ -169,6 +170,7 @@ def __create_node_group__(): compose_light_g.links.new(out_mat_node.inputs["Color"], sum_final_n.outputs["Vector"]) compose_light_g.links.new(out_mat_node.inputs["Alpha"], input_n.outputs["Alpha"]) + compose_light_g.links.new(out_mat_node.inputs["Alpha Type"], input_n.outputs["Alpha Type"]) compose_light_g.links.new(output_n.inputs["Shader"], out_mat_node.outputs["Shader"]) compose_light_g.links.new(output_n.inputs["Color"], sum_final_n.outputs["Vector"]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py index d85c820..83183db 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/unlit_tex/__init__.py @@ -118,17 +118,16 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" + + out_shader_n = node_tree.nodes[UnlitTex.OUT_SHADER_NODE] # set proper blend method if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 + out_shader_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): - out_shader_n = node_tree.nodes[UnlitTex.OUT_SHADER_NODE] - # alpha test pass has to get fully opaque input, thus remove transparency linkage if out_shader_n.inputs['Alpha'].links: node_tree.links.remove(out_shader_n.inputs['Alpha'].links[0]) @@ -140,13 +139,16 @@ def finalize(node_tree, material): alpha_test.add_pass(node_tree, shader_from, alpha_from, shader_to) if blend_add.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" - if material.blend_method == "OPAQUE" and node_tree.nodes[UnlitTex.OUT_SHADER_NODE].inputs['Alpha'].links: + if out_shader_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[UnlitTex.OUT_SHADER_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[UnlitTex.OUT_SHADER_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py index 759bf5f..9c21604 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/unlit_vcol_tex/__init__.py @@ -160,19 +160,18 @@ def finalize(node_tree, material): """ material.use_backface_culling = True - material.blend_method = "OPAQUE" + material.surface_render_method = "DITHERED" + + out_shader_n = node_tree.nodes[UnlitVcolTex.OUT_SHADER_NODE] # set proper blend method if alpha_test.is_set(node_tree): - material.blend_method = "CLIP" - material.alpha_threshold = 0.05 + out_shader_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if: # 1. awhite is enabled, as alpha test pass is called before awhite is aplied # 2. multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if awhite.is_set(node_tree) or blend_mult.is_set(node_tree): - out_shader_n = node_tree.nodes[UnlitVcolTex.OUT_SHADER_NODE] - # alpha test pass has to get fully opaque input, thus remove transparency linkage if out_shader_n.inputs['Alpha'].links: node_tree.links.remove(out_shader_n.inputs['Alpha'].links[0]) @@ -184,13 +183,16 @@ def finalize(node_tree, material): alpha_test.add_pass(node_tree, shader_from, alpha_from, shader_to) if blend_add.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): - material.blend_method = "BLEND" + out_shader_n.inputs["Alpha Type"].default_value = 1.0 + material.surface_render_method = "BLENDED" - if material.blend_method == "OPAQUE" and node_tree.nodes[UnlitVcolTex.OUT_SHADER_NODE].inputs['Alpha'].links: + if out_shader_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[UnlitVcolTex.OUT_SHADER_NODE].inputs['Alpha'].links: node_tree.links.remove(node_tree.nodes[UnlitVcolTex.OUT_SHADER_NODE].inputs['Alpha'].links[0]) @staticmethod diff --git a/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py b/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py index f832ac1..2de23b4 100644 --- a/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py +++ b/addon/io_scs_tools/internals/shaders/std_node_groups/output_shader_ng.py @@ -24,6 +24,9 @@ OUTPUT_SHADER_G = _MAT_consts.node_group_prefix + "OutputShaderGroup" _ALPHA_CLAMP = "AlphaClamp" +_ALPHA_THRESHOLD = "AlphaThreshold" +_ALPHA_TYPE_SWITCH = "AlphaTypeSwitch" +_ALPHA_TYPE_MIX_SWITCH = "AlphaTypeMixSwitch" _EMISSION_OUT_SHADER = "EmissionMaterial" _TRANSPARENT_OUT_SHADER = "TransparentMaterial" _MIX_SHADER = "MixShader" @@ -87,10 +90,12 @@ def __create_node_group__(): # inputs defining output_shader_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketColor", name = "Color") output_shader_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha") + output_shader_g.interface.new_socket(in_out = "INPUT", socket_type = "NodeSocketFloat", name = "Alpha Type", description = "0 = CLIP\n1 = BLEND") # always set to full opaque by default since this behaviour is expected by shaders # since this behaviour is epxected by effects from before. output_shader_g.interface.items_tree['Alpha'].default_value = 1 + output_shader_g.interface.items_tree['Alpha Type'].default_value = -1 # outputs defining output_shader_g.interface.new_socket(in_out = "OUTPUT", socket_type = "NodeSocketShader", name = "Shader") @@ -101,33 +106,59 @@ def __create_node_group__(): input_n.location = (start_pos_x - pos_x_shift, start_pos_y) output_n = output_shader_g.nodes.new("NodeGroupOutput") - output_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y) + output_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y) emission_shader_n = output_shader_g.nodes.new("ShaderNodeEmission") emission_shader_n.name = emission_shader_n.label = _EMISSION_OUT_SHADER - emission_shader_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y + 200) + emission_shader_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 200) alpha_clamp_n = output_shader_g.nodes.new("ShaderNodeClamp") alpha_clamp_n.name = alpha_clamp_n.label = _ALPHA_CLAMP - alpha_clamp_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y) + alpha_clamp_n.location = (start_pos_x, start_pos_y - 150) alpha_clamp_n.inputs['Min'].default_value = 0.000001 # blender clips if alpha is way above, so it's safe to use it alpha_clamp_n.inputs['Max'].default_value = 1 + alpha_threshold_n = output_shader_g.nodes.new("ShaderNodeMath") + alpha_threshold_n.name = alpha_threshold_n.label = _ALPHA_THRESHOLD + alpha_threshold_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y - 150) + alpha_threshold_n.operation = "GREATER_THAN" + alpha_threshold_n.inputs[1].default_value = 0.05 + + alpha_type_switch_n = output_shader_g.nodes.new("ShaderNodeMath") + alpha_type_switch_n.name = alpha_type_switch_n.label = _ALPHA_TYPE_SWITCH + alpha_type_switch_n.location = (start_pos_x, start_pos_y) + alpha_type_switch_n.operation = "GREATER_THAN" + alpha_type_switch_n.inputs[1].default_value = 0.0 + + alpha_type_mix_switch_n = output_shader_g.nodes.new("ShaderNodeMix") + alpha_type_mix_switch_n.name = alpha_type_mix_switch_n.label = _ALPHA_TYPE_MIX_SWITCH + alpha_type_mix_switch_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y) + alpha_type_mix_switch_n.data_type = "FLOAT" + transparent_shader_n = output_shader_g.nodes.new("ShaderNodeBsdfTransparent") transparent_shader_n.name = transparent_shader_n.label = _TRANSPARENT_OUT_SHADER - transparent_shader_n.location = (start_pos_x + pos_x_shift * 1, start_pos_y - 200) + transparent_shader_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y - 200) mix_shader_n = output_shader_g.nodes.new("ShaderNodeMixShader") mix_shader_n.name = mix_shader_n.label = _MIX_SHADER - mix_shader_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y) + mix_shader_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y) # links # pass 1 - output_shader_g.links.new(emission_shader_n.inputs['Color'], input_n.outputs['Color']) + output_shader_g.links.new(alpha_type_switch_n.inputs['Value'], input_n.outputs['Alpha Type']) output_shader_g.links.new(alpha_clamp_n.inputs['Value'], input_n.outputs['Alpha']) # pass 2 - output_shader_g.links.new(mix_shader_n.inputs[0], alpha_clamp_n.outputs[0]) + output_shader_g.links.new(alpha_threshold_n.inputs['Value'], alpha_clamp_n.outputs['Result']) + + # pass 3 + output_shader_g.links.new(emission_shader_n.inputs['Color'], input_n.outputs['Color']) + output_shader_g.links.new(alpha_type_mix_switch_n.inputs['Factor'], alpha_type_switch_n.outputs['Value']) + output_shader_g.links.new(alpha_type_mix_switch_n.inputs['A'], alpha_threshold_n.outputs['Value']) + output_shader_g.links.new(alpha_type_mix_switch_n.inputs['B'], alpha_clamp_n.outputs['Result']) + + # pass 4 + output_shader_g.links.new(mix_shader_n.inputs[0], alpha_type_mix_switch_n.outputs[0]) output_shader_g.links.new(mix_shader_n.inputs[1], transparent_shader_n.outputs[0]) output_shader_g.links.new(mix_shader_n.inputs[2], emission_shader_n.outputs[0]) From 4d4538fc2db651fd3c99cfe3057679f699917636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 5 Feb 2025 06:55:37 +0100 Subject: [PATCH 24/56] fixing normals, flavors replace * Added force replacer for old flavors (tsnmap16 and tsnmapuv16) * Added code for auto-fixes when unofficial version will change * Added support for "dif.spec.weight.add.env.nofresnel" shader * Tweaks in "baked" shader * Tweaks in "dif.spec.amod.dif.spec" shader * Added "/umatlib" to aliasing dirs * Added button to fix normals in "interior" shader * Added some new texture types used by "sky" shader * Updated supported_effects.bin (removed old flavors) --- addon/io_scs_tools/imp/pit.py | 9 +- .../internals/persistent/file_load.py | 62 ++++- .../internals/shaders/eut2/__init__.py | 4 + .../internals/shaders/eut2/baked/add_env.py | 4 +- .../internals/shaders/eut2/baked/spec.py | 2 +- .../eut2/dif_spec_amod_dif_spec/__init__.py | 22 +- .../eut2/dif_spec_weight_add_env/nofresnel.py | 53 ++++ .../io_scs_tools/internals/shaders/shader.py | 6 - addon/io_scs_tools/operators/material.py | 3 +- addon/io_scs_tools/operators/mesh.py | 40 +++ addon/io_scs_tools/properties/material.py | 231 +++++++++++++++++- addon/io_scs_tools/shader_presets.txt | 76 ++++-- addon/io_scs_tools/supported_effects.bin | Bin 195778 -> 142938 bytes addon/io_scs_tools/ui/material.py | 4 +- addon/io_scs_tools/ui/tool_shelf.py | 8 +- addon/io_scs_tools/utils/info.py | 25 ++ 16 files changed, 509 insertions(+), 40 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/nofresnel.py diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index 6139217..c5c8506 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -189,12 +189,19 @@ def _get_look(section): mat_effect = mat_effect.replace(".night", ".day") lprint("W Night version of building shader detected in material %r, switching it to day!", (mat_alias,)) + # Extra treatment for deprecated/removed shaders and flavors + # # If day/night version of "window" shader is detected, switch it to "lit". if mat_effect.startswith("eut2.window") and mat_effect.endswith((".day", ".night")): mat_effect = mat_effect.replace(".day", ".lit").replace(".night", ".lit") + lprint("W Outdated Day/Night version of window shader detected in material %r, switching it to lit!", (mat_alias,)) - lprint("W Outdated Day or Night version of window shader detected in material %r, switching it to lit!", (mat_alias,)) + # If tsnmap16/tsnmapuv16 flavor is detected, switch it to "tsnmap/tsnmapuv + if any(x in mat_effect for x in (".tsnmap16", ".tsnmapuv16")): + + mat_effect = mat_effect.replace(".tsnmap16", ".tsnmap").replace(".tsnmapuv16", ".tsnmapuv") + lprint('W Outdated tsnmap16/tsnmapuv16 flavor detected in material %r, switching it to tsnmap/tsnmapuv!', (mat_alias,)) look_mat_settings[mat_alias] = (mat_effect, mat_flags, attributes, textures, sec) diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 785cb34..76322cc 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -52,12 +52,22 @@ def post_load(scene): ("2.4", apply_fixes_for_2_4), ) + VERSIONS_LIST_UNOFFICIAL = ( + ("4", apply_fixes_for_un_4), + ) + for version, func in VERSIONS_LIST: if _info_utils.cmp_ver_str(last_load_bt_ver, version) <= 0: - - # try to add apply fixed function as callback, if failed execute fixes right now - if not AsyncPathsInit.append_callback(func): - func() + # for, for unofficial versions + for version2, func2 in VERSIONS_LIST_UNOFFICIAL: + if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) <= 0: + # try to add apply fixed function as callback, if failed execute fixes right now + if not AsyncPathsInit.append_callback(func2): + func2() + else: + # try to add apply fixed function as callback, if failed execute fixes right now + if not AsyncPathsInit.append_callback(func): + func() # as last update "last load" Blender Tools version to current _get_scs_globals().last_load_bt_version = _info_utils.get_tools_version() @@ -344,3 +354,47 @@ def apply_fixes_for_2_4(): # 1. reload all materials _reload_materials() + +def apply_fixes_for_un_4(): + """ + Applies fixes for unofficial 2.4.xxxxxx.4 or less: + 1. Pre-reload changes and collect data + 2. Reload materials since some got removed/restructed attributes + a) Replace tsnmap16 and tsnmapuv16 with tsnmap and tsnmapuv + 3. + """ + + print("INFO\t- Applying fixes for unofficial version <= 4") + + + # 1. do pre-reload changes and collect data + for mat in bpy.data.materials: + + effect_name = mat.scs_props.mat_effect_name + + # tsnmap16 and tsnmapuv16 are removed from game, thus replace them. + if any(x in effect_name for x in (".tsnmap16", ".tsnmapuv16")): + mat.scs_props.mat_effect_name = mat.scs_props.mat_effect_name.replace(".tsnmap16", ".tsnmap").replace(".tsnmapuv16", ".tsnmapuv") + + # SOME EXAMPLE CODE, IGNORE + # window.[day|night] got transformed into window.lit + # if effect_name in ("eut2.window.day", "eut2.window.night"): + # mat.scs_props.mat_effect_name = "eut2.window.lit" + # mat.scs_props.active_shader_preset_name = "window.lit" + + # 2. reload all materials + _reload_materials() + + + # Due to update from Blender 3.6, we let user know he is migrating to Blender 4.3 + windows = bpy.data.window_managers[0].windows + if len(windows) > 0: + msg = ( + "\nWelcome folks. You just migrated to Blender 4.3! Yey", + "Big thanks, that you decided to try my unofficial BT update. I appreciate it.", + "I hope you will enjoy new features and fixes I've added to this version.", + "For full changelog and more details, visit official topic on: https://www.forum.scssoft.com" + ) + + with bpy.context.temp_override(window=windows[0]): + bpy.ops.wm.scs_tools_show_3dview_report('INVOKE_DEFAULT', message="\n".join(msg)) \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index cd48395..d264f01 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -206,6 +206,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_oclu_weight_add_env import DifSpecOcluWeightAddEnv as Shader + elif effect.startswith("dif.spec.weight.add.env.nofresnel"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_weight_add_env.nofresnel import DifSpecWeightAddEnvNoFresnel as Shader + elif effect.startswith("dif.spec.weight.add.env"): from io_scs_tools.internals.shaders.eut2.dif_spec_weight_add_env import DifSpecWeightAddEnv as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py index b50dddc..2e5822c 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/add_env.py @@ -158,7 +158,7 @@ def set_mask_uv(node_tree, uv_layer): :type uv_layer: str """ - pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this + BakedSpec.set_base_uv(node_tree, uv_layer) @staticmethod def set_mask_1_texture(node_tree, image): @@ -193,7 +193,7 @@ def set_mask_1_uv(node_tree, uv_layer): :type uv_layer: str """ - pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this + BakedSpec.set_base_uv(node_tree, uv_layer) @staticmethod def set_paint_flavor(node_tree, switch_on): diff --git a/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py b/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py index 528e836..5d1dc3a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py +++ b/addon/io_scs_tools/internals/shaders/eut2/baked/spec.py @@ -111,4 +111,4 @@ def set_over_uv(node_tree, uv_layer): :type uv_layer: str """ - pass # NOTE: as in "baked" shader textures use this same UVs as base_tex, we just ignore this \ No newline at end of file + Baked.set_base_uv(node_tree, uv_layer) \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py index 705a222..a0cbcad 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec/__init__.py @@ -138,13 +138,28 @@ def set_aux0(node_tree, aux_property): node_tree.nodes[DifSpecAmodDifSpec.DECAL_BLEND_FACTOR_NODE].inputs["Factor1"].default_value = aux_property[0]['value'] node_tree.nodes[DifSpecAmodDifSpec.DECAL_BLEND_FACTOR_NODE].inputs["Factor2"].default_value = aux_property[1]['value'] + @staticmethod + def set_mask_uv(node_tree, uv_layer): + """Set UV layer to mask texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for mask texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecAmodDifSpec.SEC_UVMAP_NODE].uv_map = uv_layer + @staticmethod def set_mask_texture(node_tree, image): """Set mask texture to shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param image: texture image which should be assigned to over texture node + :param image: texture image which should be assigned to mask texture node :type image: bpy.types.Texture """ @@ -174,10 +189,7 @@ def set_over_uv(node_tree, uv_layer): :type uv_layer: str """ - if uv_layer is None or uv_layer == "": - uv_layer = _MESH_consts.none_uv - - node_tree.nodes[DifSpecAmodDifSpec.SEC_UVMAP_NODE].uv_map = uv_layer + DifSpecAmodDifSpec.set_mask_uv(node_tree, uv_layer) @staticmethod def set_over_texture(node_tree, image): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/nofresnel.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/nofresnel.py new file mode 100644 index 0000000..e95c1e8 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/nofresnel.py @@ -0,0 +1,53 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.dif_spec_weight_add_env import DifSpecWeightAddEnv + + +class DifSpecWeightAddEnvNoFresnel(DifSpecWeightAddEnv): + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + # init parent + DifSpecWeightAddEnv.init(node_tree) + + # set fresnel factor to 0 + node_tree.nodes[DifSpecWeightAddEnv.ADD_ENV_GROUP_NODE].inputs['Apply Fresnel'].default_value = 0.0 + + @staticmethod + def set_fresnel(node_tree, bias_scale): + """Set fresnel bias and scale value to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param bias_scale: bias and scale factors as tuple: (bias, scale) + :type bias_scale: (float, float) + """ + pass # NOTE: fresnel is not supported on this shader so just skip it diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 08a7efc..3d949a0 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -62,15 +62,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".tsnmapuv") or ".tsnmapuv." in effect: flavors["nmap"] = True - if effect.endswith(".tsnmapuv16") or ".tsnmapuv16." in effect: - flavors["nmap"] = True - if effect.endswith(".tsnmap") or ".tsnmap." in effect: flavors["nmap"] = True - if effect.endswith(".tsnmap16") or ".tsnmap16." in effect: - flavors["nmap"] = True - if effect.endswith(".tsnmapcalc") or ".tsnmapcalc." in effect: flavors["nmap"] = True diff --git a/addon/io_scs_tools/operators/material.py b/addon/io_scs_tools/operators/material.py index 06b6037..8abe169 100644 --- a/addon/io_scs_tools/operators/material.py +++ b/addon/io_scs_tools/operators/material.py @@ -850,7 +850,8 @@ def execute(self, context): is_aliased_directory = ("/material/road" in tex_raw_path or "/material/terrain" in tex_raw_path or - "/material/custom" in tex_raw_path) + "/material/custom" in tex_raw_path or + "/umatlib" in tex_raw_path) # abort if empty texture or not aliased directory if not (tex_raw_path != "" and is_aliased_directory): diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index 62b484f..9dfdf63 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -210,6 +210,45 @@ def execute(self, context): return {'FINISHED'} + class SCS_TOOLS_OT_FixVertexNormals(bpy.types.Operator): + bl_label = "Fix Vertex Normals" + bl_idname = "mesh.scs_tools_fix_vertex_normals" + bl_description = "Fix Vertex Normals for proper interior paralax effect." + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if context.object is None or context.object.mode != "OBJECT" or not context.object.select_get(): + return False + + material = context.object.active_material + if material and material.scs_props.mat_effect_name: + effect = material.scs_props.mat_effect_name + if "eut2.interior" in effect: + return True + + return False + + def execute(self, context): + mesh = context.object.data + + normals_changed = 0 + new_normals = [] + + # Change normals vector to direct upwards (0, 0, 1) + for loop in mesh.loops: + new_normals.append((0.0, 0.0, 1.0)) + normals_changed += 1 + + # Set the new split normals + mesh.normals_split_custom_set(new_normals) + mesh.update() + + + self.report({"INFO"}, "Changed %i split normals!" % normals_changed) + return {'FINISHED'} + + class VertexColorTools: """ Wrapper class for better navigation in file @@ -732,6 +771,7 @@ def execute(self, context): LampTool.SCS_TOOLS_OT_SetLampmaskUV, InteriorWindowTool.SCS_TOOLS_OT_SetGlassReflectionUV, + InteriorWindowTool.SCS_TOOLS_OT_FixVertexNormals, VertexColorTools.SCS_TOOLS_OT_AddVertexColorsToActive, VertexColorTools.SCS_TOOLS_OT_AddVertexColorsToAll, diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index a36f03e..72bfc3e 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -311,7 +311,8 @@ def get_texture_types(): """ return {'base', 'reflection', 'over', 'oclu', 'mask' , 'mask_1' , 'mask_2', 'mask_3', 'mult', 'iamod', 'lightmap', 'paintjob', 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'nmap_over' , 'layer0', 'layer1', - 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b'} + 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b', + 'texture_sky_weather_base_mask_a', 'texture_sky_weather_over_mask_b'} def get_id(self): """Gets unique ID for material within current Blend file. If ID does not exists yet it's calculated. @@ -530,6 +531,30 @@ def update_shader_texture_sky_weather_over_b(self, context): def update_shader_texture_sky_weather_over_b_settings(self, context): __update_shader_texture_tobj_file__(self, context, "sky_weather_over_b") + def update_shader_texture_sky_weather_base_mask_a(self, context): + __update_shader_texture__(self, context, "sky_weather_base_mask_a") + + def update_shader_texture_sky_weather_base_mask_a_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_base_mask_a") + + def update_shader_texture_sky_weather_base_mask_b(self, context): + __update_shader_texture__(self, context, "sky_weather_base_mask_b") + + def update_shader_texture_sky_weather_base_mask_b_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_base_mask_b") + + def update_shader_texture_sky_weather_over_mask_a(self, context): + __update_shader_texture__(self, context, "sky_weather_over_mask_a") + + def update_shader_texture_sky_weather_over_mask_a_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_over_mask_a") + + def update_shader_texture_sky_weather_over_mask_b(self, context): + __update_shader_texture__(self, context, "sky_weather_over_mask_b") + + def update_shader_texture_sky_weather_over_mask_b_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "sky_weather_over_mask_b") + _cached_mat_num = -1 """Caching number of all materials to properly fix material ids when duplicating material""" @@ -2054,6 +2079,210 @@ def update_shader_texture_sky_weather_over_b_settings(self, context): options={'HIDDEN'}, ) + # TEXTURE: SKY_WEATHER_BASE_MASK_A + shader_texture_sky_weather_base_mask_a: StringProperty( + name="Texture Sky Weather Base Mask A", + description="Texture Sky Weather Base Mask A for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_base_mask_a, + ) + shader_texture_sky_weather_base_mask_a_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_base_mask_a_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_base_mask_a_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_base_mask_a_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_base_mask_a_settings + ) + shader_texture_sky_weather_base_mask_a_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_base_mask_a_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_base_mask_a_uv: CollectionProperty( + name="Texture Sky Weather Base Mask A UV Sets", + description="Texture Sky Weather Base Mask A UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: SKY_WEATHER_BASE_MASK_B + shader_texture_sky_weather_base_mask_b: StringProperty( + name="Texture Sky Weather Base Mask B", + description="Texture Sky Weather Base Mask B for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_base_mask_b, + ) + shader_texture_sky_weather_base_mask_b_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_base_mask_b_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_base_mask_b_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_base_mask_b_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_base_mask_b_settings + ) + shader_texture_sky_weather_base_mask_b_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_base_mask_b_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_base_mask_b_uv: CollectionProperty( + name="Texture Sky Weather Base Mask B UV Sets", + description="Texture Sky Weather Base Mask B UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: SKY_WEATHER_OVER_MASK_A + shader_texture_sky_weather_over_mask_a: StringProperty( + name="Texture Sky Weather Over Mask A", + description="Texture Sky Weather Over Mask A for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_over_mask_a, + ) + shader_texture_sky_weather_over_mask_a_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_over_mask_a_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_over_mask_a_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_over_mask_a_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_over_mask_a_settings + ) + shader_texture_sky_weather_over_mask_a_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_over_mask_a_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_over_mask_a_uv: CollectionProperty( + name="Texture Sky Weather Over Mask A UV Sets", + description="Texture Sky Weather Over Mask A UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: SKY_WEATHER_OVER_MASK_B + shader_texture_sky_weather_over_mask_b: StringProperty( + name="Texture Sky Weather Over Mask B", + description="Texture Sky Weather Over Mask B for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_sky_weather_over_mask_b, + ) + shader_texture_sky_weather_over_mask_b_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_sky_weather_over_mask_b_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_sky_weather_over_mask_b_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_sky_weather_over_mask_b_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_sky_weather_over_mask_b_settings + ) + shader_texture_sky_weather_over_mask_b_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_sky_weather_over_mask_b_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_sky_weather_over_mask_b_uv: CollectionProperty( + name="Texture Sky Weather Over Mask B UV Sets", + description="Texture Sky Weather Over Mask B UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + # MAPPING: PERTURBATION shader_mapping_perturbation: CollectionProperty( name="Perturbation UV Sets", diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index dae91b2..a110035 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -435,7 +435,7 @@ Shader { Shader { PresetName: "dif.spec" Effect: "eut2.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -471,7 +471,7 @@ Shader { Shader { PresetName: "dif.spec.add.env" Effect: "eut2.dif.spec.add.env" - Flavors: ( "INDENV" "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "INVERTOPAC" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "INDENV" "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "INVERTOPAC" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -522,7 +522,7 @@ Shader { Shader { PresetName: "dif.spec.add.env.nofresnel" Effect: "eut2.dif.spec.add.env.nofresnel" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -615,7 +615,7 @@ Shader { Shader { PresetName: "dif.spec.mult.dif.spec" Effect: "eut2.dif.spec.mult.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -656,7 +656,7 @@ Shader { Shader { PresetName: "dif.spec.mult.dif.spec.add.env" Effect: "eut2.dif.spec.mult.dif.spec.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -712,7 +712,7 @@ Shader { Shader { PresetName: "dif.spec.mult.dif.iamod.dif.add.env" Effect: "eut2.dif.spec.mult.dif.iamod.dif.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" ) Flags: 0 Attribute { Format: FLOAT3 @@ -773,7 +773,7 @@ Shader { Shader { PresetName: "dif.spec.mult.dif.spec.iamod.dif.spec" Effect: "eut2.dif.spec.mult.dif.spec.iamod.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" ) Flags: 0 Attribute { Format: FLOAT3 @@ -819,7 +819,7 @@ Shader { Shader { PresetName: "dif.spec.oclu" Effect: "eut2.dif.spec.oclu" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -860,7 +860,7 @@ Shader { Shader { PresetName: "dif.spec.oclu.add.env" Effect: "eut2.dif.spec.oclu.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -916,7 +916,7 @@ Shader { Shader { PresetName: "dif.spec.oclu.weight.add.env" Effect: "eut2.dif.spec.oclu.weight.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1054,7 +1054,7 @@ Shader { Shader { PresetName: "dif.spec.weight.add.env" Effect: "eut2.dif.spec.weight.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1102,6 +1102,52 @@ Shader { TexCoord: ( -1 ) } } +Shader { + PresetName: "dif.spec.weight.add.env.nofresnel" + Effect: "eut2.dif.spec.weight.add.env.nofresnel" + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 0.5 0.5 0.5 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 15.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.4 0.4 0.4 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/generic_reflection" + TexCoord: ( -1 ) + } +} Shader { PresetName: "dif.spec.weight.mult2" Effect: "eut2.dif.spec.weight.mult2" @@ -1435,7 +1481,7 @@ Shader { Shader { PresetName: "lamp" Effect: "eut2.lamp" - Flavors: ( "ANIM" "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "ENVMAP" ) + Flavors: ( "ANIM" "NMAP_TS|NMAP_TS_UV" "SHADOW" "ENVMAP" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1566,7 +1612,7 @@ Shader { Shader { PresetName: "retroreflective" Effect: "eut2.retroreflective" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "RETROREFLECTIVE_DIM_ALLDIR|RETROREFLECTIVE_PIKO_ALLDIR" "RETROREFLECTIVE_DECAL" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "RETROREFLECTIVE_DIM_ALLDIR|RETROREFLECTIVE_PIKO_ALLDIR" "RETROREFLECTIVE_DECAL" ) Flags: 0 Texture { Tag: "texture[X]:texture_base" @@ -1682,7 +1728,7 @@ Shader { Shader { PresetName: "truckpaint" Effect: "eut2.truckpaint" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "COLORMASK|AIRBRUSH" "FLIPFLAKE" "ALTUV" "ASAFEWA" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "COLORMASK|AIRBRUSH" "FLIPFLAKE" "ALTUV" "ASAFEWA" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1893,7 +1939,7 @@ Shader { Shader { PresetName: "dif.spec.amod.dif.spec" Effect: "eut2.dif.spec.amod.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16" "SHADOW" "ASAFEWA" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "ASAFEWA" ) Flags: 0 Attribute { Format: FLOAT3 diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index b38ef7bb64a8366efaf4c422154500a1b64bdc1c..41cddd85cce23dc6846717211a75fa19bf5119fd 100644 GIT binary patch literal 142938 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 z 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 literal 195778 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%0D2eju>IfK3xn}i(W~G4bc%a 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 z zGE#stMahb_p@mR>z>670gao6xt&QUw1p77+0qPuI$33ivmKJj4K$lcG^-Q|?k+#SJ zK89qDQ>bovX>g6e(U)%U8`mCe@zn8>sBQs(WAb*7ks#shd7 zdD_!q9$Ka)%(eW}%79-4T`v4bPPf8G-~Aufkw7?rGF@2K&1rLBIAlcPyxRDY9|Z12 z&6Czr8PuM26d{!E073kU-_|!2md8|Gkgtnw;xm)3^21QcSd8*%2J~v>s&=--{}vji z{>q`;xVN0qbS(3yEBY^yB^;PHwGN%(1yox_V6hOMbzi!m<579BSEEtOJzCO#jx)TU zvr6nfk4+(B7~xzmq^cHQxsuK$7>GoOisp$WY$qsG2zYibz4#ceEF%eNTa1?knN}PF zM4{u_$kQ4i=WkHrS9J`qucC)8#7Bb;cPsndgKLjK@N!DJwAR<)U96(R8eH%^JvAxy zXO-t;roXQLj!`e<3!Qh21v9`v>gs^E8K`}>bR*RuNrG>8qFx0kIV37q8r%wq0O4ot zO>sHx`!{xr{}!Rs0+g}>%I2(%@k2*0;kos;$SOOR%nD;AXgC9dXRcAIZF@VCgRNYy zbf)VvHN-#=ps=9fQ26w*Zte3w@9~y;Njh6V%@st+toe9Fe3?g&osGX;25x@|Dtq|T)+^{^LX$}?{+bm&X7uroL)dnIKYah;@&4tz zH*fE33!HU2UcRw~WgOaQX*|ZTS>;>yHAOBQ%$BapT${8dN0_RJ&`YKLZbs`gbpE51 zJ?!5r#tcfUIU1^`Sv$6QMz7vU7lEyXoW_7XO(=veYgcH>za_@E%#_w6%7-uHVbfVb zWUT96{27f1OXf>jDVLh-nx*q82hlmvr9O*9{ZWZz7dH=1mdFl2E_|2@c-h20>J8p2&wkGS>~cTiSi(v3!6U)kVkDbM zI#vq+Vs6@b7Q$xkSy^IdK{PqV(NuS0I(7!w3X~4?lwsl>5)#*$kBG%XZ+$ya}jlrIm`?ULi6YH-hg3OD87CB`ra@STVNrhB(fBP zF(*reQ);Y_n$wyh8Dq~<*fUFxErS@8!31sK*iy#RT7rMjk5R3@kkx|Ds70caIAcQ7 zWacE>mV{DBdNh>SPLMe=-3(TD3*N@GOas7v&uBd^G5LTgl}v|gQ}d+qZEil_eMM@vf>M3LYN+(VkORQ=LNF`)qFVAr_5tbLi z9oiV|T36c7S1cYOutH?9j93=4ED_C_5!QtC%!63pRK?em6zp(*1_Lw@tYl0gB_~uC znC4!-R&|Qgxwg?{+i-eL62BB398s)bo{OOw5HR$XVMVr6o5r@9eE4S1jDkEb0D*y9 zGW640vy0S55;o_MwZ_c(lm>I!@*_TWIc?)0KNgu^&`qZ~MiKv?D--eIB4LksC`Eve z8jSpRN=>oXe{m8LkxCIVv`a_uIEOS_wlU`R&YVRkEW|iPIw-CZ-kQ&EW+=X;^)NK7 zn65ARZ%~A|W1=*E#s+&SpzR~et3S}|C?dCQaG1EV_(BOm zCoys?i24y@KwWly@oq)dl;jZ8+`PiJMBd`JQ6%9*W^@NeNH8!}d5yHm$d4U%2yywzp$ zuAue}=FA~QU7Y5T-|UZDr@CQb=;Ss(Y)p0R(W5VKUr?P4AR?#xVDtqu}qgA2AqZzvQQ1!%Nw?pn%BWUMNys{Yd0 zR_? '/material/road'\n" "-> '/material/terrain'\n" "-> '/material/custom'\n" + "-> '/umatlib'\n" "Additional requirement for aliasing is also single texture material or\n" "exceptionally multi texture material of 'dif.spec.weight.mult2' family.\n\n" "Currently aliasing can not be done because:") diff --git a/addon/io_scs_tools/ui/tool_shelf.py b/addon/io_scs_tools/ui/tool_shelf.py index 746aed9..f4d3e76 100644 --- a/addon/io_scs_tools/ui/tool_shelf.py +++ b/addon/io_scs_tools/ui/tool_shelf.py @@ -508,15 +508,14 @@ class SCS_TOOLS_PT_InteriorWindowTool(_ToolShelfBlDefs, Panel): Creates Interior Window Tool panel for SCS Tools tab. """ bl_label = "Interior Window Tool" - bl_context = "mesh_edit" @classmethod def poll(cls, context): - return context.mode == "EDIT_MESH" + return context.mode in {'EDIT_MESH', 'OBJECT'} def draw(self, context): if not self.poll(context): - self.layout.label(text="Not in 'Edit Mode'!", icon="INFO") + self.layout.label(text="Not in 'Edit/Object Mode'!", icon="INFO") return layout = self.layout @@ -531,6 +530,9 @@ def draw(self, context): props = body_row.operator("mesh.scs_tools_set_glassreflection_uv", text="Disable") props.glass_state = _IWT_consts.GlassReflection.Disable.name + body_col = layout.column(align=True) + body_col.operator("mesh.scs_tools_fix_vertex_normals", text="Fix Vertex Normals", icon='NORMALS_VERTEX_FACE') + class SCS_TOOLS_PT_VColoring(_ToolShelfBlDefs, Panel): bl_label = "VColoring" diff --git a/addon/io_scs_tools/utils/info.py b/addon/io_scs_tools/utils/info.py index ed36fc8..ff3373c 100644 --- a/addon/io_scs_tools/utils/info.py +++ b/addon/io_scs_tools/utils/info.py @@ -128,3 +128,28 @@ def cmp_ver_str(version_str, version_str2): # otherwise we directly assume that first is greater return 1 + +def cmp_ver_str_unofficial(version_str, version_str2): + """Compares two version string of format "X.X.X..." where X is number. + + :param version_str: version string to check (should be in format: "X" where X is unofficial update number) + :type version_str: str + :param version_str2: version string to check (should be in format: "X" where X is unofficial update number) + :type version_str2: str + :return: -1 if first is smaller; 0 if equal; 1 if first is greater; + :rtype: int + """ + + version_str = version_str.split(".") + version_str2 = version_str2.split(".") + + # Fix for users migrating from official BT (without unofficial update version at BT version) + while len(version_str) <= 3: + version_str.append('0') + + if int(version_str[3]) < int(version_str2[0]): + return -1 + elif int(version_str[3]) == int(version_str2[0]): + return 0 + else: + return 1 \ No newline at end of file From b5582aceebdf73de98a9188461f1050104b30ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 18 Feb 2025 05:03:10 +0100 Subject: [PATCH 25/56] piko.alldir "support", sky shader tweaks * Added support for "piko.alldir" flavor. BT can now import and export it properly, and it will not cause "Unable to map attribute (v.factor)" error in game. "Factor" vertex color is responsible for a slight change in mesh size/vertex position up to about 100m from the camera (IDK how it exactly works). Since it's added as "Factor" vertex color, user can change values by themselves and adjust it if needed (RGB = XYZ axis move, A is a duplicate of Z, idk if game use it). * "sky" shader tweaks - replaced old default values that indicated to removed/moved textures and added new ones that were missing (shader still is WIP and flavors/shader code must be updated to work correctly) --- addon/io_scs_tools/consts.py | 1 + addon/io_scs_tools/exp/pim/exporter.py | 38 +++++++++++++--- addon/io_scs_tools/exp/pim/piece.py | 48 ++++++++++++++------- addon/io_scs_tools/exp/pim/piece_stream.py | 2 +- addon/io_scs_tools/exp/pim_ef/exporter.py | 41 +++++++++++++++++- addon/io_scs_tools/exp/pim_ef/piece.py | 20 +++++++-- addon/io_scs_tools/exp/pim_ef/piece_face.py | 23 ++++++++++ addon/io_scs_tools/imp/pim.py | 32 +++++++++++--- addon/io_scs_tools/imp/pim_ef.py | 35 ++++++++++++--- addon/io_scs_tools/operators/mesh.py | 28 ++++++++++++ addon/io_scs_tools/shader_presets.txt | 28 ++++++++++-- addon/io_scs_tools/ui/material.py | 7 ++- addon/io_scs_tools/utils/mesh.py | 40 ++++++++++++++++- 13 files changed, 296 insertions(+), 47 deletions(-) diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index acae933..3b00c44 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -276,6 +276,7 @@ class Mesh: default_uv = "UV" default_vcol = "Col" + default_vfactor = "Factor" class PrefabLocators: diff --git a/addon/io_scs_tools/exp/pim/exporter.py b/addon/io_scs_tools/exp/pim/exporter.py index fb7f5ac..f9e4e5d 100644 --- a/addon/io_scs_tools/exp/pim/exporter.py +++ b/addon/io_scs_tools/exp/pim/exporter.py @@ -204,6 +204,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat missing_uv_layers = {} # stores missing uvs specified by materials of this object missing_vcolor = False # indicates if object is missing vertex color layer missing_vcolor_a = False # indicates if object is missing vertex color alpha layer + missing_vfcolor = False # indicates if object is missing vertex color factor layer missing_skinned_verts = set() # indicates if object is having only partial skin, which is not allowed in our models has_unnormalized_skin = False # indicates if object has vertices which bones weight sum is smaller then one last_tangents_uv_layer = None # stores uv layer for which tangents were calculated, so tangents won't be calculated all over again @@ -425,23 +426,45 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat vcol += (alpha * 2,) - # 5. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before + # 5. vfcol -> vfcol_lay = mesh.color_attributes[2].data; vfcol_lay[loop_i].color + vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name + if vfactor_shader: + if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component + vfcol = (0.0,) * 4 + missing_vfcolor = True + else: + vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] + + if vfcolors.domain == 'POINT': + color = Color(vfcolors.data[vert_i].color[:3]) + alpha = vfcolors.data[vert_i].color[3] + elif vfcolors.domain == 'CORNER': + color = Color(vfcolors.data[loop_i].color[:3]) + alpha = vfcolors.data[loop_i].color[3] + else: + raise TypeError("Invalid vertex color domain type!") + + color = color.from_scene_linear_to_srgb() + + vfcol = tuple(round(value * 255) / 1.0 for value in (*color, alpha)) + + # 6. tangent -> loop.tangent; loop.bitangent_sign -> calc_tangents() has to be called before if pim_materials[pim_mat_name].get_nmap_uv_name(): # calculate tangents only if needed tangent = (tangent_transf_mat @ loop.tangent).normalized() tangent = (tangent[0], tangent[1], tangent[2], loop.bitangent_sign) else: tangent = None - # 6. There we go, vertex data collected! Now create internal vertex index, for triangle and skin stream construction + # 7. There we go, vertex data collected! Now create internal vertex index, for triangle and skin stream construction # Construct unique vertex index - donated by mesh and vertex index, as we may export more mesh objects into same piece, # thus only vertex index wouldn't be unique representation. unique_vert_i = "%i|%i" % (mesh_i, vert_i) - piece_vert_index = mesh_piece.add_vertex(unique_vert_i, position, normal, uvs, uvs_aliases, vcol, tangent) + piece_vert_index = mesh_piece.add_vertex(unique_vert_i, position, normal, uvs, uvs_aliases, vcol, vfcol, tangent) - # 7. Add vertex to triangle creation list + # 8. Add vertex to triangle creation list triangle_pvert_indices.append(piece_vert_index) - # 8. Get skinning data for vertex and save it to skin stream + # 9. Get skinning data for vertex and save it to skin stream if is_skin_used: bone_weights = {} bone_weights_sum = 0 @@ -463,7 +486,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat if bone_weights_sum < 1: has_unnormalized_skin = True - # 9. Terrain Points: save vertex to terrain points storage, if present in correct vertex group + # 10. Terrain Points: save vertex to terrain points storage, if present in correct vertex group if has_terrain_points: for group in mesh.vertices[vert_i].groups: @@ -526,6 +549,9 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat if missing_vcolor_a: lprint("W Object %r is missing vertex color alpha layer with name %r! Default alpha will be exported (0.5)", (mesh_obj.name, _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix)) + if missing_vfcolor: + lprint("W Object %r is missing vertex factor color layer with name %r! Default factor will be exported (0.0)", + (mesh_obj.name, _MESH_consts.default_vfactor)) if len(missing_skinned_verts) > 0: lprint("E Object %r from SCS Root %r has %s vertices which are not skinned to any bone, expect errors during conversion!", (mesh_obj.name, root_object.name, len(missing_skinned_verts))) diff --git a/addon/io_scs_tools/exp/pim/piece.py b/addon/io_scs_tools/exp/pim/piece.py index 30653a2..3968556 100644 --- a/addon/io_scs_tools/exp/pim/piece.py +++ b/addon/io_scs_tools/exp/pim/piece.py @@ -59,8 +59,8 @@ def get_global_triangle_count(): return Piece.__global_triangle_count @staticmethod - def __calc_vertex_hash(index, normal, uvs, rgba, tangent): - """Calculates vertex hash from original vertex index, uvs components and vertex color. + def __calc_vertex_hash(index, normal, uvs, rgba, factor, tangent): + """Calculates vertex hash from original vertex index, uvs components, vertex color and vertex factor color. :param index: original index from Blender mesh :type index: str :param normal: normalized vector representation of the normal @@ -69,6 +69,8 @@ def __calc_vertex_hash(index, normal, uvs, rgba, tangent): :type uvs: list of (tuple | mathutils.Vector) :param rgba: rgba representation of vertex color in SCS values :type rgba: tuple | mathutils.Color + :param factor: rgba representation of vertex factor color in SCS values + :type factor: tuple | mathutils.Color :param tangent: vertex tangent in SCS coordinates or none, if piece doesn't have tangents :type tangent: tuple | None :return: calculated vertex hash @@ -78,17 +80,17 @@ def __calc_vertex_hash(index, normal, uvs, rgba, tangent): if tangent: vertex_hash = (index, - int(normal[0] * fprec), - int(normal[1] * fprec), - int(normal[2] * fprec), - int(rgba[0] * fprec), - int(rgba[1] * fprec), - int(rgba[2] * fprec), - int(rgba[3] * fprec), - int(tangent[0] * fprec), - int(tangent[1] * fprec), - int(tangent[2] * fprec), - int(tangent[3] * fprec)) + int(normal[0] * fprec), + int(normal[1] * fprec), + int(normal[2] * fprec), + int(rgba[0] * fprec), + int(rgba[1] * fprec), + int(rgba[2] * fprec), + int(rgba[3] * fprec), + int(tangent[0] * fprec), + int(tangent[1] * fprec), + int(tangent[2] * fprec), + int(tangent[3] * fprec)) else: vertex_hash = (index, int(normal[0] * fprec), @@ -99,6 +101,12 @@ def __calc_vertex_hash(index, normal, uvs, rgba, tangent): int(rgba[2] * fprec), int(rgba[3] * fprec)) + if factor: + vertex_hash += (int(factor[0] * fprec), + int(factor[1] * fprec), + int(factor[2] * fprec), + int(factor[3] * fprec)) + for uv in uvs: vertex_hash += (int(uv[0] * fprec), int(uv[1] * fprec)) @@ -155,7 +163,7 @@ def add_triangle(self, triangle): return True - def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tangent): + def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, factor, tangent): """Adds new vertex to position and normal streams :param vert_index: original vertex index from Blender mesh :type vert_index: str @@ -169,13 +177,15 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange :type uvs_aliases: list[list[str]] :param rgba: rgba representation of vertex color in SCS values :type rgba: tuple | mathutils.Color + :param factor: rgba representation of vertex factor color in SCS values + :type factor: tuple | mathutils.Color :param tangent: tuple representation of vertex tangent in SCS values or None if piece doesn't have tangents :type tangent: tuple | None :return: vertex index inside piece streams ( use it for adding triangles ) :rtype: int """ - vertex_hash = self.__calc_vertex_hash(vert_index, normal, uvs, rgba, tangent) + vertex_hash = self.__calc_vertex_hash(vert_index, normal, uvs, rgba, factor, tangent) # save vertex if the vertex with the same properties doesn't exists yet in streams if vertex_hash not in self.__vertices_hash: @@ -213,6 +223,14 @@ def add_vertex(self, vert_index, position, normal, uvs, uvs_aliases, rgba, tange stream = self.__streams[Stream.Types.RGBA] stream.add_entry(rgba) + if factor: + # create factor stream on demand + if Stream.Types.FACTOR not in self.__streams: + self.__streams[Stream.Types.FACTOR] = Stream(Stream.Types.FACTOR, -1) + + stream = self.__streams[Stream.Types.FACTOR] + stream.add_entry(factor) + vert_index_internal = stream.get_size() - 1 # streams has to be alligned so I can take last one for the index self.__vertices_hash[vertex_hash] = vert_index_internal diff --git a/addon/io_scs_tools/exp/pim/piece_stream.py b/addon/io_scs_tools/exp/pim/piece_stream.py index 8b779c5..ee9a49d 100644 --- a/addon/io_scs_tools/exp/pim/piece_stream.py +++ b/addon/io_scs_tools/exp/pim/piece_stream.py @@ -31,9 +31,9 @@ class Types: TANGENT = "_TANGENT" RGB = "_RGB" RGBA = "_RGBA" + FACTOR = "_FACTOR" # NOTE: used only in piko.alldir flavor UV = "_UV" # NOTE: there can be up to 9 uv streams TUV = "_TUV" # NOTE: there can be up to 9 tuv streams - FACTOR = "_FACTOR" # NOTE: used only in piko.alldir flavor __format = "" # defined by type of tag __tag = Types.POSITION diff --git a/addon/io_scs_tools/exp/pim_ef/exporter.py b/addon/io_scs_tools/exp/pim_ef/exporter.py index f154135..98d5397 100644 --- a/addon/io_scs_tools/exp/pim_ef/exporter.py +++ b/addon/io_scs_tools/exp/pim_ef/exporter.py @@ -177,6 +177,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat missing_uv_layers = {} # stores missing uvs specified by materials of this object missing_vcolor = False # indicates if object is missing vertex color layer missing_vcolor_a = False # indicates if object is missing vertex color alpha layer + missing_vfcolor = False # indicates if object is missing vertex factor color layer missing_skinned_verts = set() # indicates if object is having only partial skin, which is not allowed in our models has_unnormalized_skin = False # indicates if object has vertices which bones weight sum is smaller then one @@ -220,6 +221,8 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat uvs_names = collections.OrderedDict() vert_rgbas = [] rgbas_names = collections.OrderedDict() + vert_factors = [] + factors_names = collections.OrderedDict() tex_coord_alias_map = pim_materials[pim_mat_name].get_tex_coord_map() for loop_i in poly.loop_indices: @@ -312,11 +315,40 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat rgbas.append(vcol) rgbas_names[_MESH_consts.default_vcol] = True + # 5. vfcol -> vfcol_lay = mesh.color_attributes[2].data; vfcol_lay[loop_i].color + factors = [] + vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name + if vfactor_shader: + if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component + vfcol = (0.0,) * 4 + missing_vfcolor = True + else: + vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] + + if vfcolors.domain == 'POINT': + color = Color(vfcolors.data[vert_i].color[:3]) + alpha = vfcolors.data[vert_i].color[3] + elif vfcolors.domain == 'CORNER': + color = Color(vfcolors.data[loop_i].color[:3]) + alpha = vfcolors.data[loop_i].color[3] + else: + raise TypeError("Invalid vertex color domain type!") + + color = color.from_scene_linear_to_srgb() + + # round and convert from 0.0-1.0 to 0-255 range, then make it float again + vfcol = tuple(round(value * 255) / 1.0 for value in (*color, alpha)) + + factors.append(vfcol) + factors_names[_MESH_consts.default_vfactor] = True + + vert_factors.append(factors) + # export rest of the vertex colors too (also multiply with 2 and with vcol multiplicator) for vcol_layer in mesh.color_attributes: # we already computed thoose so ignore them - if vcol_layer.name in [_MESH_consts.default_vcol, _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix]: + if vcol_layer.name in [_MESH_consts.default_vcol, _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix, _MESH_consts.default_vfactor]: continue color = vcol_layer.data[loop_i].color @@ -402,7 +434,9 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat list(uvs_names.keys()), uvs_aliases, tuple(vert_rgbas[::winding_order]), - list(rgbas_names.keys()) + list(rgbas_names.keys()), + tuple(vert_factors[::winding_order]), + list(factors_names.keys()) ) # as we captured all hard edges collect them now and put it into Piece @@ -437,6 +471,9 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat if missing_vcolor_a: lprint("W Object %r is missing vertex color alpha layer with name %r! Default alpha will be exported (0.5)", (mesh_obj.name, _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix)) + if missing_vfcolor: + lprint("W Object %r is missing vertex factor color layer with name %r! Default RGBA color will be exported (0.0, 0.0, 0.0, 0.0)!", + (mesh_obj.name, _MESH_consts.default_vfactor)) if len(missing_skinned_verts) > 0: lprint("E Object %r from SCS Root %r has %s vertices which are not skinned to any bone, expect errors during conversion!", (mesh_obj.name, root_object.name, len(missing_skinned_verts))) diff --git a/addon/io_scs_tools/exp/pim_ef/piece.py b/addon/io_scs_tools/exp/pim_ef/piece.py index f469582..bbe8f27 100644 --- a/addon/io_scs_tools/exp/pim_ef/piece.py +++ b/addon/io_scs_tools/exp/pim_ef/piece.py @@ -62,7 +62,7 @@ def get_global_face_count(): @staticmethod def __calc_vertex_hash(index, position): - """Calculates vertex hash from original vertex index, uvs components and vertex color. + """Calculates vertex hash from original vertex index, uvs components, vertex color and vertex factor color. :param index: original index from Blender mesh :type index: int :param position: vector of 3 floats presenting vertex position in SCS values @@ -104,7 +104,7 @@ def __init__(self, index): Piece.__global_piece_count += 1 - def add_face(self, material, vert_indicies, vert_normals, vert_uvs, uvs_names, uvs_aliases, vert_rgbas, rgbas_names): + def add_face(self, material, vert_indicies, vert_normals, vert_uvs, uvs_names, uvs_aliases, vert_rgbas, rgbas_names, vert_factors, factors_names): """Adds new face to piece. :param material: material that should be used on for this piece @@ -123,6 +123,10 @@ def add_face(self, material, vert_indicies, vert_normals, vert_uvs, uvs_names, u :type vert_rgbas: list[tuple | mathutils.Color] | tuple[tuple | mathutils.Color] :param rgbas_names: tuple or list of vertex color layer names used on vertex :type rgbas_names: list[str] | tuple[str] + :param vert_factors: list of factors vertex colors in SCS values + :type vert_factors: list[tuple | mathutils.Color] | tuple[tuple | mathutils.Color] + :param factors_names: tuple or list of vertex factor color layer names used on vertex + :type factors_names: list[str] | tuple[str] :return: True if added; False otherwise :rtype: bool """ @@ -136,8 +140,12 @@ def add_face(self, material, vert_indicies, vert_normals, vert_uvs, uvs_names, u return False # check integrity between all parameters - if not len(vert_indicies) == len(vert_normals) == len(vert_uvs) == len(vert_rgbas): - return False + if vert_factors: + if not len(vert_indicies) == len(vert_normals) == len(vert_uvs) == len(vert_rgbas) == len(vert_factors): + return False + else: + if not len(vert_indicies) == len(vert_normals) == len(vert_uvs) == len(vert_rgbas): + return False face = Face(len(self.__faces), material, vert_indicies) @@ -152,6 +160,10 @@ def add_face(self, material, vert_indicies, vert_normals, vert_uvs, uvs_names, u # add rgbas per vertex face.add_rgbas(vert_rgbas[i], rgbas_names) + # add factors per vertex if available + if vert_factors: + face.add_factors(vert_factors[i], factors_names) + self.__faces.append(face) Piece.__global_face_count += 1 diff --git a/addon/io_scs_tools/exp/pim_ef/piece_face.py b/addon/io_scs_tools/exp/pim_ef/piece_face.py index 4b455bc..670e535 100644 --- a/addon/io_scs_tools/exp/pim_ef/piece_face.py +++ b/addon/io_scs_tools/exp/pim_ef/piece_face.py @@ -116,6 +116,29 @@ def add_rgbas(self, rgbas, rgbas_names): """:type: Stream""" stream.add_entry(rgba) + def add_factors(self, factors, factors_names): + """Adds next vertex factor colors to the end of the stream. + + NOTE: There is no check between length of stream and number of indicies present in face + + :param factors: next vertex vertex factor colors + :type factors: tuple[tuple[float]] | tuple[mathutils.Vector] + :param factors_names: tuple or list of uv vertex factor color layer names used on vertex + :type factors_names: list[str] | tuple[str] + """ + + for i, factor in enumerate(factors): + + stream_type = Stream.Types.FACTOR + vfcol_type = Stream.Types.FACTOR + str(i) + + if vfcol_type not in self.__streams: + self.__streams[vfcol_type] = Stream(stream_type, i, factors_names[i]) + + stream = self.__streams[vfcol_type] + """:type: Stream""" + stream.add_entry(factor) + def get_stream_count(self): """Gets count of all streams used in this face. diff --git a/addon/io_scs_tools/imp/pim.py b/addon/io_scs_tools/imp/pim.py index 999fc79..83a9a11 100644 --- a/addon/io_scs_tools/imp/pim.py +++ b/addon/io_scs_tools/imp/pim.py @@ -144,6 +144,7 @@ def _get_piece_streams(section): mesh_tangents = [] mesh_rgb = {} mesh_rgba = {} + mesh_factor = {} mesh_uv = {} mesh_scalars = [] mesh_tuv = [] @@ -197,6 +198,9 @@ def _get_piece_streams(section): elif stream_tag.startswith("_RGBA") and stream_format == 'FLOAT4': mesh_rgba[str(_MESH_consts.default_vcol)] = data_block # print('data_block.props: %s' % str(data_block)) + elif stream_tag.startswith("_FACTOR") and stream_format == 'FLOAT4': + mesh_factor[str(_MESH_consts.default_vfactor)] = data_block + # print('data_block.props: %s' % str(data_block)) elif stream_tag.startswith("_UV") and stream_format == 'FLOAT2': mesh_uv[str(_MESH_consts.default_uv + num_suffix)] = { "data": data_block, @@ -210,7 +214,7 @@ def _get_piece_streams(section): for data_line in sec.data: data_line.reverse() # flip triangle normals mesh_triangles.append(data_line) - return mesh_vertices, mesh_normals, mesh_tangents, mesh_rgb, mesh_rgba, mesh_scalars, mesh_uv, mesh_tuv, mesh_triangles + return mesh_vertices, mesh_normals, mesh_tangents, mesh_rgb, mesh_rgba, mesh_factor, mesh_scalars, mesh_uv, mesh_tuv, mesh_triangles def get_part_properties(section): @@ -391,6 +395,7 @@ def _create_piece( mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, object_skinning, mesh_uv, @@ -445,6 +450,11 @@ def _create_piece( else: mesh_rgb_final = [] + # VERTEX FACTORS + mesh_factor_final = [] + if mesh_factor: + mesh_factor_final = mesh_factor + vcolor_corrupt = False for vc_layer_name in mesh_rgb_final: @@ -461,6 +471,9 @@ def _create_piece( if vcolor_corrupt: lprint("W Piece %r has vertices with vertex color greater the 1.0, clamping it!", (name,)) + for vfc_layer_name in mesh_factor_final: + _mesh_utils.bm_make_vfc_layer(5, bm, vfc_layer_name, mesh_factor_final[vfc_layer_name]) + context.window_manager.progress_update(0.5) bm.to_mesh(mesh) @@ -601,6 +614,7 @@ def _create_piece( mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, object_skinning, mesh_uv, @@ -754,6 +768,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, mesh_uv, mesh_tuv, @@ -766,6 +781,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa mesh_normals, mesh_rgb, mesh_rgba, + mesh_factor, scs_globals.import_welding_precision) objects_data[ob_index] = ( @@ -777,6 +793,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, mesh_uv, mesh_tuv, @@ -788,12 +805,14 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa # print('ob_material: %s' % ob_material) # print('mesh_vertices: %s' % mesh_vertices) # print('mesh_rgba 1: %s' % str(mesh_rgba)) + # print('mesh_factor: %s' % str(mesh_factor)) # print('mesh_uv count: %s' % len(mesh_uv)) # print('mesh_triangles: %s' % mesh_triangles) # print('mesh_faces: %s' % mesh_faces) # print('mesh_face_materials: %s' % mesh_face_materials) # print('mesh_edges: %s' % mesh_edges) # print('piece_count: %s' % str(piece_count)) + # print('---------------------') piece_count -= 1 elif section.type == 'Part': if scs_globals.import_pim_file: @@ -935,13 +954,14 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa objects_data[obj_i][5], # mesh_tangents objects_data[obj_i][6], # mesh_rgb objects_data[obj_i][7], # mesh_rgba - objects_data[obj_i][8], # mesh_scalars + objects_data[obj_i][8], # mesh_factor + objects_data[obj_i][9], # mesh_scalars object_skinning, - objects_data[obj_i][9], # mesh_uv - objects_data[obj_i][10], # mesh_tuv - objects_data[obj_i][11], # mesh_triangles + objects_data[obj_i][10], # mesh_uv + objects_data[obj_i][11], # mesh_tuv + objects_data[obj_i][12], # mesh_triangles materials_data, - objects_data[obj_i][12], # points_to_weld_list + objects_data[obj_i][13], # points_to_weld_list terrain_points_trans, ) diff --git a/addon/io_scs_tools/imp/pim_ef.py b/addon/io_scs_tools/imp/pim_ef.py index b1143bb..d8c8900 100644 --- a/addon/io_scs_tools/imp/pim_ef.py +++ b/addon/io_scs_tools/imp/pim_ef.py @@ -80,6 +80,7 @@ def _get_piece_streams(section): mesh_uv_aliases = {} mesh_rgb = {} mesh_rgba = {} + mesh_factor = {} mesh_scalars = {} for sec in section.sections: if sec.type == "Stream": @@ -178,6 +179,12 @@ def _get_piece_streams(section): # print(' face_data_block:\n%s' % str(face_data_block)) mesh_rgb[face_stream_name].append(face_data_block) + elif face_stream_tag.startswith("_FACTOR") and face_stream_type == "FLOAT4": + if face_stream_name not in mesh_factor: + mesh_factor[face_stream_name] = [] + # print(' face_data_block:\n%s' % str(face_data_block)) + mesh_factor[face_stream_name].append(face_data_block) + elif face_stream_tag.startswith("_UV") and face_stream_type == "FLOAT2": if face_stream_name not in mesh_uv: mesh_uv[face_stream_name] = [] @@ -204,6 +211,7 @@ def _get_piece_streams(section): mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, mesh_uv, mesh_uv_aliases, @@ -223,6 +231,7 @@ def _create_piece( mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, object_skinning, mesh_uv, @@ -287,6 +296,11 @@ def _create_piece( if mesh_rgb: mesh_rgb_final.update(mesh_rgb) + # VERTEX FACTORS + mesh_factor_final = [] + if mesh_factor: + mesh_factor_final = mesh_factor + vcolor_corrupt = False for vc_layer_name in mesh_rgb_final: @@ -304,6 +318,9 @@ def _create_piece( if vcolor_corrupt: lprint("W Piece %r has vertices with vertex color greater the 1.0, clamping it!", (name,)) + for vfc_layer_name in mesh_factor_final: + _mesh_utils.bm_make_vfc_layer(7, bm, vfc_layer_name, mesh_factor_final[vfc_layer_name]) + bm.to_mesh(mesh) mesh.update() bm.free() @@ -532,6 +549,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, mesh_uv, mesh_uv_aliases, @@ -548,6 +566,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa mesh_tangents, mesh_rgb, mesh_rgba, + mesh_factor, mesh_scalars, mesh_uv, mesh_uv_aliases, @@ -561,6 +580,7 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa # print('ob_material: %s' % ob_material) # print('mesh_vertices: %s' % mesh_vertices) # print('mesh_rgba 1: %s' % str(mesh_rgba)) + # print('mesh_factor: %s' % str(mesh_factor)) # print('mesh_uv count: %s' % len(mesh_uv)) # print('mesh_triangles: %s' % mesh_triangles) # print('mesh_faces: %s' % mesh_faces) @@ -683,14 +703,15 @@ def load_pim_file(context, filepath, terrain_points_trans=None, preview_model=Fa objects_data[obj_i][4], # mesh_tangents objects_data[obj_i][5], # mesh_rgb objects_data[obj_i][6], # mesh_rgba - objects_data[obj_i][7], # mesh_scalars + objects_data[obj_i][7], # mesh_factor + objects_data[obj_i][8], # mesh_scalars object_skinning, - objects_data[obj_i][8], # mesh_uv - objects_data[obj_i][9], # mesh_uv_aliases - objects_data[obj_i][10], # mesh_tuv - objects_data[obj_i][11], # mesh_faces - objects_data[obj_i][12], # mesh_face_materials - objects_data[obj_i][13], # mesh_edges + objects_data[obj_i][9], # mesh_uv + objects_data[obj_i][10], # mesh_uv_aliases + objects_data[obj_i][11], # mesh_tuv + objects_data[obj_i][12], # mesh_faces + objects_data[obj_i][13], # mesh_face_materials + objects_data[obj_i][14], # mesh_edges terrain_points_trans, materials_data, ) diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index 9dfdf63..c79ec38 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -389,11 +389,16 @@ def poll(cls, context): def execute(self, context): default_color = tuple(Color((0.5,) * 3).from_srgb_to_scene_linear()) + (1.0,) + default_factor = tuple((0.0,) * 4) layer_name = _MESH_consts.default_vcol layer_a_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix + layer_factor_name = _MESH_consts.default_vfactor old_active_col_i = context.object.data.color_attributes.active_index + mat = context.active_object.active_material + vfactor_shader = ("piko.alldir") in mat.scs_props.mat_effect_name + for curr_lay_name in (layer_name, layer_a_name): if curr_lay_name not in context.object.data.color_attributes: @@ -405,6 +410,15 @@ def execute(self, context): for vertex_col_data in context.object.data.color_attributes[curr_lay_name].data: vertex_col_data.color = default_color + # factor vertex color layer + if vfactor_shader and layer_factor_name not in context.object.data.color_attributes: + vfcolor = context.object.data.color_attributes.new(name=layer_factor_name, type='BYTE_COLOR', domain='CORNER') + vfcolor.name = layer_factor_name # repeat naming step to make sure it's properly named + + # setting neutral value (0.0) to all factors + for vertex_fac_col_data in context.object.data.color_attributes[layer_factor_name].data: + vertex_fac_col_data.color = default_factor + # restore active or set to default vcol if there was none if old_active_col_i is None: context.object.data.color_attributes.active_color = context.object.data.color_attributes[layer_name] @@ -425,9 +439,11 @@ def poll(cls, context): def execute(self, context): default_color = tuple(Color((0.5,) * 3).from_srgb_to_scene_linear()) + (1.0,) + default_factor = tuple((0.0,) * 4) layer_name = _MESH_consts.default_vcol layer_a_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix + layer_factor_name = _MESH_consts.default_vfactor objs_using_active_material = [] @@ -447,6 +463,9 @@ def execute(self, context): for obj in objs_using_active_material: old_active_col_i = obj.data.color_attributes.active_index + mat = context.active_object.active_material + vfactor_shader = ("piko.alldir") in mat.scs_props.mat_effect_name + for curr_lay_name in (layer_name, layer_a_name): if curr_lay_name not in obj.data.color_attributes: @@ -457,6 +476,15 @@ def execute(self, context): # setting neutral value (0.5) to all colors for vertex_col_data in obj.data.color_attributes[curr_lay_name].data: vertex_col_data.color = default_color + + # factor vertex color layer + if vfactor_shader and layer_factor_name not in obj.data.color_attributes: + vfcolor = obj.data.color_attributes.new(name=layer_factor_name, type='BYTE_COLOR', domain='CORNER') + vfcolor.name = layer_factor_name # repeat naming step to make sure it's properly named + + # setting neutral value (0.0) to all factors + for vertex_fac_col_data in obj.data.color_attributes[layer_factor_name].data: + vertex_fac_col_data.color = default_factor # restore active or set to default vcol if there was none if old_active_col_i is None: diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index a110035..946e9a3 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1706,22 +1706,42 @@ Shader { } Texture { Tag: "texture[X]:texture_sky_weather_base_a" - Value: "/model/skybox/nice_00/nice00_08_80_g" + Value: "/asset/skybox/s_nice_00/s_nice00_10_80_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_base_b" - Value: "/model/skybox/nice_00/nice00_08_80_g" + Value: "/asset/skybox/s_nice_00/s_nice00_10_80_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_over_a" - Value: "/model/skybox/bad_00/bad00_08_80_g" + Value: "/asset/skybox/s_bad_02/s_bad02_03_10_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_over_b" - Value: "/model/skybox/nice_00/nice00_08_80_g" + Value: "/asset/skybox/s_bad_02/s_bad02_03_10_g" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_sky_weather_base_mask_a" + Value: "/asset/skybox/s_nice_05/s_nice05_05_00_c" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_sky_weather_base_mask_b" + Value: "/asset/skybox/s_nice_05/s_nice05_05_00_c" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_sky_weather_over_mask_a" + Value: "/asset/skybox/s_bad_02/s_bad02_05_00_c" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_sky_weather_over_mask_b" + Value: "/asset/skybox/s_bad_02/s_bad02_05_00_c" TexCoord: ( 0 ) } } diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index 0192956..f4f15cd 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -244,7 +244,9 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader active_vcolors = bpy.context.active_object.data.color_attributes is_valid_vcolor = _MESH_consts.default_vcol in active_vcolors is_valid_vcolor_a = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix in active_vcolors - if not is_valid_vcolor or not is_valid_vcolor_a: + is_valid_vfactor = _MESH_consts.default_vfactor in active_vcolors + is_valid_vfactor_shader = ("piko.alldir") in mat.scs_props.mat_effect_name + if not is_valid_vcolor or not is_valid_vcolor_a or (not is_valid_vfactor and is_valid_vfactor_shader): vcol_missing_box = layout.box() @@ -260,6 +262,9 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader if not is_valid_vcolor_a: info_msg += "-> '" + _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix + "'\n" + if not is_valid_vfactor and is_valid_vfactor_shader: + info_msg += "-> '" + _MESH_consts.default_vfactor + "'\n" + info_msg += "You can use 'Add Vertex Colors To (Active/All)' button to add needed layers or add layers manually." _shared.draw_warning_operator(col, "Vertex Colors Missing", info_msg, text="More Info", icon='INFO') diff --git a/addon/io_scs_tools/utils/mesh.py b/addon/io_scs_tools/utils/mesh.py index 4b57984..d24fd2b 100644 --- a/addon/io_scs_tools/utils/mesh.py +++ b/addon/io_scs_tools/utils/mesh.py @@ -27,7 +27,7 @@ from io_scs_tools.utils import convert as _convert -def make_points_to_weld_list(mesh_vertices, mesh_normals, mesh_rgb, mesh_rgba, equal_decimals_count): +def make_points_to_weld_list(mesh_vertices, mesh_normals, mesh_rgb, mesh_rgba, mesh_factor, equal_decimals_count): """Makes a map of duplicated vertices indices into it's original counter part.""" # take first present vertex color data @@ -38,6 +38,11 @@ def make_points_to_weld_list(mesh_vertices, mesh_normals, mesh_rgb, mesh_rgba, e else: mesh_final_rgba = {} + if mesh_factor: + mesh_final_factor = mesh_factor + else: + mesh_final_factor = {} + posnorm_dict_tmp = {} perc = 10 ** equal_decimals_count # represent precision for duplicates for val_i, val in enumerate(mesh_vertices): @@ -50,6 +55,11 @@ def make_points_to_weld_list(mesh_vertices, mesh_normals, mesh_rgb, mesh_rgba, e for col_channel in mesh_final_rgba[vc_layer_name][val_i]: key += str(int(col_channel * perc)) + # also include factor vertex colors in key if present + for vfc_layer_name in mesh_final_factor: + for col_channel in mesh_final_factor[vfc_layer_name][val_i]: + key += str(int(col_channel * perc)) + if key not in posnorm_dict_tmp: posnorm_dict_tmp[key] = [val_i] else: @@ -233,6 +243,34 @@ def bm_make_vc_layer(pim_version, bm, vc_layer_name, vc_layer_data): vcol_a = (alpha / 2,) * 3 + (1.0,) loop[color_a_lay] = vcol_a +def bm_make_vfc_layer(pim_version, bm, vfc_layer_name, vfc_layer_data): + """Add Vertex Color Layer for Factor to the BMesh object. + + :param pim_version: PIM version of the File from which data have been read + :type pim_version: int + :param bm: BMesh data to add Vertex Color Layer to + :type bm: bmesh.types.BMesh + :param vfc_layer_name: Name for the layer + :type vfc_layer_name: str + :param vfc_layer_data: Vertex Color Layer data + :type vfc_layer_data: list + """ + # only 5 and 7 versions are supported currently + assert (pim_version == 5 or pim_version == 7) + + color_lay = bm.loops.layers.color.new(vfc_layer_name) + + for face_i, face in enumerate(bm.faces): + f_v = [x.index for x in face.verts] + for loop_i, loop in enumerate(face.loops): + if pim_version == 5: + vfcol = vfc_layer_data[f_v[loop_i]] + else: + vfcol = vfc_layer_data[face_i][loop_i] + + # Technically, vfcol[3] is repeated vfcol[2] value, and probably is not even used in the game, but just in case use vfcol[3] + vfcol = (vfcol[0] / 255, vfcol[1] / 255, vfcol[2] / 255, vfcol[3] / 255 ) + loop[color_lay] = vfcol def bm_delete_loose(mesh): """Deletes loose vertices in the mesh. From 223517bb94a4ab952983449e9709cf02b7358db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 21 Feb 2025 06:57:29 +0100 Subject: [PATCH 26/56] dif.spec.weight fix + mat 'effect' aliasing * Fixed problems with tsnmap flavors in "dif.spec.weight" shader (removed remains of unused flavors) * Added some missing information in collision description in Visibility Tool (new collision prefixes/suffixes) * Added partial support for aliasing materials using "effect" format --- .../io_scs_tools/internals/containers/mat.py | 59 +++++++--- .../internals/containers/parsers/mat.py | 110 +++++++++++++++--- addon/io_scs_tools/operators/material.py | 5 + addon/io_scs_tools/operators/object.py | 2 +- addon/io_scs_tools/shader_presets.txt | 20 +--- 5 files changed, 144 insertions(+), 52 deletions(-) diff --git a/addon/io_scs_tools/internals/containers/mat.py b/addon/io_scs_tools/internals/containers/mat.py index e1d1444..8c72a6c 100644 --- a/addon/io_scs_tools/internals/containers/mat.py +++ b/addon/io_scs_tools/internals/containers/mat.py @@ -25,8 +25,8 @@ class MatContainer: - def __init__(self, data_dict, effect): - """Create MAT file container with mapped data dictionary on attributes and textures. + def __init__(self, data_dict, effect, mat_format): + """Create MAT file container with mapped data dictionary on attributes, textures and tobjs data. It also stores material effect name. :param data_dict: all attributes from material represented with dictionary, @@ -34,33 +34,57 @@ def __init__(self, data_dict, effect): :type data_dict: dict[str, object] :param effect: shader effect full name :type effect: str + :param mat_format: material format (material|effect) + :type mat_format: str """ self.__effect = "" self.__attributes = {} self.__textures = {} + self.__tobjs = {} if effect is not None: self.__effect = effect for key in data_dict.keys(): - if key.startswith("texture"): + if mat_format == "material": - tex_type = "texture_name" - tex_val = "texture" + if key.startswith("texture"): - # take care of texture saved as arrays eg: texture[0] - if key.find("[") != -1: + tex_type = "texture_name" + tex_val = "texture" - tex_type = "texture_name" + key[key.find("["):] - tex_val = "texture" + key[key.find("["):] + # take care of texture saved as arrays eg: texture[0] + if key.find("[") != -1: - self.__textures[data_dict[tex_type]] = data_dict[tex_val] + tex_type = "texture_name" + key[key.find("["):] + tex_val = "texture" + key[key.find("["):] - else: + self.__textures[data_dict[tex_type]] = data_dict[tex_val] + + else: + + self.__attributes[key.replace("[", "").replace("]", "")] = data_dict[key] + + elif mat_format == "effect": + + # parse textures + if key == "texture": + + for tex_type in data_dict[key].keys(): + tex_val = data_dict[key][tex_type] + self.__textures[tex_type] = tex_val["source"] + + # TODO: parse tobj data + + # parse attributes + else: + + self.__attributes[key.replace("[", "").replace("]", "")] = data_dict[key] - self.__attributes[key.replace("[", "").replace("]", "")] = data_dict[key] + else: + lprint("E Unsupported MAT format %r!", (mat_format,)) def get_textures(self): """Returns shader textures defined in MAT container. @@ -69,6 +93,13 @@ def get_textures(self): """ return self.__textures + def get_tobjs(self): + """Returns textures tobj data defined in MAT container. + + :rtype: dict[str, tuple] + """ + return self.__textures + def get_attributes(self): """Returns shader attributes defined in MAT container. @@ -94,14 +125,14 @@ def get_data_from_file(filepath): if filepath: if os.path.isfile(filepath) and filepath.lower().endswith(".mat"): - data_dict, effect = _mat.read_data(filepath) + data_dict, effect, mat_format = _mat.read_data(filepath) if data_dict: if len(data_dict) < 1: lprint('\nI MAT file "%s" is empty!', (_path_utils.readable_norm(filepath),)) return None - container = MatContainer(data_dict, effect) + container = MatContainer(data_dict, effect, mat_format) else: lprint('\nI MAT file "%s" is empty!', (_path_utils.readable_norm(filepath),)) return None diff --git a/addon/io_scs_tools/internals/containers/parsers/mat.py b/addon/io_scs_tools/internals/containers/parsers/mat.py index f62fb4f..62737c8 100644 --- a/addon/io_scs_tools/internals/containers/parsers/mat.py +++ b/addon/io_scs_tools/internals/containers/parsers/mat.py @@ -11,22 +11,27 @@ def read_data(filepath, print_info=False): :type filepath: str :param print_info: switch for printing parsing info :type print_info: bool - :return: tuple of dictionary of mapped material attributes and effect name - :rtype: (dict, effect) + :return: tuple of dictionary of mapped material attributes, effect name and mat format + :rtype: (dict, effect, mat_format) """ + _FORMAT_G = "mat_format" _EFFECT_G = "effect" _CONTENT_G = "content" _ATTR_NAME_G = "attr_name" _ATTR_VALUE_G = "attr_val" + _ATTR_VALUE_NEST_G = "attr_val_nest" if print_info: print('** MAT Parser ...') print(' filepath: %r' % str(filepath)) - material_pattern = compile(r'material\W*:\W*\"(?P<%s>(.|\n)+)\"\W*\{\W*(?P<%s>(.|\n)+)\W*\}' % (_EFFECT_G, _CONTENT_G)) + # skip any whitespace at the beginning of file (game does not care about it), then match file format (not hardcoded to later check if it's supported), ), effect name and content + material_pattern = compile(r'(^\s*)(?P<%s>[\S]+)\W*:\W*\"(?P<%s>[\w.]+)\"\W*\{\W*(?P<%s>(.|\n)+)\W*\}' % (_FORMAT_G, _EFFECT_G, _CONTENT_G)) """Regex pattern for matching whole material file.""" attr_pattern = compile(r'(?P<%s>.+):(?P<%s>.+)' % (_ATTR_NAME_G, _ATTR_VALUE_G)) """Regex pattern for matching one attribute.""" + nested_pattern = compile(r'(?P<%s>\w+):"(?P<%s>[^"]+)"\{(?P<%s>[^}]+)\}' % (_ATTR_NAME_G ,_ATTR_VALUE_G, _ATTR_VALUE_NEST_G)) + """Regex pattern for matching nested data in content.""" attr_dict = {} @@ -37,29 +42,98 @@ def read_data(filepath, print_info=False): # match whole file m = material_pattern.match(f_data) - effect = m.group(_EFFECT_G) + mat_format = m.group(_FORMAT_G) + effect = m.group(_EFFECT_G).replace(".rfx", "") content = m.group(_CONTENT_G).replace(" ", "").replace("\t", "") if print_info: + print("Format:", mat_format) print("Effect:", effect) - print("Content:", content) + print("Content:\n", content, sep='') - for i, attr_m in enumerate(attr_pattern.finditer(content)): + if mat_format == "material": + for i, attr_m in enumerate(attr_pattern.finditer(content)): - attr_name = attr_m.group(_ATTR_NAME_G) - attr_value = attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") + attr_name = attr_m.group(_ATTR_NAME_G) + attr_value = attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") - try: - parsed_attr_value = literal_eval(attr_value) - except ValueError: - parsed_attr_value = None - lprint("W Ignoring unrecognized/malformed MAT file attribute:\n\t Name: %r; Value: %r;", (attr_name, attr_value)) + try: + parsed_attr_value = literal_eval(attr_value) + except ValueError: + parsed_attr_value = None + lprint("W Ignoring unrecognized/malformed MAT file attribute:\n\t Name: %r; Value: %r;", (attr_name, attr_value)) + + if print_info: + print("\tName:", attr_name, " -> Value(", type(parsed_attr_value).__name__, "):", parsed_attr_value) + + # fill successfully parsed values into dictionary + if parsed_attr_value is not None: + attr_dict[attr_name] = parsed_attr_value + elif mat_format == "effect": + + nested_list = {} + + # extract nested data from content + for i, attr_m in enumerate(nested_pattern.finditer(content)): + attr_name = attr_m.group(_ATTR_NAME_G) + attr_value = attr_m.group(_ATTR_VALUE_G) + attr_value_nest = attr_m.group(_ATTR_VALUE_NEST_G) + + if attr_name not in nested_list: + nested_list[attr_name] = {} + + # parse nested_attr_value into a dictionary + nested_attr_value_dict = {} + for i, n_attr_m in enumerate(attr_pattern.finditer(attr_value_nest)): + + nested_attr_name = n_attr_m.group(_ATTR_NAME_G) + nested_attr_value = n_attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") + + if print_info: + print("\tName:", nested_attr_name, " -> Value(", type(nested_attr_value).__name__, "):", nested_attr_value) + + nested_attr_value_dict[nested_attr_name] = nested_attr_value + + nested_list[attr_name][attr_value] = nested_attr_value_dict + + # remove nested data from content to not broke parsing of normal attributes + content = nested_pattern.sub('', content) + + if print_info: + print("Nested dict:\n", nested_list, sep='') + + # parse normal attributes + for i, attr_m in enumerate(attr_pattern.finditer(content)): + + attr_name = attr_m.group(_ATTR_NAME_G) + attr_value = attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") + + try: + parsed_attr_value = literal_eval(attr_value) + except ValueError: + parsed_attr_value = None + lprint("W Ignoring unrecognized/malformed MAT file attribute:\n\t Name: %r; Value: %r;", (attr_name, attr_value)) + + if print_info: + print("\tName:", attr_name, " -> Value(", type(parsed_attr_value).__name__, "):", parsed_attr_value) + + # fill successfully parsed values into dictionary + if parsed_attr_value is not None: + attr_dict[attr_name] = parsed_attr_value if print_info: - print("\tName:", attr_name, " -> Value(", type(parsed_attr_value).__name__, "):", parsed_attr_value) + print("Attr dict:\n", attr_dict, sep='') + + # combine nested_list with attr_dict + for name, value in nested_list.items(): + attr_dict[name] = value + + if print_info: + print("Combined dict:\n", attr_dict, sep='') + + lprint("I Mat format `%s` is not fully support.\n\t Some attributes unique for this format will not be parsed if used!", (mat_format, )) - # fill successfully parsed values into dictionary - if parsed_attr_value is not None: - attr_dict[attr_name] = parsed_attr_value + else: + lprint("E Unknown mat format: `%s`", (mat_format, )) - return attr_dict, effect + return attr_dict, effect, mat_format diff --git a/addon/io_scs_tools/operators/material.py b/addon/io_scs_tools/operators/material.py index 8abe169..2e8129f 100644 --- a/addon/io_scs_tools/operators/material.py +++ b/addon/io_scs_tools/operators/material.py @@ -873,6 +873,11 @@ def execute(self, context): mat_cont = mat_container.get_data_from_file(mat_abs_path) + # abort if data couldn't be recived + if not mat_cont: + self.report({'ERROR'}, "Couldn't read aliased material, aliasing aborted!") + return {'CANCELLED'} + # set attributes for attr_tuple in mat_cont.get_attributes().items(): diff --git a/addon/io_scs_tools/operators/object.py b/addon/io_scs_tools/operators/object.py index c23bf74..eee6f9e 100755 --- a/addon/io_scs_tools/operators/object.py +++ b/addon/io_scs_tools/operators/object.py @@ -1247,7 +1247,7 @@ class SCS_TOOLS_OT_SwitchStaticCollisionObjects(bpy.types.Operator, _BaseViewOpe """Switch visibility of static collision objects.""" bl_label = "View Static Collision Objects" bl_idname = "object.scs_tools_switch_static_collision_objects" - bl_description = "View only objects which SCS Part is prefixed with 'coll', marking it as static collision object" + \ + bl_description = "View only objects which SCS Part is prefixed with 'coll', '_col' or suffixed with '_c', marking it as static collision object" + \ _BaseViewOperator.bl_base_description def execute(self, context): diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 946e9a3..82be1b8 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1018,7 +1018,7 @@ Shader { Shader { PresetName: "dif.spec.weight" Effect: "eut2.dif.spec.weight" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_16|NMAP_TS_UV_16|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2490,15 +2490,6 @@ Flavor { TexCoord: ( 0 ) } } -Flavor { - Type: "NMAP_TS_16" - Name: "tsnmap16" - Texture { - Tag: "texture[X]:texture_nmap" - Value: "" - TexCoord: ( 0 ) - } -} Flavor { Type: "NMAP_TS_CALC" Name: "tsnmapcalc" @@ -2531,15 +2522,6 @@ Flavor { TexCoord: ( 6 ) } } -Flavor { - Type: "NMAP_TS_UV_16" - Name: "tsnmapuv16" - Texture { - Tag: "texture[X]:texture_nmap" - Value: "" - TexCoord: ( 6 ) - } -} Flavor { Type: "NMAP_UV_DETAIL" Name: "tsnmapuv" From 8205102e75a2d53e849233695540d0bb70a0c60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 24 Feb 2025 04:56:52 +0100 Subject: [PATCH 27/56] aliasing update * Fixed error that cause, that textures couldn't be aliased (excluded base texture to not cause problems when file name will be different than in mat file) * Added more support for aliasing materials in 'effect' format. Now tool can convert new attribute names to old format * Added aliasing for TOBJ settings (only with 'effect' materials, as this format store this data in .mat files) --- .../io_scs_tools/internals/containers/mat.py | 37 ++++- .../internals/containers/parsers/mat.py | 75 +++++---- .../containers/parsers/mat_convert.py | 143 ++++++++++++++++++ addon/io_scs_tools/operators/material.py | 47 +++++- 4 files changed, 255 insertions(+), 47 deletions(-) create mode 100644 addon/io_scs_tools/internals/containers/parsers/mat_convert.py diff --git a/addon/io_scs_tools/internals/containers/mat.py b/addon/io_scs_tools/internals/containers/mat.py index 8c72a6c..78b5cea 100644 --- a/addon/io_scs_tools/internals/containers/mat.py +++ b/addon/io_scs_tools/internals/containers/mat.py @@ -39,6 +39,7 @@ def __init__(self, data_dict, effect, mat_format): """ self.__effect = "" + self.__mat_format = "" self.__attributes = {} self.__textures = {} self.__tobjs = {} @@ -46,6 +47,9 @@ def __init__(self, data_dict, effect, mat_format): if effect is not None: self.__effect = effect + if format is not None: + self.__mat_format = mat_format + for key in data_dict.keys(): if mat_format == "material": @@ -69,14 +73,34 @@ def __init__(self, data_dict, effect, mat_format): elif mat_format == "effect": - # parse textures + # parse textures & tobjs if key == "texture": for tex_type in data_dict[key].keys(): tex_val = data_dict[key][tex_type] self.__textures[tex_type] = tex_val["source"] - # TODO: parse tobj data + # w_address for 3d cube reflections? not used in blender + attr_keys = ["u_address", "v_address"] + + # initialize empty tobj data + tobjs = [None] * len(attr_keys) + + # technically, i can return true/false if "repeat", but for eventual future use, + # it's better to return the actual value for now. + for i, attr in enumerate(attr_keys): + if "sampler" in tex_val: + tobjs[i] = "repeat" + + elif attr in tex_val: + if tex_val[attr].startswith("repeat"): + tobjs[i] = "repeat" + elif tex_val[attr].startswith("clamp"): + tobjs[i] = "extend" + elif tex_val[attr].startswith("mirror"): + tobjs[i] = "mirror" + + self.__tobjs[tex_type] = tuple(tobjs) # parse attributes else: @@ -98,7 +122,7 @@ def get_tobjs(self): :rtype: dict[str, tuple] """ - return self.__textures + return self.__tobjs def get_attributes(self): """Returns shader attributes defined in MAT container. @@ -114,6 +138,13 @@ def get_effect(self): """ return self.__effect + def get_format(self): + """Returns material format defined in MAT container. + + :rtype: str + """ + return self.__mat_format + def get_data_from_file(filepath): """Returns entire data in data container from specified raw material file. diff --git a/addon/io_scs_tools/internals/containers/parsers/mat.py b/addon/io_scs_tools/internals/containers/parsers/mat.py index 62737c8..2cd5ebe 100644 --- a/addon/io_scs_tools/internals/containers/parsers/mat.py +++ b/addon/io_scs_tools/internals/containers/parsers/mat.py @@ -3,6 +3,38 @@ from io_scs_tools.utils.printout import lprint +def parse_attributes(content, attr_pattern, print_info=False): + """Parse attributes from content and return them as dictionary. + + :param content: content of material file + :type content: str + :param attr_pattern: regex pattern for matching one attribute + :type attr_pattern: re.Pattern + :param print_info: switch for printing parsing info + :type print_info: bool + :return: dictionary of mapped material attributes + :rtype: dict + """ + attr_dict = {} + for i, attr_m in enumerate(attr_pattern.finditer(content)): + + attr_name = attr_m.group("attr_name") + attr_value = attr_m.group("attr_val").replace("{", "(").replace("}", ")") + + try: + parsed_attr_value = literal_eval(attr_value) + except ValueError: + parsed_attr_value = None + lprint("W Ignoring unrecognized/malformed MAT file attribute:\n\t Name: %r; Value: %r;", (attr_name, attr_value)) + + if print_info: + print("\tName:", attr_name, " -> Value(", type(parsed_attr_value).__name__, "):", parsed_attr_value) + + # fill successfully parsed values into dictionary + if parsed_attr_value is not None: + attr_dict[attr_name] = parsed_attr_value + + return attr_dict def read_data(filepath, print_info=False): """Reads data from mat file and returns it's attributes as dictionary. @@ -33,8 +65,6 @@ def read_data(filepath, print_info=False): nested_pattern = compile(r'(?P<%s>\w+):"(?P<%s>[^"]+)"\{(?P<%s>[^}]+)\}' % (_ATTR_NAME_G ,_ATTR_VALUE_G, _ATTR_VALUE_NEST_G)) """Regex pattern for matching nested data in content.""" - attr_dict = {} - with open(filepath, encoding="utf8") as f: f_data = f.read() f.close() @@ -52,23 +82,8 @@ def read_data(filepath, print_info=False): print("Content:\n", content, sep='') if mat_format == "material": - for i, attr_m in enumerate(attr_pattern.finditer(content)): - - attr_name = attr_m.group(_ATTR_NAME_G) - attr_value = attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") + attr_dict = parse_attributes(content, attr_pattern, print_info) - try: - parsed_attr_value = literal_eval(attr_value) - except ValueError: - parsed_attr_value = None - lprint("W Ignoring unrecognized/malformed MAT file attribute:\n\t Name: %r; Value: %r;", (attr_name, attr_value)) - - if print_info: - print("\tName:", attr_name, " -> Value(", type(parsed_attr_value).__name__, "):", parsed_attr_value) - - # fill successfully parsed values into dictionary - if parsed_attr_value is not None: - attr_dict[attr_name] = parsed_attr_value elif mat_format == "effect": nested_list = {} @@ -87,7 +102,7 @@ def read_data(filepath, print_info=False): for i, n_attr_m in enumerate(attr_pattern.finditer(attr_value_nest)): nested_attr_name = n_attr_m.group(_ATTR_NAME_G) - nested_attr_value = n_attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") + nested_attr_value = n_attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")").replace('"', '') if print_info: print("\tName:", nested_attr_name, " -> Value(", type(nested_attr_value).__name__, "):", nested_attr_value) @@ -102,24 +117,8 @@ def read_data(filepath, print_info=False): if print_info: print("Nested dict:\n", nested_list, sep='') - # parse normal attributes - for i, attr_m in enumerate(attr_pattern.finditer(content)): - - attr_name = attr_m.group(_ATTR_NAME_G) - attr_value = attr_m.group(_ATTR_VALUE_G).replace("{", "(").replace("}", ")") - - try: - parsed_attr_value = literal_eval(attr_value) - except ValueError: - parsed_attr_value = None - lprint("W Ignoring unrecognized/malformed MAT file attribute:\n\t Name: %r; Value: %r;", (attr_name, attr_value)) - - if print_info: - print("\tName:", attr_name, " -> Value(", type(parsed_attr_value).__name__, "):", parsed_attr_value) - - # fill successfully parsed values into dictionary - if parsed_attr_value is not None: - attr_dict[attr_name] = parsed_attr_value + # parse attributes + attr_dict = parse_attributes(content, attr_pattern, print_info) if print_info: print("Attr dict:\n", attr_dict, sep='') @@ -131,8 +130,6 @@ def read_data(filepath, print_info=False): if print_info: print("Combined dict:\n", attr_dict, sep='') - lprint("I Mat format `%s` is not fully support.\n\t Some attributes unique for this format will not be parsed if used!", (mat_format, )) - else: lprint("E Unknown mat format: `%s`", (mat_format, )) diff --git a/addon/io_scs_tools/internals/containers/parsers/mat_convert.py b/addon/io_scs_tools/internals/containers/parsers/mat_convert.py new file mode 100644 index 0000000..dc22553 --- /dev/null +++ b/addon/io_scs_tools/internals/containers/parsers/mat_convert.py @@ -0,0 +1,143 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +# Converting script based on the mwl4 script from https://github.com/mwl4/ConverterPIX/ + +class AttributeConvert: + def __init__(self, name, value_count, start_index): + self.name = name + self.value_count = value_count + self.start_index = start_index + +default_map_to_material = { + "additional_ambient": AttributeConvert("add_ambient", 1, 0), + "glass_tint_color": AttributeConvert("tint", 3, 0), + "glass_tint_opacity": AttributeConvert("tint_opacity", 1, 0), + "shadowmap_bias": AttributeConvert("shadow_bias", 1, 0), + "paintjob_base_color": AttributeConvert("aux[8]", 3, 0), + "specular_secondary": AttributeConvert("aux[3]", 4, 0), + "shininess_secondary": AttributeConvert("aux[3]", 4, 3), + "reflection_secondary": AttributeConvert("reflection2", 1, 0), + "lod_selector": AttributeConvert("aux[1]", 1, 0), + "shadow_offset": AttributeConvert("aux[0]", 1, 0), + "amod_decal_blending_factors": AttributeConvert("aux[0]", 2, 0), + "texgen_0_gen": AttributeConvert("aux[0]", 4, 0), + "texgen_0_rot": AttributeConvert("aux[0]", 4, 2), + "texgen_1_gen": AttributeConvert("aux[1]", 4, 0), + "texgen_1_rot": AttributeConvert("aux[1]", 4, 2), + "far_color": AttributeConvert("aux[2]", 4, 0), + "far_specular_power": AttributeConvert("aux[2]", 4, 3), + "depth_bias": AttributeConvert("aux[0]", 1, 0), + "luminance_output": AttributeConvert("aux[5]", 2, 0), + "luminance_night": AttributeConvert("aux[5]", 2, 1), + "interior_atlas_dimensions": AttributeConvert("aux[1]", 2, 0), + "interior_glass_color": AttributeConvert("aux[2]", 4, 0), + "interior_unit_room_dimensions": AttributeConvert("aux[0]", 2, 0), + "water_distances": AttributeConvert("aux[0]", 3, 0), + "water_near_color": AttributeConvert("aux[1]", 3, 0), + "water_horizon_color": AttributeConvert("aux[2]", 3, 0), + "water_layer_0_yaw": AttributeConvert("aux[3]", 4, 0), + "water_layer_0_speed": AttributeConvert("aux[3]", 4, 1), + "water_layer_0_scale": AttributeConvert("aux[3]", 4, 2), + "water_layer_1_yaw": AttributeConvert("aux[4]", 4, 0), + "water_layer_1_speed": AttributeConvert("aux[4]", 4, 1), + "water_layer_1_scale": AttributeConvert("aux[4]", 4, 2), + "water_mirror": AttributeConvert("aux[5]", 1, 0), + "animsheet_cfg_fps": AttributeConvert("aux[0]", 3, 0), + "animsheet_cfg_frames_row": AttributeConvert("aux[0]", 3, 1), + "animsheet_cfg_frames_total": AttributeConvert("aux[0]", 3, 2), + "animsheet_frame_width": AttributeConvert("aux[1]", 2, 0), + "animsheet_frame_height": AttributeConvert("aux[1]", 2, 1), + "detail_fadeout_from": AttributeConvert("aux[5]", 4, 0), + "detail_fadeout_range": AttributeConvert("aux[5]", 4, 1), + "detail_blend_bias": AttributeConvert("aux[5]", 4, 2), + "detail_uv_scale": AttributeConvert("aux[5]", 4, 3), + "animation_speed": AttributeConvert("aux[0]", 1, 0), + "showroom_r_color": AttributeConvert("aux[0]", 1, 0), + "showroom_speed": AttributeConvert("aux[4]", 3, 0), + "flake_uvscale": AttributeConvert("aux[5]", 4, 0), + "flake_shininess": AttributeConvert("aux[5]", 4, 1), + "flake_clearcoat_rolloff": AttributeConvert("aux[5]", 4, 2), + "flake_vratio": AttributeConvert("aux[5]", 4, 3), + "flake_color": AttributeConvert("aux[6]", 4, 0), + "flake_density": AttributeConvert("aux[6]", 4, 3), + "flip_color": AttributeConvert("aux[7]", 4, 0), + "flip_strength": AttributeConvert("aux[7]", 4, 3), + "mix00_diffuse_secondary": AttributeConvert("aux[0]", 3, 0), + "mult_uvscale": AttributeConvert("aux[5]", 4, 0), + "mult_uvscale_secondary": AttributeConvert("aux[5]", 4, 2), + "sheet_frame_size_r": AttributeConvert("aux[0]", 4, 0), + "sheet_frame_size_g": AttributeConvert("aux[0]", 4, 2), + "sheet_frame_size_b": AttributeConvert("aux[1]", 4, 0), + "sheet_frame_size_a": AttributeConvert("aux[1]", 4, 2), + "paintjob_r_color": AttributeConvert("aux[5]", 3, 0), + "paintjob_g_color": AttributeConvert("aux[6]", 3, 0), + "paintjob_b_color": AttributeConvert("aux[7]", 3, 0), +} + +class AttributeConverter: + def __init__(self): + self.temp_values = {} + + def effect_to_material(self, attribute, value): + """Converts effect attribute to material attribute if present in the mapping list. + + :param attribute: attribute name to convert + :type attribute: str + :param value: value of attribute to convert + :type value: int, float, tuple + :return: tuple of converted attribute name and value + :rtype: (attr, val) + """ + if attribute in default_map_to_material: + conversion = default_map_to_material[attribute] + attr = conversion.name + + if not isinstance(value, tuple): + value = (value,) + + # check for values in temp_values + if attr in self.temp_values: + val = list(self.temp_values[attr]) + else: + val = [None] * conversion.value_count + + # insert values into correct positions + for i, v in enumerate(value): + val[conversion.start_index + i] = v + + # save updated value in temp_values if not fully filled + if None in val or 0.0 in val: + self.temp_values[attr] = tuple(val) + else: + self.temp_values.pop(attr, None) + + # set none to 0.0 + val = tuple(v if v is not None else 0.0 for v in val) + + # return single value if only one + if len(val) == 1: + val = val[0] + + else: + attr = attribute + val = value + + return attr, val diff --git a/addon/io_scs_tools/operators/material.py b/addon/io_scs_tools/operators/material.py index 2e8129f..4aa543a 100644 --- a/addon/io_scs_tools/operators/material.py +++ b/addon/io_scs_tools/operators/material.py @@ -878,11 +878,18 @@ def execute(self, context): self.report({'ERROR'}, "Couldn't read aliased material, aliasing aborted!") return {'CANCELLED'} + from io_scs_tools.internals.containers.parsers.mat_convert import AttributeConverter + converter = AttributeConverter() + # set attributes for attr_tuple in mat_cont.get_attributes().items(): - attr_key = attr_tuple[0] - attr_val = attr_tuple[1] + # convert attributes from effect to material format + if mat_cont.get_format() == "effect": + attr_key, attr_val = converter.effect_to_material(attr_tuple[0], attr_tuple[1]) + else: + attr_key = attr_tuple[0] + attr_val = attr_tuple[1] if attr_key == "substance": @@ -912,10 +919,40 @@ def execute(self, context): # set textures for tex_tuple in mat_cont.get_textures().items(): - if "shader_texture_" + tex_tuple[0] not in material.scs_props.keys(): - continue + # temp fix to not replace base texture cause it can have other name than .mat file + # "if" should be remove when aliasing detecting system will change from dds to mat + if not tex_tuple[0] == "texture_base": + + # convert local paths to raw paths + if not tex_tuple[1].startswith("/"): + file_name = _path_utils.get_filename(tex_raw_path) + raw_path = tex_raw_path.replace(file_name, "") + + # replace local path with raw path + tex_tuple = (tex_tuple[0], raw_path + tex_tuple[1]) + + if "shader_texture_" + tex_tuple[0][8:] not in material.scs_props.keys(): + continue + + setattr(material.scs_props, "shader_texture_" + tex_tuple[0][8:], tex_tuple[1]) + + # set tobjs + for tobj_tuple in mat_cont.get_tobjs().items(): + tex_attrs = tobj_tuple[1] + tobj_prop = getattr(material.scs_props, "shader_texture_" + tobj_tuple[0][8:] + "_settings", None) + + # ('u_repeat', 'v_repeat', 'tsnormal', 'color_space_linear') + tobj_prop.add("u_repeat") if "repeat" in tex_attrs[0] else tobj_prop.discard("u_repeat") + tobj_prop.add("v_repeat") if "repeat" in tex_attrs[1] else tobj_prop.discard("v_repeat") + # tobj_prop.add("w_repeat") if tex_attrs[12] and "repeat" in tex_attrs[2] else tobj_prop.discard("w_repeat") + + # check if tobj is nmap and set tsnormal + tobj_prop.add("tsnormal") if tobj_tuple[0][8:] == "nmap" else tobj_prop.discard("tsnormal") + + # check if tobj is in linear color space and set color_space_linear + # tobj_prop.add("color_space_linear") if (--TODO--) else tobj_prop.discard("color_space_linear") - setattr(material.scs_props, "shader_texture_" + tex_tuple[0], tex_tuple[1]) + setattr(material.scs_props, "shader_texture_" + tobj_tuple[0][8:] + "_settings", tobj_prop) # report success of aliasing if mat_cont.get_effect() == material.scs_props.mat_effect_name: From 0c1fe80d8d311b3dd4721d1ae91867c356440ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 27 Feb 2025 02:32:09 +0100 Subject: [PATCH 28/56] vfcol fix, aliasing fixes * Fixed error when exporting models without vfcol data (no piko.alldir effect in model from root) * Added "water" to aliasable shaders (because it's in "umatlib") * Some additional info about nmaps in aliasing info * Fixed error when you try to load aliased effect material with additional texture type not used in your shader * Changes in utils/path.py functions - added new functions for material handling (some old changed to be universal) --- addon/io_scs_tools/exp/pim/exporter.py | 3 +- addon/io_scs_tools/properties/material.py | 31 ++++++ addon/io_scs_tools/ui/material.py | 13 ++- addon/io_scs_tools/utils/material.py | 2 + addon/io_scs_tools/utils/path.py | 129 +++++++++++++++++----- 5 files changed, 147 insertions(+), 31 deletions(-) diff --git a/addon/io_scs_tools/exp/pim/exporter.py b/addon/io_scs_tools/exp/pim/exporter.py index f9e4e5d..6b271cb 100644 --- a/addon/io_scs_tools/exp/pim/exporter.py +++ b/addon/io_scs_tools/exp/pim/exporter.py @@ -427,6 +427,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat vcol += (alpha * 2,) # 5. vfcol -> vfcol_lay = mesh.color_attributes[2].data; vfcol_lay[loop_i].color + vfcol = None vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name if vfactor_shader: if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component @@ -434,7 +435,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat missing_vfcolor = True else: vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] - + if vfcolors.domain == 'POINT': color = Color(vfcolors.data[vert_i].color[:3]) alpha = vfcolors.data[vert_i].color[3] diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 72bfc3e..aabb2dd 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -75,6 +75,24 @@ def __update_look__(self, context): for scs_root in scs_roots: _looks.update_look_from_material(scs_root, context.active_object.active_material, is_preset_change) +def __update_shader_material__(self, context): + """Hookup function for update of shader material in Blender. + + :param context: Blender context + :type context: bpy.types.Context + :param tex_type: string representing texture type + :type tex_type: str + """ + + __update_look__((), context) + + material = _material_utils.get_material_from_context(context) + + if material: + shader_material_filepath = getattr(self, "shader_material") + + # always correct scs path string + shader_material_filepath = self["shader_material"] = _path_utils.get_scs_material_str(shader_material_filepath) def __update_shader_attribute__(self, context, attr_type): """Hookup function for updating shader attributes in Blender. @@ -357,6 +375,9 @@ def update_active_shader_preset_name(self, context): __update_look__(self, context) del self["preset_change"] # remove indicator of shader preset update + def update_shader_material(self, context): + __update_shader_material__(self, context) + def update_shader_attribute_add_ambient(self, context): __update_shader_attribute__(self, context, "add_ambient") @@ -598,6 +619,16 @@ def update_shader_texture_sky_weather_over_mask_b_settings(self, context): update=__update_look__ ) + # SHADER MATERIAL + shader_material: StringProperty( + name="Shader Material", + description="Shader Material for active Aliasing", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_material, + ) + # SHADER ATTRIBUTES shader_attribute_add_ambient: FloatProperty( name="Add Ambient", diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index f4f15cd..b15a9f4 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -294,7 +294,11 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader alias_icon = _shared.get_on_off_icon(mat.scs_props.enable_aliasing) alias_row.prop(mat.scs_props, "enable_aliasing", icon=alias_icon, text=alias_text, toggle=True) - normalized_base_tex_path = mat.scs_props.shader_texture_base.replace("\\", "/") + # Fix for "water" shader, where there is no "base" texture. Water is used as aliased in umatlib. + if "water" in mat.scs_props.mat_effect_name: + normalized_base_tex_path = mat.scs_props.shader_texture_layer0.replace("\\", "/") + else: + normalized_base_tex_path = mat.scs_props.shader_texture_base.replace("\\", "/") is_aliasing_path = ("/material/road" in normalized_base_tex_path or "/material/terrain" in normalized_base_tex_path or "/material/custom" in normalized_base_tex_path or @@ -304,6 +308,7 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader ( len(shader_data["textures"]) == 1 or (len(shader_data["textures"]) == 2 and "tsnmap" in mat.scs_props.mat_effect_name) or + (len(shader_data["textures"]) >= 2 and "water" in mat.scs_props.mat_effect_name) or (len(shader_data["textures"]) == 2 and "dif.spec.weight.mult2" in mat.scs_props.mat_effect_name) )) @@ -317,14 +322,14 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader "-> '/material/terrain'\n" "-> '/material/custom'\n" "-> '/umatlib'\n" - "Additional requirement for aliasing is also single texture material or\n" - "exceptionally multi texture material of 'dif.spec.weight.mult2' family.\n\n" + "Additional requirement for aliasing is also single texture material (excluding nmap) or\n" + "exceptionally multi texture material of 'dif.spec.weight.mult2' or 'water' family.\n\n" "Currently aliasing can not be done because:") if not is_aliasing_path: aliasing_info_msg += "\n-> Your 'Base' texture doesn't point to any of this (sub)directories." if not is_aliasable: - aliasing_info_msg += "\n-> Current shader type use multiple textures or it's not 'dif.spec.weight.mult2' family type." + aliasing_info_msg += "\n-> Current shader type use multiple textures or it's not 'dif.spec.weight.mult2' or 'water' family type." _shared.draw_warning_operator(alias_row, "Aliasing Info", aliasing_info_msg, icon='INFO') diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index 821f0c5..a680b4e 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -499,6 +499,8 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac if override_back_data: for key in scs_props_keys: is_key_used = False + if key.startswith("shader_material"): + is_key_used = True if key.startswith("shader_attribute"): for used_attribute in used_attribute_types: if used_attribute in key[16:]: diff --git a/addon/io_scs_tools/utils/path.py b/addon/io_scs_tools/utils/path.py index 1ae2ad7..d0a6d86 100644 --- a/addon/io_scs_tools/utils/path.py +++ b/addon/io_scs_tools/utils/path.py @@ -258,6 +258,20 @@ def is_valid_shader_texture_path(shader_texture): return False +def is_valid_shader_material_path(shader_material): + """It returns True if there is valid Shader Material file, otherwise False. + + :param shader_material: SCS material path, can be absolute or relative + :type shader_material: str + :return: True if there is valid Shader Material file, otherwise False + :rtype: bool + """ + + if shader_material.endswith(".mat") or shader_material.endswith(".umat"): + return is_valid_shader_texture_path(shader_material) + + return False + def is_valid_shader_presets_library_path(): """It returns True if there is valid "*.txt" file in the Shader Presets Library directory, otherwise False.""" @@ -414,6 +428,29 @@ def get_shader_presets_filepath(): return shader_presets_file +def get_material_extens_and_strip_path(material_path): + """Gets all supported material extensions and strips given input path for any of it. + + :param material_path: shader material raw path value + :type material_path: str + :return: list of extensions and stripped path as tuple + :rtype: tuple[list[str], str] + """ + + extensions = [".mat", ".umat"] + + # strip of any extensions ( endswith is most secure, because of possible multiple extensions ) + if material_path.endswith(".umat"): + + extensions.insert(0, material_path[-5:]) + material_path = material_path[:-5] + + elif material_path.endswith(".mat"): + + extensions.insert(0, material_path[-4:]) + material_path = material_path[:-4] + + return extensions, material_path def get_texture_path_from_tobj(tobj_filepath, raw_value=False): """Get absolute path of texture from given tobj filepath. @@ -503,42 +540,48 @@ def get_texture_extens_and_strip_path(texture_path): return extensions, texture_path - -def get_scs_texture_str(texture_string): - """Get texture string as presented in SCS files: "/material/environment/vehicle_reflection" - without any file extensions. Input path can also have texture object extension or supported images extensions. +def get_scs_file_str(file_string, file_type): + """Get file string as presented in SCS files: "/folder1/folder2/file" + without any file extensions. Input path can also have object extension or supported extensions. Path will be searched and returned in this order: 1. relative path on current SCS Project Base Path 2. relative path on parent base dirs of current SCS Project Base Path in the case of mod/dlc 3. find absolute file path 4. return unchanged texture string path - :param texture_string: texture string for which texture should be found e.g.: "/material/environment/vehicle_reflection" or absolute path - :type texture_string: str - :return: relative path to texture object or absolute path to texture object or unchanged texture string + :param file_string: file string for which file should be found e.g.: "/folder1/folder2/file" or absolute path + :type file_string: str + :param file_type: type of file for which path should be found + :type file_type: str (material|texture) + :return: relative path to file object or absolute path to file object or unchanged file string :rtype: str """ scs_project_path = _get_scs_globals().scs_project_path - orig_texture_string = texture_string + orig_file_string = file_string # remove any directory separators left overs from different platform - texture_string = texture_string.replace("/", os.sep).replace("\\", os.sep) + file_string = file_string.replace("/", os.sep).replace("\\", os.sep) - extensions, texture_string = get_texture_extens_and_strip_path(texture_string) + if file_type == "material": + extensions, file_string = get_material_extens_and_strip_path(file_string) + elif file_type == "texture": + extensions, file_string = get_texture_extens_and_strip_path(file_string) + else: + raise ValueError("Invalid file type: %s" % file_type) # if texture string starts with scs project path we can directly strip of project path - if startswith(texture_string, scs_project_path): - texture_string = texture_string[len(scs_project_path):] + if startswith(file_string, scs_project_path): + file_string = file_string[len(scs_project_path):] else: # check if texture string came from base project while scs project path is in dlc/mod folder # first find longest matching path try: - common_path_len = len(os.path.commonpath([scs_project_path, texture_string])) + common_path_len = len(os.path.commonpath([scs_project_path, file_string])) except ValueError: # if ValueError is raised then paths do not match for sure, thus set it to 0 common_path_len = 0 - nonmatched_path_part = texture_string[common_path_len + 1:] + nonmatched_path_part = file_string[common_path_len + 1:] if nonmatched_path_part.startswith("base" + os.sep) or nonmatched_path_part.startswith("base_") or nonmatched_path_part.startswith("dlc_"): @@ -547,10 +590,10 @@ def get_scs_texture_str(texture_string): # NOTE: find calls is inverted in relation to number of parents dirs for infix, find_calls_count in (("..", 2), (".." + os.sep + "..", 1)): - modif_texture_string = os.path.join(scs_project_path, infix + os.sep + nonmatched_path_part) + modif_file_string = os.path.join(scs_project_path, infix + os.sep + nonmatched_path_part) # we got a hit if one or two directories up is the same path as texture string - if is_samepath(modif_texture_string, texture_string): + if is_samepath(modif_file_string, file_string): slash_idx = 0 for _ in range(0, find_calls_count): slash_idx = nonmatched_path_part.find(os.sep, slash_idx) @@ -558,21 +601,55 @@ def get_scs_texture_str(texture_string): # catch invalid cases that needs investigation assert slash_idx != -1 - texture_string = nonmatched_path_part[slash_idx:] + file_string = nonmatched_path_part[slash_idx:] - # check for relative TOBJ, TGA, PNG + # check for relative files through extensions for ext in extensions: - texture_path = get_abs_path("//" + texture_string.strip(os.sep) + ext) - if texture_path and os.path.isfile(texture_path): - return "//" + texture_string.replace(os.sep, "/").strip("/") + ext + file_path = get_abs_path("//" + file_string.strip(os.sep) + ext) + if file_path and os.path.isfile(file_path): + return "//" + file_string.replace(os.sep, "/").strip("/") + ext - # check for absolute TOBJ, TGA, PNG + # check for absolute files through extensions for ext in extensions: - texture_path = get_abs_path(texture_string + ext, skip_mod_check=True) - if texture_path and os.path.isfile(texture_path): - return texture_string.replace(os.sep, "/") + ext + file_path = get_abs_path(file_string + ext, skip_mod_check=True) + if file_path and os.path.isfile(file_path): + return file_string.replace(os.sep, "/") + ext + + return orig_file_string + +def get_scs_material_str(material_string): + """Get material string as presented in SCS files: "/umatlib/specual/collision_umat" + without any file extensions. Input path can also have supported material extensions. + Path will be searched and returned in this order: + 1. relative path on current SCS Project Base Path + 2. relative path on parent base dirs of current SCS Project Base Path in the case of mod/dlc + 3. find absolute file path + 4. return unchanged material string path + + :param material_string: material string for which material should be found e.g.: "/umatlib/specual/collision_umat" or absolute path + :type material_string: str + :return: relative path to material object or absolute path to material object or unchanged material string + :rtype: str + """ + + return get_scs_file_str(material_string, "material") + +def get_scs_texture_str(texture_string): + """Get texture string as presented in SCS files: "/material/environment/vehicle_reflection" + without any file extensions. Input path can also have texture object extension or supported images extensions. + Path will be searched and returned in this order: + 1. relative path on current SCS Project Base Path + 2. relative path on parent base dirs of current SCS Project Base Path in the case of mod/dlc + 3. find absolute file path + 4. return unchanged texture string path + + :param texture_string: texture string for which texture should be found e.g.: "/material/environment/vehicle_reflection" or absolute path + :type texture_string: str + :return: relative path to texture object or absolute path to texture object or unchanged texture string + :rtype: str + """ - return orig_texture_string + return get_scs_file_str(texture_string, "texture") def get_tobj_path_from_shader_texture(shader_texture, check_existance=True): From 4f251bbc4e4e58ce0a2cb248bbff03a536d43e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 4 Mar 2025 04:47:08 +0100 Subject: [PATCH 29/56] Revert some changes in material.py and path.py files This reverts some changes in commit 0c1fe80d8d311b3dd4721d1ae91867c356440ddc. It's because I know more now how aliasing works, and that code no more have sens. Also removed umatlib and water from aliased dirs/shaders --- addon/io_scs_tools/properties/material.py | 31 ------ addon/io_scs_tools/ui/material.py | 15 +-- addon/io_scs_tools/utils/material.py | 2 - addon/io_scs_tools/utils/path.py | 129 +++++----------------- 4 files changed, 30 insertions(+), 147 deletions(-) diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index aabb2dd..72bfc3e 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -75,24 +75,6 @@ def __update_look__(self, context): for scs_root in scs_roots: _looks.update_look_from_material(scs_root, context.active_object.active_material, is_preset_change) -def __update_shader_material__(self, context): - """Hookup function for update of shader material in Blender. - - :param context: Blender context - :type context: bpy.types.Context - :param tex_type: string representing texture type - :type tex_type: str - """ - - __update_look__((), context) - - material = _material_utils.get_material_from_context(context) - - if material: - shader_material_filepath = getattr(self, "shader_material") - - # always correct scs path string - shader_material_filepath = self["shader_material"] = _path_utils.get_scs_material_str(shader_material_filepath) def __update_shader_attribute__(self, context, attr_type): """Hookup function for updating shader attributes in Blender. @@ -375,9 +357,6 @@ def update_active_shader_preset_name(self, context): __update_look__(self, context) del self["preset_change"] # remove indicator of shader preset update - def update_shader_material(self, context): - __update_shader_material__(self, context) - def update_shader_attribute_add_ambient(self, context): __update_shader_attribute__(self, context, "add_ambient") @@ -619,16 +598,6 @@ def update_shader_texture_sky_weather_over_mask_b_settings(self, context): update=__update_look__ ) - # SHADER MATERIAL - shader_material: StringProperty( - name="Shader Material", - description="Shader Material for active Aliasing", - default=_MAT_consts.unset_bitmap_filepath, - options={'HIDDEN'}, - subtype='NONE', - update=update_shader_material, - ) - # SHADER ATTRIBUTES shader_attribute_add_ambient: FloatProperty( name="Add Ambient", diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index b15a9f4..0c56450 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -294,21 +294,15 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader alias_icon = _shared.get_on_off_icon(mat.scs_props.enable_aliasing) alias_row.prop(mat.scs_props, "enable_aliasing", icon=alias_icon, text=alias_text, toggle=True) - # Fix for "water" shader, where there is no "base" texture. Water is used as aliased in umatlib. - if "water" in mat.scs_props.mat_effect_name: - normalized_base_tex_path = mat.scs_props.shader_texture_layer0.replace("\\", "/") - else: - normalized_base_tex_path = mat.scs_props.shader_texture_base.replace("\\", "/") + normalized_base_tex_path = mat.scs_props.shader_texture_base.replace("\\", "/") is_aliasing_path = ("/material/road" in normalized_base_tex_path or "/material/terrain" in normalized_base_tex_path or - "/material/custom" in normalized_base_tex_path or - "/umatlib" in normalized_base_tex_path) + "/material/custom" in normalized_base_tex_path) is_aliasable = ('textures' in shader_data and ( len(shader_data["textures"]) == 1 or (len(shader_data["textures"]) == 2 and "tsnmap" in mat.scs_props.mat_effect_name) or - (len(shader_data["textures"]) >= 2 and "water" in mat.scs_props.mat_effect_name) or (len(shader_data["textures"]) == 2 and "dif.spec.weight.mult2" in mat.scs_props.mat_effect_name) )) @@ -321,15 +315,14 @@ def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader "-> '/material/road'\n" "-> '/material/terrain'\n" "-> '/material/custom'\n" - "-> '/umatlib'\n" "Additional requirement for aliasing is also single texture material (excluding nmap) or\n" - "exceptionally multi texture material of 'dif.spec.weight.mult2' or 'water' family.\n\n" + "exceptionally multi texture material of 'dif.spec.weight.mult2' family.\n\n" "Currently aliasing can not be done because:") if not is_aliasing_path: aliasing_info_msg += "\n-> Your 'Base' texture doesn't point to any of this (sub)directories." if not is_aliasable: - aliasing_info_msg += "\n-> Current shader type use multiple textures or it's not 'dif.spec.weight.mult2' or 'water' family type." + aliasing_info_msg += "\n-> Current shader type use multiple textures or it's not 'dif.spec.weight.mult2' family type." _shared.draw_warning_operator(alias_row, "Aliasing Info", aliasing_info_msg, icon='INFO') diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index a680b4e..821f0c5 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -499,8 +499,6 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac if override_back_data: for key in scs_props_keys: is_key_used = False - if key.startswith("shader_material"): - is_key_used = True if key.startswith("shader_attribute"): for used_attribute in used_attribute_types: if used_attribute in key[16:]: diff --git a/addon/io_scs_tools/utils/path.py b/addon/io_scs_tools/utils/path.py index d0a6d86..1ae2ad7 100644 --- a/addon/io_scs_tools/utils/path.py +++ b/addon/io_scs_tools/utils/path.py @@ -258,20 +258,6 @@ def is_valid_shader_texture_path(shader_texture): return False -def is_valid_shader_material_path(shader_material): - """It returns True if there is valid Shader Material file, otherwise False. - - :param shader_material: SCS material path, can be absolute or relative - :type shader_material: str - :return: True if there is valid Shader Material file, otherwise False - :rtype: bool - """ - - if shader_material.endswith(".mat") or shader_material.endswith(".umat"): - return is_valid_shader_texture_path(shader_material) - - return False - def is_valid_shader_presets_library_path(): """It returns True if there is valid "*.txt" file in the Shader Presets Library directory, otherwise False.""" @@ -428,29 +414,6 @@ def get_shader_presets_filepath(): return shader_presets_file -def get_material_extens_and_strip_path(material_path): - """Gets all supported material extensions and strips given input path for any of it. - - :param material_path: shader material raw path value - :type material_path: str - :return: list of extensions and stripped path as tuple - :rtype: tuple[list[str], str] - """ - - extensions = [".mat", ".umat"] - - # strip of any extensions ( endswith is most secure, because of possible multiple extensions ) - if material_path.endswith(".umat"): - - extensions.insert(0, material_path[-5:]) - material_path = material_path[:-5] - - elif material_path.endswith(".mat"): - - extensions.insert(0, material_path[-4:]) - material_path = material_path[:-4] - - return extensions, material_path def get_texture_path_from_tobj(tobj_filepath, raw_value=False): """Get absolute path of texture from given tobj filepath. @@ -540,48 +503,42 @@ def get_texture_extens_and_strip_path(texture_path): return extensions, texture_path -def get_scs_file_str(file_string, file_type): - """Get file string as presented in SCS files: "/folder1/folder2/file" - without any file extensions. Input path can also have object extension or supported extensions. + +def get_scs_texture_str(texture_string): + """Get texture string as presented in SCS files: "/material/environment/vehicle_reflection" + without any file extensions. Input path can also have texture object extension or supported images extensions. Path will be searched and returned in this order: 1. relative path on current SCS Project Base Path 2. relative path on parent base dirs of current SCS Project Base Path in the case of mod/dlc 3. find absolute file path 4. return unchanged texture string path - :param file_string: file string for which file should be found e.g.: "/folder1/folder2/file" or absolute path - :type file_string: str - :param file_type: type of file for which path should be found - :type file_type: str (material|texture) - :return: relative path to file object or absolute path to file object or unchanged file string + :param texture_string: texture string for which texture should be found e.g.: "/material/environment/vehicle_reflection" or absolute path + :type texture_string: str + :return: relative path to texture object or absolute path to texture object or unchanged texture string :rtype: str """ scs_project_path = _get_scs_globals().scs_project_path - orig_file_string = file_string + orig_texture_string = texture_string # remove any directory separators left overs from different platform - file_string = file_string.replace("/", os.sep).replace("\\", os.sep) + texture_string = texture_string.replace("/", os.sep).replace("\\", os.sep) - if file_type == "material": - extensions, file_string = get_material_extens_and_strip_path(file_string) - elif file_type == "texture": - extensions, file_string = get_texture_extens_and_strip_path(file_string) - else: - raise ValueError("Invalid file type: %s" % file_type) + extensions, texture_string = get_texture_extens_and_strip_path(texture_string) # if texture string starts with scs project path we can directly strip of project path - if startswith(file_string, scs_project_path): - file_string = file_string[len(scs_project_path):] + if startswith(texture_string, scs_project_path): + texture_string = texture_string[len(scs_project_path):] else: # check if texture string came from base project while scs project path is in dlc/mod folder # first find longest matching path try: - common_path_len = len(os.path.commonpath([scs_project_path, file_string])) + common_path_len = len(os.path.commonpath([scs_project_path, texture_string])) except ValueError: # if ValueError is raised then paths do not match for sure, thus set it to 0 common_path_len = 0 - nonmatched_path_part = file_string[common_path_len + 1:] + nonmatched_path_part = texture_string[common_path_len + 1:] if nonmatched_path_part.startswith("base" + os.sep) or nonmatched_path_part.startswith("base_") or nonmatched_path_part.startswith("dlc_"): @@ -590,10 +547,10 @@ def get_scs_file_str(file_string, file_type): # NOTE: find calls is inverted in relation to number of parents dirs for infix, find_calls_count in (("..", 2), (".." + os.sep + "..", 1)): - modif_file_string = os.path.join(scs_project_path, infix + os.sep + nonmatched_path_part) + modif_texture_string = os.path.join(scs_project_path, infix + os.sep + nonmatched_path_part) # we got a hit if one or two directories up is the same path as texture string - if is_samepath(modif_file_string, file_string): + if is_samepath(modif_texture_string, texture_string): slash_idx = 0 for _ in range(0, find_calls_count): slash_idx = nonmatched_path_part.find(os.sep, slash_idx) @@ -601,55 +558,21 @@ def get_scs_file_str(file_string, file_type): # catch invalid cases that needs investigation assert slash_idx != -1 - file_string = nonmatched_path_part[slash_idx:] + texture_string = nonmatched_path_part[slash_idx:] - # check for relative files through extensions + # check for relative TOBJ, TGA, PNG for ext in extensions: - file_path = get_abs_path("//" + file_string.strip(os.sep) + ext) - if file_path and os.path.isfile(file_path): - return "//" + file_string.replace(os.sep, "/").strip("/") + ext + texture_path = get_abs_path("//" + texture_string.strip(os.sep) + ext) + if texture_path and os.path.isfile(texture_path): + return "//" + texture_string.replace(os.sep, "/").strip("/") + ext - # check for absolute files through extensions + # check for absolute TOBJ, TGA, PNG for ext in extensions: - file_path = get_abs_path(file_string + ext, skip_mod_check=True) - if file_path and os.path.isfile(file_path): - return file_string.replace(os.sep, "/") + ext - - return orig_file_string - -def get_scs_material_str(material_string): - """Get material string as presented in SCS files: "/umatlib/specual/collision_umat" - without any file extensions. Input path can also have supported material extensions. - Path will be searched and returned in this order: - 1. relative path on current SCS Project Base Path - 2. relative path on parent base dirs of current SCS Project Base Path in the case of mod/dlc - 3. find absolute file path - 4. return unchanged material string path - - :param material_string: material string for which material should be found e.g.: "/umatlib/specual/collision_umat" or absolute path - :type material_string: str - :return: relative path to material object or absolute path to material object or unchanged material string - :rtype: str - """ - - return get_scs_file_str(material_string, "material") - -def get_scs_texture_str(texture_string): - """Get texture string as presented in SCS files: "/material/environment/vehicle_reflection" - without any file extensions. Input path can also have texture object extension or supported images extensions. - Path will be searched and returned in this order: - 1. relative path on current SCS Project Base Path - 2. relative path on parent base dirs of current SCS Project Base Path in the case of mod/dlc - 3. find absolute file path - 4. return unchanged texture string path - - :param texture_string: texture string for which texture should be found e.g.: "/material/environment/vehicle_reflection" or absolute path - :type texture_string: str - :return: relative path to texture object or absolute path to texture object or unchanged texture string - :rtype: str - """ + texture_path = get_abs_path(texture_string + ext, skip_mod_check=True) + if texture_path and os.path.isfile(texture_path): + return texture_string.replace(os.sep, "/") + ext - return get_scs_file_str(texture_string, "texture") + return orig_texture_string def get_tobj_path_from_shader_texture(shader_texture, check_existance=True): From 54de631e7e078695bc2f1ba83b5327dbeffc6df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 25 Mar 2025 01:04:06 +0100 Subject: [PATCH 30/56] some fixes for 4.4 * Fixed error during export (due to 4.4) * Fixed error during using of Wrap Tool (due to 4.4) * Fixed log errors when creating/importing first root locator * (updated info about blender & tool versions) --- addon/io_scs_tools/__init__.py | 4 ++-- addon/io_scs_tools/exp/pim/piece.py | 22 ++++++++++---------- addon/io_scs_tools/operators/bases/export.py | 2 +- addon/io_scs_tools/operators/mesh.py | 3 ++- addon/io_scs_tools/operators/scene.py | 8 ++++--- addon/io_scs_tools/properties/object.py | 15 ++++++------- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 50bee18..6bb907f 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,8 +22,8 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 4), - "blender": (4, 2, 0), + "version": (2, 4, "aeadde03", 5), + "blender": (4, 4, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", "tracker_url": "http://forum.scssoft.com/viewforum.php?f=163", diff --git a/addon/io_scs_tools/exp/pim/piece.py b/addon/io_scs_tools/exp/pim/piece.py index 3968556..62244ee 100644 --- a/addon/io_scs_tools/exp/pim/piece.py +++ b/addon/io_scs_tools/exp/pim/piece.py @@ -80,17 +80,17 @@ def __calc_vertex_hash(index, normal, uvs, rgba, factor, tangent): if tangent: vertex_hash = (index, - int(normal[0] * fprec), - int(normal[1] * fprec), - int(normal[2] * fprec), - int(rgba[0] * fprec), - int(rgba[1] * fprec), - int(rgba[2] * fprec), - int(rgba[3] * fprec), - int(tangent[0] * fprec), - int(tangent[1] * fprec), - int(tangent[2] * fprec), - int(tangent[3] * fprec)) + int(normal[0] * fprec), + int(normal[1] * fprec), + int(normal[2] * fprec), + int(rgba[0] * fprec), + int(rgba[1] * fprec), + int(rgba[2] * fprec), + int(rgba[3] * fprec), + int(tangent[0] * fprec), + int(tangent[1] * fprec), + int(tangent[2] * fprec), + int(tangent[3] * fprec)) else: vertex_hash = (index, int(normal[0] * fprec), diff --git a/addon/io_scs_tools/operators/bases/export.py b/addon/io_scs_tools/operators/bases/export.py index be4ffaf..26d8438 100644 --- a/addon/io_scs_tools/operators/bases/export.py +++ b/addon/io_scs_tools/operators/bases/export.py @@ -33,7 +33,7 @@ class SCSExportHelper: export_scene_name = None """Stores name of the export scene (used to skip custom drawing elements update on export scene).""" - def __init__(self): + def __init__(self, *args, **kwargs): self.cached_objects = None """:type list[bpy.types.Object]: Used in selection mode to cache selected objects, to avoid multiple collecting.""" self.objects_states = dict() diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index c79ec38..3c7c0e3 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -286,7 +286,8 @@ class SCS_TOOLS_OT_WrapVertexColors(bpy.types.Operator): def poll(cls, context): return context.object is not None and context.object.mode == "VERTEX_PAINT" and len(context.object.data.color_attributes) > 0 - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.original_col = {} def __del__(self): diff --git a/addon/io_scs_tools/operators/scene.py b/addon/io_scs_tools/operators/scene.py index fb03e18..e13084f 100644 --- a/addon/io_scs_tools/operators/scene.py +++ b/addon/io_scs_tools/operators/scene.py @@ -109,8 +109,9 @@ class SCS_TOOLS_OT_ExportByScope(bpy.types.Operator, _SCSExportHelper): bl_description = "Export SCS models depending on selected export scope." bl_options = set() - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + bpy.types.Operator.__init__(self, *args, **kwargs) + _SCSExportHelper.__init__(self, *args, **kwargs) self.can_mouse_rotate = False """:type bool: Flag indiciating whether mouse move even will rotate view or no. Initiated by left mouse button press.""" @@ -1071,7 +1072,8 @@ class SCS_TOOLS_OT_ConvertAllPaths(bpy.types.Operator): bl_idname = "scene.scs_tools_convert_all_paths" bl_description = "Converts all paths given in Custom Paths list + current SCS Project Base" - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.include_current_project = True @classmethod diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index e87a4a1..4bd83b8 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -90,16 +90,17 @@ def name_update(self, context): scs_root_obj = _object_utils.get_scs_root(context.active_object) # if there is more of parts with same name, make postfixed name (this will cause another name update) - if len(_inventory.get_indices(scs_root_obj.scs_object_part_inventory, self.name)) == 2: # duplicate + if scs_root_obj != None: + if len(_inventory.get_indices(scs_root_obj.scs_object_part_inventory, self.name)) == 2: # duplicate - i = 1 - new_name = _name_utils.tokenize_name(self.name + "_" + str(i).zfill(2)) - while _inventory.get_index(scs_root_obj.scs_object_part_inventory, new_name) != -1: + i = 1 new_name = _name_utils.tokenize_name(self.name + "_" + str(i).zfill(2)) - i += 1 + while _inventory.get_index(scs_root_obj.scs_object_part_inventory, new_name) != -1: + new_name = _name_utils.tokenize_name(self.name + "_" + str(i).zfill(2)) + i += 1 - if new_name != self.name: - self.name = new_name + if new_name != self.name: + self.name = new_name if "scs_part_old_name" in self: From a0d1900c532981b64af0f6d07435531a755331a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 5 Apr 2025 20:28:58 +0200 Subject: [PATCH 31/56] shader_presets update * Added env_factor to truckpaint and glass shaders (some SCS models use it) --- addon/io_scs_tools/shader_presets.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 82be1b8..5f15b82 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1417,6 +1417,11 @@ Shader { Tag: "add_ambient" Value: ( 0.0 ) } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } Attribute { Format: FLOAT2 Tag: "fresnel" @@ -1770,6 +1775,11 @@ Shader { Tag: "add_ambient" Value: ( 0.0 ) } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } Attribute { Format: FLOAT2 Tag: "fresnel" From a96a27969add2822cac51b14487f14506c63e839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 5 Apr 2025 22:37:00 +0200 Subject: [PATCH 32/56] export fix * Fixed export through file -> export --- addon/io_scs_tools/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 6bb907f..0ea621f 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 5), + "version": (2, 4, "aeadde03", 6), "blender": (4, 4, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", @@ -222,6 +222,11 @@ class SCS_TOOLS_OT_Export(bpy.types.Operator, _SCSExportHelper, ExportHelper): filename_ext = ".pim" filter_glob: StringProperty(default=str("*" + filename_ext), options={'HIDDEN'}) + def __init__(self, *args, **kwargs): + bpy.types.Operator.__init__(self, *args, **kwargs) + _SCSExportHelper.__init__(self, *args, **kwargs) + ExportHelper.__init__(self) + def execute(self, context): # convert filepath to None if empty, so export will ignore given menu file path and try to export to other none menu set paths if self.filepath == "": From 1b7200a8105306b55e4afdfcdf87827da5c9e282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 5 May 2025 18:33:58 +0200 Subject: [PATCH 33/56] Support for oinv and tsnmapuv2 + some tweaks * Added support for "oinv" (inverted opacity) for "dif.spec.add.env" shader. * Added partial support for "tsnmapuv2" for "dif.spec.weight.add.env" shader. Flavor can be created and exported, but it will not be properly displayed in blender. * Added possibility to create "FriendlyTag" for "textures" in shader_presets.txt. Thanks to that, it's now possible to create custom names for texture labels e.g. instead of "Mask 1" it can be named "Lamp Mask" etc. * Added custom texture labels to some shaders thanks to "FriendlyTags". --- .../internals/shaders/eut2/dif/__init__.py | 22 ++++ .../internals/shaders/flavors/oinv.py | 104 ++++++++++++++++++ .../io_scs_tools/internals/shaders/shader.py | 6 + addon/io_scs_tools/shader_presets.txt | 58 +++++++++- addon/io_scs_tools/supported_effects.bin | Bin 142938 -> 142098 bytes addon/io_scs_tools/ui/material.py | 4 +- 6 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/flavors/oinv.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py index e979c5e..7ef30b8 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif/__init__.py @@ -30,6 +30,7 @@ from io_scs_tools.internals.shaders.flavors import nmap from io_scs_tools.internals.shaders.flavors import paint from io_scs_tools.internals.shaders.flavors import tg0 +from io_scs_tools.internals.shaders.flavors import oinv from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import material as _material_utils @@ -557,3 +558,24 @@ def set_paint_flavor(node_tree, switch_on): else: paint.delete(node_tree) + + @staticmethod + def set_oinv_flavor(node_tree, switch_on): + """Set oinv flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + opacity_n = node_tree.nodes[Dif.OPACITY_NODE] + compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] + + if switch_on: + + location = (opacity_n.location.x + 185, opacity_n.location.y + 40) + oinv.init(node_tree, location, opacity_n.outputs["Value"], compose_lighting_n.inputs["Alpha"]) + + else: + oinv.delete(node_tree) diff --git a/addon/io_scs_tools/internals/shaders/flavors/oinv.py b/addon/io_scs_tools/internals/shaders/flavors/oinv.py new file mode 100644 index 0000000..9d2d2b2 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/flavors/oinv.py @@ -0,0 +1,104 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +FLAVOR_ID = "oinv" +INV_OPAC_NODE = "InvertOpacity" + + +def __create_node__(node_tree): + """Create node for oinv node. + + :param node_tree: node tree on which oinv flavor will be used + :type node_tree: bpy.types.NodeTree + """ + inv_opac_n = node_tree.nodes.new("ShaderNodeInvert") + inv_opac_n.name = inv_opac_n.label = INV_OPAC_NODE + inv_opac_n.inputs[0].default_value = 1.0 + + +def init(node_tree, location, opac_from, oinv_to): + """Initialize oinv flavor + + :param node_tree: node tree on which oinv flavor will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location (int, int) + :param opac_from: node socket from which oinv flavor should get opacity + :type opac_from: bpy.types.NodeSocket + :param oinv_to: node socket to which result of inverted opacity should be send + :type oinv_to: bpy.types.NodeSocket + """ + + if INV_OPAC_NODE not in node_tree.nodes: + __create_node__(node_tree) + + node_tree.nodes[INV_OPAC_NODE].location = location + + # links creation + nodes = node_tree.nodes + + node_tree.links.new(nodes[INV_OPAC_NODE].inputs["Color"], opac_from) + node_tree.links.new(oinv_to, nodes[INV_OPAC_NODE].outputs[0]) + + # FIXME: move to old system after: https://developer.blender.org/T68406 is resolved + flavor_frame = node_tree.nodes.new(type="NodeFrame") + flavor_frame.name = flavor_frame.label = FLAVOR_ID + + +def delete(node_tree): + """Delete oinv flavor nodes from node tree. + + :param node_tree: node tree from which oinv flavor should be deleted + :type node_tree: bpy.types.NodeTree + """ + + if INV_OPAC_NODE in node_tree.nodes: + + out_socket = None + in_socket = None + + for link in node_tree.links: + + if link.to_node == node_tree.nodes[INV_OPAC_NODE]: + out_socket = link.from_socket + + if link.from_node == node_tree.nodes[INV_OPAC_NODE]: + in_socket = link.to_socket + + node_tree.nodes.remove(node_tree.nodes[INV_OPAC_NODE]) + + # if out and in socket were properly recovered recreate link state without oinv flavor + if out_socket and in_socket: + node_tree.links.new(out_socket, in_socket) + + if FLAVOR_ID in node_tree.nodes: + node_tree.nodes.remove(node_tree.nodes[FLAVOR_ID]) + + +def is_set(node_tree): + """Check if flavor is set or not. + + :param node_tree: node tree which should be checked for existance of this flavor + :type node_tree: bpy.types.NodeTree + :return: True if flavor exists; False otherwise + :rtype: bool + """ + return FLAVOR_ID in node_tree.nodes + diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 3d949a0..40de242 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -62,6 +62,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".tsnmapuv") or ".tsnmapuv." in effect: flavors["nmap"] = True + if effect.endswith(".tsnmapuv2") or ".tsnmapuv2." in effect: + flavors["nmap"] = True + if effect.endswith(".tsnmap") or ".tsnmap." in effect: flavors["nmap"] = True @@ -74,6 +77,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".linv") or ".linv." in effect: flavors["linv"] = True + if effect.endswith(".oinv") or ".oinv." in effect: + flavors["oinv"] = True + if effect.endswith(".lvcol") or ".lvcol." in effect: flavors["lvcol"] = True diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 5f15b82..599a00b 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1054,7 +1054,7 @@ Shader { Shader { PresetName: "dif.spec.weight.add.env" Effect: "eut2.dif.spec.weight.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_UV_2" "SHADOW" "PAINT" "TEXGEN0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1486,7 +1486,7 @@ Shader { Shader { PresetName: "lamp" Effect: "eut2.lamp" - Flavors: ( "ANIM" "NMAP_TS|NMAP_TS_UV" "SHADOW" "ENVMAP" ) + Flavors: ( "LAMP_ANIM" "NMAP_TS|NMAP_TS_UV" "SHADOW" "ENVMAP" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1515,6 +1515,7 @@ Shader { } Texture { Tag: "texture[X]:texture_mask" + FriendlyTag: "Lamp Mask" Value: "" TexCoord: ( 1 ) } @@ -1589,6 +1590,7 @@ Shader { } Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Mask" Value: "" TexCoord: ( 0 ) } @@ -1907,16 +1909,19 @@ Shader { } Texture { Tag: "texture[X]:texture_layer0" + FriendlyTag: "Waves 0" Value: "/material/terrain/water/waves_lake" TexCoord: ( -1 ) } Texture { Tag: "texture[X]:texture_layer1" + FriendlyTag: "Waves 1" Value: "/material/terrain/water/waves_lake" TexCoord: ( -1 ) } Texture { Tag: "texture[X]:texture_reflection" + FriendlyTag: "Environment cube" Value: "/material/environment/generic_reflection" TexCoord: ( -1 ) } @@ -2009,11 +2014,13 @@ Shader { } Texture { Tag: "texture[X]:texture_mask" + FriendlyTag: "Decal strength" Value: "" TexCoord: ( 1 ) } Texture { Tag: "texture[X]:texture_over" + FriendlyTag: "Decal" Value: "" TexCoord: ( 1 ) } @@ -2073,26 +2080,31 @@ Shader { } Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Atlas" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_layer0" + FriendlyTag: "Emissive (Day)" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_layer1" + FriendlyTag: "Emissive (Night)" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_mask" + FriendlyTag: "LUT" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal Map" Value: "" TexCoord: ( 0 ) } @@ -2161,31 +2173,37 @@ Shader { } Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Atlas" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_over" + FriendlyTag: "Curtains" Value: "" TexCoord: ( 2 ) } Texture { Tag: "texture[X]:texture_layer0" + FriendlyTag: "Emissive (Day)" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_layer1" + FriendlyTag: "Emissive (Night)" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_mask" + FriendlyTag: "LUT" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal Map" Value: "" TexCoord: ( 0 ) } @@ -2222,6 +2240,7 @@ Shader { } Texture { Tag: "texture[X]:texture_over" + FriendlyTag: "Specular map" Value: "" TexCoord: ( 0 ) } @@ -2243,11 +2262,13 @@ Shader { } Texture { Tag: "texture[X]:texture_over" + FriendlyTag: "Specular map" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_mask" + FriendlyTag: "Env factor map" Value: "" TexCoord: ( 0 ) } @@ -2281,6 +2302,7 @@ Flavor { Name: "a" Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Alpha source" Value: "" TexCoord: ( 0 ) } @@ -2290,7 +2312,7 @@ Flavor { Name: "altuv" } Flavor { - Type: "ANIM" + Type: "LAMP_ANIM" Name: "anim" Attribute { Format: FLOAT4 @@ -2306,21 +2328,25 @@ Flavor { } Texture { Tag: "texture[X]:texture_mask" + FriendlyTag: "Lamp Mask R" Value: "" TexCoord: ( 1 ) } Texture { Tag: "texture[X]:texture_mask_1" + FriendlyTag: "Lamp Mask G" Value: "" TexCoord: ( 1 ) } Texture { Tag: "texture[X]:texture_mask_2" + FriendlyTag: "Lamp Mask B" Value: "" TexCoord: ( 1 ) } Texture { Tag: "texture[X]:texture_mask_3" + FriendlyTag: "Lamp Mask A" Value: "" TexCoord: ( 1 ) } @@ -2473,6 +2499,10 @@ Flavor { Type: "INDENV" Name: "indenv" } +Flavor { + Type: "INVERTOPAC" + Name: "oinv" +} Flavor { Type: "LUMMULVCOL" Name: "lvcol" @@ -2496,6 +2526,7 @@ Flavor { Name: "tsnmap" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" Value: "" TexCoord: ( 0 ) } @@ -2505,6 +2536,7 @@ Flavor { Name: "tsnmapcalc" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" Value: "" TexCoord: ( 0 ) } @@ -2514,6 +2546,7 @@ Flavor { Name: "tsnmapcalc" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" Value: "" TexCoord: ( 0 ) } @@ -2528,6 +2561,7 @@ Flavor { Name: "tsnmapuv" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" Value: "" TexCoord: ( 6 ) } @@ -2537,11 +2571,28 @@ Flavor { Name: "tsnmapuv" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" + Value: "" + TexCoord: ( 6 ) + } + Texture { + Tag: "texture[X]:texture_nmap_detail" Value: "" TexCoord: ( 6 ) } +} +Flavor { + Type: "NMAP_TS_UV_2" + Name: "tsnmapuv2" + Texture { + Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" + Value: "" + TexCoord: ( 0 ) + } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal map (hi-freq detail)" Value: "" TexCoord: ( 6 ) } @@ -2559,6 +2610,7 @@ Flavor { Name: "paint" Texture { Tag: "texture[X]:texture_mask_1" + FriendlyTag: "Paint mask" Value: "" TexCoord: ( 0 ) } diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 41cddd85cce23dc6846717211a75fa19bf5119fd..41a39c601452c528c5d4263e579600c285884ca3 100644 GIT binary patch literal 142098 zcmbWg>2hY-aV@6nutQgzXI;&{8m?#!5W7T4Bqdr_C~o*MI#2~vHAECp1gg54{N)Eg zg!Kb@I=_rFbLF`9J|EE1pB4+}>>+dI%3FD}k5Z$3YJe}D7#{N0DoU!2{4c7AdD@$CJZADx{) zKKzgQ*6rt)cQ(fDyYpA|=qHa4e`F87zx(j&7w^umZr=NdXXp3luP;C9P~W<^`_A0%v-5ZFu77oQZOgg;aC7$A$5(IPoqclu`0yt-=F6+=>zB9Z zcNg-^`!ikUyUU9YpZn~O4?h^=>bl>4xPJfQ@!^Ma?T0&-wf!}({`~6h<=uz-&(2<7 zU%h*Meg2C}nX&eQ=Z}PWbNl-4^8V)XI@kEyx<=jh>)SVHH?Pi2jyJczeth_|xk$*) zC%U+Nb$)HTd3^Zu`5bKquj#0d4}Z0N5(<{_Ud1+S^yfT&*4Od9l(uh0h26Jt{oyST z=0LhOm19Sb4=-$S3rtd-XRF{JAHEsVyZ`X=%%t|;PwesR9N=1X3K0ZqQ?X~~*Y7`k z{`l|*b4=Y=nTufLf49GXxKR`qj6Z&M_5KnPVkuzf5S}Ug-QA^(DntEYPNmXv3Avcf zFcL2c=l5bxYNxjIzpz(d3Lq~a=4ruaK*Eh$)8oVU?cvX_E-r6Hsrl3Hh?GsAybWcTw}LavZ`}8gg;}>cqe}rBD)nn zQeu;5?|&&~KqeXl=ervgCeuAW{7wS9*{L(h4^j+?ZU~Yv6IPj=N8I%GM`yo&Ep!%O zN&<-uyB*6Ojx3({p-cZsF3%)jSks}6q+@7TRw`E*UTFI-=d1B>F<3n>Y9eZhryV!B zv~%OP$A`a}gWtZo{;)Wl&#-Ms6`@#Iy0Q*blr`Q;Z-)}sz?bg7Jp%YA~>3iaW2MR6!54|C+}q%9&2%F{NB*3D=yg_ z<|S5qd3SXy@x}SC&XnL5u>{^4?|>Q>qS~$*ul0Zd8gPDH23dDwY>b^BII=|1CVmh{ zmdcZmLO5#?VC)bwOs}}aVMB}gm=1V4PDt$uxBs`)iRxoG8Eh`u=_H}JOdcP;lbbsR z_E@(Wkz2&$!;kWH1NPM5luHDs-_IQ=WeZc-#E%dE{QRLxc7y21@AB*}xBh(^6rvhN5w(M1?Bzo7irzB%*i~ z{s_)H2Uztqm|`-_O4@GV?(4`CZ`{Ci0towaEt}^xT>Gx>LiMhTaWAn15@|<+cpfg=)^uN2K~JYnvGR{q5e8JZWnZ>KJWU&`PsyG#b3ABC)`vvPID?1Vk=1K+aL? z8LW(Am4Rrn`f!lJ8RYb6aJU&?$8UbPI(vJ2bK5CYfp~sfw*B$thpX$0tD85T3`q#^`0&TP?RR&#|91K6y@Uu6?f0>Lm&zLv zfKr|_nEUrB%-?0CSXjL|f-P*-yK@`Ko&xc0NVH!nZEJHL4&-u>J9r}wuvmr@EpdwFqnKkBb$#j&~q znJppeuPMm&Ijmp?vvFT7YJVLu5lwlSRy z00c9sD!Z;WBj7KSAa)lZH4l^b{neWrgymU^Cgb+?#|i_F-4OnSiLdge0#7YNrg0DSe~?!BC1*@+WMQGdT% znyK8^=am2Bf4iT1`dyu>Fz1Y5Yk7S5(`D3mx9?HMeyxUm_DW1=L1JnppeX8keE7Rb zEaoA$>KxI*lIm_e%XlG#YX;-!^qi;aF}h3Bk4YQRwUSPAX&Iz11^s)KfqGJiAPV|c z`_1T3nMCXYCJa-g($|Uzr~k)?f1RRoDfxer9Q}Vo+E%P8AEzN+v7E&+EkX_%{lTJM zj}Z(rLh`nbc4SiF#n*F*Dl?v}m`gGWKUAXahY1JCh`bPTOSTh`R3SF^j2b%~6vsH_ zN}TDte3v*{HBxRZ#wg$_VE5GNqelvd=(dR{$ME^(K>7CW?fLyLBwgWsIWDWxsd&vz z>+vha0CU-3WPRQPNSvp&!#9sdsdN+DWsy}fRwJ*eLT=C_3dcBteNyt|z%eSt$gw7e zLsyAtaT$y16{RJ9~4dg%ylSX_~F=@!`ozGWMXrdwif{_(ZPnz|vEC zCZIA>*O=*E=plRu164HKr zsCrVGv?@jd@>$D9;kIFnj9{m~5LTb1$;znBP8cpeoL_$`hyI$@lt~_@HqOtjs7P)y z0O58h(UHa?R0>-m>-dpJzuLUyuiLy7N z!i_9FAj!}U>M#?s@R3{+=^PvD4knciNWnQTgi+4;WM|5c52a!;+)>oOW-=PoMks4% zC5jd#CTd^5zWk+@qNM(#>M>d9Kc~&rKbzsHO=#icFpECEcDh4((q!y70-#Apj5 zHiQC2dXog5&vgNA5=0RA3pEU^tY(}lW&MhzOsmDyiGvau5=S6>Y&02 z-x*VS=$NHhS%ML>NuBs1Ofo`k)bj$Ew5oX6#TR7DR{r+(qT=Tm${OL)PV^MvWc)g9 zO$?G;plwLSIEvCH%Ic#r+a8Pc@|lUIEe3!D5fQi7IV1*x8viAO8=FJ_v9=syntQ3WWQK>`lnL$fbT5zDVBirZ)o@JG!P!CSvy`dz_HHdx;8waN77|Rtl2g%e!WJtcq z2MTNP)fmhH7ds*TGDlD4r35w43N!1CVJu6AexyM~XC#BDs&{v>v^6bZHpTlfO~}Nc zf#bYM5`3CI8j@fiZ6T0!?saj5F)iaWbWHKmh+>A3QTy|@$DcpnpCoWh%$|o;LhS?{VF$$wkh28>we(phDXggVW1s$L=%UT6Xtr_ujkY^1=4}ORiN0? zx{%QlyPQPvQ?&+win%AU`VKs@x!6kWd`XXo^;$W?xENWinvd<*ktpLWI$8&CHjMRc zO=93YB<V~lBe zv7Asfh+++o0wGo>LRX?z_r&ZX`nGZ3UVZT+3GTp8!hSTHFF5}0G8Mp#cwQkrLIn#5 zNRBEH3qeOfY~(_T*4$z&F6?G;M?)SU=-8mMQvm0ksj;mtP z?MULgKZMLymMB`}L8jN6M}S7B4SPd!L8+Cv%cUq_m`R2g&?qH9dsSe!Wsc^UGDkG~ z*Q&`rep*E4cvVIx^YeusynzK%+&K1DAwnMEnwC*UzPo%c-4BhsI^l=4E_ z1kbOfO;R|@z6>-e=lNe%J2`h-PfqcE8bqGus)Vq5tB~qIRiYAJ&NtRRo?23Lci^1N zkH}6I$QL;_q=-SS>yOCH7h9iUxs=r_yY;B{po*Dqw2sT!9KDQb?%fDCmnD2jX?1MD zT`-_Ca)OmPWfuzLK-ZGq1diLMis{{E^#vxmkbnV1WnumdtB*)(PrTr$rKxa$*SrBX zXr$6=BKiSdNW$d5j1+%$IR*Ryx~#T&H3y{!GNhB1AIHXI;WTTKqA3V-lm8nIwn2&1 zJ_HkCe{4!-^`nNxkg~2q(N`=K;>Enc2vz#x!PUFktk3qv*!*vL$s=HW#6TO`$wG{~ z1*{pv{*F4W`}^}-X@GhA?)FYjLgWk6+pBvygQ9eyZFs?q4z>%+EEgtkJM`A=m1;i; zflwA!Oc_Ky<%XvkMfLZPO|I`>-hQ}wb&1H1@Up5cr%<;jWH*_$q8Fe5jZ6CN&aba8 z@1?u6V3DWoAIDHiFJ+BqxJA0KMnU$Y%@;bZp7S$FHEPOcWb zRL_yxm~n$;<`j^`y0{*M$-vX9Tgndc=M3D;w2JQ5=0^2Z0(rjDz&wce{1BVP!pe>l z5UxkT1l5&8)soz9Im)5kiT}~ekR+C`zk+({U_KT*5C-8b*otRsW5LZ&n#34h zw*NIhqjb;w*Ej-MNbgHqE{ z45RtKLQJXkRozBuj>L!+^8GzG`qM5j@-g!j-=TSOaPo`{A|iN>L3zloOcoM5`SLChrau@>WaJXR97ZKArOn#)tM;zA!oK^LNebZING zwzB!I$OXG|=kapBV#yoLF!kq;Vog@2`ST-g=%@AeS;irY6j{Hxc1qF9Xecs>)dtuk zOI})V59wj7TMV8y>F#&Jhm!$c{G-5Ed`?DF0i>UVMLdP^5XFM%2o1bb9VyB>NgEwRn=HfA5vM!!}U+THy$5oDa#$ew{LF=_|`+VAAv3JR@+ zfZLAX7uyqw0H1q#1I;Ivdc2Xg3MpHVa0gb2j0}?*Jb^>Z2DN9GqRgqJ*q)=HoKiPu zchGQ36z=54Fl#fC^%Fk&`XWl7FdEWfeeW8F%9LVGc_bfyvU5@9p3>heAuLs_Lne;> zuK9gMb>Jx?y4F&LPoBUK)m>#0_PG}4O=67@DM-jD8L1M1{ZgtW4!=ms{Y!IGK92Ciqp{W zfz#|5PFI%YW-{riScnCy|G$!B`bv&;YXY{I*ukL%7$WuN1H`+y;>IVK0@v5VhDeZ} z!>BS&m`m?HWAKOg~!Iin5T!Yqnd%xp?8@We%e=pehysRn_m5)fEHSB*kk82-35l~J^OU}R!4MoRzsp9GN4 z==k{Aj^p!0?Fq7{K*tx1oz2>NgVD60?BG?xG1U`z#-K5bnw7U(x?!x zIEU)EU|E=Tw(Q=&JAZ#A7tP3SnM8fDgco~O*CP>^Ei~sr0BAS6uHhO% znv`KpQtZ5=?q3S!wieu~u!e}cxx?6=`+574nGoz+v=lHdl)Atr++2@Hl88a*qkxZ! zEZV?Q*$`fwBQ~1I#I=3sTulrJb$88QIWiX9gZX`tC}KDz04yrPUO&TBmxK~N!%`-e$_q6dj^~BY_nAQ~D|OXh)9;R$ZX@}Q#;7z@iZ3d5fT-b< zYNb0ia$8St=D?FR>Pp5{D1&L9wvT||=U~X(gCq z9fhEy*d_K=u!s|VvJK9gIU?0#=NpOWD383@q7y~{81Ni<{|Mr0u|tQL{2xC!RI`~QD-Is(s_l7%l{OyVL68;ZnW1~9-9DHQg%pdjAb2NJ?gAcjRy;@4V8c3>-osaTYRvy^Ry z=DpjZUSY7uiNd~%O=F*{U)*{t?ILEMr7eSG8FFB7iFu=hG%puWy$q&iq26cUZ{o&Q+jTCP1m%GdBS07&5-EgmNrRh$4?WlY@w&-I6>nIR5 z6}o7f0%vVeX%L239N@#-4ub@AlXl+V9MEEeJr_C97xlRDwfDcJ{>_>Ue|;xE4D(8F z$eRvAG`947ZvrrPFrFGNfix>65t8azYR3VU!Dq056Ebe&l~9cWeDjzYVjsa=Ia`Dv z&$)!{6KTMEu79&rt5+pZnP z0otN8(?xG!2)&uj)rkY@Zh4cahd}UoWM#JAHL}vtu@^dR+W7*~uO+3FeuWf=&dQcd zLJKI&niV4B6GPw4TIdR4hz_4q7m3FK0HEbGYXAazR~z1ep<6*H@AlB0m=9PoP!d5+ z>plac9a!r?U(2Zptd5OXsE>@a;wxrF77R&|VM%oRjm*oTn;w%=W^6wtwPeMy$s$-^ zxnsnDnz^kCtLRPM4N9$(2mR79Sf;83BeP>nIY5vLWtlNohM)T)uHQo~7cHeW6w;T% zzOGNi71!TWfW}Nl)^Aou9H=3;9R>O}G7?l_Rl-BAXS`Bb)h5r&J$X~#9`cDGAJ}VI z9<9(jAzD_%SQLe4#v^SlVzl1|KV zdg7!-ou(OEC^G3ySmj8ulecdud({o$OANJHgkuS+p*|Pqw7@r>q3TM=!HpuG?Fw(D z|7#FQm^mqF-D>I4+QFYN3Il~~^V;KiAGWas4LPlN`|SeVajKYYtz zI0olCIWroo$(9XBzV_sLG`LSwFsRbUv49c;3anL|pU=Ead!{jTg;2+E8f*FiOjE&q zq>Z`JZx{*Y#Su%I%)Ds?fGD7`WKQe=i1`DB81 zQ7`JV5uT5f23110nY=DyM1vjSVLQ3f+>Y-bszNyQ#{*Ffa2*fi=nkZnO@}hd+beeE@i7`H1p{G@=y|@|0CM)J~xG1smH1mP));5 zKxT7&Ri0?sIZ5j|(8eLDr`zQU&*yhoFO^9GiX2QBj+R3@a{(vg9bh10s`guG$jd#V zL2l&T08ahD$#a&6(HE~Wd7>j#BZv4(gTj$4dqhCg&+?;ixw^Y02R{T@aluc%3{nf8 znk3`8Tt#ypqk!5fl@v;YDY?&xpL8s!t!JHD%J78;4!YQ4`(s`;U0KmCzI5ID6Q^>e z>;lFv^48ummf;Wxak18Nyfrh$p=NW)sgG>6m)u7bk_Ww3sfd~!iV$-1{oSP;7u}rU zi|S_ZsR-=to2K;-H^K$)KS&igZXW~=jUhDuh)X`>w-5b028x8%or@|cc8q66B($~t zO#~6?&{ATa?x3T@&hR)eZl|0O1BLqfk98OpOHOYTp*}hIcy3HD5;(}>3m>z>Lqa7B zfT)greC9%Nss2aFe92Q^Wm?GmxB$Pf}#`^z4BwtH16zf`;X|QQvG7ld#NFt$!6g}k7 zC%87?XLB%%!vU<=gCzncJ86}4XcFkDH1lo!Fzv&={4&7Zo!*#pd$XKfPJucKiv4?U zQgZ*mztb%c!`>~6lGpcgIgcy>0is33$~{VLPPP8Q#Ayd?;&E$Q3W#J#Mal}&(;Z1K z;(J#605-2Bgg;1+M!BgcNveAbOzI{{*_D3=xw)Rf|3dNZP7jGZH?;IqDsvBLdi=U3 z8o9Rbxf{>|mI{7U&BzyI^_ps(vMXJs)LN*V7U&;AobfXB717EeIz~S=&`(qBeMQIV zTx>JMzu;PC{)&!8*Yl@zo`CTr@`N#PB0v^@4=fOYhJ?x`V1d|cRdm$EBFuEd~K-}e`5Q$@K z+Nea29#`_NgPSdta)DX8a$rx#|IQtvgceH zom0M0UVhkR^B{EDhvp*~$9Vt*ZC6#tSKntJZ||-zr14+7EB@IgI}~t+Hz{eva2Z*| z`aLsMNzAX8R)4uGl_d)ma?^JM3_vb9r^cBSq7L>Av{e%p4IZUm9N@5UTZhq*umQsI z5&t1z|0~#JPXdAP$%A55m6uwSA?Olvb}FPU&0+jUhfhNQJ{u%ynMa-3rYImKbAlDF z<$a5~aElp$S2bzN@;ZI-P&Lfy)D~qMXF4jyg}}-WC5gm#BpRlg&H;J(4n^8kr%`Fc z4Msgfw-(3Z!9c`d;qk`8slGn76rvaqK_1F6M%ufxpxV@5?i>((W)a^uEmg+Jb1noM zd@0=dlmq79l%GhC52Rp)8AXWTV1gNNLeXqNsj|BEQgCy$Cu~H1qNa6!$4twItT}Yw zR~`~n<-PkVH;2%P3YglE)r8BN1qS`S#9gG56^f(7aE9uU)%rMVNELAq$d0A*yb^rLHBDM^$5-YYS32;C8b;^n@6jYp~I zoI0gC{#Z|b{Gx%7{6+U<3-yAv+;{!*{FVH?VDX;;V`#SH;`bG#t&l7S|M>Ma`I#l* zk*8z+2vs~X`!XZ}5mAT~V$4$amphf!Wq58@-jL%A|IUKkPT(Ur0&Z^mn@Y+|5g7+9 zi6GiNZXSY0lAv}W)-FtQbNi`PVZ7==-90CQ&4H_OQtnkKcOB&1ftI$tl$}WME+edO ziJvr5K5;(r??3b|uJ+<_ghc^&=m86onVbM5nq5B2@RS>>qE9WSTX3x|{OXcgCdH6c zN%eOCEdBgO(cXXIci(RD3hfreczZ{at5aLmqi!Lg`?u%kXD_cWZ!V%XXt)(e zbtgoGqIrfX@-Oz%0dsx&QXG{xac5_kVMTA$t`qQ9>yo35JdhM(9p~&bqStYBTjM^F zYM6>owZXE%#7_l5ONbZH7|iX(Hu>99j-P@i+i`L}6{VKHm4}yne6FT!(!L{Df_pDZ z!8295m%&4-KP)A?y0jRkXKM>;V*z+|7Jc@a$mBD(7Zn^8f+kIjU`0^&e`d1U11#r( zgyTxL?uU8+N~H>Y0whBw6I-n~Wn#d1&>*?V%?uhS^yunRd~PE9BE{1GHZk+v5X8 zcy3GwHr98a(QsCKb&pbW`nu}#Gi|36hb)eOBZG|#RF5)kJ@nDRMy@t@dAurTaXBW9-iO2ydb7V>10^LTpX326j)bX@urJ*cza8Ux^VmD zP|+Z*%+L_3TN0I#ny1B0GZAUP^oqZdtZjdb4edvBawQwuNT3}2KpE06!buY?Wx3N^ zj!cWjz%1C~vpxs#7c8W3+0-$Nal5M$W72tVW0*()v(TONfH!A2C&3=Ji$78V8%i6< z^DxXszn*6C;Ylm_)b(}>Jyq$#e|tSA)ui^8Y@&;xXi{-G`z}ZKCin^Ab(wzfM>@bZ z_6|rE#-kSWtVlwevt^$v>AH%Ydm$hC>|g|2QGN)rUINFxB|z7wylgB)o!Jg*Dd103 zz?6PJp+aE``Cxd-wU#eH@?^I!O<;Gw>3g!eF@B)!dHtlWP8a`A?B>RwV zyr91VzE#vNhk^SRO|RD`C4|mzt5qyq~_l z)uym#T@JGR{5cWu#AmM1chwkXmPXxM!g#8@HsbDb?)9#}XR}&3^keUF*Y*yzu({+3Tz8_nK=_gptcIWxNK@TZjXr5BXXOE5|8Ob>y%jTSLudAzp9C9 zA5qNCxBDi26guLfMW*^36^^C9S-g8xQcWGb#Yu#9^{(Cj7G~rw00G**#uZvAY`R+` zue#?VNCIX_X5yP9NP;&JD}EFUy8VcI717G3Hs1VusH(*&E?1XS9teDpOptQ#xnL|2 zn(7@e5x>B*#)RqVk2EC$_U~zXZNrRoN#O>6rmho@s9&$j4ih-~nV(%jB1cyMSU^o! z_x-3AUU>2uU%qfxiZeML^^;7w&nj!-I)%yD{zhKM=gIXz?c`Xd56oww+c-E}bYHnc z#jLpFp8y!h&VeoG>Ht(VTS-+I_*k4-q7$Y9+i-r8Gn6@0c9J!iUgv)wr9hAVFUWbT?%mNc*D5eRifNDCG;CCr_ zG)S zRZ%lB8{if#5Vo8aE^$M+l5B{#H7||l&_Raonl@^}7_&VyhzadLmz3h+j~!(c%1*@$ zyhnseNYi=BL|w{waT}M+xFA-GN2yX7>2?Itc2xck{~3m`c;V#20|1bX>6|D$mg-Nm zUmV%{qwt`65(vtRT)}1}TS^Ez^(s+`=^p-+SY`;b*hC@vRM>C@$%M#HsX`L~sCZjr zMZf8B*e>aD1FFczQ#f!`L(?^n1>6ISKsQBKp~GS75cH`^Yx*C6aL?!`#x@5cda3RM zr2@bN>!em9{39L24k5{sBEg!aZq<;Eu0zNv8RfhBuVst0-Y+9dv%ZTOSan2H0X{OHn1Kn;0@wJDW@@iz6s!Ck&PJ1VOp zicDw05d|2uNS8E@K{^h8Q(c3oFCvsxE*Vp&fD{TDXdM)6$Am!f-|f86rs<8Z<2#t4 z{9=~BI84}-OxwySoh6A)ixsUd?XIp)|Ic2_7NZ$}sX-)Wt6gA1m8~$tNl`^5N}`M> zM=g_x&lYl_Crxut!sLIfrM-2Y83O3lmA;sd$@+lJhv^7GsPP=0K{&q+v$b1Gt*R8CiK>6G-q@Q;Q^dk}xhsI&yUi7NN^3Bcma&OPM#Lmm&R(U=6hB@KaNa8EU7<9od^xsx#G&<5#Q|Z=}<>4rRC(0&ZFcO^G_bVP59+qk$TVjccGOE<|R|1?e zwS;1S(S+FD*2xZL%z?sg#L<(5?O5CKPVU?ArYjzou*Jw?yZZn!R2*v&he`A}^wdLJ zXcdC4&>wJGg4Z!htWi)SA@FCtAielVgOpZWyx;lit8@^Xd&WQiG*wS zzmy)0FHss3S-7YL4A}tg*8=GBjQh8+j0y*^K5KQ@M%MFbL9P}5Pcm4bK%y&;t)CF8 zy9hdboo42{^8v{sxA1Uqc#v21OHC3CH;WC18r=o?lo83*_s3;}K6pA~O}sn|5^~%7 z&}kAC?sD?tAIsErZ}=brnk4oJk;h@=tU?gU#y0yxGG<*+7f&Dh<*4$K%Z>M7q9#Nj zjH2>&=?=tns#Dxn9LsL6IuUOcH=FL<2=I4|T^X*=uGm#w+TeKjHz#`1VcX&u=+ zg>u&_#*3tB2LOCdHOn5a}DRSb{2+v%sU${HPCIp9@IX}dw%;g!vg^>tSkE5`w zY%y^_ADoV61nsGp)LQAZGYn$vG<{gG%vUvlx3o!DRbq53>L5nt`)-*iGQWVZ{QZP5qR z+eoon4LJ;lweGZ9te^lDO+&qGYRwHs2P`D2^=A?F)s0+DA6NLXeY98yY)KY71rOQ? z^`+dGV)@{uitM}4Ni8kNjQ}ryO-FpJXgttpIPtsFg=s_>BuE=-KtrCPoW>UzKw9aL zEN+ScgD+K%`4Ei>XJgRhlm;D*`~1-*4pGZKmY<1NpJ=y(=r{8VjjK*Aztr3-nw7!?6_GMhb{p3C1D{EnZ&$FI97bKiw!wwzU_It1Sx^yV?{k#3An!E4CN{ zj0(ij5l`@se41?dlpcAvg#Tt`un*_gpW^#~vk&j?5w^KeT;O_}Yf>@7CyM}x)QRQokUa#ML;fUIvA zcaxWKRZo)I$5^Yhy4v=rSCw+dwI(gWnM4BhFBeDWOPl>|zNKvs+_d z(r;-RfJUpESjb$?!uL9(&0 zoP@O(a>4Ot{o3);N(YKPq&5-bY17L%a7G+7pnF~&42*MdZddow#Dl4YP-tg=urs;2 zcrc~lZ@9*{$GO|4H~;9(PUSBz-sp8S@AW2ioXAtPviK){nr5dECdKUFTlBK8KTa2= za~ku*ax09E%c){I@S^o8R>4Dk5Yjxr2uRL5n&f8P_g8Yyh`i-@HPLr=p0mpk@MH#+ zgTQH&WHj!CuiA;|SR@tmk*UE$&7D<4*80_1|GdQ3zEmunSi@&2Zn5HHuLX#^#7}=# z?$+S`-R@cIUZZ=A19>)96E%oD?L;Drh1k4#jI5k1Sd{0X>_c4wVfN~&u-sf`qb)<& zl1@wq7lzr$fYKcoJJvAGHm^YPI^C$f?BltZFNokpw%5@yO+1K&0Zrr#O>)Xb{gUm2 z0Ilc$ZX0m0U(qBWz7IZF18fP;mv1RY8EcjQK9%s=cA|WcJUTvr?+(zuGRMSl3Wt1B zcLOEjgasfsYR%r{99jNwe<}>72&OD~|Ds0_6zQG_gTjL2#}_TI10a?Pdl8z!-?GCV z$@>LC(S()~b^5cwh-f>sHTX{i05KzqDlu!LtyA}=GM&K#WkoH1l963P3}24;<&D>J0bT;f{-rr|S9BSmqrEQ8^Cyf=$k*hw{tRX}1!ay0 zcHCrYv81oslCCe$Kfhc*|J!4s&2v;>cfq9fhoJI?Q>F~loR`NU&?uXCdr!=S$>jJc z3XfDsPYa51&gE=8!vYYM9THMIi&T6--&7BnQp%+WUDthPO&}JG_Jl`QXJlH{Arr@3 zwHfT{0_H=?n)q5q{{6VLoJGZBmJD`Tea`UQIX(On4o@1uA&tBsd8sV>-`#LL1izHP z=?pj5RB^OPknzq=D(%)weDmjH^RLh}RBB(aYp}0%J$F3YCPoKXMp|labt;E9Eu2iR zuOc$_&aOyWhUtqcf4=3EvG-NH2q@qzvPs(ovoh+EIUPW)OLoy!tfR%%wxS?UqU`B% zePoYg>OLe-u*`-`N>wecs)-2)rx`^G5(q3c>{z113X>j&fXaqhU{k*lqf9WqsvEnI=Y=sjVAoN89W!)t|pz$UjH0FT-%f4=G>6QP@h|*1TIHPVse%F z7|w z1dn?hb=o(`5f4p%psg3NV*ME*aZj0?%e~jnj9`61ICohbMiv67BGrPNP2r4)MGarM z;h8sAN~gF?+kQxeT9Z-%0RpavbcR7Hf*J!}jB7oA&raQMKi=T1{Y*bTkpq7J-H0+^ zqlg_$uq9-!y@e-ys}LmRV?>Z%Yl%ZDVwwHJz6StqU-rNwIJfMX1>T{X2L@lHWINla zw_5Nx7e|j;FXDLNE&xQUpbhCY6Qbkzr*}4e|swbyp?-v+u#(a+z{} z99?cOWL=Ux+*5MEK2D__^46M3-#7^gUh5?e6oS%LNUBdMA(fZwTN2^e3ldG`O!DA{ zhI~I_lnUpEEhsDyI40-zbD_q$7$BdrpwrKdi04QIj&`gFO3iFT6(EmC6J6>P4~J0^ zpc-#!%|LLsyq|Kk!GSg|_!Yy{XM=>xX(7H5Ot?4O*m}-idJraAuKRx{_JH5zi4RsR z(8}~%V;EZla_|gsy9zM_7IhknvHo&)gq%r`VCHbPblxjf=>7{Hn0^GT5t@S&vgR-o zRQ&O|w|v>{14jAbBbVZJc2)TFn1DjmJztipoIehvS@B$I8K|z0b^$F*qpJvHN(+Dt zrW0tm>#o|VD+~M)Hk$S^H08;Nj8cqelc`oJzs^9?&N1jw9)K3NHA?AqLitx%vg zH*C(bYRZnpG0I?wIU;mtz?$mW9-rfgh&jqbzEQ(lr0yoy`pd*z*vmKu+`LZQ6q+u z=j!tE@G;uR)33#K3h`n|nTBoF7aavuNDv|?t0U~Uf^kGufX&VAjlc#;L{fCnEvSzK zK13<}ZEFg$Ii?Ay`-J*}>(Ij&-NNN_e|`B&=VG}2mmjXKFXT&>On1D=&uNQ@cLrXf z2}+GpV?j2)9EjkDhqYHbJM$~H?3P;iqq(k%q#V_e0rAMo>DLj=?I9- zm66%IihvfkzG_fPezLz9mKFp+9C2U|a(sJzevXmj9^(ScAol=8oQc82A#esrwj?^V z_oa-nq0|s`b9`MXW$~)R?JYKMFE6h2w;HL~L#pehywG&2t#MKiyBkPGm<4H3p9Na; za#riJ*Ai;1xA*s5-+qjgZxj`LbymC2Sfyri7@MbH6=cHLol0KrT4Ri^rcqT@&ce&K z9SY6Uv_i#3x+LtuDrM4G_l(p`#Dh!Ko8U=A%bYH?#)xLtl{)RAnR1DBV+taUvG7EG zM3ZHh{+yajj0d}b>pN5m9>&yELfwK6&s(sA_`RJ|X8WS7Gps0>J8jt)8t4!0EziXR z&TL91%5F;}KUW#91x|}LG-PRBx+qG6WfJ}5bhiL~!*);C70hKgzjd1qzgR?RKpP2{ ztl@~So8dPUKyOrS^~oIu>I7;|C1(`pn~$hOEV+BE+%F5LXvG#oip<<^AuoDx3~dS! z-7gMMudA?WkpBBe#q)q!U-meMe+f79CtL|;ms4xe)k>gu^6`QknY^>3=1j0ga+M8d zbXK-gv*=^Dr~kWBCjEUXmxBL?%>g0VF%@Tnd^YN6 zV~Ai#R;}N#^hg<<9K$PBrEvT#k-qy3oUAA@A|dTg>qRIIGe4EYBqu09TLcFaS#-53 zDU<<8e8sT)`PIec?LxSY;0Dv;dibLg{xL|F^|m-e>?1n>Dg@9!$f0(IJ5B;kwO6Pzve>&SAC2}YW(cTy(o{s;m2zMt7}e59d%6>GcU^uVf*ueZ zNH}4o28zhd6gVpQSdl1YIV@7OR=;u%R=Cg8H66d(Rk~KvwF?Xi(gS6`3q^H<_bn;O z_360rH>APUTGB)z9Iih4z{cEMn^@!I1|)Du8`t;OA71HgDkhxu{tZaZ!B4&3+l`k_ zJ(sCA*tyFx%Nb1naR{uLWxzedrGu5Y7+iahM&Sa!o zuERM>dNl#|!MAlz9b+#7fHH%HGfm)?4pQAbv^8ZkSlBj`OS6xjC|B{#V2oCP1Op$1 zUi*aYbcqSdgLW|bvkvw{=VmNFouP3^pqL8#D4!H>i+7mqwqc|lEr` zPOQ2wl8I#;O)iii)^w@Ii&%vGujqXQq|KE{tIm6;nR$DnaD9FD@%HZeLXMvV(+SYt`D9CZVgC{=o;=H-IV1)aD{%wpeUmrb0KoL;pReZvJ#|=!#0Y zbL@1lnSK9p$VG)FcdRDFF7o?QKO*k&uLJwX%Nin??dA2QFN=6^z#)VswsT}ZQ_8*{ z2Ld5&_h>g!Bh9H}WT*O~2f8?K!EZoRq%5m*G^C53QK_-j{34b?#Zzy$RuIyd*CuU>={KU9@msR;V(h+fc_xAk$ z7t3c8ltTsS^@2ey&zvzE%>e3uH42yJ(B)TJ_89W@l|?<K2{$Q7=d-2`+xUy1mhF zZ1iTXOo&82C(IR-W^&j3iWaS~UJn?_&>hjCbkrEDuH>7FKU2|uk)Ppk^^D!G(|2;y zXkx%F%jL)=6ziGMe3rsJV^4J}_SN&En>w{5-54Xpt|`=;OLoy~xByLaxL`7&W%(BT zKjikJ;f+G+Ol4h>CL-OLs ziknoQmi1Hp!`|z7hUh+?%MVYV-M_!PJb!!k_Ui8LRyyB^z+Gkow_)OTDx9U@%5XaV zPR6-Ds*BJCP2u8v24Yr7EJd852wTG9{Rqmb!;Wc%;^V*n@aW(%-@$ZP3Cx%PB#Q9Z z^~ql(w?pcg=xbOCIT}xYBV+LRUYohhKQ_ltY`?f}zc4jA-1L^AapfN2a#}D8CZgWW zNn|Nq3XKOfb&t&um4OV3``X9sEd(vfFc;D+Kpt|H2Nd?*Qr=Wr-_pSsxxogFBq`Vo zHgJ^7^G~V!2rr?1@^VKYGh(82VOp2PIk_&#a5Ry-IzW^aNE7htp0T-33TUS&Nz;n+C3~1l%7?XMn+@}|pH@8>!m!Asrku;V5 z^rC{vsX*JoqigmoVA^d)FZ`*CglV*`C_QQ=+&P`e;t4j>&BbHRiCHVvxYSJ4XdV&q zE_yo-+vW$kx=14!<7~oc_@ePg|sL4 zMq;yrVxcSBsUTdFI?taaXWCla*8D$d)c(I6|33Rtv)Bn8ll?(CLm3xPjN8a^4dn4|oG2pr5v)R83nNG)qhsdv!d!T6B4FqkB40JLpH3 z51dfBou3^sq+x#YzQi_@_|0IHVz8Gyt#m{iE1WW=QoRdhDLPMCrfecOM}F~?V>|4p z8{S2N5ZA3p_Au`8SD^T3q&LQvI9gk;E0J$fVeQ=9@1MM>XchO8!kQpVmNe;fST0B1 z!0g3t#2#!V?RuKyrs&CyyVVEG=&>TG?S*q~1I=iW(VSwJs`}V`%ZWx%zJB5qjW*4A z+z5_crpx>4`ugSV`JJR#^23eqE=5`=S**vW)C8Z9^r?jqF$d0jBW!DsYRk=+T*kEB{^pw3ImAnI5mo% ztREw3qJSj2l}E&H#A3T&YS~o(Ie9?T8Gtgp5Q;yd-*$#O0-lKK;(6aw7l<+_mH%99 zsUr_5dtAKl7SA{8!ry^%#Hp&D`dldDfc2vk>k8WbnEfVz{e^eGw#7fVR@4x4S>VdAkdzotbkZzH!k0NPqYBKPs6 z1!rGmvBBa{EphXPah5R42be65(Y}+Mq|z{Z*;{zX7GoKoeNVO7zi`HdH)Qx~-R1^qY_!DTq!^@)YqggQHDXJH{9B`^VA&lhQ-YzSB*;G!Qdh3(2lg`wo5^Vj(ei1s+l3VpLC3? z#=faA`|RS*IO|$}L75ObCw1M3#XQx&Di3^Je{17by0h%ohVIDd!V@z9`Ed2R$23_Vx%cvaYugGAfG*xl(AJ=zNK? zW~4|85qDJaDT(VF_C$zx8HZT`Jg`fo1mu==;rE|iUP@Q6BMc9t;Y8?u@xwlYKK9~F zom*!suhkpuKA)Ac2%;2k{<>hd7++r~>Dh4bY@EEn?bo10PYE8DQ( zsec@wuJ1`P864J+$Fdwi0YoUoy%S_(X`fRb1%sQ2XyeO(jD3_AGgbj2m;}Hf5y#_r z2wY-&Ts(y4Qs#@ti6!}&iNgg%6i+$yY`){qv^-oP$QeS-UlplHpLc50J&*9AUEN^SPQ$gg z%PW6xmH}z~UFKt^$f@Pn-~@@-NmL|C4`cVlSN{6YmGK9JQRMQG|6%0g8ML-j#e^0ZiS#KUpLhg z7QF>rRjQM!=In`6W$;lW$UuQkYlrBtZZo%PXtwZC4}7y&O$`MD1!1%%6?a(OAJzSY z;OZI2>b|guq>GA_OfExRwo-CGzM-j_tZ=p+ZUrGA4yWaiC(kwK_r*HYCwe0?!v=P@ z!Z_;Zx2MawyDAXLNGQvSi#w9D{y4~jP8rC4|N{X@r zgOgj4NfdRlm9N-VOz--O9Q576`P-LQ_L-`FbFfBjUg?Sm4Ct{{k_>EFb;_{hdKMbU zk1?cNL&JC{bN3jw)jPJImx{K92mp9;$p%5#hswb`EGcl~l7ldOOjsqiyoB%cRd#$j z`zTax?ocZj*CG-7hAPt&-H_PQsckhdQ2NF-vkATa1M^W~In@_y z22Taw9?md?*nRlU%zZScR*GF?%@|Sk7mLTJz#b`LxM~?-=|>DXJFQh5TPhL>gRUFl zx(vAZSB4t?gOuM3IcjEZ;Q3j;ZUvRjg9O*Zf~GIBdwzdFe%a{i#E zBopb!s|jHWoeYTLo+wN91Q2R!qD&c%v~;FO;7%u*gA`;0Oyx3sz1(9Zn{#LS11DFs z@mjAg2$D{#La<|=pf=1B&I7t*C~j_bEp%+WgX}>E3@=jdeB*-S_@fz>-qf))OwlIV zvbz!GRP$!t0Db%x*lc!#&?vBp`J4qHAT`5sdbV>nL%)?>5wk zI6hI4*|A)eAF`SOXq7-y7>-D+x$!BX&;^3WLTtJ@Dog9Go~ZR{Y)S3s^Q{nP84E|X z^E;_{&K*6;Gf+cJ3Pt`?g@u~o`Lq6?v|<0>0IxZ4uU_PBlb#9mc4OLNY^rQw9>q`u zG9_YS4}#KV*)?Jj(UGcRrnI%-)bdU0!vq@XwW^@fw{D(xsmvPYoQx7R61#AKp9ctb zYL}fiagkRFcM}o|+GB5({~6$$)7~EtT3r&anrSH>@|q zESYApVVYW2I?sJGA8rKxpNFj#@erT30K(N7GRw=vs8NVY5i1D37DwW5?liw7Zit=k zWMdcwNmc)SvKNe%G`Jtm_t20@d@vQC&mmo_B4F`r%#%4C*ysrWV;zSwC>j`2jF^|$ zz+AIVXy26ULIftrug!IO-=$T@#3PH`GVCk^YNl<-{^`zQvju0KwQ~dD#*vgHJt1>C zCLUb56fefkXN6X`nk+>lgEE&9ve3z)0IIp6N?aO33Wosm|SG!goXKgr?*{EQ$0)-aj&_1URES;MsQR~>8G4wvr zhJ<{5sb;CRDuKy`=L;J`d#@8wA#-kXj#hgVwGvy+CyhTA+X|sggV1M#84o^#9`3498mwsT zp$$Jh(vp9ZYpIJey0EaXEGLjfY$Y;8F}}mj03^e+{B%Hfs&rTyKeP@JRlieBnMMY- zf35zkOz^Q9;Gwpde{Nv#O6oj7*LCU3?u7Z2wvLR%FeF|2Nk%Is#3`T9@nTh0x;WJw zWl48;!G7FDCJ_@%O!yW7h&+!sCz-$9g8mo%T~>FTlvKO1jU|}G=kGa&7DXSnbt|V) zr}-(FNZxCsR@KVH*mLewn8bBt5~oHMcjZM33JE5!h6H^Uv1K~{CEWq8h(CyJ_N&38 z1w`B5ERtdhg+HAc+7*Ckc;Ab?j7OqATKHT}n6*i}BGbi1xIVVk4UKX-i`W%L&$}~l z>0M4(DuM((j>6Hxoe{uEK_@;eNwib1wf{h+zR>iPkjYY5aZy4LXg238wUq$T zV07BYd`$pr&jBzL4D0~o@oC%y(rdvOgG^fg(LDXQs7U0&z7{N)Idz@;Ow5%zeFY2@9U!fmr)Tgyq zlqT zapbrs%sQ=k`5>6`x|EDh5}VpIAEyEm&x&zidUs!=Hc*aB3d+$dJ#>c69p`dvvV+*a zXFQ)s*@OTQl7|9M%@ZZ7Uh@_PHYid(tIb(3viDjm21a(l5r=R21?%#BjU z&5Y`!l)L`tmw^!E+B^T8pO*U|X>m|`v92FSt6#5(cE~JSe(-l4&tzF7Z1Vl}8@cdU zx_Wxh5Br00VoKX73WG(JmU~dE<=~}-G=~_Q1xHh0;;)rCM9z)F$xrVcAvC!hxEv8% z5Hbh|FWV11oR8mjpb}^(sKo~H@rNVHZ3hiW_QT!huYPeR^1HgZJQsnl7mG+jouz~q zaci*^6G+Ul6}Kulhv)E+w%kz2MKW|-Ap?jBJpctNU{#eY4&Dw5SwJu#5?)1&)~lQf z>FQF;0Bw+6grXD%gi2-r)3u>7%qX8DB<&F83rSD#JkGuHyed8gwRqT1tp4J#Tm#-72XUnB35l`YXs;7Bz5j4`dEp1+ z(QBR-9I{f|6`@njWV^w@g4@k{QTTf4gnlm*)HCNlcMzhz4IMnG*z-xZ|)rz>^I)`&dTN9vh%mM7u9v( zRWOk!1-`oTdJNDEnJv+@<75Q%cAo_yeHxLI-QwBP4uA#wo#%s#7~Wo9Tzz=E#6CWP zvm+s5^)w-bQPTUXU3fFQjiS?7kM!r~{n<%7Vn32bK+{)?NUnF07}z>uY2O{yoLL`LE z5L}T*XBuKgBL6_LhPt+S^0NaSVd7CNp>>RBE$8lOOCH-){(L$}7!f@dM}5fCC_@af zrK)m}22&f-orkbn{9h z#}@2oNA!f~_WttB`f9Ik-kiO^ziEcWn zz*NmA>fyHOdme=gHrwyJoI7dLQ(F~E~>77sg+VU%XWkojJv z?mRs4L{QfJS;n7QSy<{2F0tL|#$hnNEKjZx+L67bu~{WqetVH>hR2%+4&?9AMbmc2 zs^8t7^!&Qe6yynGjiuh2dAm!M8lVtT}+aut${21QH-Oa6XZvQOyQB2k-9=l|iX%(HbW zY=*dUcDARZ>N#FO7iO4%wv5r$03w>+#uYXfc|1vL=2r%Ot}XK| zk$dWSA_;K`k$L@O-t|2i(gDZvHzRZ+4NSvvL5rgHQS43;DhzpnsEl@Cf~K(Q5GVGq zb;`7Y6v{pAW6HUzGC-XRh7ELyA^$Q&zEITGqJq-kK$ay*tR z)r~Cmk$5Vh7hxsiAS>n9_b=u8%2$`b0i_qd5zN`zvzN5LZ#7~?W~*3R>_9?!+rm$i z3e6HuDQH@j0Es>%6yDDcy{~FuDffroUVko)XjKqZ^PiSN2#Ixu+3$}2%&V(aE6d3| z?KaSh+w6WMp+_nJZ$)LfBT>6wINwq{*S+ciMT;4kQo7&l@VTaFZHj|GVvsGW(}vkdqK{-S_5Bfz7BEv5y)PJJf^8 zMjVelfI_p<`vegO79o@8T$yjD#s`2%7+_dkWjG(&m7^liju#y}^yrjl3cyljyIuO` z_C{>1PK)UjCm>lqc;{aMTeE17@9SG$S>+PO&2nmP+?N+c2jPgxFYyN*F%1AJvnuu} z3ec3vq?uQsDeRsOi?^=?dsQe<8O74#W(QEGomk*w1{@U!EP&G2>PX%HDxK|}}cvzQQ%W$>wrk=)vJOMyzP&^6+U z5LQnTr7g`b5`eTtJR?rUm_|}KUQe%xh(^<$`ejuSM`Vfl+%&#~xvV{Wdp_ENBtfbh zf{XA_Jpo5VW|X}f%O&C|IXPHa@`HtXOya%zHlL&*ij))lWZLOmV>lr)TVvAQTEmbK z`s|JnV-0{Pfc0lj7p!&!?JL}N5}S&iC}yPMhgz+eL8#6rUGv>eRSa=;H~Z)e38f5d zzjoo4>Dwk2^{lLT2X(UTRyC&xBbG?mH5r^bCMt&vWK&Qb z!v@DiDDPN>=8`^rk0C|E*g%O3fG8_hzr_i;wn^5uQxZz3z>(G*W!&4UxA5tYY6I4! z_DOjm3g4tE-N>UjeUDFlkW0KM^wssHzFnVUi+LV`lsbH7iqY#^2SHK6ITZ&G98~QG z2*L<;dl3K~yvOsDYt@&YPFPJV7ZE{g>h-B9*q5VcPl$k7i*GQTyUwA7`? z3j|{W>CyrbVonHGp)k%i1e=QqY|Pm?ihw;<)Cx>lsDSzbiy|CI_3?o!H&cBq`ckL! z3Y$w0O_zt)aKZzUC1;stXSRl!jsvb1ZY?oK*uJYm$!e*%=2iy)delm(_jEq?R#Y5$ zmFE|i@vBm%YE;tbDVu6p)>GPzQ`z*uvDZP>0#x6Yd)-D7H&<`16lW{TCL6UFY~Hdy zimw1p1pVZ3CxC*2x_3P|5!>If6Wv<-*s%Yy51p_~2!&J}0SV3J9B01fkE<{o@Dza4 zZYT2#IaX+3LAJ@wRT@i5;9;^QBb`&=X;YY)MJ2wbr((biMFyT_NyLCYAPc4yX%Z8_ zLl)&Irl4){fc~wlQJyH%(1y~$j_UJ-__-dlx!Fk5+qgMg+g5fssdkl!a$|KBU?~j% zhS|Vybtmz`0is|sXvcoI5c+F1+K)_8wY@-qMC&SxAo~esyuQEw@XCHcK)uXi+jF3g z-1*?2kRF85LjTbqjfie-=u;VYKE+ZN5#|kk47fSHG&VAxVs^RgURI^q4Xz|8CG|yZn+jy zIM8u?Kl=Ydrr0%twOv`%H)CZ{qQlcSJw0o z9%JU0>}s^$Ru!%|!RN>cq53LCMcmm4zS#3r#YQ87qYctjrN2Op`4cNx+jjy$vi&1L zU>QY@VtCL`ZT5Fm0VX>44-?HC_)(l9KbiNnS3 zt36jsf5@j}c)1jXnYv@;-+%bMuhQhrGZ zlV!&4hAZ&Mo!}ggrZim>TH^-=k{6v~4u_;JfnwDdc`QQq>}OFB7r~aybS`S4?t`bL zxm}l2?FJTw#d7R#R0s4u_05o%7*H5VvQG8ay9$fV>OZhfKcHMuwmlU4=t09#ts|BS z-5iKK<;M8gFhxv0QxBh}`z~85{=M00+>Ij<^4UwRg)W8{f@d7~P?k#*evKh=6R~B? zN?Eio-=Fac$~Vn2L6LZixMRENESoLAz`bLvMYNVqk+HOmWCD!^kr`sM@r;*xMm%-e zQi|s>eDwh&mrN1S5$-%y_0@=5p&Ie-yx%jY$A_QotY98R9QTt`T{A#KAQ;?XmyYFE zeZB9)QrS}$EHo5RD1OWI=gH;WA{{2l=KjC2C}RPjAhLp%F@^{ai=+E0T!t&%)9Ccp3+HmGVmpI&bkSX$F7Rtx!%77Pfyq#RVQBaPGaH18_NA z-&t4Wrj$)?9+mA{%7Q1GWU|a_o%Nw_k;(oxJ6*H)Uwv-@vV0mZ%}TlHMuj%_^AF*c z`zYzidqtQ@tz8YJK(M${JIje~rrZO9>Y>Qo=YtM05lvp+Fd|nIO&lpvz8x`|c zA3DLgT+~Cv>}&2^Og=HDz}mTOSP-9rCH(FtM2r57Mc(QRCe zDIgy(GvbfT!i@aGdF1AP(Hs>7CrE=Cbq-Ioh#clZQv@3GWJd(PMwEn9hqE>Y>b7yQ zy48`)V+|veai{IBNgmcUO3P)Z&t;v0M+R5(VAs#0ZpIM&LP z*$oEa=xPKF`xFO2)G}W#z40r#a(b3ZS+IrgCA9{hm>je9BGqOptB*{A3;kAZyR5jG zR5dSVru3DX$s6J9d9-$rtVe~{J68ajI?mAWstI8^I${OczRJQfI&;OTMzT0Gy8-o2 zpk?mhYpEc&=J5DA_H&Ru^ov6;>UuE_Y^8yM2~XT)WCXEB0pp}REDi9eVa;sfj};_g zbdxRJo3W)yjr515h-{aGuqXN50)9$I>?J26RhT^kq4Cja#?70gF zd%7!*b*fw-i&43m0XokTT0#w zwkAXwOh}wkroDRr5-{YmUP!}}t4YWKZ4i*QZjINJV(eFh3D$$cQBz;hNP*#QErwbO z0SDi>9&udojg-9FrpS|cdbKKM?&U|e#$+uY(ulk{1h8fQI&)%Q8Mv+qNT0XhqWy9p zmZnq2B|e#??IgwGjR@lJQYJm^%Y84eg-7MiX0s|PVyyD?;Y9S?U(3i;d35QO^R^}C z+F~iBfBfBHI-DsLorajqYL;~3Xa`Fne;vz@0Z1hFoUhP@ktBJX*(SMVJfa`Ne>Dou zMl3oxKV(f zoS4b&z>^I}ww&(`3l)E{zg~j&0ayR!*i-x`6#=#-r80rPpQcAkm>_}hj8r^C&Mc{y zeavI_G|C;82Y>K=2P(uA9>hZxP!=54_2v2Jm-l`K3fkGlfEHdoK7eZRdv!u?)hde8 zK6G53uyc=)Lq&%p+Of5@SPQ)bl2_hcimSbmLjygfkBTkWcGT-)8PEZ);PhNY8Q6s- z=EOndScaR0=)#@N3JhcTT$4WwgrJN;bv5;M==p1^ZZUF;g}O|8 zBavcdZiBA=P#&u09s`)yZ@8Jvfe``yAfx34~2%RNR*+cGPm^>*PhkwkV@9ZbT~4U{g+q87c# zG4m@t>$^XUMqrazXV7t6FaobIuj0#O&{vSzluwiekCiYEN=~qmPLHh(OFZ0qPYXnJ;nG&+SAUCnBf&+cY!-`CMK$yUU6Sf!okhI4Q~}(= zBO{(k|C|-)@)Zs##f)Q)5aJ+8B7(NC#y@2`va6#sJp&_2`TXi#9FQd}eJGf5W$)&- zxT29Bj2y+*60)Ch@~u|=yePSJMx+p3_%0zoNthfHhAm^6`s$dgE!~t+Owq*aGaS_h zRs~Ny5VxxWDq1kJqE4;}0av5LI<(pE4YlwlRuRlgkoWCiuwKW&fN|9Z-Zua8@%HZe zLavT`CEwhaSZuD8J&^E+3Z`~9#m(*A+w<#B@85m)_Wa!!^5g9UfhmC;%;^~Cx%%^o zqOMGEGnQ@EVEkP>qIOnB;MZAh1Ct?rA|`^%SkSb_fY3XP2+=?4B3B8cx}EN{G}G zWwqWdW~j5d@~sH0iiYf=1A19%lcA^KJ*=pQh_&|Fh`>Y5#h=h%kZ1Rs@Qj|Yx;W@) z?_lBTjVK3*+1R0U2S$pBSAZ$1OPm65jENBBOLYFV0KVqD#HhKV#rT3G_upJdItox!<-U=#0HGfXa#J`E@U1y#E{(?pxyXX62)>2bNIU_ zD0NZ9?wHekAF4Jz%XjsA^FtPv>iAtAIPz}TmU)cuJrQbJb%J0#h!^ihMJ`kSU_iKI z1cXnX;V(a*@Y4|CLa1*=X=HRLVAJosHvyp#8w3+f3@AyC&XL_(RPVVkHbX7vTC zRKPuiaTDpD9t<2yQQMzwd-we9%Pae0$$mIkV|A*+IS5E~3Nl%19e74r6gbZ&l4hdDnSu36 z1w4~=X*8?>!v`s)GeQH%UoKkKA89dPZa0C=gyf&5AwI#{IZYgfkxIvEoZU+7OWaB} zSZrZhMzNY8CbBLIS(r!&SaVH@p1W$b((O&vdhxul?fQ3<4<1_j?Z|k8W_+_fETPKs zB3vHnNE_Ha~xGi~90mn6Gz5O#fY@I9+LaND^o{ZE?$j*-o&W?MUxVdJ5CYUJMd}0#6_?Qtu890s`t!+%wG3 z!@^%~0ghB(gDU;V=imj4!c~P4&&|H7P;QT_QJijutdTwy3Paru#`k)~pGFFlvc#it zbVUXZi4B=9)pe`D?Lu4^$__%oX&HWOQ76kKY8rAb!Rf{*6h4?dV(UhxzP|KrakC1Q zg$66K6LE5qWu$nN{(gP=OC;Jemh2GSFB?GW&p|PpBXU3BZ>O860?R@ANXr2}FUgirV=K55r`)XM{I&>UlUR6H7 z)iQE@ul5mACo?Ztl{I|B#FrKq$8!nYSj}(;A`x>1-~gKQB$SNZkn(Tj?5wITyw1-` z)dzX-vaN7aw6I}bfoZq??Fal*dLfAd1yECAsqb86H=R($RJt`GJRMo`q~fFY>VU{U zntB#dh1L_7EmUgdr|ND;0Va>IgAn&Dz&oA2xsf9*9XP%y%`LUUK5uWhd-GDzMvj#P z40)DNEDAI3>?%I88|cNH4yXm_{OX4*so8vL1jr}k8cL?mrnuTQvb~!!jGXw}UkUN~ zq7CvpU)?vQDW7&yaq<+QvR^ZOU;%ENg`BVeihgK;gJGg7xj3&7gcT!I9QvDgve-N# z@I1kUzIZBsyOtKgKt;KLK}h zdG+Qqxhwwe?B(_4&BfXE?Z^0juNV{*vqWM66J}NyfvPa*8UG|P&SF7Bym80?PQSl; zbK?cU?>AQ{mjqm^rM}M2G+n8c!>rrD>;2R{E>WDQx&L6fGMUCG=7h_xx0PPmY9~8W zcx)fYuqVE|k^FM5;yZS|1(DIc(^#%^yM=HOVB2Yb126mxV$cP2u|`*sSyktjQ{Boz z(Tsd`{PP&C+fNTf5<#c24{NlaCG2$>bnKX@qiu&$f$gx4KGm#Q`9Zf%>@FQp_|qjz zb$x{>p||?pLvE2W7l(!vP&Sns;#D>jNETEFxm7t85OXZUi5`eEMt4B@{!?oaQs>(( zmJcC#)@T)qJg&%2Av2Jor?fOoku_i9yEaXQ2Qu806~5iCivj`Fdz9tG9f>PGlbl zv?~L0fH?D5D@2}*IHwx*pDq`57Fpcul&p)2(h9Rwdaz?RexsoU@v-vd=vBm1L`vQz zrZ_=^&;gQeGLe4H=X$NH2%@mGIKUHo6C6ZortOUC3~e{alrzz+aFgJLlPEXV*%;+Y zRY4f3e&<)>MbEvVFiNx`83H><`txm_thE%n%m#7Iymqk#yIqk zzCgA9XI!suHw6=&o<@u*aA{{qhn^W_(;;#D6jM^XyTi(hJMF}pY<47yb+MF%toB<$ zB&2-HDt$>J7Jxdf{>T6K7r$ZuxB0d_b3FR;Aciee=#~~&mpcp0xB*pTo-0x5Q^s7J z%;2`H4cL?Wz}4UnkblLX`SJX{vNX=Dma9{(`I~BOV@~CjNJGdS3=_|XNYksJ+`-u% zsETm-aEK2yMfjj~*ub!zvRPGNA5jawS8FX*MGB?Rlf=u96r$p8+`&P z0gDabm6`*Cpkw&lSnUO2Ss$LbiVd%;?f`;eOhW_|W=vp&uKi78Q?l9l!dQhWgpO08 z&b%yBU9Aov0kb`6Azt6~{DJwA!Y132`No=!urU^9ea6onFYPwDQiS-1b)tcoj{_$6 z*$0ULND5BH`-@Od$-uKW7*jT9%K%9ybj&6KR(q_(O3M#5=TEyD@Ul2LXw@d)Pr&dU z6oKZ^Brg2{UOAz>u!>eS$<}8Q>X$qeK3P$ST{%WW1i0I`oW6YWE5%~*y2Q&Sw_Laf z6-edx7mkaQ+x`fz{(!n*SwR17&FG|IMV9jVy(e{5BR6u3MZ3MM?joGa$YV?8Txeg; z*QZ&=bbX)UN(w>4?=@)Dw2H$}s?HDu)|hP<+2Q*tVw%01b-S=+G0CIl*UQ(K`(a$J zzKQ4qYs*YY_>&ojE#jpa%3W?oWC8CP`ivcPyHPZWb+&S^(9^EP;&GGV>(e9-X(Bt z2IRF=1akHzz4l!ADCvzg5HLLi0p+G*`8i95=RDhI`9w!7@jBp7n&R;3k{laYn5 znBSoWyD|R2P+cnyL$zuW#e+Uayw`y-t|3#z>n3arD!@#Yr>aYOOQ~TxO@C0tLC&kx z;f;|=v|{vaPnC9I{racHQO{M9LR7`#FQT<7gW$9JRaN6ilHQPg7iDZ^Gf-5j4|xD= zb|mAgJdubkVBQ7qbH9$^dEV(tT~0HmdSafS9mG*BmI?huotL|Z%6V`e;TDcVau!IW z!OPC(@|;mfUK`B_e*xwm82tI-ZW@h@lW@xLZ0xRWg^pTtCHjT^GwUbc79es#&eipu zttXncQL{M8j-~`cK?&kYkxAhCgdfHExyz#a7!0CnNv!-K4kw!KT?|m2n>AGK%6JH(e>vr| zOZmvE*0dPHo}~zkf>=t6j9xo|f?WR-?G4S?C}@gJtYyv=tG?VfXQw#5^~AsS3fhmn zy1(-rDv2Ma5Iyaq@2S9a8Y+t40BewK4PXfGKaMKUCgrL*^iwR-V11FIgqFYPJyzA0JUc?=~Y9r zvdd_EUfC!;_;OYH$pmi^qlZG2aOb8#Lj2b91L*O5?SW((Tu$>iwGxjt09P#P(o$*S zaUdFXrd2+^>Y-(d<*;qPK@hjKvC~g2oQ`{ilwF$1{yP^#w*hD-k1+p*B4heNJz~Go&U4(1jokuAIn&(waq8f zln+i*gO;L(9&~S2&gu&QKfr?^t=iNY@}Pfv@vkH4_QQpDNLqJIUT`b)wZsWPMCy{f zVQ7IryBI+Z1k z$SpQn?n#)Hss!rzHT$e&qO9Z&**?11 zcJqta?qvCv#MlFu7jG`bD&EU)?47;6ytvXNhNK#sb3&1M0Tj zkDDTDw0wJ#weAq=-+jUu%Va?6TfiYDOA{^ex{DA-o3M$$qV)y(_0v0bBZ^r!6gUz} z21xG(rS$v+D1TuXlL;Qr?abew_2Y|^j=B%KVA-b*YXpve-rTd4cm@)I~H&ciEP z@BENbbs-MO)HkzUA9Dzi_AvG3XmpdIxOMCSwT7rLsg1b3B z^wh@jCs*a_kG0vVwxvq-QFiLlFQ%{`*;4rFikGtp!kka#&mzxnW|`_v`}F$A?;g9k zIQ%qv!}WtID}f4aJgw*U_1)$D&E>U!1DiEk&&cHGJx4U>Qdc5}S$T4+zo!;9m@dph zA*x^W@&W{)3ltNtCXf-A#kV&VKbqex;mA!j=`?b{VAEQOZt<1-!>h)$3 zq;;0W?;q-gp6a~xJEz%V+~FBb{du5ihZ%JkpwEf{LZC&keFz5$dI*vOZ5*ntY;Dry QOVxsa!0?cd|9 literal 142938 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 z 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 diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index 0c56450..c590d7a 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -593,6 +593,8 @@ def draw_texture_item(layout, mat, split_perc, texture, read_only): tag_id_string = tag_id[1] texture_type = tag_id_string[8:] shader_texture_id = str('shader_' + tag_id_string) + frendly_tag = texture.get('FriendlyTag', None) + texture_label = frendly_tag if frendly_tag else str(texture_type.title()) if hide_state == 'True': return @@ -600,7 +602,7 @@ def draw_texture_item(layout, mat, split_perc, texture, read_only): texture_box = layout.box().column() # create column for compact display with alignment header_split = texture_box.row(align=True) - header_split.label(text=texture_type.title(), icon='TEXTURE') + header_split.label(text=texture_label, icon='TEXTURE') if hasattr(mat.scs_props, shader_texture_id): From 64151e8aa9c958661ca1a4ac0d6450ef1b107695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 5 Jun 2025 16:56:09 +0200 Subject: [PATCH 34/56] "sky" shader update Updated sky shader to works with new attributes. (Not ideal but still better than nothing) --- .../internals/shaders/eut2/sky/__init__.py | 218 ++++++++++++++++-- .../flavors/{sky_back.py => sky_bottom.py} | 6 +- addon/io_scs_tools/properties/material.py | 2 +- addon/io_scs_tools/shader_presets.txt | 138 +++++------ 4 files changed, 272 insertions(+), 92 deletions(-) rename addon/io_scs_tools/internals/shaders/flavors/{sky_back.py => sky_bottom.py} (94%) diff --git a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py index a8365b6..a8ac4b9 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/sky/__init__.py @@ -18,10 +18,9 @@ # Copyright (C) 2015-2022: SCS Software -from mathutils import Color from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader -from io_scs_tools.internals.shaders.flavors import sky_back, sky_stars +from io_scs_tools.internals.shaders.flavors import sky_bottom, sky_stars from io_scs_tools.internals.shaders.eut2.std_node_groups import vcolor_input_ng from io_scs_tools.internals.shaders.eut2.sky import texture_types from io_scs_tools.internals.shaders.eut2.sky import uv_rescale_ng @@ -52,6 +51,7 @@ class Sky(BaseShader): SEPARATE_UV_NODE_PREFIX = "Separate UV " TEX_NODE_PREFIX = "Tex " + MASK_NODE_PREFIX = "Mask " TEX_OOB_BOOL_NODE_PREFIX = "OOBBool " TEX_FINAL_MIX_NODE_PREFIX = "FinalTexel " TEX_FINAL_A_MIX_NODE_PREFIX = "FinalTexelA " @@ -96,7 +96,7 @@ def init(node_tree): diff_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2000) for tex_type_i, tex_type in enumerate(texture_types.get()): - Sky.__create_texel_component_nodes__(node_tree, tex_type, (start_pos_x + pos_x_shift, start_pos_y + 1500 - tex_type_i * 550)) + Sky.__create_texel_component_nodes__(node_tree, tex_type, (start_pos_x + pos_x_shift, start_pos_y + 1500 - tex_type_i * 750)) base_a_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[0]] base_b_tex_final_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + texture_types.get()[1]] @@ -121,37 +121,37 @@ def init(node_tree): weather_base_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_base_mix_n.name = weather_base_mix_n.label = Sky.WEATHER_BASE_MIX_NODE - weather_base_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1400) + weather_base_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1250) # +1400 weather_base_mix_n.data_type = "RGBA" weather_base_mix_n.blend_type = "MIX" weather_base_alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_base_alpha_mix_n.name = weather_base_alpha_mix_n.label = Sky.WEATHER_BASE_A_MIX_NODE - weather_base_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1150) + weather_base_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1000) # +1150 weather_base_alpha_mix_n.data_type = "RGBA" weather_base_alpha_mix_n.blend_type = "MIX" weather_over_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_over_mix_n.name = weather_over_mix_n.label = Sky.WEATHER_OVER_MIX_NODE - weather_over_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 300) + weather_over_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y - 250) # +300 weather_over_mix_n.data_type = "RGBA" weather_over_mix_n.blend_type = "MIX" weather_over_alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_over_alpha_mix_n.name = weather_over_alpha_mix_n.label = Sky.WEATHER_OVER_A_MIX_NODE - weather_over_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 50) + weather_over_alpha_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y - 500) # +50 weather_over_alpha_mix_n.data_type = "RGBA" weather_over_alpha_mix_n.blend_type = "MIX" weather_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_mix_n.name = weather_mix_n.label = Sky.WEATHER_MIX_NODE - weather_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 850) + weather_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 500) # +850 weather_mix_n.data_type = "RGBA" weather_mix_n.blend_type = "MIX" weather_alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") weather_alpha_mix_n.name = weather_alpha_mix_n.label = Sky.WEATHER_A_MIX_NODE - weather_alpha_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 600) + weather_alpha_mix_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 250) # +600 weather_alpha_mix_n.data_type = "RGBA" weather_alpha_mix_n.blend_type = "MIX" @@ -185,6 +185,7 @@ def init(node_tree): # pass 3 node_tree.links.new(vcol_mult_n.inputs[0], vcol_group_n.outputs['Vertex Color']) + node_tree.links.new(opacity_stars_mix_n.inputs[1], vcol_group_n.outputs['Vertex Color Alpha']) # pass 4 node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs[0]) @@ -250,6 +251,11 @@ def __create_texel_component_nodes__(node_tree, tex_type, location): tex_n.location = (location[0], location[1]) tex_n.width = 140 + mask_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_n.name = mask_n.label = Sky.MASK_NODE_PREFIX + tex_type + mask_n.location = (location[0], location[1] - 280) + mask_n.width = 140 + mix_col_n = node_tree.nodes.new("ShaderNodeMix") mix_col_n.name = mix_col_n.label = Sky.TEX_FINAL_MIX_NODE_PREFIX + tex_type mix_col_n.location = (location[0] + pos_x_shift * 2, location[1] + 150) @@ -279,6 +285,7 @@ def __create_texel_component_links__(node_tree, tex_type, uv_socket): separate_uv_n = node_tree.nodes[Sky.SEPARATE_UV_NODE_PREFIX + tex_type] texel_obb_n = node_tree.nodes[Sky.TEX_OOB_BOOL_NODE_PREFIX + tex_type] tex_n = node_tree.nodes[Sky.TEX_NODE_PREFIX + tex_type] + mask_n = node_tree.nodes[Sky.MASK_NODE_PREFIX + tex_type] mix_col_n = node_tree.nodes[Sky.TEX_FINAL_MIX_NODE_PREFIX + tex_type] mix_a_n = node_tree.nodes[Sky.TEX_FINAL_A_MIX_NODE_PREFIX + tex_type] @@ -286,11 +293,12 @@ def __create_texel_component_links__(node_tree, tex_type, uv_socket): node_tree.links.new(texel_obb_n.inputs[0], separate_uv_n.outputs['Y']) node_tree.links.new(tex_n.inputs[0], uv_socket) + node_tree.links.new(mask_n.inputs[0], uv_socket) node_tree.links.new(mix_col_n.inputs['Factor'], texel_obb_n.outputs[0]) node_tree.links.new(mix_col_n.inputs['A'], tex_n.outputs['Color']) node_tree.links.new(mix_a_n.inputs['Factor'], texel_obb_n.outputs[0]) - node_tree.links.new(mix_a_n.inputs['A'], tex_n.outputs['Alpha']) + node_tree.links.new(mix_a_n.inputs['A'], mask_n.outputs['Color']) @staticmethod def finalize(node_tree, material): @@ -302,15 +310,18 @@ def finalize(node_tree, material): :type material: bpy.types.Material """ + out_shader_node = node_tree.nodes[Sky.OUT_SHADER_NODE] + material.use_backface_culling = True + out_shader_node.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" - out_shader_node = node_tree.nodes[Sky.OUT_SHADER_NODE] - if sky_stars.is_set(node_tree): + if sky_bottom.is_set(node_tree): out_shader_node.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" - if sky_back.is_set(node_tree): + + if sky_stars.is_set(node_tree): out_shader_node.inputs["Alpha Type"].default_value = -1.0 material.surface_render_method = "DITHERED" @@ -331,6 +342,10 @@ def set_diffuse(node_tree, color): node_tree.nodes[Sky.DIFF_COL_NODE].outputs['Color'].default_value = color + ########################## + #### BASE A/B #### + ########################## + @staticmethod def set_sky_weather_base_a_texture(node_tree, image): """Set base_a texture to shader. @@ -404,6 +419,10 @@ def set_sky_weather_base_b_uv(node_tree, uv_layer): Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + ########################## + #### OVER A/B #### + ########################## + @staticmethod def set_sky_weather_over_a_texture(node_tree, image): """Set over_a texture to shader. @@ -474,6 +493,158 @@ def set_sky_weather_over_b_uv(node_tree, uv_layer): Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + ########################## + #### BASE MASK A/B #### + ########################## + + @staticmethod + def set_sky_weather_base_mask_a_texture(node_tree, image): + """Set base_mask_a texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_base_a()].image = image + + @staticmethod + def set_sky_weather_base_mask_a_texture_settings(node_tree, settings): + """Set base_mask_a texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_base_a()], settings) + + @staticmethod + def set_sky_weather_base_mask_a_uv(node_tree, uv_layer): + """Set UV layer to base_mask_a texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base_mask_a texture + :type uv_layer: str + """ + + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + + @staticmethod + def set_sky_weather_base_mask_b_texture(node_tree, image): + """Set base_mask_b texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_base_b()].image = image + + @staticmethod + def set_sky_weather_base_mask_b_texture_settings(node_tree, settings): + """Set base_mask_b texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_base_b()], settings) + + @staticmethod + def set_sky_weather_base_mask_b_uv(node_tree, uv_layer): + """Set UV layer to base_mask_b texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base_mask_b texture + :type uv_layer: str + """ + + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + + ########################## + #### OVER MASK A/B #### + ########################## + + @staticmethod + def set_sky_weather_over_mask_a_texture(node_tree, image): + """Set over_mask_a texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_over_a()].image = image + + @staticmethod + def set_sky_weather_over_mask_a_texture_settings(node_tree, settings): + """Set over_mask_a texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_over_a()], settings) + + @staticmethod + def set_sky_weather_over_mask_a_uv(node_tree, uv_layer): + """Set UV layer to over_mask_a texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over_mask_a texture + :type uv_layer: str + """ + + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + + @staticmethod + def set_sky_weather_over_mask_b_texture(node_tree, image): + """Set over_mask_b texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_over_b()].image = image + + @staticmethod + def set_sky_weather_over_mask_b_texture_settings(node_tree, settings): + """Set over_mask_b texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Sky.MASK_NODE_PREFIX + texture_types.get_over_b()], settings) + + @staticmethod + def set_sky_weather_over_mask_b_uv(node_tree, uv_layer): + """Set UV layer to over_mask_b texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over_mask_b texture + :type uv_layer: str + """ + + Sky.set_sky_weather_base_a_uv(node_tree, uv_layer) + + ########################## + #### AUX/FLAVORS #### + ########################## + @staticmethod def set_aux0(node_tree, aux_property): """Set blend factors. @@ -515,7 +686,7 @@ def set_aux1(node_tree, aux_property): for tex_type_i, tex_type in enumerate(texture_types.get()): - if (not sky_stars.is_set(node_tree)) and (not sky_back.is_set(node_tree)): # enabled + if (not sky_stars.is_set(node_tree)) and (not sky_bottom.is_set(node_tree)): # enabled v_cutoff = aux_property[tex_type_i]['value'] else: # disabled v_cutoff = float("-inf") @@ -542,8 +713,8 @@ def set_aux2(node_tree, aux_property): node_tree.nodes[Sky.RESCALE_UV_GROUP_NODE].inputs['Rescale Enabled'].default_value = rescale_enabled @staticmethod - def set_sky_stars_flavor(node_tree, switch_on): - """Set sky stars flavor to this shader. + def set_sky_bottom_flavor(node_tree, switch_on): + """Set sky bottom flavor to this shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -552,13 +723,13 @@ def set_sky_stars_flavor(node_tree, switch_on): """ if switch_on: - sky_stars.init(node_tree) + sky_bottom.init(node_tree) else: - sky_stars.delete(node_tree) - + sky_bottom.delete(node_tree) + @staticmethod - def set_sky_back_flavor(node_tree, switch_on): - """Set sky back flavor to this shader. + def set_sky_stars_flavor(node_tree, switch_on): + """Set sky stars flavor to this shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree @@ -567,6 +738,7 @@ def set_sky_back_flavor(node_tree, switch_on): """ if switch_on: - sky_back.init(node_tree) + sky_stars.init(node_tree) else: - sky_back.delete(node_tree) + sky_stars.delete(node_tree) + diff --git a/addon/io_scs_tools/internals/shaders/flavors/sky_back.py b/addon/io_scs_tools/internals/shaders/flavors/sky_bottom.py similarity index 94% rename from addon/io_scs_tools/internals/shaders/flavors/sky_back.py rename to addon/io_scs_tools/internals/shaders/flavors/sky_bottom.py index 39fe47f..3e3a9cf 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/sky_back.py +++ b/addon/io_scs_tools/internals/shaders/flavors/sky_bottom.py @@ -16,14 +16,14 @@ # # ##### END GPL LICENSE BLOCK ##### -# Copyright (C) 2021: SCS Software +# Copyright (C) 2025: SCS Software -FLAVOR_ID = "sky_back" +FLAVOR_ID = "sky_bottom" def init(node_tree): - """Initialize sky background flavor. + """Initialize sky bottom flavor. :param node_tree: node tree on which it will be used :type node_tree: bpy.types.NodeTree diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 72bfc3e..3b21d58 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -312,7 +312,7 @@ def get_texture_types(): return {'base', 'reflection', 'over', 'oclu', 'mask' , 'mask_1' , 'mask_2', 'mask_3', 'mult', 'iamod', 'lightmap', 'paintjob', 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'nmap_over' , 'layer0', 'layer1', 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b', - 'texture_sky_weather_base_mask_a', 'texture_sky_weather_over_mask_b'} + 'sky_weather_base_mask_a', 'sky_weather_base_mask_b', 'sky_weather_over_mask_a', 'sky_weather_over_mask_b'} def get_id(self): """Gets unique ID for material within current Blend file. If ID does not exists yet it's calculated. diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 599a00b..3edf3a6 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -2,10 +2,71 @@ Header { FormatVersion: 1 Type: "Presets" Note: "Shader Presets for SCS Blender Tools" - Author: "Simon Lusenc (50keda)" + Author: "Simon Lusenc (50keda), Michaleczeq" Version: 2 Date: "2017-10-03" } +Shader { + PresetName: "baked" + Effect: "eut2.baked" + Flavors: ( "NMAP_TS" "SHADOW" "DAY_MASK" ) + Flags: 0 + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } +} +Shader { + PresetName: "baked.spec" + Effect: "eut2.baked.spec" + Flavors: ( "NMAP_TS" "SHADOW" "DAY_MASK" ) + Flags: 0 + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + FriendlyTag: "Specular map" + Value: "" + TexCoord: ( 0 ) + } +} +Shader { + PresetName: "baked.spec.add.env" + Effect: "eut2.baked.spec.add.env" + Flavors: ( "NMAP_TS" "PAINT_MASK" "SHADOW" "DAY_MASK" ) + Flags: 0 + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + FriendlyTag: "Specular map" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + FriendlyTag: "Env factor map" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/vehicle_reflection" + TexCoord: ( -1 ) + } +} Shader { PresetName: "building.add.env.day" Effect: "eut2.building.add.env.day" @@ -1681,7 +1742,7 @@ Shader { Shader { PresetName: "sky" Effect: "eut2.sky" - Flavors: ( "SKYPART_BACKGROUND|SKYPART_STARS" ) + Flavors: ( "SKYPART_NOTEXTURE|SKYPART_STARS" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1713,41 +1774,49 @@ Shader { } Texture { Tag: "texture[X]:texture_sky_weather_base_a" + FriendlyTag: "Base A" Value: "/asset/skybox/s_nice_00/s_nice00_10_80_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_base_b" + FriendlyTag: "Base B" Value: "/asset/skybox/s_nice_00/s_nice00_10_80_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_over_a" + FriendlyTag: "Over A" Value: "/asset/skybox/s_bad_02/s_bad02_03_10_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_over_b" + FriendlyTag: "Over B" Value: "/asset/skybox/s_bad_02/s_bad02_03_10_g" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_base_mask_a" + FriendlyTag: "Mask Base A" Value: "/asset/skybox/s_nice_05/s_nice05_05_00_c" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_base_mask_b" + FriendlyTag: "Mask Base B" Value: "/asset/skybox/s_nice_05/s_nice05_05_00_c" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_over_mask_a" + FriendlyTag: "Mask Over A" Value: "/asset/skybox/s_bad_02/s_bad02_05_00_c" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_sky_weather_over_mask_b" + FriendlyTag: "Mask Over B" Value: "/asset/skybox/s_bad_02/s_bad02_05_00_c" TexCoord: ( 0 ) } @@ -2217,67 +2286,6 @@ Shader { TexCoord: ( 1 ) } } -Shader { - PresetName: "baked" - Effect: "eut2.baked" - Flavors: ( "NMAP_TS" "SHADOW" "DAY_MASK" ) - Flags: 0 - Texture { - Tag: "texture[X]:texture_base" - Value: "" - TexCoord: ( 0 ) - } -} -Shader { - PresetName: "baked.spec" - Effect: "eut2.baked.spec" - Flavors: ( "NMAP_TS" "SHADOW" "DAY_MASK" ) - Flags: 0 - Texture { - Tag: "texture[X]:texture_base" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_over" - FriendlyTag: "Specular map" - Value: "" - TexCoord: ( 0 ) - } -} -Shader { - PresetName: "baked.spec.add.env" - Effect: "eut2.baked.spec.add.env" - Flavors: ( "NMAP_TS" "PAINT_MASK" "SHADOW" "DAY_MASK" ) - Flags: 0 - Attribute { - Format: FLOAT2 - Tag: "fresnel" - Value: ( 0.2 0.9 ) - } - Texture { - Tag: "texture[X]:texture_base" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_over" - FriendlyTag: "Specular map" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_mask" - FriendlyTag: "Env factor map" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/vehicle_reflection" - TexCoord: ( -1 ) - } -} Flavor { Type: "AIRBRUSH" Name: "airbrush" @@ -2637,8 +2645,8 @@ Flavor { } } Flavor { - Type: "SKYPART_BACKGROUND" - Name: "back" + Type: "SKYPART_NOTEXTURE" + Name: "bottom" Attribute { Format: FLOAT4 Tag: "aux[1]" From 52337eb4ea9d780a36555a95d786b59383341ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 12 Jun 2025 06:20:24 +0200 Subject: [PATCH 35/56] Billboard shader support + updated old * Added support for "billboard" shader. * Some new FriendlyTags in old shaders and tg0 update * Added support for "nmap" flavor in "dif.lum.spec" shader * Added some data for future "heightmap" flavor support --- addon/io_scs_tools/imp/pit.py | 10 + .../internals/shaders/eut2/__init__.py | 4 + .../shaders/eut2/billboard/__init__.py | 416 ++++++++++++++++++ .../io_scs_tools/internals/shaders/shader.py | 3 + addon/io_scs_tools/properties/material.py | 187 +++++++- addon/io_scs_tools/shader_presets.txt | 51 ++- addon/io_scs_tools/supported_effects.bin | Bin 142098 -> 150302 bytes addon/io_scs_tools/ui/material.py | 4 +- addon/io_scs_tools/utils/material.py | 2 +- 9 files changed, 672 insertions(+), 5 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/billboard/__init__.py diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index c5c8506..115cb35 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -203,6 +203,16 @@ def _get_look(section): mat_effect = mat_effect.replace(".tsnmap16", ".tsnmap").replace(".tsnmapuv16", ".tsnmapuv") lprint('W Outdated tsnmap16/tsnmapuv16 flavor detected in material %r, switching it to tsnmap/tsnmapuv!', (mat_alias,)) + # If day/night version of "eut2.billboard" shader is detected, remove "day/night" and in "night" case, switch to "eut2.billboard.lit" + if mat_effect.startswith("eut2.billboard") and mat_effect.endswith((".day", ".night")): + + if mat_effect.endswith(".night"): + mat_effect = mat_effect.replace("billboard", "billboard.lit") + lprint("W Night version of billboard shader detected in material %r, switching it to lit!", (mat_alias,)) + else: + mat_effect = mat_effect.replace(".day", "") + lprint("W Day version of billboard shader detected in material %r, removing it from effect!", (mat_alias,)) + look_mat_settings[mat_alias] = (mat_effect, mat_flags, attributes, textures, sec) return look_name, look_mat_settings diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index d264f01..2bb6d34 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -254,6 +254,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif import Dif as Shader + elif effect.startswith("billboard"): + + from io_scs_tools.internals.shaders.eut2.billboard import Billboard as Shader + elif effect.startswith("baked.spec"): if ".add.env" in effect: diff --git a/addon/io_scs_tools/internals/shaders/eut2/billboard/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/billboard/__init__.py new file mode 100644 index 0000000..08fcae7 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/billboard/__init__.py @@ -0,0 +1,416 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.eut2.std_node_groups import alpha_remap_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import compose_lighting_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import lighting_evaluator_ng +from io_scs_tools.internals.shaders.eut2.std_node_groups import vcolor_input_ng +from io_scs_tools.internals.shaders.flavors import nmap +from io_scs_tools.utils import convert as _convert_utils +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import asafew + + +class Billboard(BaseShader): + DIFF_COL_NODE = "DiffuseColor" + SPEC_COL_NODE = "SpecularColor" + GEOM_NODE = "Geometry" + UVMAP_NODE = "FirstUVs" + VCOL_GROUP_NODE = "VColorGroup" + BASE_TEX_NODE = "BaseTex" + DIFF_MULT_NODE = "DiffMultiplier" + SPEC_MULT_NODE = "SpecMultiplier" + VCOLOR_MULT_NODE = "VertexColorMultiplier" + VCOLOR_SCALE_NODE = "VertexColorScale" + REMAP_ALPHA_GNODE = "RemapAlphaToWeight" + LIGHTING_EVAL_NODE = "LightingEvaluator" + COMPOSE_LIGHTING_NODE = "ComposeLighting" + OUTPUT_NODE = "Output" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree, disable_remap_alpha=False): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # node creation + vcol_group_n = node_tree.nodes.new("ShaderNodeGroup") + vcol_group_n.name = Billboard.VCOL_GROUP_NODE + vcol_group_n.label = Billboard.VCOL_GROUP_NODE + vcol_group_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1650) + vcol_group_n.node_tree = vcolor_input_ng.get_node_group() + + uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") + uvmap_n.name = Billboard.UVMAP_NODE + uvmap_n.label = Billboard.UVMAP_NODE + uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1500) + uvmap_n.uv_map = _MESH_consts.none_uv + + geometry_n = node_tree.nodes.new("ShaderNodeNewGeometry") + geometry_n.name = Billboard.GEOM_NODE + geometry_n.label = Billboard.GEOM_NODE + geometry_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1350) + + diff_col_n = node_tree.nodes.new("ShaderNodeRGB") + diff_col_n.name = Billboard.DIFF_COL_NODE + diff_col_n.label = Billboard.DIFF_COL_NODE + diff_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1700) + + spec_col_n = node_tree.nodes.new("ShaderNodeRGB") + spec_col_n.name = Billboard.SPEC_COL_NODE + spec_col_n.label = Billboard.SPEC_COL_NODE + spec_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1900) + + spec_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + spec_mult_n.name = Billboard.SPEC_MULT_NODE + spec_mult_n.label = Billboard.SPEC_MULT_NODE + spec_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1900) + spec_mult_n.operation = "MULTIPLY" + + vcol_scale_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_scale_n.name = Billboard.VCOLOR_SCALE_NODE + vcol_scale_n.label = Billboard.VCOLOR_SCALE_NODE + vcol_scale_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1550) + vcol_scale_n.operation = "MULTIPLY" + vcol_scale_n.inputs[1].default_value = (2,) * 3 + + base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + base_tex_n.name = Billboard.BASE_TEX_NODE + base_tex_n.label = Billboard.BASE_TEX_NODE + base_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1500) + base_tex_n.width = 140 + + vcol_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_mult_n.name = Billboard.VCOLOR_MULT_NODE + vcol_mult_n.label = Billboard.VCOLOR_MULT_NODE + vcol_mult_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1500) + vcol_mult_n.operation = "MULTIPLY" + + diff_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + diff_mult_n.name = Billboard.DIFF_MULT_NODE + diff_mult_n.label = Billboard.DIFF_MULT_NODE + diff_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1650) + diff_mult_n.operation = "MULTIPLY" + diff_mult_n.inputs[1].default_value = (0, 0, 0) + + lighting_eval_n = node_tree.nodes.new("ShaderNodeGroup") + lighting_eval_n.name = Billboard.LIGHTING_EVAL_NODE + lighting_eval_n.label = Billboard.LIGHTING_EVAL_NODE + lighting_eval_n.location = (start_pos_x + pos_x_shift * 7, start_pos_y + 1800) + lighting_eval_n.node_tree = lighting_evaluator_ng.get_node_group() + + compose_lighting_n = node_tree.nodes.new("ShaderNodeGroup") + compose_lighting_n.name = Billboard.COMPOSE_LIGHTING_NODE + compose_lighting_n.label = Billboard.COMPOSE_LIGHTING_NODE + compose_lighting_n.location = (start_pos_x + pos_x_shift * 8, start_pos_y + 2000) + compose_lighting_n.node_tree = compose_lighting_ng.get_node_group() + compose_lighting_n.inputs["Alpha"].default_value = 1.0 + compose_lighting_n.inputs["Alpha Type"].default_value = -1.0 + + output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") + output_n.name = Billboard.OUTPUT_NODE + output_n.label = Billboard.OUTPUT_NODE + output_n.location = (start_pos_x + pos_x_shift * 9, start_pos_y + 1800) + + remap_alpha_n = None + if not disable_remap_alpha: + remap_alpha_n = node_tree.nodes.new("ShaderNodeGroup") + remap_alpha_n.name = remap_alpha_n.label = Billboard.REMAP_ALPHA_GNODE + remap_alpha_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1900) + remap_alpha_n.node_tree = alpha_remap_ng.get_node_group() + remap_alpha_n.inputs['Factor1'].default_value = 1.0 + remap_alpha_n.inputs['Factor2'].default_value = 0.0 + + # links creation + node_tree.links.new(base_tex_n.inputs['Vector'], uvmap_n.outputs['UV']) + node_tree.links.new(vcol_scale_n.inputs[0], vcol_group_n.outputs['Vertex Color']) + + node_tree.links.new(vcol_mult_n.inputs[0], vcol_scale_n.outputs[0]) + node_tree.links.new(vcol_mult_n.inputs[1], base_tex_n.outputs['Color']) + + node_tree.links.new(diff_mult_n.inputs[0], diff_col_n.outputs['Color']) + node_tree.links.new(diff_mult_n.inputs[1], vcol_mult_n.outputs[0]) + + node_tree.links.new(lighting_eval_n.inputs['Normal Vector'], geometry_n.outputs['Normal']) + node_tree.links.new(lighting_eval_n.inputs['Incoming Vector'], geometry_n.outputs['Incoming']) + + node_tree.links.new(compose_lighting_n.inputs['Specular Color'], spec_col_n.outputs['Color']) + node_tree.links.new(compose_lighting_n.inputs['Diffuse Color'], diff_mult_n.outputs[0]) + node_tree.links.new(compose_lighting_n.inputs['Specular Lighting'], lighting_eval_n.outputs['Specular Lighting']) + node_tree.links.new(compose_lighting_n.inputs['Diffuse Lighting'], lighting_eval_n.outputs['Diffuse Lighting']) + node_tree.links.new(compose_lighting_n.inputs['Alpha'], base_tex_n.outputs["Alpha"]) + + node_tree.links.new(output_n.inputs['Surface'], compose_lighting_n.outputs['Shader']) + + if not disable_remap_alpha: + node_tree.links.new(remap_alpha_n.inputs['Alpha'], base_tex_n.outputs['Alpha']) + node_tree.links.new(spec_mult_n.inputs[1], remap_alpha_n.outputs['Weighted Alpha']) + else: + node_tree.links.new(spec_mult_n.inputs[1], base_tex_n.outputs['Alpha']) + + node_tree.links.new(spec_mult_n.inputs[0], spec_col_n.outputs['Color']) + + node_tree.links.new(compose_lighting_n.inputs['Specular Color'], spec_mult_n.outputs[0]) + + @staticmethod + def finalize(node_tree, material): + """Finalize node tree and material settings. Should be called as last. + + :param node_tree: node tree on which this shader should be finalized + :type node_tree: bpy.types.NodeTree + :param material: material used for this shader + :type material: bpy.types.Material + """ + + compose_lighting_n = node_tree.nodes[Billboard.COMPOSE_LIGHTING_NODE] + + # Unique shader - billboard shader do not use backface culling + material.use_backface_culling = False + material.surface_render_method = "DITHERED" + compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 + + if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Billboard.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + node_tree.links.remove(node_tree.nodes[Billboard.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) + + @staticmethod + def set_add_ambient(node_tree, factor): + """Set ambient factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param factor: add ambient factor + :type factor: float + """ + + node_tree.nodes[Billboard.COMPOSE_LIGHTING_NODE].inputs["AddAmbient"].default_value = factor + + @staticmethod + def set_diffuse(node_tree, color): + """Set diffuse color to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param color: diffuse color + :type color: Color or tuple + """ + + color = _convert_utils.to_node_color(color) + + node_tree.nodes[Billboard.DIFF_COL_NODE].outputs['Color'].default_value = color + + @staticmethod + def set_specular(node_tree, color): + """Set specular color to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param color: specular color + :type color: Color or tuple + """ + + color = _convert_utils.to_node_color(color) + + node_tree.nodes[Billboard.SPEC_COL_NODE].outputs['Color'].default_value = color + + @staticmethod + def set_shininess(node_tree, factor): + """Set shininess factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param factor: shininess factor + :type factor: float + """ + + node_tree.nodes[Billboard.LIGHTING_EVAL_NODE].inputs["Shininess"].default_value = factor + + @staticmethod + def set_shadow_bias(node_tree, value): + """Set shadow bias attirbute for this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param value: shador bias factor + :type value: float + """ + + pass # NOTE: shadow bias won't be visualized as game uses it's own implementation + + @staticmethod + def set_base_texture(node_tree, image): + """Set base texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to base texture node + :type image: bpy.types.Image + """ + + node_tree.nodes[Billboard.BASE_TEX_NODE].image = image + + @staticmethod + def set_base_texture_settings(node_tree, settings): + """Set base texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Billboard.BASE_TEX_NODE], settings) + + @staticmethod + def set_base_uv(node_tree, uv_layer): + """Set UV layer to base texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[Billboard.UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[Billboard.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[Billboard.GEOM_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal']) + else: + nmap.delete(node_tree) + + @staticmethod + def set_nmap_texture(node_tree, image): + """Set normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Image + """ + + nmap.set_texture(node_tree, image) + + @staticmethod + def set_nmap_texture_settings(node_tree, settings): + """Set normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + nmap.set_texture_settings(node_tree, settings) + + @staticmethod + def set_nmap_uv(node_tree, uv_layer): + """Set UV layer to normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + nmap.set_uv(node_tree, uv_layer) + + @staticmethod + def set_flat_flavor(node_tree, switch_on): + """Set flat shading flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + lighting_eval_n = node_tree.nodes[Billboard.LIGHTING_EVAL_NODE] + + if switch_on: + lighting_eval_n.inputs["Flat Lighting"].default_value = 1.0 + else: + lighting_eval_n.inputs["Flat Lighting"].default_value = 0.0 + + @staticmethod + def set_asafew_flavor(node_tree, switch_on): + """Set alpha test safe weight flavor to this shader. + + NOTE: there is no safety check if remap was enabled on initialization + thus calling this setter can result in error. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + remap_alpha_n = node_tree.nodes[Billboard.REMAP_ALPHA_GNODE] + + if switch_on: + asafew.init(node_tree, remap_alpha_n) + else: + asafew.delete(node_tree, remap_alpha_n) + + @staticmethod + def set_vertex_pox_mapping(node_tree, uv_layer): + """Set Vertex Position UV layer to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + + pass # NOTE: vertex position is not supported in this shader. \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 40de242..73ae791 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -95,6 +95,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".paint") or ".paint." in effect: flavors["paint"] = True + if effect.endswith(".lit") or ".lit." in effect: + flavors["lit"] = True + if effect.endswith(".decal.over") and ".retroreflective" in effect: flavors["retroreflective_decal"] = True diff --git a/addon/io_scs_tools/properties/material.py b/addon/io_scs_tools/properties/material.py index 3b21d58..3bc5ac3 100644 --- a/addon/io_scs_tools/properties/material.py +++ b/addon/io_scs_tools/properties/material.py @@ -312,7 +312,8 @@ def get_texture_types(): return {'base', 'reflection', 'over', 'oclu', 'mask' , 'mask_1' , 'mask_2', 'mask_3', 'mult', 'iamod', 'lightmap', 'paintjob', 'flakenoise', 'nmap', 'base_1', 'mult_1', 'detail', 'nmap_detail', 'nmap_over' , 'layer0', 'layer1', 'sky_weather_base_a', 'sky_weather_base_b', 'sky_weather_over_a', 'sky_weather_over_b', - 'sky_weather_base_mask_a', 'sky_weather_base_mask_b', 'sky_weather_over_mask_a', 'sky_weather_over_mask_b'} + 'sky_weather_base_mask_a', 'sky_weather_base_mask_b', 'sky_weather_over_mask_a', 'sky_weather_over_mask_b', + 'heightmap', 'heightmap_over', 'heightmap_jitter'} def get_id(self): """Gets unique ID for material within current Blend file. If ID does not exists yet it's calculated. @@ -555,6 +556,25 @@ def update_shader_texture_sky_weather_over_mask_b(self, context): def update_shader_texture_sky_weather_over_mask_b_settings(self, context): __update_shader_texture_tobj_file__(self, context, "sky_weather_over_mask_b") + def update_shader_texture_heightmap(self, context): + __update_shader_texture__(self, context, "heightmap") + + def update_shader_texture_heightmap_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "heightmap") + + def update_shader_texture_heightmap_over(self, context): + __update_shader_texture__(self, context, "heightmap_over") + + def update_shader_texture_heightmap_over_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "heightmap_over") + + def update_shader_texture_heightmap_jitter(self, context): + __update_shader_texture__(self, context, "heightmap_jitter") + + def update_shader_texture_heightmap_jitter_settings(self, context): + __update_shader_texture_tobj_file__(self, context, "heightmap_jitter") + + _cached_mat_num = -1 """Caching number of all materials to properly fix material ids when duplicating material""" @@ -2283,13 +2303,178 @@ def update_shader_texture_sky_weather_over_mask_b_settings(self, context): options={'HIDDEN'}, ) + # TEXTURE: HEIGHTMAP + shader_texture_heightmap: StringProperty( + name="Texture Heightmap", + description="Texture Heightmap for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_heightmap, + ) + shader_texture_heightmap_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_heightmap_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_heightmap_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_heightmap_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_heightmap_settings + ) + shader_texture_heightmap_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_heightmap_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_heightmap_uv: CollectionProperty( + name="Texture Heightmap UV Sets", + description="Texture Heightmap UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: HEIGHTMAP_OVER + shader_texture_heightmap_over: StringProperty( + name="Texture Heightmap Over", + description="Texture Heightmap Over for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_heightmap_over, + ) + shader_texture_heightmap_over_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_heightmap_over_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_heightmap_over_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_heightmap_over_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_heightmap_over_settings + ) + shader_texture_heightmap_over_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_heightmap_over_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_heightmap_over_uv: CollectionProperty( + name="Texture Heightmap Over UV Sets", + description="Texture Heightmap Over UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + + # TEXTURE: HEIGHTMAP_JITTER + shader_texture_heightmap_jitter: StringProperty( + name="Texture Heightmap Jitter", + description="Texture Heightmap Jitter for active Material", + default=_MAT_consts.unset_bitmap_filepath, + options={'HIDDEN'}, + subtype='NONE', + update=update_shader_texture_heightmap_jitter, + ) + shader_texture_heightmap_jitter_imported_tobj: StringProperty( + name="Imported TOBJ Path", + description="Use imported TOBJ path reference which will be exported into material (NOTE: export will not take care of any TOBJ files!)", + default="", + update=__update_look__ + ) + shader_texture_heightmap_jitter_locked: BoolProperty( + name="Texture Locked", + description="Tells if texture is locked and should not be changed by user(intended for internal usage only)", + default=False + ) + shader_texture_heightmap_jitter_map_type: StringProperty( + name="Texture Map Type", + description="Stores texture mapping type and should not be changed by user(intended for internal usage only)", + default="2d" + ) + shader_texture_heightmap_jitter_settings: EnumProperty( + name="Settings", + description="TOBJ settings for this texture", + items=__get_texture_settings__(), + default=set(), + options={'ENUM_FLAG'}, + update=update_shader_texture_heightmap_jitter_settings + ) + shader_texture_heightmap_jitter_tobj_load_time: StringProperty( + name="Last TOBJ load time", + description="Time string of last loading", + default="", + ) + shader_texture_heightmap_jitter_use_imported: BoolProperty( + name="Use Imported", + description="Use custom provided path for TOBJ reference", + default=False, + update=__update_look__ + ) + shader_texture_heightmap_jitter_uv: CollectionProperty( + name="Texture Heightmap Jitter UV Sets", + description="Texture Heightmap Jitter UV sets for active Material", + type=UVMappingItem, + options={'HIDDEN'}, + ) + # MAPPING: PERTURBATION + shader_mapping_unknown: CollectionProperty( + name="Unknown UV Sets", + description="Unknown UV sets for active Material", + type=UVAddMappingItem, + options={'HIDDEN'}, + ) shader_mapping_perturbation: CollectionProperty( name="Perturbation UV Sets", description="Perturbation UV sets for active Material", type=UVAddMappingItem, options={'HIDDEN'}, ) + shader_mapping_vertex_pos: CollectionProperty( + name="Vertex position UV Sets", + description="Vertex position UV sets for active Material", + type=UVAddMappingItem, + options={'HIDDEN'}, + ) # # Following String property is fed from MATERIAL SUBSTANCE data list, which is usually loaded from # # "//base/material/material.db" file and stored at "scs_inventories.matsubs". diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 3edf3a6..c391605 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -67,6 +67,42 @@ Shader { TexCoord: ( -1 ) } } +Shader { + PresetName: "billboard" + Effect: "eut2.billboard" + Flavors: ( "LIT" "NMAP_TS_CALC" "SHADOW" "ASAFEW" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 0.0 0.0 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 4.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Mapping { + Tag: "vertex_pos" + FriendlyTag: "Vertex Shift" + TexCoord: ( 1 ) + } +} Shader { PresetName: "building.add.env.day" Effect: "eut2.building.add.env.day" @@ -321,6 +357,7 @@ Shader { Flags: 0 Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Shadow (8bit)" Value: "" TexCoord: ( 0 ) } @@ -454,7 +491,7 @@ Shader { Shader { PresetName: "dif.lum.spec" Effect: "eut2.dif.lum.spec" - Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "LUMMULVCOL" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "LUMMULVCOL" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1694,6 +1731,7 @@ Shader { Flags: 0 Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Shadow (8bit)" Value: "" TexCoord: ( 0 ) } @@ -2511,6 +2549,15 @@ Flavor { Type: "INVERTOPAC" Name: "oinv" } +Flavor { + Type: "LIT" + Name: "lit" + Texture { + Tag: "texture[X]:texture_lightmap" + Value: "" + TexCoord: ( 0 ) + } +} Flavor { Type: "LUMMULVCOL" Name: "lvcol" @@ -2690,7 +2737,7 @@ Flavor { Attribute { Format: FLOAT4 Tag: "aux[0]" - FriendlyTag: "TexGen0 (ScaleX, ScaleZ, Rotation, Unk)" + FriendlyTag: "TexGen0 (ScaleX, ScaleZ, Rotation, LodSwitch)" Value: ( 10.0 10.0 0.0 0.0 ) } } diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 41a39c601452c528c5d4263e579600c285884ca3..0bac978f776546d74e4e530f1711cc6462170ad5 100644 GIT binary patch literal 150302 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¬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 literal 142098 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 QOVxsa!0?cd|9 diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index c590d7a..b8e83ee 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -825,6 +825,8 @@ def draw_mapping_item(layout, mat, split_perc, mapping): tag = mapping.get('Tag', None) shader_mapping_id = str('shader_mapping_' + tag) + frendly_tag = mapping.get('FriendlyTag', None) + mapping_label = frendly_tag if frendly_tag else str(tag) #lprint("D ----- Shader: %s", (shader_mapping_id,)) @@ -832,7 +834,7 @@ def draw_mapping_item(layout, mat, split_perc, mapping): mapping_row = mapping_box.row(align=True) item_space = mapping_row.split(factor=split_perc, align=True) tag_layout = item_space.row() - tag_layout.label(text=tag.title(), icon='UV') + tag_layout.label(text=mapping_label, icon='UV') if hasattr(mat.scs_props, shader_mapping_id): # UV LAYERS FOR MAPPING diff --git a/addon/io_scs_tools/utils/material.py b/addon/io_scs_tools/utils/material.py index 821f0c5..9698b60 100644 --- a/addon/io_scs_tools/utils/material.py +++ b/addon/io_scs_tools/utils/material.py @@ -718,7 +718,7 @@ def set_shader_data_to_material(material, section, is_import=False, override_bac for mapping_type in used_mapping_types.keys(): # skip unknown mapping type - if mapping_type not in ("perturbation"): + if mapping_type not in ("perturbation", "vertex_pos", "unknown"): lprint("D Trying to apply unknown mapping type to SCS material: %r", (mapping_type,)) continue From 23993d37d4224e3b9d053290f5f9bd2700b5ef0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 13 Jun 2025 06:16:47 +0200 Subject: [PATCH 36/56] Support for dif.spec.add.env.over.dif.opac * Added support for "eut2.dif.spec.add.env.over.dif.opac" shader. --- .../internals/shaders/eut2/__init__.py | 4 + .../__init__.py | 156 ++++++++++++++++++ addon/io_scs_tools/shader_presets.txt | 61 +++++++ 3 files changed, 221 insertions(+) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 2bb6d34..6bd5bcf 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -164,6 +164,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_mult_dif_spec import DifSpecMultDifSpec as Shader + elif effect.startswith("dif.spec.add.env.over.dif.opac"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_add_env_over_dif_opac import DifSpecAddEnvOverDifOpac as Shader + elif effect.startswith("dif.spec.add.env.nofresnel"): from io_scs_tools.internals.shaders.eut2.dif_spec_add_env.nofresnel import DifSpecAddEnvNoFresnel as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py new file mode 100644 index 0000000..2e7799c --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py @@ -0,0 +1,156 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_glass +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv +from io_scs_tools.utils import material as _material_utils + + +class DifSpecAddEnvOverDifOpac(DifSpecAddEnv): + SEC_UVMAP_NODE = "SecUVMap" + OVER_TEX_NODE = "OverTex" + COLOR_MIX_NODE = "ColorMix" + ALPHA_MIX_NODE = "AlphaMix" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parents + DifSpecAddEnv.init(node_tree) + + base_tex_n = node_tree.nodes[DifSpecAddEnv.BASE_TEX_NODE] + vcol_mult_n = node_tree.nodes[DifSpecAddEnv.VCOLOR_MULT_NODE] + spec_mult_n = node_tree.nodes[DifSpecAddEnv.SPEC_MULT_NODE] + add_env_group_n = node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE] + remap_alpha_n = node_tree.nodes[DifSpecAddEnv.REMAP_ALPHA_GNODE] + opacity_n = node_tree.nodes[DifSpecAddEnv.OPACITY_NODE] + + # Default is 1.0. I changed it to higher to make it more accurate to the game. If models preview will be broken, remove it. + add_env_n = node_tree.nodes[DifSpecAddEnv.ADD_ENV_GROUP_NODE] + add_env_n.inputs['Strength Multiplier'].default_value = 2.0 + + # move existing + spec_mult_n.location.x += pos_x_shift + add_env_group_n.location.x += pos_x_shift + opacity_n.location.y -= 300 + + # node creation + sec_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + sec_uv_n.name = sec_uv_n.label = DifSpecAddEnvOverDifOpac.SEC_UVMAP_NODE + sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) + sec_uv_n.uv_map = _MESH_consts.none_uv + + over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_tex_n.name = over_tex_n.label = DifSpecAddEnvOverDifOpac.OVER_TEX_NODE + over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) + over_tex_n.width = 140 + + color_mix_n = node_tree.nodes.new("ShaderNodeMix") + color_mix_n.name = color_mix_n.label = DifSpecAddEnvOverDifOpac.COLOR_MIX_NODE + color_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + color_mix_n.data_type = "RGBA" + color_mix_n.blend_type = "MIX" + + alpha_mix_n = node_tree.nodes.new("ShaderNodeMix") + alpha_mix_n.name = alpha_mix_n.label = DifSpecAddEnvOverDifOpac.ALPHA_MIX_NODE + alpha_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2000) + alpha_mix_n.data_type = "RGBA" + alpha_mix_n.blend_type = "MIX" + alpha_mix_n.inputs['B'].default_value = (0.0,) * 3 + (1.0,) + + # links creation + node_tree.links.new(sec_uv_n.outputs['UV'], over_tex_n.inputs['Vector']) + + node_tree.links.new(base_tex_n.outputs['Color'], color_mix_n.inputs['A']) + node_tree.links.new(over_tex_n.outputs['Color'], color_mix_n.inputs['B']) + node_tree.links.new(over_tex_n.outputs['Alpha'], color_mix_n.inputs['Factor']) + node_tree.links.new(over_tex_n.outputs['Alpha'], alpha_mix_n.inputs['Factor']) + + node_tree.links.new(remap_alpha_n.outputs['Weighted Alpha'], alpha_mix_n.inputs['A']) + + node_tree.links.new(alpha_mix_n.outputs['Result'], add_env_group_n.inputs['Base Texture Alpha']) + node_tree.links.new(color_mix_n.outputs['Result'], vcol_mult_n.inputs[1]) + + + @staticmethod + def set_reflection2(node_tree, value): + """Set reflection2 factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param value: reflection factor + :type value: float + """ + + pass # NOTE: reflection attribute doesn't change anything in rendered material, so pass it + + @staticmethod + def set_over_texture(node_tree, image): + """Set over texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to over texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecAddEnvOverDifOpac.OVER_TEX_NODE].image = image + + @staticmethod + def set_over_texture_settings(node_tree, settings): + """Set over texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecAddEnvOverDifOpac.OVER_TEX_NODE], settings) + + @staticmethod + def set_over_uv(node_tree, uv_layer): + """Set UV layer to over texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecAddEnvOverDifOpac.SEC_UVMAP_NODE].uv_map = uv_layer diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index c391605..0e1148a 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -663,6 +663,67 @@ Shader { TexCoord: ( -1 ) } } +Shader { + PresetName: "dif.spec.add.env.over.dif.opac" + Effect: "eut2.dif.spec.add.env.over.dif.opac" + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 60.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection2" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.5 0.5 0.5 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/vehicle_reflection" + TexCoord: ( -1 ) + } +} Shader { PresetName: "dif.spec.fade.dif.spec" Effect: "eut2.dif.spec.fade.dif.spec" From 2aeb54ba0fc426d4ccf9aa7cfdc4e7e3a1d45b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sun, 15 Jun 2025 08:39:05 +0200 Subject: [PATCH 37/56] New shader and flavor support * Added support for "dif.spec.amod.dif.spec.add.env" flavor * Added support for "opasrc01" flavor in "dif_spec_mult_dif_spec" * Fresnel attribute values are now displayed on one line instead of 2. --- .../internals/shaders/eut2/__init__.py | 4 + .../__init__.py | 47 ++++ .../eut2/dif_spec_mult_dif_spec/__init__.py | 27 ++ .../internals/shaders/flavors/opasrc1.py | 106 +++++++ .../io_scs_tools/internals/shaders/shader.py | 3 + addon/io_scs_tools/shader_presets.txt | 259 +++++++++++------- addon/io_scs_tools/supported_effects.bin | Bin 150302 -> 152584 bytes addon/io_scs_tools/ui/material.py | 2 +- 8 files changed, 354 insertions(+), 94 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec_add_env/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/flavors/opasrc1.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 6bd5bcf..4f87921 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -144,6 +144,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_over_dif_opac import DifSpecOverDifOpac as Shader + elif effect.startswith("dif.spec.amod.dif.spec.add.env"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_amod_dif_spec_add_env import DifSpecAmodDifSpecAddEnv as Shader + elif effect.startswith("dif.spec.amod.dif.spec"): from io_scs_tools.internals.shaders.eut2.dif_spec_amod_dif_spec import DifSpecAmodDifSpec as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec_add_env/__init__.py new file mode 100644 index 0000000..07fabf7 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_amod_dif_spec_add_env/__init__.py @@ -0,0 +1,47 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.dif_spec_amod_dif_spec import DifSpecAmodDifSpec +from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv + + +class DifSpecAmodDifSpecAddEnv(DifSpecAmodDifSpec, StdAddEnv): + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + # init parents + DifSpecAmodDifSpec.init(node_tree) + StdAddEnv.add(node_tree, + DifSpecAmodDifSpec.GEOM_NODE, + node_tree.nodes[DifSpecAmodDifSpec.SPEC_COL_NODE].outputs['Color'], + node_tree.nodes[DifSpecAmodDifSpec.REMAP_ALPHA_GNODE].outputs['Weighted Alpha'], + node_tree.nodes[DifSpecAmodDifSpec.LIGHTING_EVAL_NODE].outputs['Normal'], + node_tree.nodes[DifSpecAmodDifSpec.COMPOSE_LIGHTING_NODE].inputs['Env Color']) + diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py index 57e3eff..5fc149f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py @@ -21,6 +21,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec from io_scs_tools.internals.shaders.flavors import tg1 +from io_scs_tools.internals.shaders.flavors import opasrc1 from io_scs_tools.utils import material as _material_utils @@ -181,3 +182,29 @@ def set_aux1(node_tree, aux_property): tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) else: tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) + + @staticmethod + def set_opasrc1_flavor(node_tree, switch_on): + """Set opasrc1 flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + vcol_group_n = node_tree.nodes[DifSpecMultDifSpec.VCOL_GROUP_NODE] + base_tex_n = node_tree.nodes[DifSpecMultDifSpec.BASE_TEX_NODE] + + compose_lighting_n = node_tree.nodes[DifSpecMultDifSpec.COMPOSE_LIGHTING_NODE] + + if switch_on: + + location = (5 * 185, 1800) + opasrc1.init(node_tree, location, + vcol_group_n.outputs["Vertex Color Alpha"], + base_tex_n.outputs["Alpha"], + compose_lighting_n.inputs["Alpha"]) + + else: + opasrc1.delete(node_tree) \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/flavors/opasrc1.py b/addon/io_scs_tools/internals/shaders/flavors/opasrc1.py new file mode 100644 index 0000000..ed19d67 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/flavors/opasrc1.py @@ -0,0 +1,106 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +FLAVOR_ID = "opasrc01" +MULT_OPAC_SRC_NODE = "MultOpaqueSource" + + +def __create_node__(node_tree): + """Create node for oinv node. + + :param node_tree: node tree on which oinv flavor will be used + :type node_tree: bpy.types.NodeTree + """ + mult_opac_src_n = node_tree.nodes.new("ShaderNodeMath") + mult_opac_src_n.name = mult_opac_src_n.label = MULT_OPAC_SRC_NODE + mult_opac_src_n.operation = "MULTIPLY" + mult_opac_src_n.inputs[1].default_value = 1.0 + + +def init(node_tree, location, alpha_from, opac_from, alpha_to): + """Initialize oinv flavor + + :param node_tree: node tree on which oinv flavor will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location (int, int) + :param alpha_from: node socket from which opasrc flavor should get alpha + :type alpha_from: bpy.types.NodeSocket + :param opac_from: node socket from which opasrc flavor should get opacity + :type opac_from: bpy.types.NodeSocket + :param alpha_to: node socket to which result of alpha and opaque mult should be send + :type alpha_to: bpy.types.NodeSocket + """ + + if MULT_OPAC_SRC_NODE not in node_tree.nodes: + __create_node__(node_tree) + + node_tree.nodes[MULT_OPAC_SRC_NODE].location = location + + # links creation + nodes = node_tree.nodes + + node_tree.links.new(alpha_from, nodes[MULT_OPAC_SRC_NODE].inputs[0]) + node_tree.links.new(opac_from, nodes[MULT_OPAC_SRC_NODE].inputs[1]) + + node_tree.links.new(nodes[MULT_OPAC_SRC_NODE].outputs[0], alpha_to) + + # FIXME: move to old system after: https://developer.blender.org/T68406 is resolved + flavor_frame = node_tree.nodes.new(type="NodeFrame") + flavor_frame.name = flavor_frame.label = FLAVOR_ID + +def delete(node_tree): + """Delete oinv flavor nodes from node tree. + + :param node_tree: node tree from which opasrc flavor should be deleted + :type node_tree: bpy.types.NodeTree + """ + + if MULT_OPAC_SRC_NODE in node_tree.nodes: + + out_socket = None + in_socket = None + + for link in node_tree.links: + + if link.to_node == node_tree.nodes[MULT_OPAC_SRC_NODE]: + out_socket = link.from_socket + + if link.from_node == node_tree.nodes[MULT_OPAC_SRC_NODE]: + in_socket = link.to_socket + + node_tree.nodes.remove(node_tree.nodes[MULT_OPAC_SRC_NODE]) + + # if out and in socket were properly recovered recreate link state without opasrc flavor + if out_socket and in_socket: + node_tree.links.new(out_socket, in_socket) + + if FLAVOR_ID in node_tree.nodes: + node_tree.nodes.remove(node_tree.nodes[FLAVOR_ID]) + +def is_set(node_tree): + """Check if flavor is set or not. + + :param node_tree: node tree which should be checked for existance of this flavor + :type node_tree: bpy.types.NodeTree + :return: True if flavor exists; False otherwise + :rtype: bool + """ + return FLAVOR_ID in node_tree.nodes \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 73ae791..084a743 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -80,6 +80,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".oinv") or ".oinv." in effect: flavors["oinv"] = True + if effect.endswith(".opasrc01") or ".opasrc01." in effect: + flavors["opasrc1"] = True + if effect.endswith(".lvcol") or ".lvcol." in effect: flavors["lvcol"] = True diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 0e1148a..78f6ca6 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -724,6 +724,129 @@ Shader { TexCoord: ( -1 ) } } +Shader { + PresetName: "dif.spec.amod.dif.spec" + Effect: "eut2.dif.spec.amod.dif.spec" + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "ASAFEWA" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Decal blending factors" + Value: ( 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 20.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 1.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + FriendlyTag: "Decal strength" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_over" + FriendlyTag: "Decal" + Value: "" + TexCoord: ( 1 ) + } +} +Shader { + PresetName: "dif.spec.amod.dif.spec.add.env" + Effect: "eut2.dif.spec.amod.dif.spec.add.env" + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "ASAFEWA" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Decal blending factors" + Value: ( 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 20.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + FriendlyTag: "Decal strength" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_over" + FriendlyTag: "Decal" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } +} Shader { PresetName: "dif.spec.fade.dif.spec" Effect: "eut2.dif.spec.fade.dif.spec" @@ -772,9 +895,9 @@ Shader { } } Shader { - PresetName: "dif.spec.mult.dif.spec" - Effect: "eut2.dif.spec.mult.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + PresetName: "dif.spec.mult.dif.iamod.dif.add.env" + Effect: "eut2.dif.spec.mult.dif.iamod.dif.add.env" + Flavors: ( "NMAP_TS|NMAP_TS_UV" ) Flags: 0 Attribute { Format: FLOAT3 @@ -784,12 +907,12 @@ Shader { Attribute { Format: FLOAT3 Tag: "specular" - Value: ( 0.400000006 0.400000006 0.400000006 ) + Value: ( 1.0 1.0 1.0 ) } Attribute { Format: FLOAT Tag: "shininess" - Value: ( 25.0 ) + Value: ( 60.0 ) } Attribute { Format: FLOAT @@ -801,6 +924,16 @@ Shader { Tag: "reflection" Value: ( 0.0 ) } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.8000000119 0.8000000119 0.8000000119 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -811,11 +944,21 @@ Shader { Value: "" TexCoord: ( 1 ) } + Texture { + Tag: "texture[X]:texture_iamod" + Value: "" + TexCoord: ( 2 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } } Shader { - PresetName: "dif.spec.mult.dif.spec.add.env" - Effect: "eut2.dif.spec.mult.dif.spec.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + PresetName: "dif.spec.mult.dif.spec" + Effect: "eut2.dif.spec.mult.dif.spec" + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "OPASRC01" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -842,16 +985,6 @@ Shader { Tag: "reflection" Value: ( 0.0 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 0.5 0.5 0.5 ) - } - Attribute { - Format: FLOAT2 - Tag: "fresnel" - Value: ( 0.2 0.9 ) - } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -862,16 +995,11 @@ Shader { Value: "" TexCoord: ( 1 ) } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/vehicle_reflection" - TexCoord: ( -1 ) - } } Shader { - PresetName: "dif.spec.mult.dif.iamod.dif.add.env" - Effect: "eut2.dif.spec.mult.dif.iamod.dif.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV" ) + PresetName: "dif.spec.mult.dif.spec.add.env" + Effect: "eut2.dif.spec.mult.dif.spec.add.env" + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -881,12 +1009,12 @@ Shader { Attribute { Format: FLOAT3 Tag: "specular" - Value: ( 1.0 1.0 1.0 ) + Value: ( 0.400000006 0.400000006 0.400000006 ) } Attribute { Format: FLOAT Tag: "shininess" - Value: ( 60.0 ) + Value: ( 25.0 ) } Attribute { Format: FLOAT @@ -898,16 +1026,16 @@ Shader { Tag: "reflection" Value: ( 0.0 ) } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.5 0.5 0.5 ) + } Attribute { Format: FLOAT2 Tag: "fresnel" Value: ( 0.2 0.9 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 0.8000000119 0.8000000119 0.8000000119 ) - } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -918,14 +1046,9 @@ Shader { Value: "" TexCoord: ( 1 ) } - Texture { - Tag: "texture[X]:texture_iamod" - Value: "" - TexCoord: ( 2 ) - } Texture { Tag: "texture[X]:texture_reflection" - Value: "/material/environment/building_reflection/building_ref" + Value: "/material/environment/vehicle_reflection" TexCoord: ( -1 ) } } @@ -2139,60 +2262,6 @@ Shader { TexCoord: ( 1 ) } } -Shader { - PresetName: "dif.spec.amod.dif.spec" - Effect: "eut2.dif.spec.amod.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "ASAFEWA" ) - Flags: 0 - Attribute { - Format: FLOAT3 - Tag: "diffuse" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT3 - Tag: "specular" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[0]" - FriendlyTag: "Decal blending factors" - Value: ( 1.0 1.0 ) - } - Attribute { - Format: FLOAT - Tag: "shininess" - Value: ( 20.0 ) - } - Attribute { - Format: FLOAT - Tag: "add_ambient" - Value: ( 0.0 ) - } - Attribute { - Format: FLOAT - Tag: "reflection" - Value: ( 1.0 ) - } - Texture { - Tag: "texture[X]:texture_base" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_mask" - FriendlyTag: "Decal strength" - Value: "" - TexCoord: ( 1 ) - } - Texture { - Tag: "texture[X]:texture_over" - FriendlyTag: "Decal" - Value: "" - TexCoord: ( 1 ) - } -} Shader { PresetName: "interior.lit" Effect: "eut2.interior.lit" @@ -2717,6 +2786,10 @@ Flavor { Type: "NOCULL" Name: "nocull" } +Flavor { + Type: "OPASRC01" + Name: "opasrc01" +} Flavor { Type: "PAINT" Name: "paint" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 0bac978f776546d74e4e530f1711cc6462170ad5..1405f194413ffd75e8ebc6a8eef4a22b8f3e8e1f 100644 GIT binary patch literal 152584 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�?qJz$)Y) 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 literal 150302 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¬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 diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index b8e83ee..ed8111b 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -504,7 +504,7 @@ def draw_attribute_item(layout, mat, split_perc, attribute): elif tag == 'env_factor': value_layout.prop(mat.scs_props, 'shader_attribute_env_factor', text="") elif tag == 'fresnel': - value_layout.column().prop(mat.scs_props, 'shader_attribute_fresnel', text="") + value_layout.prop(mat.scs_props, 'shader_attribute_fresnel', text="") elif tag == 'tint': value_layout.prop(mat.scs_props, 'shader_attribute_tint', text="") elif tag == 'tint_opacity': From 07b6e62c93cebf2bbdba8801d10cd16052571293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 16 Jun 2025 07:07:27 +0200 Subject: [PATCH 38/56] Support for flavors, matterial ui change * Added support for "attr" flavor for "dif", "dif.spec" and "dif.spec.mult.dif.spec" * Added support for "opasrc01" flavor for "dif.spec.mult.dif.spec.add.env" * Added support for "tsnmapcalc" flavor for "dif.anim" * Some changes in blocking flavor buttons in material tab - now if flavor can't be used, only that flavor will be grayed, instead of the whole row. --- addon/io_scs_tools/shader_presets.txt | 33 +++++++--- addon/io_scs_tools/supported_effects.bin | Bin 152584 -> 154826 bytes addon/io_scs_tools/ui/material.py | 75 +++++++++-------------- 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 78f6ca6..08c1f12 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -365,7 +365,7 @@ Shader { Shader { PresetName: "dif" Effect: "eut2.dif" - Flavors: ( "FLAT" "SHADOW" "PAINT" "TEXGEN0" "TEXMTX0" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "FLAT" "SHADOW" "PAINT" "TEXGEN0" "TEXMTX0" "ALPHA" "DEPTH" "BLEND_ATTR|BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -402,7 +402,7 @@ Shader { PresetName: "dif.anim" Effect: "eut2.dif.anim" # NOTE: intentionally disabled TEXGEN0 flavor as it conflicts with usage of aux[0] attribute - Flavors: ( "FADESHEET|FLIPSHEET" "ALPHA" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS_CALC_OVER0" "FADESHEET|FLIPSHEET" "ALPHA" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -533,7 +533,7 @@ Shader { Shader { PresetName: "dif.spec" Effect: "eut2.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_ATTR|BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -958,7 +958,7 @@ Shader { Shader { PresetName: "dif.spec.mult.dif.spec" Effect: "eut2.dif.spec.mult.dif.spec" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "OPASRC01" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "OPASRC01" "DEPTH" "BLEND_ATTR|BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -999,7 +999,7 @@ Shader { Shader { PresetName: "dif.spec.mult.dif.spec.add.env" Effect: "eut2.dif.spec.mult.dif.spec.add.env" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "OPASRC01" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2544,13 +2544,17 @@ Flavor { Name: "add" } Flavor { - Type: "BLEND_OVER" - Name: "over" + Type: "BLEND_ATTR" + Name: "attr" } Flavor { Type: "BLEND_MULT" Name: "mult" } +Flavor { + Type: "BLEND_OVER" + Name: "over" +} Flavor { Type: "COLORMASK" Name: "colormask" @@ -2726,6 +2730,21 @@ Flavor { TexCoord: ( 0 ) } } +Flavor { + Type: "NMAP_TS_CALC_OVER0" + Name: "tsnmapcalc" + Texture { + Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_over" + Value: "" + TexCoord: ( 0 ) + } +} Flavor { Type: "NMAP_TS_CALC_OVER" Name: "tsnmapcalc" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 1405f194413ffd75e8ebc6a8eef4a22b8f3e8e1f..abc96c318c0170fcba101680f1f9303309024a15 100644 GIT binary patch literal 154826 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%0D6p(JD+1&O!yZ{MWL*cxtsfO6{|LcV?vS+X4{Z zSBZ}U3Sza*@K|J@dX;+zaEC8p686|TLs+_oxSXuNf@u`~@Y0Za@&;(;C zcQ{gM<8j`WN-Z Bff@h+ literal 152584 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�?qJz$)Y) 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 diff --git a/addon/io_scs_tools/ui/material.py b/addon/io_scs_tools/ui/material.py index ed8111b..a1b1718 100644 --- a/addon/io_scs_tools/ui/material.py +++ b/addon/io_scs_tools/ui/material.py @@ -175,59 +175,42 @@ def draw_flavors(layout, mat): column = layout.column(align=True) column.alignment = "LEFT" - # draw switching operator for each flavor (if there is more variants draw operator for each variant) - enabled_flavors = {} # store enabled flavors, for later analyzing which rows should be disabled - """:type: dict[int, io_scs_tools.internals.shader_presets.ui_shader_preset_item.FlavorVariant]""" - flavor_rows = [] # store row UI layout of flavor operators, for later analyzing which rows should be disabled + # 1. Collect currently active flavor variants + active_flavors = {} for i, flavor in enumerate(preset.flavors): + for variant in flavor.variants: + if ("." + variant.suffix + ".") in effect_flavor_part or effect_flavor_part.endswith("." + variant.suffix): + active_flavors[i] = variant.suffix + # 2. Draw switching operator for each flavor (if there is more variants draw operator for each variant) + for i, flavor in enumerate(preset.flavors): + row = column.row(align=True) - flavor_rows.append(row) for flavor_variant in flavor.variants: - is_in_middle = "." + flavor_variant.suffix + "." in effect_flavor_part - is_on_end = effect_flavor_part.endswith("." + flavor_variant.suffix) - flavor_enabled = is_in_middle or is_on_end - - icon = _shared.get_on_off_icon(flavor_enabled) - props = row.operator("material.scs_tools_switch_flavor", text=flavor_variant.suffix, icon=icon, depress=flavor_enabled) + # is that flavor active? + is_active = ("." + flavor_variant.suffix + ".") in effect_flavor_part or effect_flavor_part.endswith("." + flavor_variant.suffix) + + # simulate turning on the flavor + simulated = [] + for j, f in enumerate(preset.flavors): + if i == j: + simulated.append(flavor_variant.suffix) + else: + suffix = active_flavors.get(j) + if suffix: + simulated.append(suffix) + new_flavor_str = "." + ".".join(simulated) if simulated else "" + + is_compatible = _shader_presets.has_section(preset.name, new_flavor_str) + + sub = row.row(align=True) + sub.enabled = is_compatible or is_active + icon = _shared.get_on_off_icon(is_active) + props = sub.operator("material.scs_tools_switch_flavor", text=flavor_variant.suffix, icon=icon, depress=is_active) props.flavor_name = flavor_variant.suffix - props.flavor_enabled = flavor_enabled - - if flavor_enabled: - enabled_flavors[i] = flavor_variant - - # now as we drawn the flavors and we know which ones are enabled, - # search the ones that are not compatible with currently enabled flavors and disable them in UI! - for i, flavor in enumerate(preset.flavors): - - # enabled flavors have to stay enabled so skip them - if i in enabled_flavors: - continue - - for flavor_variant in flavor.variants: - - # 1. construct proposed new flavor string: - # combine strings of enabled flavors and current flavor variant - new_flavor_str = "" - curr_flavor_added = False - for enabled_i in enabled_flavors.keys(): - - if i < enabled_i and not curr_flavor_added: - new_flavor_str += "." + flavor_variant.suffix - curr_flavor_added = True - - new_flavor_str += "." + enabled_flavors[enabled_i].suffix - - if not curr_flavor_added: - new_flavor_str += "." + flavor_variant.suffix - - # 2. check if proposed new flavor combination exists in cache: - # if not then row on current flavor index has to be disabled - if not _shader_presets.has_section(preset.name, new_flavor_str): - flavor_rows[i].enabled = False - break + props.flavor_enabled = is_active @staticmethod def draw_parameters(layout, mat, scs_inventories, split_perc, is_imported_shader=False): From 939be079808b6f436b466c2c8e3117440ec4ee89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 16 Jun 2025 23:33:55 +0200 Subject: [PATCH 39/56] New shaders, node pos tweaks, tg1 fix * Added support for "dif.spec.over.dif.opac.add.env" shader * Added support for "dif.spec.weight.mask.dif.spec.weight" shader (idk if material preview is correct because scs not used that shader yet) * Added support for "attr" flavor in "dif.spec.oclu", "dif.spec.over.dif.opac" and "dif.spec.weight" shaders * Fixed "tg1" flavor in "dif_spec_add_env_over_dif_opac" and "dif_spec_oclu" shaders --- .../internals/shaders/eut2/__init__.py | 8 + .../__init__.py | 45 ++- .../__init__.py | 2 +- .../eut2/dif_spec_mult_dif_spec/__init__.py | 2 +- .../__init__.py | 2 +- .../shaders/eut2/dif_spec_oclu/__init__.py | 44 ++- .../eut2/dif_spec_over_dif_opac/__init__.py | 2 +- .../__init__.py | 46 +++ .../__init__.py | 283 ++++++++++++++++++ addon/io_scs_tools/shader_presets.txt | 136 ++++++++- addon/io_scs_tools/supported_effects.bin | Bin 154826 -> 161578 bytes 11 files changed, 560 insertions(+), 10 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac_add_env/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mask_dif_spec_weight/__init__.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 4f87921..e56e549 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -139,6 +139,10 @@ def get_shader(effect): elif effect.startswith("decalshadow"): from io_scs_tools.internals.shaders.eut2.decalshadow import Decalshadow as Shader + + elif effect.startswith("dif.spec.over.dif.opac.add.env"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_over_dif_opac_add_env import DifSpecOverDifOpacAddEnv as Shader elif effect.startswith("dif.spec.over.dif.opac"): @@ -226,6 +230,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_weight_weight_dif_spec_weight import DifSpecWeightWeightDifSpecWeight as Shader + elif effect.startswith("dif.spec.weight.mask.dif.spec.weight"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mask_dif_spec_weight import DifSpecWeightMaskDifSpecWeight as Shader + elif effect.startswith("dif.spec.weight.mult2.weight2"): from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mult2_weight2 import DifSpecWeightMult2Weight2 as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py index 2e7799c..4878e97 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_add_env_over_dif_opac/__init__.py @@ -18,9 +18,9 @@ # Copyright (C) 2025: SCS Software -from io_scs_tools.internals.shaders.eut2.parameters import get_fresnel_glass from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif_spec_add_env import DifSpecAddEnv +from io_scs_tools.internals.shaders.flavors import tg1 from io_scs_tools.utils import material as _material_utils @@ -70,7 +70,7 @@ def init(node_tree): # node creation sec_uv_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uv_n.name = sec_uv_n.label = DifSpecAddEnvOverDifOpac.SEC_UVMAP_NODE - sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) + sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) sec_uv_n.uv_map = _MESH_consts.none_uv over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") @@ -154,3 +154,44 @@ def set_over_uv(node_tree, uv_layer): uv_layer = _MESH_consts.none_uv node_tree.nodes[DifSpecAddEnvOverDifOpac.SEC_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_tg1_flavor(node_tree, switch_on): + """Set second texture generation flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + if switch_on and not tg1.is_set(node_tree): + + out_node = node_tree.nodes[DifSpecAddEnv.GEOM_NODE] + in_node = node_tree.nodes[DifSpecAddEnvOverDifOpac.OVER_TEX_NODE] + + out_node.location.x -= 185 + location = (out_node.location.x + 185, out_node.location.y) + + tg1.init(node_tree, location, out_node.outputs["Position"], in_node.inputs["Vector"]) + + elif not switch_on: + + tg1.delete(node_tree) + + @staticmethod + def set_aux1(node_tree, aux_property): + """Set second texture generation scale and rotation. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: secondary specular color represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + if tg1.is_set(node_tree): + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py index 7c46ece..1ea395e 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_iamod_dif_add_env/__init__.py @@ -94,7 +94,7 @@ def init(node_tree): third_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") third_uvmap_n.name = DifSpecMultDifIamodDifAddEnv.THIRD_UVMAP_NODE third_uvmap_n.label = DifSpecMultDifIamodDifAddEnv.THIRD_UVMAP_NODE - third_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) + third_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 750) third_uvmap_n.uv_map = _MESH_consts.none_uv iamod_tex_n = node_tree.nodes.new("ShaderNodeTexImage") diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py index 5fc149f..d0ac7e5 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec/__init__.py @@ -70,7 +70,7 @@ def init(node_tree): sec_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uvmap_n.name = DifSpecMultDifSpec.SEC_UVMAP_NODE sec_uvmap_n.label = DifSpecMultDifSpec.SEC_UVMAP_NODE - sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1200) + sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) sec_uvmap_n.uv_map = _MESH_consts.none_uv mult_tex_n = node_tree.nodes.new("ShaderNodeTexImage") diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py index 3a11eac..49ae833 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_mult_dif_spec_iamod_dif_spec/__init__.py @@ -72,7 +72,7 @@ def init(node_tree): third_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") third_uvmap_n.name = DifSpecMultDifSpecIamodDifSpec.THIRD_UVMAP_NODE third_uvmap_n.label = DifSpecMultDifSpecIamodDifSpec.THIRD_UVMAP_NODE - third_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) + third_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 750) third_uvmap_n.uv_map = _MESH_consts.none_uv iamod_tex_n = node_tree.nodes.new("ShaderNodeTexImage") diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py index be4917b..34d9e17 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py @@ -20,6 +20,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.internals.shaders.flavors import tg1 from io_scs_tools.utils import material as _material_utils @@ -63,7 +64,7 @@ def init(node_tree): # node creation sec_uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uvmap_n.name = sec_uvmap_n.label = DifSpecOclu.SEC_UVMAP_NODE - sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) + sec_uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) sec_uvmap_n.uv_map = _MESH_consts.none_uv oclu_tex_n = node_tree.nodes.new("ShaderNodeTexImage") @@ -141,3 +142,44 @@ def set_oclu_uv(node_tree, uv_layer): uv_layer = _MESH_consts.none_uv node_tree.nodes[DifSpecOclu.SEC_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_tg1_flavor(node_tree, switch_on): + """Set zero texture generation flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + if switch_on and not tg1.is_set(node_tree): + + out_node = node_tree.nodes[DifSpecOclu.GEOM_NODE] + in_node = node_tree.nodes[DifSpecOclu.OCLU_TEX_NODE] + + out_node.location.x -= 185 + location = (out_node.location.x + 185, out_node.location.y) + + tg1.init(node_tree, location, out_node.outputs["Position"], in_node.inputs["Vector"]) + + elif not switch_on: + + tg1.delete(node_tree) + + @staticmethod + def set_aux1(node_tree, aux_property): + """Set second texture generation scale and rotation. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: secondary specular color represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + if tg1.is_set(node_tree): + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py index 525ba9a..a4aa157 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac/__init__.py @@ -61,7 +61,7 @@ def init(node_tree): sec_uv_n = node_tree.nodes.new("ShaderNodeUVMap") sec_uv_n.name = DifSpecOverDifOpac.SEC_UVMAP_NODE sec_uv_n.label = DifSpecOverDifOpac.SEC_UVMAP_NODE - sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1200) + sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) sec_uv_n.uv_map = _MESH_consts.none_uv over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac_add_env/__init__.py new file mode 100644 index 0000000..70140cf --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_over_dif_opac_add_env/__init__.py @@ -0,0 +1,46 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.dif_spec_over_dif_opac import DifSpecOverDifOpac +from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv + + +class DifSpecOverDifOpacAddEnv(DifSpecOverDifOpac, StdAddEnv): + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + # init parents + DifSpecOverDifOpac.init(node_tree) + StdAddEnv.add(node_tree, + DifSpecOverDifOpac.GEOM_NODE, + node_tree.nodes[DifSpecOverDifOpac.SPEC_COL_NODE].outputs['Color'], + node_tree.nodes[DifSpecOverDifOpac.BASE_TEX_NODE].outputs['Alpha'], + node_tree.nodes[DifSpecOverDifOpac.LIGHTING_EVAL_NODE].outputs['Normal'], + node_tree.nodes[DifSpecOverDifOpac.COMPOSE_LIGHTING_NODE].inputs['Env Color']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mask_dif_spec_weight/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mask_dif_spec_weight/__init__.py new file mode 100644 index 0000000..d231136 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mask_dif_spec_weight/__init__.py @@ -0,0 +1,283 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.utils import material as _material_utils + +class DifSpecWeightMaskDifSpecWeight(DifSpec): + SEC_UVMAP_NODE = "SecondUVMap" + THIRD_UVMAP_NODE = "ThirdUVMap" + MASK_TEX_NODE = "MaskTex" + OVER_TEX_NODE = "OverTex" + BASE_OVER_MIX_NODE = "BaseOverColorMix" + BASE_OVER_A_MIX_NODE = "BaseOverAlphaMix" + SEC_SPEC_COL_NODE = "SecSpecularColor" + SEC_SPEC_MIX_NODE = "SecSpecMix" + SEC_SHININESS_MIX_NODE = "SecShininnesMix" + VCOL_SPEC_MULT_NODE = "VColSpecMult" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + DifSpec.init(node_tree, disable_remap_alpha=True) + + base_tex_n = node_tree.nodes[DifSpec.BASE_TEX_NODE] + spec_col_n = node_tree.nodes[DifSpec.SPEC_COL_NODE] + spec_multi_n = node_tree.nodes[DifSpec.SPEC_MULT_NODE] + vcol_scale_n = node_tree.nodes[DifSpec.VCOLOR_SCALE_NODE] + vcol_multi_n = node_tree.nodes[DifSpec.VCOLOR_MULT_NODE] + lighting_eval_n = node_tree.nodes[DifSpec.LIGHTING_EVAL_NODE] + compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] + output_n = node_tree.nodes[DifSpec.OUTPUT_NODE] + + # delete existing + node_tree.nodes.remove(node_tree.nodes[DifSpec.OPACITY_NODE]) + + # move existing + spec_multi_n.location.x += pos_x_shift + spec_multi_n.location.y += 100 + lighting_eval_n.location.x += pos_x_shift + compose_lighting_n.location.x += pos_x_shift + output_n.location.x += pos_x_shift + + # node creation + # - column -1 - + sec_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + sec_uv_n.name = sec_uv_n.label = DifSpecWeightMaskDifSpecWeight.SEC_UVMAP_NODE + sec_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 1100) + sec_uv_n.uv_map = _MESH_consts.none_uv + + third_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + third_uv_n.name = third_uv_n.label = DifSpecWeightMaskDifSpecWeight.THIRD_UVMAP_NODE + third_uv_n.location = (start_pos_x - pos_x_shift, start_pos_y + 900) + third_uv_n.uv_map = _MESH_consts.none_uv + + # - column 1 - + sec_spec_col_n = node_tree.nodes.new("ShaderNodeRGB") + sec_spec_col_n.name = sec_spec_col_n.label = DifSpecWeightMaskDifSpecWeight.SEC_SPEC_COL_NODE + sec_spec_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2100) + + mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_tex_n.name = mask_tex_n.label = DifSpecWeightMaskDifSpecWeight.MASK_TEX_NODE + mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) + mask_tex_n.width = 140 + + over_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_tex_n.name = over_tex_n.label = DifSpecWeightMaskDifSpecWeight.OVER_TEX_NODE + over_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) + over_tex_n.width = 140 + + # - column 3 - + sec_shininess_mix_n = node_tree.nodes.new("ShaderNodeMix") + sec_shininess_mix_n.name = sec_shininess_mix_n.label = DifSpecWeightMaskDifSpecWeight.SEC_SHININESS_MIX_NODE + sec_shininess_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2350) + sec_shininess_mix_n.data_type = "RGBA" + sec_shininess_mix_n.blend_type = "MIX" + + sec_spec_mix_n = node_tree.nodes.new("ShaderNodeMix") + sec_spec_mix_n.name = sec_spec_mix_n.label = DifSpecWeightMaskDifSpecWeight.SEC_SPEC_MIX_NODE + sec_spec_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2100) + sec_spec_mix_n.data_type = "RGBA" + sec_spec_mix_n.blend_type = "MIX" + + base_over_a_mix_n = node_tree.nodes.new("ShaderNodeMix") + base_over_a_mix_n.name = base_over_a_mix_n.label = DifSpecWeightMaskDifSpecWeight.BASE_OVER_A_MIX_NODE + base_over_a_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1850) + base_over_a_mix_n.data_type = "RGBA" + base_over_a_mix_n.blend_type = "MIX" + + base_over_mix_n = node_tree.nodes.new("ShaderNodeMix") + base_over_mix_n.name = base_over_mix_n.label = DifSpecWeightMaskDifSpecWeight.BASE_OVER_MIX_NODE + base_over_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1350) + base_over_mix_n.data_type = "RGBA" + base_over_mix_n.blend_type = "MIX" + + # - column 5 - + vcol_spec_mul_n = node_tree.nodes.new("ShaderNodeVectorMath") + vcol_spec_mul_n.name = vcol_spec_mul_n.label = DifSpecWeightMaskDifSpecWeight.VCOL_SPEC_MULT_NODE + vcol_spec_mul_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1900) + vcol_spec_mul_n.operation = "MULTIPLY" + + # links creation + # - column -1 - + node_tree.links.new(sec_uv_n.outputs['UV'], mask_tex_n.inputs['Vector']) + node_tree.links.new(third_uv_n.outputs['UV'], over_tex_n.inputs['Vector']) + + # - column 1 - + node_tree.links.new(sec_spec_col_n.outputs['Color'], sec_spec_mix_n.inputs['B']) + + node_tree.links.new(spec_col_n.outputs['Color'], sec_spec_mix_n.inputs['A']) + + node_tree.links.new(base_tex_n.outputs['Color'], base_over_mix_n.inputs['A']) + node_tree.links.new(base_tex_n.outputs['Alpha'], base_over_a_mix_n.inputs['A']) + + node_tree.links.new(mask_tex_n.outputs['Color'], sec_shininess_mix_n.inputs['Factor']) + node_tree.links.new(mask_tex_n.outputs['Color'], sec_spec_mix_n.inputs['Factor']) + node_tree.links.new(mask_tex_n.outputs['Color'], base_over_a_mix_n.inputs['Factor']) + node_tree.links.new(mask_tex_n.outputs['Color'], base_over_mix_n.inputs['Factor']) + + node_tree.links.new(over_tex_n.outputs['Color'], base_over_mix_n.inputs['B']) + node_tree.links.new(over_tex_n.outputs['Alpha'], base_over_a_mix_n.inputs['A']) + + # - column 3 - + node_tree.links.new(sec_shininess_mix_n.outputs['Result'], lighting_eval_n.inputs['Shininess']) + node_tree.links.new(sec_spec_mix_n.outputs['Result'], spec_multi_n.inputs[0]) + node_tree.links.new(base_over_mix_n.outputs['Result'], spec_multi_n.inputs[1]) + node_tree.links.new(vcol_scale_n.outputs[0], vcol_spec_mul_n.inputs[1]) + node_tree.links.new(base_over_mix_n.outputs['Result'], vcol_multi_n.inputs[1]) + + # - column 4 - + node_tree.links.new(spec_multi_n.outputs['Vector'], vcol_spec_mul_n.inputs[0]) + + # - column 5 - + node_tree.links.new(vcol_spec_mul_n.outputs['Vector'], compose_lighting_n.inputs['Specular Color']) + + @staticmethod + def set_shininess(node_tree, factor): + """Set shininess factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param factor: shininess factor + :type factor: float + """ + + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.SEC_SHININESS_MIX_NODE].inputs["A"].default_value = (factor,) * 4 + + @staticmethod + def set_mask_texture(node_tree, image): + """Set mask texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to mask texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.MASK_TEX_NODE].image = image + + @staticmethod + def set_mask_texture_settings(node_tree, settings): + """Set mask texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecWeightMaskDifSpecWeight.MASK_TEX_NODE], settings) + + @staticmethod + def set_mask_uv(node_tree, uv_layer): + """Set UV layer to mask texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for mask texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.SEC_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_over_texture(node_tree, image): + """Set over texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to over texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.OVER_TEX_NODE].image = image + + @staticmethod + def set_over_texture_settings(node_tree, settings): + """Set over texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecWeightMaskDifSpecWeight.OVER_TEX_NODE], settings) + + @staticmethod + def set_over_uv(node_tree, uv_layer): + """Set UV layer to over texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.THIRD_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_aux3(node_tree, aux_property): + """Set secondary specular color to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: secondary specular color represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + color = (aux_property[0]["value"], aux_property[1]["value"], aux_property[2]["value"], 1.0) + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.SEC_SPEC_COL_NODE].outputs["Color"].default_value = color + + factor = aux_property[3]["value"] + node_tree.nodes[DifSpecWeightMaskDifSpecWeight.SEC_SHININESS_MIX_NODE].inputs["B"].default_value = (factor,) * 4 + + @staticmethod + def set_reflection2(node_tree, value): + """Set second reflection factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param value: reflection factor + :type value: float + """ + + pass # NOTE: reflection attribute doesn't change anything in rendered material, so pass it@staticmethod \ No newline at end of file diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 08c1f12..249dd45 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1101,7 +1101,7 @@ Shader { Shader { PresetName: "dif.spec.oclu" Effect: "eut2.dif.spec.oclu" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_ATTR|BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1254,7 +1254,53 @@ Shader { Shader { PresetName: "dif.spec.over.dif.opac" Effect: "eut2.dif.spec.over.dif.opac" - Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD|ENVMAP" ) + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_ATTR|BLEND_OVER|BLEND_ADD" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0, 1.0, 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 24.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection2" + Value: ( 0.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 1 ) + } +} +Shader { + PresetName: "dif.spec.over.dif.opac.add.env" + Effect: "eut2.dif.spec.over.dif.opac.add.env" + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1286,6 +1332,16 @@ Shader { Tag: "reflection2" Value: ( 0.0 ) } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.5 0.5 0.5 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.2 0.9 ) + } Texture { Tag: "texture[X]:texture_base" Value: "" @@ -1300,7 +1356,7 @@ Shader { Shader { PresetName: "dif.spec.weight" Effect: "eut2.dif.spec.weight" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "HEIGHTMAP" "BLEND_ATTR|BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1430,6 +1486,63 @@ Shader { TexCoord: ( -1 ) } } +Shader { + PresetName: "dif.spec.weight.mask.dif.spec.weight" + Effect: "eut2.dif.spec.weight.mask.dif.spec.weight" + Flavors: ( "SHADOW" "PAINT" "DEPTH" "BLEND_ATTR" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 20.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[3]" + FriendlyTag: "Specular2 & Shininess2" + Value: ( 1.0 1.0 1.0 20.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection2" + Value: ( 0.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_over" + Value: "" + TexCoord: ( 2 ) + } +} Shader { PresetName: "dif.spec.weight.mult2" Effect: "eut2.dif.spec.weight.mult2" @@ -2675,6 +2788,23 @@ Flavor { Type: "FLAT" Name: "flat" } +# Flavor { +# Type: "HEIGHTMAP" +# Name: "heightmap" +# Attribute { +# Format: FLOAT4 +# Tag: "aux[7]" +# # NOTE: can't say in which order these values should be set now (there is no any shader using this flavor now) +# FriendlyTag: "Heightmap (Feather size, Height range, Shape type)" +# Value: ( 0.01 -1.0 1.0 0.0 ) +# } +# Texture { +# Tag: "texture[X]:texture_heightmap" +# FriendlyTag: "Heightmap" +# Value: "" +# TexCoord: ( 0 ) +# } +# } Flavor { Type: "INDENV" Name: "indenv" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index abc96c318c0170fcba101680f1f9303309024a15..c93ce39af3bccce349ea7422e00f74c7d9e1c4d6 100644 GIT binary patch literal 161578 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 KA(@iD-24yu9EfTF literal 154826 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%0D6p(JD+1&O!yZ{MWL*cxtsfO6{|LcV?vS+X4{Z zSBZ}U3Sza*@K|J@dX;+zaEC8p686|TLs+_oxSXuNf@u`~@Y0Za@&;(;C zcQ{gM<8j`WN-Z Bff@h+ From ec592a8dbfede1833285b38df18b6797d595fbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 19 Jun 2025 00:22:34 +0200 Subject: [PATCH 40/56] New Shaders + flavor support * Added support for "dif.spec.weight.mult2.mask2" shader (idk if material preview is correct because scs not used that shader yet) * Added support for "a" flavor for "fakeshadow". * Added preview for "nocull" flavor in "fakeshadow" and "shadowonly" * Added support for "attr" flavor in "dif.weight.dif" * Fixed Shininnes preview in "dif.spec.weight.mult2.weight2" shader * Fixed flavors order i "shadowonly" shader --- .../internals/shaders/eut2/__init__.py | 4 + .../dif_spec_weight_mult2_mask2/__init__.py | 403 ++++++++++++++++++ .../dif_spec_weight_mult2_weight2/__init__.py | 38 +- .../__init__.py | 4 +- .../shaders/eut2/fakeshadow/__init__.py | 130 +++++- .../shaders/eut2/shadowonly/__init__.py | 23 +- .../internals/shaders/flavors/nocull.py | 56 +++ .../io_scs_tools/internals/shaders/shader.py | 3 + addon/io_scs_tools/shader_presets.txt | 179 +++++++- addon/io_scs_tools/supported_effects.bin | Bin 161578 -> 161578 bytes 10 files changed, 811 insertions(+), 29 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_mask2/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/flavors/nocull.py diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index e56e549..fe74a8a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -238,6 +238,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mult2_weight2 import DifSpecWeightMult2Weight2 as Shader + elif effect.startswith("dif.spec.weight.mult2.mask2"): + + from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mult2_mask2 import DifSpecWeightMult2Mask2 as Shader + elif effect.startswith("dif.spec.weight.mult2"): from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mult2 import DifSpecWeightMult2 as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_mask2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_mask2/__init__.py new file mode 100644 index 0000000..b25c463 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_mask2/__init__.py @@ -0,0 +1,403 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mult2 import DifSpecWeightMult2 +from io_scs_tools.internals.shaders.eut2.std_node_groups import mult2_mix_ng +from io_scs_tools.internals.shaders.flavors import tg1 +from io_scs_tools.utils import material as _material_utils + +class DifSpecWeightMult2Mask2(DifSpecWeightMult2): + SEC_UVMAP_NODE = "SecondUVMap" + THIRD_UVMAP_NODE = "ThirdUVMap" + SEC_UV_SCALE_NODE = "SecUVScale" + SEC_SPEC_COLOR_NODE = "SecSpecularColor" + SEC_SHININESS_MIX_NODE = "SecShininnesMix" + SPEC_COLOR_MIX_NODE = "SpecularColorMix" + BASE_1_TEX_NODE = "Base1Tex" + MULT_1_TEX_NODE = "Mult1Tex" + MASK_TEX_NODE = "MaskTex" + SEC_MULT2_MIX_GROUP_NODE = "SecMult2MixGroup" + COMBINED_ALPHA_MIX_NODE = "CombinedAlphaMixes" + COMBINED_MIX_NODE = "CombinedMixes" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + DifSpecWeightMult2.init(node_tree) + + spec_col_n = node_tree.nodes[DifSpecWeightMult2.SPEC_COL_NODE] + mult2_mix_gn = node_tree.nodes[DifSpecWeightMult2.MULT2_MIX_GROUP_NODE] + spec_mult_n = node_tree.nodes[DifSpecWeightMult2.SPEC_MULT_NODE] + vcol_mult_n = node_tree.nodes[DifSpecWeightMult2.VCOLOR_MULT_NODE] + opacity_mult_n = node_tree.nodes[DifSpecWeightMult2.OPACITY_NODE] + lighting_eval_n = node_tree.nodes[DifSpecWeightMult2.LIGHTING_EVAL_NODE] + + # delete existing + node_tree.nodes.remove(opacity_mult_n) + + # move existing + for node in node_tree.nodes: + if node.location.x > start_pos_x + pos_x_shift * 3: + node.location.x += pos_x_shift + + # nodes creation + # - column -1 - + sec_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + sec_uv_n.name = sec_uv_n.label = DifSpecWeightMult2Mask2.SEC_UVMAP_NODE + sec_uv_n.location = (start_pos_x - pos_x_shift * 3, start_pos_y + 600) + sec_uv_n.uv_map = _MESH_consts.none_uv + + third_uv_n = node_tree.nodes.new("ShaderNodeUVMap") + third_uv_n.name = third_uv_n.label = DifSpecWeightMult2Mask2.THIRD_UVMAP_NODE + third_uv_n.location = (start_pos_x - pos_x_shift * 3, start_pos_y + 200) + third_uv_n.uv_map = _MESH_consts.none_uv + + # - column 1 - + sec_uv_scale_n = node_tree.nodes.new("ShaderNodeMapping") + sec_uv_scale_n.name = sec_uv_scale_n.label = DifSpecWeightMult2Mask2.SEC_UV_SCALE_NODE + sec_uv_scale_n.location = (start_pos_x - pos_x_shift, start_pos_y + 600) + sec_uv_scale_n.vector_type = "POINT" + sec_uv_scale_n.inputs['Location'].default_value = sec_uv_scale_n.inputs['Rotation'].default_value = (0.0,) * 3 + sec_uv_scale_n.inputs['Scale'].default_value = (1.0,) * 3 + sec_uv_scale_n.width = 140 + + # - column 3 - + sec_spec_col_n = node_tree.nodes.new("ShaderNodeRGB") + sec_spec_col_n.name = sec_spec_col_n.label = DifSpecWeightMult2Mask2.SEC_SPEC_COLOR_NODE + sec_spec_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2100) + + # - column 4 - + base_1_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + base_1_tex_n.name = base_1_tex_n.label = DifSpecWeightMult2Mask2.BASE_1_TEX_NODE + base_1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) + base_1_tex_n.width = 140 + + mult_1_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mult_1_tex_n.name = mult_1_tex_n.label = DifSpecWeightMult2Mask2.MULT_1_TEX_NODE + mult_1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 600) + mult_1_tex_n.width = 140 + + mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_tex_n.name = mask_tex_n.label = DifSpecWeightMult2Mask2.MASK_TEX_NODE + mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 300) + mask_tex_n.width = 140 + + # - column 5 - + sec_shininess_mix_n = node_tree.nodes.new("ShaderNodeMix") + sec_shininess_mix_n.name = sec_shininess_mix_n.label = DifSpecWeightMult2Mask2.SEC_SHININESS_MIX_NODE + sec_shininess_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2400) + sec_shininess_mix_n.data_type = "RGBA" + sec_shininess_mix_n.blend_type = "MIX" + + spec_col_mix_n = node_tree.nodes.new("ShaderNodeMix") + spec_col_mix_n.name = spec_col_mix_n.label = DifSpecWeightMult2Mask2.SPEC_COLOR_MIX_NODE + spec_col_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2150) + spec_col_mix_n.data_type = "RGBA" + spec_col_mix_n.blend_type = "MIX" + + sec_mult2_mix_gn = node_tree.nodes.new("ShaderNodeGroup") + sec_mult2_mix_gn.name = sec_mult2_mix_gn.label = DifSpecWeightMult2Mask2.SEC_MULT2_MIX_GROUP_NODE + sec_mult2_mix_gn.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 800) + sec_mult2_mix_gn.node_tree = mult2_mix_ng.get_node_group() + + # - column 6 - + combined_a_mix_n = node_tree.nodes.new("ShaderNodeMix") + combined_a_mix_n.name = combined_a_mix_n.label = DifSpecWeightMult2Mask2.COMBINED_ALPHA_MIX_NODE + combined_a_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1250) + combined_a_mix_n.data_type = "RGBA" + combined_a_mix_n.blend_type = "MIX" + + combined_mix_n = node_tree.nodes.new("ShaderNodeMix") + combined_mix_n.name = combined_mix_n.label = DifSpecWeightMult2Mask2.COMBINED_MIX_NODE + combined_mix_n.location = (start_pos_x + pos_x_shift * 4, start_pos_y + 1000) + combined_mix_n.data_type = "RGBA" + combined_mix_n.blend_type = "MIX" + + # links creation + # - column -1 - + node_tree.links.new(sec_uv_n.outputs["UV"], base_1_tex_n.inputs["Vector"]) + node_tree.links.new(sec_uv_n.outputs["UV"], sec_uv_scale_n.inputs["Vector"]) + + node_tree.links.new(third_uv_n.outputs["UV"], mask_tex_n.inputs["Vector"]) + + # - column 1 - + node_tree.links.new(sec_uv_scale_n.outputs["Vector"], mult_1_tex_n.inputs["Vector"]) + + # - column 3 - + node_tree.links.new(sec_spec_col_n.outputs["Color"], spec_col_mix_n.inputs["B"]) + node_tree.links.new(spec_col_n.outputs["Color"], spec_col_mix_n.inputs["A"]) + + node_tree.links.new(base_1_tex_n.outputs["Color"], sec_mult2_mix_gn.inputs["Base Color"]) + node_tree.links.new(base_1_tex_n.outputs["Alpha"], sec_mult2_mix_gn.inputs["Base Alpha"]) + + node_tree.links.new(mult_1_tex_n.outputs["Color"], sec_mult2_mix_gn.inputs["Mult Color"]) + node_tree.links.new(mult_1_tex_n.outputs["Alpha"], sec_mult2_mix_gn.inputs["Mult Alpha"]) + + node_tree.links.new(mask_tex_n.outputs["Color"], sec_shininess_mix_n.inputs["Factor"]) + node_tree.links.new(mask_tex_n.outputs["Color"], spec_col_mix_n.inputs["Factor"]) + node_tree.links.new(mask_tex_n.outputs["Color"], combined_a_mix_n.inputs["Factor"]) + node_tree.links.new(mask_tex_n.outputs["Color"], combined_mix_n.inputs["Factor"]) + + # - column 5 - + node_tree.links.new(sec_shininess_mix_n.outputs["Result"], lighting_eval_n.inputs["Shininess"]) + node_tree.links.new(spec_col_mix_n.outputs["Result"], spec_mult_n.inputs[0]) + + node_tree.links.new(mult2_mix_gn.outputs["Mix Alpha"], combined_a_mix_n.inputs["A"]) + node_tree.links.new(mult2_mix_gn.outputs["Mix Color"], combined_mix_n.inputs["A"]) + + node_tree.links.new(sec_mult2_mix_gn.outputs["Mix Alpha"], combined_a_mix_n.inputs["B"]) + node_tree.links.new(sec_mult2_mix_gn.outputs["Mix Color"], combined_mix_n.inputs["B"]) + + # - column 6 - + node_tree.links.new(combined_a_mix_n.outputs["Result"], spec_mult_n.inputs[1]) + node_tree.links.new(combined_mix_n.outputs["Result"], vcol_mult_n.inputs[1]) + + @staticmethod + def set_shininess(node_tree, factor): + """Set shininess factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param factor: shininess factor + :type factor: float + """ + + node_tree.nodes[DifSpecWeightMult2Mask2.SEC_SHININESS_MIX_NODE].inputs["A"].default_value = (factor,) * 4 + + @staticmethod + def set_reflection2(node_tree, value): + """Set second reflection. + + NOTE: just passed because it's not reflected in 3D viewport anyway + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param value: reflection value + :type value: float + """ + pass + + @staticmethod + def set_aux3(node_tree, aux_property): + """Set second specular and second shininess for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: second specular and shininess factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + color = (aux_property[0]["value"], aux_property[1]["value"], aux_property[2]["value"], 1.0) + node_tree.nodes[DifSpecWeightMult2Mask2.SEC_SPEC_COLOR_NODE].outputs["Color"].default_value = color + + factor = aux_property[3]["value"] + node_tree.nodes[DifSpecWeightMult2Mask2.SEC_SHININESS_MIX_NODE].inputs["B"].default_value = (factor,) * 4 + + @staticmethod + def set_aux5(node_tree, aux_property): + """Set UV scaling factors for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: UV scale factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + DifSpecWeightMult2.set_aux5(node_tree, aux_property) + + node_tree.nodes[DifSpecWeightMult2Mask2.SEC_UV_SCALE_NODE].inputs['Scale'].default_value[0] = aux_property[2]["value"] + node_tree.nodes[DifSpecWeightMult2Mask2.SEC_UV_SCALE_NODE].inputs['Scale'].default_value[1] = aux_property[3]["value"] + + @staticmethod + def set_base_1_texture(node_tree, image): + """Set base_1 texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to base_1 texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecWeightMult2Mask2.BASE_1_TEX_NODE].image = image + + @staticmethod + def set_base_1_texture_settings(node_tree, settings): + """Set base_1 texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecWeightMult2Mask2.BASE_1_TEX_NODE], settings) + + @staticmethod + def set_base_1_uv(node_tree, uv_layer): + """Set UV layer to base_1 texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base_1 texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecWeightMult2Mask2.SEC_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_mult_1_texture(node_tree, image): + """Set mult_1 texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to mult_1 texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecWeightMult2Mask2.MULT_1_TEX_NODE].image = image + + @staticmethod + def set_mult_1_texture_settings(node_tree, settings): + """Set mult_1 texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecWeightMult2Mask2.MULT_1_TEX_NODE], settings) + + @staticmethod + def set_mult_1_uv(node_tree, uv_layer): + """Set UV layer to mult_1 texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for mult_1 texture + :type uv_layer: str + """ + + DifSpecWeightMult2Mask2.set_base_1_uv(node_tree, uv_layer) + + @staticmethod + def set_mask_texture(node_tree, image): + """Set mask texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to mask texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[DifSpecWeightMult2Mask2.MASK_TEX_NODE].image = image + + @staticmethod + def set_mask_texture_settings(node_tree, settings): + """Set mask texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DifSpecWeightMult2Mask2.MASK_TEX_NODE], settings) + + @staticmethod + def set_mask_uv(node_tree, uv_layer): + """Set UV layer to mask texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for mask texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[DifSpecWeightMult2Mask2.THIRD_UVMAP_NODE].uv_map = uv_layer + + + + + + + + @staticmethod + def set_tg1_flavor(node_tree, switch_on): + """Set zero texture generation flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + if switch_on and not tg1.is_set(node_tree): + + out_node = node_tree.nodes[DifSpecWeightMult2Mask2.GEOM_NODE] + in_node = node_tree.nodes[DifSpecWeightMult2Mask2.SEC_UV_SCALE_NODE] + in_node2 = node_tree.nodes[DifSpecWeightMult2Mask2.BASE_1_TEX_NODE] + + out_node.location.x -= 185 * 2 + location = (out_node.location.x + 185, out_node.location.y) + + tg1.init(node_tree, location, out_node.outputs["Position"], in_node.inputs["Vector"]) + tg1.init(node_tree, location, out_node.outputs["Position"], in_node2.inputs["Vector"]) + + elif not switch_on: + + tg1.delete(node_tree) + + @staticmethod + def set_aux1(node_tree, aux_property): + """Set second texture generation scale and rotation. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: secondary specular color represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + if tg1.is_set(node_tree): + # Fix for old float2 aux[1] + if (len(aux_property)) == 2: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], 0) + else: + tg1.set_scale(node_tree, aux_property[0]['value'], aux_property[1]['value'], aux_property[2]['value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py index 0abde0b..01fc613 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_mult2_weight2/__init__.py @@ -22,7 +22,6 @@ from io_scs_tools.internals.shaders.eut2.dif_spec_weight_mult2 import DifSpecWeightMult2 from io_scs_tools.internals.shaders.eut2.std_node_groups import mult2_mix_ng from io_scs_tools.internals.shaders.flavors import tg1 -from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import material as _material_utils @@ -30,6 +29,7 @@ class DifSpecWeightMult2Weight2(DifSpecWeightMult2): SEC_UVMAP_NODE = "ThrdGeometry" SEC_UV_SCALE_NODE = "SecUVScale" SEC_SPEC_COLOR_NODE = "SecSpecularColor" + SEC_SHININESS_MIX_NODE = "SecShininnesMix" SPEC_COLOR_MIX_NODE = "SpecularColorMix" BASE_1_TEX_NODE = "Base1Tex" MULT_1_TEX_NODE = "Mult1Tex" @@ -46,10 +46,6 @@ def get_name(): def init(node_tree): """Initialize node tree with links for this shader. - NOTE: shininess is not set properly as we can't set shininess on material - via node system. So currently only primary shininess is taken into account, - but should be: lerp(shininnes, aux3[3], v_color_alpha) - :param node_tree: node tree on which this shader should be created :type node_tree: bpy.types.NodeTree """ @@ -70,6 +66,7 @@ def init(node_tree): spec_mult_n = node_tree.nodes[DifSpecWeightMult2.SPEC_MULT_NODE] vcol_mult_n = node_tree.nodes[DifSpecWeightMult2.VCOLOR_MULT_NODE] opacity_mult_n = node_tree.nodes[DifSpecWeightMult2.OPACITY_NODE] + lighting_eval_n = node_tree.nodes[DifSpecWeightMult2.LIGHTING_EVAL_NODE] # delete existing node_tree.nodes.remove(opacity_mult_n) @@ -97,6 +94,12 @@ def init(node_tree): sec_spec_col_n.name = sec_spec_col_n.label = DifSpecWeightMult2Weight2.SEC_SPEC_COLOR_NODE sec_spec_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 2100) + sec_shininess_mix_n = node_tree.nodes.new("ShaderNodeMix") + sec_shininess_mix_n.name = sec_shininess_mix_n.label = DifSpecWeightMult2Weight2.SEC_SHININESS_MIX_NODE + sec_shininess_mix_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 2400) + sec_shininess_mix_n.data_type = "RGBA" + sec_shininess_mix_n.blend_type = "MIX" + base_1_tex_n = node_tree.nodes.new("ShaderNodeTexImage") base_1_tex_n.name = base_1_tex_n.label = DifSpecWeightMult2Weight2.BASE_1_TEX_NODE base_1_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 900) @@ -138,6 +141,8 @@ def init(node_tree): node_tree.links.new(base_1_tex_n.inputs["Vector"], sec_uv_n.outputs["UV"]) # pass 1 + node_tree.links.new(sec_shininess_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) + node_tree.links.new(spec_col_mix_n.inputs["Factor"], vcol_group_n.outputs["Vertex Color Alpha"]) node_tree.links.new(spec_col_mix_n.inputs["A"], spec_col_n.outputs["Color"]) node_tree.links.new(spec_col_mix_n.inputs["B"], sec_spec_col_n.outputs["Color"]) @@ -162,6 +167,21 @@ def init(node_tree): node_tree.links.new(vcol_mult_n.inputs[1], combined_mix_n.outputs["Result"]) + # pass 4 + node_tree.links.new(lighting_eval_n.inputs["Shininess"], sec_shininess_mix_n.outputs["Result"]) + + @staticmethod + def set_shininess(node_tree, factor): + """Set shininess factor to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param factor: shininess factor + :type factor: float + """ + + node_tree.nodes[DifSpecWeightMult2Weight2.SEC_SHININESS_MIX_NODE].inputs["A"].default_value = (factor,) * 4 + @staticmethod def set_reflection2(node_tree, value): """Set second reflection. @@ -181,13 +201,15 @@ def set_aux3(node_tree, aux_property): :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param aux_property: second specular and skininess factor represented with property group + :param aux_property: second specular and shininess factor represented with property group :type aux_property: bpy.types.IDPropertyGroup """ - color = _convert_utils.aux_to_node_color(aux_property) + color = (aux_property[0]["value"], aux_property[1]["value"], aux_property[2]["value"], 1.0) + node_tree.nodes[DifSpecWeightMult2Weight2.SEC_SPEC_COLOR_NODE].outputs["Color"].default_value = color - node_tree.nodes[DifSpecWeightMult2Weight2.SEC_SPEC_COLOR_NODE].outputs[0].default_value = color + factor = aux_property[3]["value"] + node_tree.nodes[DifSpecWeightMult2Weight2.SEC_SHININESS_MIX_NODE].inputs["B"].default_value = (factor,) * 4 @staticmethod def set_aux5(node_tree, aux_property): diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py index 400280a..f0d4d6a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py @@ -203,11 +203,11 @@ def set_over_uv(node_tree, uv_layer): @staticmethod def set_aux3(node_tree, aux_property): - """Set secondary specular color to shader. + """Set second specular and second shininess for the shader. :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param aux_property: secondary specular color represented with property group + :param aux_property: second specular and shininess factor represented with property group :type aux_property: bpy.types.IDPropertyGroup """ diff --git a/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py index 7055593..ee33445 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/fakeshadow/__init__.py @@ -18,12 +18,20 @@ # Copyright (C) 2015-2019: SCS Software +from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.flavors import nocull +from io_scs_tools.internals.shaders.flavors import alpha_test +from io_scs_tools.internals.shaders.std_node_groups import output_shader_ng +from io_scs_tools.utils import material as _material_utils class Fakeshadow(BaseShader): WIREFRAME_NODE = "Wireframe" MIX_NODE = "Mix" + UVMAP_NODE = "FirstUVs" + BASE_TEX_NODE = "BaseTex" + OUT_MAT_NODE = "OutMaterial" OUTPUT_NODE = "Output" @staticmethod @@ -47,26 +55,87 @@ def init(node_tree): # node creation wireframe_n = node_tree.nodes.new("ShaderNodeWireframe") wireframe_n.name = wireframe_n.label = Fakeshadow.WIREFRAME_NODE - wireframe_n.location = (start_pos_x, start_pos_y) + wireframe_n.location = (start_pos_x - pos_x_shift, start_pos_y) wireframe_n.use_pixel_size = True wireframe_n.inputs['Size'].default_value = 2.0 + uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") + uvmap_n.name = uvmap_n.label = Fakeshadow.UVMAP_NODE + uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y - 250) + uvmap_n.uv_map = _MESH_consts.none_uv + mix_n = node_tree.nodes.new("ShaderNodeMix") mix_n.name = mix_n.label = Fakeshadow.MIX_NODE - mix_n.location = (start_pos_x + pos_x_shift, start_pos_y) + mix_n.location = (start_pos_x, start_pos_y) mix_n.data_type = "RGBA" mix_n.blend_type = "MIX" mix_n.inputs['A'].default_value = (1, 1, 1, 1) # fakeshadow color mix_n.inputs['B'].default_value = (0, 0, 0, 1) # wireframe color + base_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + base_tex_n.name = base_tex_n.label = Fakeshadow.BASE_TEX_NODE + base_tex_n.location = (start_pos_x, start_pos_y - 250) + base_tex_n.width = 140 + + out_mat_node = node_tree.nodes.new("ShaderNodeGroup") + out_mat_node.name = out_mat_node.label = Fakeshadow.OUT_MAT_NODE + out_mat_node.location = (start_pos_x + pos_x_shift, start_pos_y - 100) + out_mat_node.node_tree = output_shader_ng.get_node_group() + output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") output_n.name = output_n.label = Fakeshadow.OUTPUT_NODE output_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y) # links creation node_tree.links.new(mix_n.inputs['Factor'], wireframe_n.outputs['Fac']) - node_tree.links.new(output_n.inputs['Surface'], mix_n.outputs['Result']) + node_tree.links.new(out_mat_node.inputs['Color'], mix_n.outputs['Result']) + + node_tree.links.new(base_tex_n.inputs['Vector'], uvmap_n.outputs['UV']) + node_tree.links.new(out_mat_node.inputs['Alpha'], base_tex_n.outputs['Alpha']) + + @staticmethod + def finalize(node_tree, material): + """Finalize node tree and material settings. Should be called as last. + + :param node_tree: node tree on which this shader should be finalized + :type node_tree: bpy.types.NodeTree + :param material: material used for this shader + :type material: bpy.types.Material + """ + + material.use_backface_culling = True + material.surface_render_method = "DITHERED" + + if nocull.is_set(node_tree): + material.use_backface_culling = False + + # set proper blend method and possible alpha test pass + if alpha_test.is_set(node_tree): + + # init parent + out_mat_node = node_tree.nodes[Fakeshadow.OUT_MAT_NODE] + output_n = node_tree.nodes[Fakeshadow.OUTPUT_NODE] + + out_mat_node.inputs["Alpha Type"].default_value = 0.0 + + # links creation + node_tree.links.new(out_mat_node.outputs['Shader'], output_n.inputs['Surface']) + + @staticmethod + def set_alpha_test_flavor(node_tree, switch_on): + """Set alpha test flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if alpha test should be switched on or off + :type switch_on: bool + """ + + if switch_on: + alpha_test.init(node_tree) + else: + alpha_test.delete(node_tree) @staticmethod def set_shadow_bias(node_tree, value): @@ -81,14 +150,55 @@ def set_shadow_bias(node_tree, value): pass # NOTE: shadow bias won't be visualized as game uses it's own implementation @staticmethod - def finalize(node_tree, material): - """Finalize node tree and material settings. Should be called as last. + def set_nocull_flavor(node_tree, switch_on): + """Set nocull flavor to this shader. - :param node_tree: node tree on which this shader should be finalized + :param node_tree: node tree of current shader :type node_tree: bpy.types.NodeTree - :param material: material used for this shader - :type material: bpy.types.Material + :param switch_on: flag indication if it should be switched on or off + :type switch_on: bool """ - material.use_backface_culling = True - material.surface_render_method = "DITHERED" + if switch_on: + nocull.init(node_tree) + else: + nocull.delete(node_tree) + + @staticmethod + def set_base_texture(node_tree, image): + """Set base texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to base texture node + :type image: bpy.types.Image + """ + if alpha_test.is_set(node_tree): + node_tree.nodes[Fakeshadow.BASE_TEX_NODE].image = image + + @staticmethod + def set_base_texture_settings(node_tree, settings): + """Set base texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + if alpha_test.is_set(node_tree): + _material_utils.set_texture_settings_to_node(node_tree.nodes[Fakeshadow.BASE_TEX_NODE], settings) + + @staticmethod + def set_base_uv(node_tree, uv_layer): + """Set UV layer to base texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for base texture + :type uv_layer: str + """ + if alpha_test.is_set(node_tree): + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + node_tree.nodes[Fakeshadow.UVMAP_NODE].uv_map = uv_layer diff --git a/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py index b3d5d9d..a19349c 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/shadowonly/__init__.py @@ -20,6 +20,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.base import BaseShader +from io_scs_tools.internals.shaders.flavors import nocull from io_scs_tools.internals.shaders.flavors import alpha_test from io_scs_tools.internals.shaders.std_node_groups import output_shader_ng from io_scs_tools.utils import material as _material_utils @@ -53,7 +54,7 @@ def init(node_tree): # node creation uvmap_n = node_tree.nodes.new("ShaderNodeUVMap") uvmap_n.name = uvmap_n.label = Shadowonly.UVMAP_NODE - uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y - 100) + uvmap_n.location = (start_pos_x - pos_x_shift, start_pos_y - 200) uvmap_n.uv_map = _MESH_consts.none_uv col_n = node_tree.nodes.new("ShaderNodeRGB") @@ -68,7 +69,7 @@ def init(node_tree): out_mat_node = node_tree.nodes.new("ShaderNodeGroup") out_mat_node.name = out_mat_node.label = Shadowonly.OUT_MAT_NODE - out_mat_node.location = (start_pos_x + pos_x_shift, start_pos_y) + out_mat_node.location = (start_pos_x + pos_x_shift, start_pos_y - 100) out_mat_node.node_tree = output_shader_ng.get_node_group() output_n = node_tree.nodes.new("ShaderNodeOutputMaterial") @@ -95,6 +96,9 @@ def finalize(node_tree, material): material.use_backface_culling = True material.surface_render_method = "DITHERED" + if nocull.is_set(node_tree): + material.use_backface_culling = False + # set proper blend method and possible alpha test pass if alpha_test.is_set(node_tree): @@ -122,6 +126,21 @@ def set_alpha_test_flavor(node_tree, switch_on): else: alpha_test.delete(node_tree) + @staticmethod + def set_nocull_flavor(node_tree, switch_on): + """Set nocull flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if it should be switched on or off + :type switch_on: bool + """ + + if switch_on: + nocull.init(node_tree) + else: + nocull.delete(node_tree) + @staticmethod def set_base_texture(node_tree, image): """Set base texture to shader. diff --git a/addon/io_scs_tools/internals/shaders/flavors/nocull.py b/addon/io_scs_tools/internals/shaders/flavors/nocull.py new file mode 100644 index 0000000..51a6afd --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/flavors/nocull.py @@ -0,0 +1,56 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + + +FLAVOR_ID = "nocull" + + +def init(node_tree): + """Initialize sky bottom flavor. + + :param node_tree: node tree on which it will be used + :type node_tree: bpy.types.NodeTree + """ + + # FIXME: move to old system after: https://developer.blender.org/T68406 is resolved + flavor_frame = node_tree.nodes.new(type="NodeFrame") + flavor_frame.name = flavor_frame.label = FLAVOR_ID + + +def delete(node_tree): + """Delete flavor from node tree. + + :param node_tree: node tree from which flavor should be deleted + :type node_tree: bpy.types.NodeTree + """ + + if FLAVOR_ID in node_tree.nodes: + node_tree.nodes.remove(node_tree.nodes[FLAVOR_ID]) + + +def is_set(node_tree): + """Check if flavor is set or not. + + :param node_tree: node tree which should be checked for existance of this flavor + :type node_tree: bpy.types.NodeTree + :return: True if flavor exists; False otherwise + :rtype: bool + """ + return FLAVOR_ID in node_tree.nodes diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 084a743..6e64764 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -116,6 +116,9 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea if effect.endswith(".flipsheet") or ".flipsheet." in effect: flavors["flipsheet"] = True + if effect.endswith(".nocull") or ".nocull." in effect: + flavors["nocull"] = True + __setup_nodes__(material, effect, attr_dict, tex_dict, tex_settings_dict, {}, {}, flavors, recreate) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 249dd45..edb2ddb 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1356,7 +1356,7 @@ Shader { Shader { PresetName: "dif.spec.weight" Effect: "eut2.dif.spec.weight" - Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "HEIGHTMAP" "BLEND_ATTR|BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV|NMAP_TS_CALC" "SHADOW" "PAINT" "TEXGEN0" "ASAFEW" "ALPHA" "DEPTH" "HMAP" "BLEND_ATTR|BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1590,6 +1590,79 @@ Shader { TexCoord: ( 0 ) } } +Shader { + PresetName: "dif.spec.weight.mult2.mask2" + Effect: "eut2.dif.spec.weight.mult2.mask2" + Flavors: ( "SHADOW" "TEXGEN0" "TEXGEN1" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 65.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection" + Value: ( 7.0 ) + } + Attribute { + Format: FLOAT + Tag: "reflection2" + Value: ( 1.3 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[3]" + FriendlyTag: "Specular2 & Shininess2" + Value: ( 1.0 1.0 1.0 40.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[5]" + FriendlyTag: "Mult & Mult_1 UV Scale" + Value: ( 4.4 12.0 4.4 12.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mult" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_base_1" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mult_1" + Value: "" + TexCoord: ( 1 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 2 ) + } +} Shader { PresetName: "dif.spec.weight.mult2.weight2" Effect: "eut2.dif.spec.weight.mult2.weight2" @@ -1661,13 +1734,23 @@ Shader { Shader { PresetName: "dif.spec.weight.weight.dif.spec.weight" Effect: "eut2.dif.spec.weight.weight.dif.spec.weight" - Flavors: ( "NMAP_TS_CALC_OVER" "SHADOW" "TEXGEN0" "TEXGEN1" "ALPHA" ) + Flavors: ( "NMAP_TS_CALC_OVER" "SHADOW" "TEXGEN0" "TEXGEN1" "ALPHA" "HMAP_OVER" "CALIBRATED" ) Flags: 0 Attribute { Format: FLOAT3 Tag: "diffuse" Value: ( 1.0 1.0 1.0 ) } + # NOTE: This shader use new secondary diffuse that is not supported in old material system by conversion tool 2.20 yet. + # To prevent being , I set it to previewonly and hide it. If newer version of conversion tool will support it, this should be changed. + # This attribute will be probably named "aux[4]" in future. + Attribute { + Format: FLOAT3 + Tag: "diffuse_secondary" + Value: ( 1.0 1.0 1.0 ) + PreviewOnly: "True" + Hide: "True" + } Attribute { Format: FLOAT3 Tag: "specular" @@ -1713,7 +1796,7 @@ Shader { Shader { PresetName: "dif.weight.dif" Effect: "eut2.dif.weight.dif" - Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "SHADOW" "PAINT" "TEXGEN0" "TEXGEN1" "ALPHA" "DEPTH" "BLEND_ATTR|BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1759,7 +1842,7 @@ Shader { Shader { PresetName: "fakeshadow" Effect: "eut2.fakeshadow" - Flavors: ( "NOCULL" ) + Flavors: ( "ALPHA_MASK" "NOCULL" ) Flags: 0 Attribute { Format: FLOAT @@ -2036,7 +2119,7 @@ Shader { Shader { PresetName: "shadowonly" Effect: "eut2.shadowonly" - Flavors: ( "NOCULL" "ALPHA_MASK" ) + Flavors: ( "ALPHA_MASK" "NOCULL" ) Flags: 0 Attribute { Format: FLOAT @@ -2788,22 +2871,56 @@ Flavor { Type: "FLAT" Name: "flat" } +# NOTE: this flavors are supported by shaders, but not supported by conversion tool for now. Commented to not display in Blender. +# Flavor { +# Type: "HMAP" +# Name: "heightmap" +# # NOTE: can't say in which order aux[7] values should be set now (there is no any shader using this flavor now). What's more, conversion tool not support that attribute. +# Attribute { +# Format: FLOAT4 +# Tag: "aux[7]" +# FriendlyTag: "Heightmap (Feather size, Height range, Shape type)" +# Value: ( 0.01 -1.0 1.0 0.0 ) +# } +# Texture { +# Tag: "texture[X]:texture_heightmap" +# FriendlyTag: "Heightmap" +# Value: "" +# TexCoord: ( 0 ) +# } +# } # Flavor { -# Type: "HEIGHTMAP" +# Type: "HMAP_OVER" # Name: "heightmap" +# # NOTE: can't say in which order aux[7/8] values should be set now (there is no any shader using this flavor now). What's more, conversion tool not support that attribute. # Attribute { # Format: FLOAT4 # Tag: "aux[7]" -# # NOTE: can't say in which order these values should be set now (there is no any shader using this flavor now) # FriendlyTag: "Heightmap (Feather size, Height range, Shape type)" # Value: ( 0.01 -1.0 1.0 0.0 ) # } +# Attribute { +# Format: FLOAT4 +# Tag: "aux[8]" +# FriendlyTag: "Heightmap2 (Feather size, Height range, Shape type)" +# Value: ( 0.01 -1.0 1.0 0.0 ) +# } # Texture { # Tag: "texture[X]:texture_heightmap" # FriendlyTag: "Heightmap" # Value: "" # TexCoord: ( 0 ) # } +# Texture { +# Tag: "texture[X]:texture_heightmap_over" +# FriendlyTag: "Heightmap Over" +# Value: "" +# TexCoord: ( 1 ) +# } +# } +# Flavor { +# Type: "CALIBRATED" +# Name: "calibrated" # } Flavor { Type: "INDENV" @@ -3014,6 +3131,54 @@ Flavor { Hide: "False" } } +Flavor { + Type: "SUN_BOUNCE" + Name: "sun.bounce" + Attribute { + Format: FLOAT3 + Tag: "aux[0]" + FriendlyTag: "Sheet (playback FPS, frames per row, frames total)" + Value: ( 30.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Sheet frame (width, height)" + Value: ( 0.0 0.0 ) + } +} +Flavor { + Type: "SUN_DIRECT" + Name: "sun.direct" + Attribute { + Format: FLOAT3 + Tag: "aux[0]" + FriendlyTag: "Sheet (playback FPS, frames per row, frames total)" + Value: ( 30.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Sheet frame (width, height)" + Value: ( 0.0 0.0 ) + } +} +Flavor { + Type: "SUN_LUCENT" + Name: "sun.lucent" + Attribute { + Format: FLOAT3 + Tag: "aux[0]" + FriendlyTag: "Sheet (playback FPS, frames per row, frames total)" + Value: ( 30.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Sheet frame (width, height)" + Value: ( 0.0 0.0 ) + } +} Flavor { Type: "TEXGEN0" Name: "tg0" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index c93ce39af3bccce349ea7422e00f74c7d9e1c4d6..885fdd5bc298569d8e2d2886e86079d7a96bcd4e 100644 GIT binary patch literal 161578 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

fbwVQNA=B`omGQYvH`IM>W`3%z*o)sfC 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 KA(@iD-24yu9EfTF From a5df6bbb5628210fcc51f0a80345ac4ef180f9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 21 Jun 2025 01:06:56 +0200 Subject: [PATCH 41/56] Support for new shader & tweaks * Added support for "interior.spatial" shader * Added code to remove outdated "env_factor" attribute from "glass" and "truckpaint" shaders before model will be imported. * Added support for "sun.bounce", "sun.direct" and "sun.lucent" flavors in "flare" shader. * Added support for "tsnmap" flavor in "glass" shader --- addon/io_scs_tools/imp/pit.py | 8 + .../internals/persistent/file_load.py | 21 +- .../internals/shaders/eut2/__init__.py | 8 +- .../internals/shaders/eut2/glass/__init__.py | 62 +++ .../shaders/eut2/interior/__init__.py | 12 + .../shaders/eut2/interior/curtain.py | 2 - addon/io_scs_tools/shader_presets.txt | 484 +++++++++++------- addon/io_scs_tools/supported_effects.bin | Bin 161578 -> 165827 bytes 8 files changed, 388 insertions(+), 209 deletions(-) diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index 115cb35..3e9a2c5 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -181,6 +181,14 @@ def _get_look(section): lprint("W Needless truckpaint texture: 'texture_paintjob' in current material configuration inside material %r, ignoring it!", (mat_alias,)) + # Extra treatment for outdated env_factor in eut2.glass & eut2.truckpaint shaders + # This attribute is outdated and is not used anymore, but it is still present in many materials. + if mat_effect.startswith("eut2.glass") or mat_effect.startswith("eut2.truckpaint"): + # remove env_factor attribute + if attributes.pop("env_factor", None) is not None: + lprint("W Needless attribute: 'env_factor' in current material configuration inside material %r, ignoring it!", + (mat_alias,)) + # Extra treatment for building shaders # # If night version of it is detected, switch it to day one. diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 76322cc..08ea6a5 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -359,9 +359,9 @@ def apply_fixes_for_un_4(): """ Applies fixes for unofficial 2.4.xxxxxx.4 or less: 1. Pre-reload changes and collect data - 2. Reload materials since some got removed/restructed attributes a) Replace tsnmap16 and tsnmapuv16 with tsnmap and tsnmapuv - 3. + 2. Reload materials since some got removed/restructed attributes + 3. Show welcome message """ print("INFO\t- Applying fixes for unofficial version <= 4") @@ -386,7 +386,7 @@ def apply_fixes_for_un_4(): _reload_materials() - # Due to update from Blender 3.6, we let user know he is migrating to Blender 4.3 + # 3. Due to update from Blender 3.6, we let user know he is migrating to Blender 4.3 windows = bpy.data.window_managers[0].windows if len(windows) > 0: msg = ( @@ -397,4 +397,17 @@ def apply_fixes_for_un_4(): ) with bpy.context.temp_override(window=windows[0]): - bpy.ops.wm.scs_tools_show_3dview_report('INVOKE_DEFAULT', message="\n".join(msg)) \ No newline at end of file + bpy.ops.wm.scs_tools_show_3dview_report('INVOKE_DEFAULT', message="\n".join(msg)) + + +def apply_fixes_for_un_7(): + """ + Applies fixes for unofficial 2.4.xxxxxx.7 or less: + 1. Pre-reload changes and collect data + 2. Reload materials since some got removed/restructed attributes + """ + + print("INFO\t- Applying fixes for unofficial version <= 7") + + # 2. reload all materials + _reload_materials() diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index fe74a8a..24bbfa9 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -40,7 +40,7 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.window.lit import WindowLit as Shader - elif effect == "interior.lit": + elif effect == "interior.lit" or effect == "interior.spatial.lit": from io_scs_tools.internals.shaders.eut2.interior import InteriorLit as Shader @@ -48,10 +48,6 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.interior.curtain import InteriorCurtain as Shader - # elif effect == "interior.spatial.lit": - # - # from io_scs_tools.internals.shaders.eut2.interior.spatial import InteriorSpatial as Shader - elif effect == "reflective": from io_scs_tools.internals.shaders.eut2.reflective import Reflective as Shader @@ -64,7 +60,7 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.grass import Grass as Shader - elif effect == "glass": + elif effect.startswith("glass"): from io_scs_tools.internals.shaders.eut2.glass import Glass as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py index 4f51f58..6e78719 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py @@ -27,6 +27,7 @@ from io_scs_tools.internals.shaders.eut2.std_node_groups import lighting_evaluator_ng from io_scs_tools.internals.shaders.eut2.std_node_groups import refl_normal_ng from io_scs_tools.internals.shaders.eut2.std_node_groups import vcolor_input_ng +from io_scs_tools.internals.shaders.flavors import nmap from io_scs_tools.utils import convert as _convert_utils from io_scs_tools.utils import material as _material_utils @@ -552,3 +553,64 @@ def set_tint(node_tree, color): color = _convert_utils.to_node_color(color) node_tree.nodes[Glass.TINT_COL_NODE].outputs[0].default_value = color + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[Glass.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[Glass.GEOM_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal']) + else: + nmap.delete(node_tree) + + @staticmethod + def set_nmap_texture(node_tree, image): + """Set normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Image + """ + + nmap.set_texture(node_tree, image) + + @staticmethod + def set_nmap_texture_settings(node_tree, settings): + """Set normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + nmap.set_texture_settings(node_tree, settings) + + @staticmethod + def set_nmap_uv(node_tree, uv_layer): + """Set UV layer to normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + nmap.set_uv(node_tree, uv_layer) \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py index e84a0d0..893c4eb 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/__init__.py @@ -272,3 +272,15 @@ def set_perturbation_mapping(node_tree, uv_layer): uv_layer = _MESH_consts.none_uv node_tree.nodes[InteriorLit.PERT_UVMAP_NODE].uv_map = uv_layer + + @staticmethod + def set_lit_flavor(node_tree, switch_on): + """Set lit for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if flavor should be switched on or off + :type switch_on: bool + """ + + pass diff --git a/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py b/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py index 22ba32e..1ec8a56 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py +++ b/addon/io_scs_tools/internals/shaders/eut2/interior/curtain.py @@ -85,8 +85,6 @@ def init(node_tree): # - column 2 - node_tree.links.new(base_over_mix_n.outputs['Result'], vgcol_mult_n.inputs[0]) - - @staticmethod def set_over_texture(node_tree, image): """Set overlying texture to shader. diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index edb2ddb..a1b3e9e 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1853,6 +1853,7 @@ Shader { Shader { PresetName: "flare" Effect: "eut2.flare" + Flavors: ( "SUN_BOUNCE|SUN_DIRECT|SUN_LUCENT" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1874,6 +1875,7 @@ Shader { Shader { PresetName: "glass" Effect: "eut2.glass" + Flavors: ( "NMAP_TS" ) Flags: 0 Attribute { Format: FLOAT3 @@ -1895,11 +1897,6 @@ Shader { Tag: "add_ambient" Value: ( 0.0 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 1.0 1.0 1.0 ) - } Attribute { Format: FLOAT2 Tag: "fresnel" @@ -1961,6 +1958,291 @@ Shader { TexCoord: ( 0 ) } } +Shader { + PresetName: "interior.lit" + Effect: "eut2.interior.lit" + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 65.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.200000003 0.8999999762 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 1700.0 17.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Atlas Dimensions (X,Z)" + Value: ( 8.0 4.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "Glass Color (R,G,B,Factor)" + Value: ( 1.0 1.0 1.0 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Room Dimensions (X,Z)" + Value: ( 3.5 3.5 ) + } + Texture { + Tag: "texture[X]:texture_base" + FriendlyTag: "Atlas" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer0" + FriendlyTag: "Emissive (Day)" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer1" + FriendlyTag: "Emissive (Night)" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + FriendlyTag: "LUT" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal Map" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } + Mapping { + Tag: "perturbation" + TexCoord: ( 1 ) + } +} +Shader { + PresetName: "interior.curtain.lit" + Effect: "eut2.interior.curtain.lit" + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 65.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.200000003 0.8999999762 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 1700.0 17.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Atlas Dimensions (X,Z)" + Value: ( 8.0 4.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "Glass Color (R,G,B,Factor)" + Value: ( 1.0 1.0 1.0 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Room Dimensions (X,Z)" + Value: ( 3.5 3.5 ) + } + Texture { + Tag: "texture[X]:texture_base" + FriendlyTag: "Atlas" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_over" + FriendlyTag: "Curtains" + Value: "" + TexCoord: ( 2 ) + } + Texture { + Tag: "texture[X]:texture_layer0" + FriendlyTag: "Emissive (Day)" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer1" + FriendlyTag: "Emissive (Night)" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + FriendlyTag: "LUT" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal Map" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } + Mapping { + Tag: "perturbation" + TexCoord: ( 1 ) + } +} +Shader { + PresetName: "interior.spatial.lit" + Effect: "eut2.interior.spatial.lit" + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 65.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "fresnel" + Value: ( 0.200000003 0.8999999762 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 1700.0 17.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[1]" + FriendlyTag: "Atlas Dimensions (X,Z)" + Value: ( 8.0 4.0 ) + } + Attribute { + Format: FLOAT4 + Tag: "aux[2]" + FriendlyTag: "Glass Color (R,G,B,Factor)" + Value: ( 1.0 1.0 1.0 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[0]" + FriendlyTag: "Room Dimensions (X,Z)" + Value: ( 3.5 3.5 ) + } + Texture { + Tag: "texture[X]:texture_base" + FriendlyTag: "Atlas" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer0" + FriendlyTag: "Emissive (Day)" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_layer1" + FriendlyTag: "Emissive (Night)" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + FriendlyTag: "LUT" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal Map" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } + Mapping { + Tag: "perturbation" + TexCoord: ( 1 ) + } +} Shader { PresetName: "lamp" Effect: "eut2.lamp" @@ -2458,198 +2740,6 @@ Shader { TexCoord: ( 1 ) } } -Shader { - PresetName: "interior.lit" - Effect: "eut2.interior.lit" - Flags: 0 - Attribute { - Format: FLOAT3 - Tag: "diffuse" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT3 - Tag: "specular" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT - Tag: "shininess" - Value: ( 65.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "fresnel" - Value: ( 0.200000003 0.8999999762 ) - } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" - Value: ( 1700.0 17.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[1]" - FriendlyTag: "Atlas Dimensions (X,Z)" - Value: ( 8.0 4.0 ) - } - Attribute { - Format: FLOAT4 - Tag: "aux[2]" - FriendlyTag: "Glass Color (R,G,B,Factor)" - Value: ( 1.0 1.0 1.0 0.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[0]" - FriendlyTag: "Room Dimensions (X,Z)" - Value: ( 3.5 3.5 ) - } - Texture { - Tag: "texture[X]:texture_base" - FriendlyTag: "Atlas" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_layer0" - FriendlyTag: "Emissive (Day)" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_layer1" - FriendlyTag: "Emissive (Night)" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_mask" - FriendlyTag: "LUT" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_nmap_detail" - FriendlyTag: "Normal Map" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/building_reflection/building_ref" - TexCoord: ( -1 ) - } - Mapping { - Tag: "perturbation" - TexCoord: ( 1 ) - } -} -Shader { - PresetName: "interior.curtain.lit" - Effect: "eut2.interior.curtain.lit" - Flags: 0 - Attribute { - Format: FLOAT3 - Tag: "diffuse" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT3 - Tag: "specular" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT - Tag: "shininess" - Value: ( 65.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "fresnel" - Value: ( 0.200000003 0.8999999762 ) - } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 1.0 1.0 1.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" - Value: ( 1700.0 17.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[1]" - FriendlyTag: "Atlas Dimensions (X,Z)" - Value: ( 8.0 4.0 ) - } - Attribute { - Format: FLOAT4 - Tag: "aux[2]" - FriendlyTag: "Glass Color (R,G,B,Factor)" - Value: ( 1.0 1.0 1.0 0.0 ) - } - Attribute { - Format: FLOAT2 - Tag: "aux[0]" - FriendlyTag: "Room Dimensions (X,Z)" - Value: ( 3.5 3.5 ) - } - Texture { - Tag: "texture[X]:texture_base" - FriendlyTag: "Atlas" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_over" - FriendlyTag: "Curtains" - Value: "" - TexCoord: ( 2 ) - } - Texture { - Tag: "texture[X]:texture_layer0" - FriendlyTag: "Emissive (Day)" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_layer1" - FriendlyTag: "Emissive (Night)" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_mask" - FriendlyTag: "LUT" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_nmap_detail" - FriendlyTag: "Normal Map" - Value: "" - TexCoord: ( 0 ) - } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/building_reflection/building_ref" - TexCoord: ( -1 ) - } - Mapping { - Tag: "perturbation" - TexCoord: ( 1 ) - } -} Flavor { Type: "AIRBRUSH" Name: "airbrush" diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index 885fdd5bc298569d8e2d2886e86079d7a96bcd4e..f1d751c0e71293b02f751c41a06da6478211ba45 100644 GIT binary patch literal 165827 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 literal 161578 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

fbwVQNA=B`omGQYvH`IM>W`3%z*o)sfC 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 Date: Tue, 1 Jul 2025 22:39:44 +0200 Subject: [PATCH 42/56] New shaders + fixes * Added support for "leaves" shader (preview still needs to be done correctly). * Added support for "light" shader (same as light_tex but without texture). * Added support for "particle" shader. * Added new flavors to "light.tex" shader (lsbounce/lsdirect/lslucent). * Added new flavors to "unlit.vcol.tex" shader (lsbounce/lsdirect/lslucent). * Added new flavors to "water" shader (stream, tostream/towater, mirror). * Replaced few icons next to shader names in shader list. * Small change in unofficial tool version detection. * Fixed alpha in light.tex shader. * Fixed errors about unsupported attributes i some shaders. * Fixed error in "water" shader, when scale is set to 0. --- addon/io_scs_tools/imp/pit.py | 5 +- .../internals/persistent/file_load.py | 17 +- .../internals/shaders/eut2/__init__.py | 14 +- .../internals/shaders/eut2/leaves/__init__.py | 172 ++++++++++++++++++ .../internals/shaders/eut2/light/__init__.py | 116 ++++++++++++ .../shaders/eut2/light_tex/__init__.py | 26 ++- .../shaders/eut2/particle/__init__.py | 52 ++++++ .../internals/shaders/eut2/water/__init__.py | 8 +- .../properties/addon_preferences.py | 16 +- addon/io_scs_tools/shader_presets.txt | 156 ++++++++++++++-- addon/io_scs_tools/supported_effects.bin | Bin 165827 -> 166152 bytes addon/io_scs_tools/utils/info.py | 15 +- 12 files changed, 564 insertions(+), 33 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/leaves/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/light/__init__.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/particle/__init__.py diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index 3e9a2c5..54280a0 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -153,7 +153,10 @@ def _get_look(section): # now strip flipflake flavor string, remove flipflake related texture & report it if has_flipflake: - mat_effect = mat_effect.replace(".flipflake", "") + if mat_effect.find(".flipflakeuv") != -1: + mat_effect = mat_effect.replace(".flipflakeuv", "") + else: + mat_effect = mat_effect.replace(".flipflake", "") if textures.pop("texture_flakenoise", None): sec.remove_section("Texture", "Tag", r"^[\w\[\]]+:texture_flakenoise$") diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 08ea6a5..6184670 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -58,16 +58,21 @@ def post_load(scene): for version, func in VERSIONS_LIST: if _info_utils.cmp_ver_str(last_load_bt_ver, version) <= 0: - # for, for unofficial versions + + # Apply fixes ONLY if your last loaded tools version is official to prevent unnecessary code execution. + while len(last_load_bt_ver.split(".")) <= 3: + + # try to add apply fixed function as callback, if failed execute fixes right now + if not AsyncPathsInit.append_callback(func): + func() + for version2, func2 in VERSIONS_LIST_UNOFFICIAL: - if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) <= 0: + if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) < 0: + # try to add apply fixed function as callback, if failed execute fixes right now if not AsyncPathsInit.append_callback(func2): func2() - else: - # try to add apply fixed function as callback, if failed execute fixes right now - if not AsyncPathsInit.append_callback(func): - func() + # as last update "last load" Blender Tools version to current _get_scs_globals().last_load_bt_version = _info_utils.get_tools_version() diff --git a/addon/io_scs_tools/internals/shaders/eut2/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/__init__.py index 24bbfa9..02c9e33 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/__init__.py @@ -32,7 +32,7 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.none import NNone as Shader - elif effect == "water": + elif effect.startswith("water"): from io_scs_tools.internals.shaders.eut2.water import Water as Shader @@ -60,6 +60,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.grass import Grass as Shader + elif effect.startswith("leaves"): + + from io_scs_tools.internals.shaders.eut2.leaves import Leaves as Shader + elif effect.startswith("glass"): from io_scs_tools.internals.shaders.eut2.glass import Glass as Shader @@ -76,6 +80,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.shadowonly import Shadowonly as Shader + elif effect.startswith("particle"): + + from io_scs_tools.internals.shaders.eut2.particle import Particle as Shader + elif effect.startswith("lightmap.night"): from io_scs_tools.internals.shaders.eut2.lightmap.night import LightMapNight as Shader @@ -84,6 +92,10 @@ def get_shader(effect): from io_scs_tools.internals.shaders.eut2.light_tex import LightTex as Shader + elif effect.startswith("light"): + + from io_scs_tools.internals.shaders.eut2.light import Light as Shader + elif effect.startswith("retroreflective"): from io_scs_tools.internals.shaders.eut2.retroreflective import Retroreflective as Shader diff --git a/addon/io_scs_tools/internals/shaders/eut2/leaves/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/leaves/__init__.py new file mode 100644 index 0000000..ff4851d --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/leaves/__init__.py @@ -0,0 +1,172 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.dif import Dif +from io_scs_tools.utils import material as _material_utils + +class Leaves(Dif): + MASK_TEX_NODE = "MaskTex" + SPEC_MULT_NODE = "SpecMultiplier" + SEP_MASK_COL_NODE = "SeparateMaskCol" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + DISCLAIMER: This shader is provisional and should be reworked as soon, as more information about leaves shader will be available. + Please, do not treat that preview as real equivalent of game shader. Please, chceck everything in game after export. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + Dif.init(node_tree) + + uvmap_n = node_tree.nodes[Dif.UVMAP_NODE] + base_tex_n = node_tree.nodes[Dif.BASE_TEX_NODE] + compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] + spec_col_n = node_tree.nodes[Dif.SPEC_COL_NODE] + diff_mult_n = node_tree.nodes[Dif.DIFF_MULT_NODE] + + opacity_mult_n = node_tree.nodes[Dif.OPACITY_NODE] + vcol_scale_n = node_tree.nodes[Dif.VCOLOR_SCALE_NODE] + vcol_mult_n = node_tree.nodes[Dif.VCOLOR_MULT_NODE] + + # delete existing + node_tree.nodes.remove(opacity_mult_n) + node_tree.nodes.remove(vcol_scale_n) + node_tree.nodes.remove(vcol_mult_n) + + # node creation + # - column 1 - + mask_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + mask_tex_n.name = mask_tex_n.label = Leaves.MASK_TEX_NODE + mask_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) + mask_tex_n.width = 140 + + # - column 2 - + sep_mask_col_n = node_tree.nodes.new("ShaderNodeSeparateColor") + sep_mask_col_n.name = sep_mask_col_n.label = Leaves.SEP_MASK_COL_NODE + sep_mask_col_n.location = (start_pos_x + pos_x_shift * 2, start_pos_y + 1200) + + # - column 3 - + spec_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + spec_mult_n.name = spec_mult_n.label = Leaves.SPEC_MULT_NODE + spec_mult_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1900) + spec_mult_n.operation = "MULTIPLY" + + # links creation + # - column -1 - + node_tree.links.new(uvmap_n.outputs['UV'], mask_tex_n.inputs['Vector']) + + # - column 1 - + node_tree.links.new(spec_col_n.outputs['Color'], spec_mult_n.inputs[0]) + + node_tree.links.new(base_tex_n.outputs['Color'], diff_mult_n.inputs[1]) + node_tree.links.new(base_tex_n.outputs['Alpha'], compose_lighting_n.inputs['Alpha']) + + node_tree.links.new(mask_tex_n.outputs['Color'], sep_mask_col_n.inputs['Color']) + + # - column 2 - + # Idk if this is correct. This is made to prevent specular shading on model. It can also be done by "Flat Light" in light evaluator, + # but I think, that B channel is used for specular mask because that channel is very similar to specular effect on leaves in game. + # It also can be G or anything else. I don't have time to test that shader in game. + node_tree.links.new(sep_mask_col_n.outputs['Blue'], spec_mult_n.inputs[1]) + + # - column 3 - + node_tree.links.new(spec_mult_n.outputs['Vector'], compose_lighting_n.inputs['Specular Color']) + + @staticmethod + def finalize(node_tree, material): + """Finalize node tree and material settings. Should be called as last. + + :param node_tree: node tree on which this shader should be finalized + :type node_tree: bpy.types.NodeTree + :param material: material used for this shader + :type material: bpy.types.Material + """ + + compose_lighting_n = node_tree.nodes[Leaves.COMPOSE_LIGHTING_NODE] + + material.use_backface_culling = False + material.surface_render_method = "DITHERED" + compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 + + if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Leaves.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: + node_tree.links.remove(node_tree.nodes[Leaves.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links[0]) + + @staticmethod + def set_mask_texture(node_tree, image): + """Set mask texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assigned to mask texture node + :type image: bpy.types.Texture + """ + + node_tree.nodes[Leaves.MASK_TEX_NODE].image = image + + @staticmethod + def set_mask_texture_settings(node_tree, settings): + """Set mask texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[Leaves.MASK_TEX_NODE], settings) + + @staticmethod + def set_mask_uv(node_tree, uv_layer): + """Set UV layer to mask texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for mask texture + :type uv_layer: str + """ + + Dif.set_base_uv(node_tree, uv_layer) + + @staticmethod + def set_aux1(node_tree, aux_property): + """Set LOD selector for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: LOD selector represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + # NOTE: As long as we not use it for now, we can pass it. + pass \ No newline at end of file diff --git a/addon/io_scs_tools/internals/shaders/eut2/light/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/light/__init__.py new file mode 100644 index 0000000..9389d7f --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/light/__init__.py @@ -0,0 +1,116 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.dif import Dif + + +class Light(Dif): + SPEC_MULT_NODE = "SpecMultiplier" + RGB_TO_BW_ALPHA_NODE = "RGBToBWColor" + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + start_pos_x = 0 + start_pos_y = 0 + + pos_x_shift = 185 + + # init parent + Dif.init(node_tree) + + # delete existing + node_tree.nodes.remove(node_tree.nodes[Dif.OPACITY_NODE]) + node_tree.nodes.remove(node_tree.nodes[Dif.LIGHTING_EVAL_NODE]) + node_tree.nodes.remove(node_tree.nodes[Dif.BASE_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[Dif.UVMAP_NODE]) + + vcol_group_n = node_tree.nodes[Dif.VCOL_GROUP_NODE] + v_col_mult_n = node_tree.nodes[Dif.VCOLOR_MULT_NODE] + v_col_mult_n.inputs[1].default_value = (1.0,) *3 + compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] + compose_lighting_n.inputs['Diffuse Lighting'].default_value = (1.0,) * 4 + compose_lighting_n.inputs['Specular Lighting'].default_value = (1.0,) * 4 + + # node creation + spec_mult_n = node_tree.nodes.new("ShaderNodeVectorMath") + spec_mult_n.name = Light.SPEC_MULT_NODE + spec_mult_n.label = Light.SPEC_MULT_NODE + spec_mult_n.location = (start_pos_x + pos_x_shift * 5, start_pos_y + 1850) + spec_mult_n.operation = "MULTIPLY" + + rgb_to_bw_n = node_tree.nodes.new("ShaderNodeRGBToBW") + rgb_to_bw_n.name = Light.RGB_TO_BW_ALPHA_NODE + rgb_to_bw_n.label = Light.RGB_TO_BW_ALPHA_NODE + rgb_to_bw_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1300) + + # links creation + node_tree.links.new(spec_mult_n.inputs[0], v_col_mult_n.outputs[0]) + node_tree.links.new(compose_lighting_n.inputs["Specular Color"], spec_mult_n.outputs[0]) + + node_tree.links.new(rgb_to_bw_n.inputs["Color"], vcol_group_n.outputs["Vertex Color"]) + node_tree.links.new(compose_lighting_n.inputs["Alpha"], rgb_to_bw_n.outputs["Val"]) + + @staticmethod + def finalize(node_tree, material): + """Finalize node tree and material settings. Should be called as last. + + :param node_tree: node tree on which this shader should be finalized + :type node_tree: bpy.types.NodeTree + :param material: material used for this shader + :type material: bpy.types.Material + """ + + # in game it gets added to framebuffer, however we don't have access to frame buffer thus make approximation with alpha blending + material.surface_render_method = "BLENDED" + node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs["Alpha Type"].default_value = 1.0 + + @staticmethod + def set_env_factor(node_tree, color): + """Set environment factor color to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param color: environment color + :type color: Color or tuple + """ + + pass # NOTE: as this variant doesn't use env_factor we just ignore it + + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminance boost factor for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + pass # NOTE: as this variant doesn't use luminance effect we just ignore this factor diff --git a/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py index bdb1122..bac6ac4 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/light_tex/__init__.py @@ -84,7 +84,8 @@ def finalize(node_tree, material): :param material: material used for this shader :type material: bpy.types.Material """ - Dif.finalize(node_tree, material) + # Cause problems with proper links creation ("rgb_to_bw_n" to "compose_lighting_n") + # Dif.finalize(node_tree, material) # in game it gets added to framebuffer, however we don't have access to frame buffer thus make approximation with alpha blending material.surface_render_method = "BLENDED" @@ -150,6 +151,18 @@ def set_blend_mult_flavor(node_tree, switch_on): pass # NOTE: no support for this flavor; overriding with empty function + @staticmethod + def set_env_factor(node_tree, color): + """Set environment factor color to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param color: environment color + :type color: Color or tuple + """ + + pass # NOTE: as this variant doesn't use env_factor we just ignore it + @staticmethod def set_aux0(node_tree, aux_property): """Set depth bias. @@ -161,3 +174,14 @@ def set_aux0(node_tree, aux_property): """ pass # NOTE: no support for this parameter as there is no way to simulate eye-space z-bias + + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminance boost factor for the shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity factor represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + pass # NOTE: as this variant doesn't use luminance effect we just ignore this factor diff --git a/addon/io_scs_tools/internals/shaders/eut2/particle/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/particle/__init__.py new file mode 100644 index 0000000..8519d49 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/particle/__init__.py @@ -0,0 +1,52 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +from io_scs_tools.internals.shaders.eut2.dif import Dif + + +class Particle(Dif): + + @staticmethod + def get_name(): + """Get name of this shader file with full modules path.""" + return __name__ + + @staticmethod + def init(node_tree): + """Initialize node tree with links for this shader. + + :param node_tree: node tree on which this shader should be created + :type node_tree: bpy.types.NodeTree + """ + + # init parent + Dif.init(node_tree) + + @staticmethod + def set_aux5(node_tree, aux_property): + """Set luminosity boost factor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param aux_property: luminosity output represented with property group + :type aux_property: bpy.types.IDPropertyGroup + """ + + pass # NOTE: as this variant doesn't use luminance effect we just ignore this factor diff --git a/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py index abc30f5..cac49aa 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/water/__init__.py @@ -328,8 +328,8 @@ def set_aux3(node_tree, aux_property): _LAYER0_NMAP_MAPPING_NODE = Water.LAYER0_NMAP_UID + Water.POSTFIX_MAPPING_NODE layer0_mapping_n = node_tree.nodes[_LAYER0_NMAP_MAPPING_NODE] - layer0_mapping_n.inputs['Scale'].default_value[0] = 1 / aux_property[2]['value'] - layer0_mapping_n.inputs['Scale'].default_value[1] = 1 / aux_property[3]['value'] + layer0_mapping_n.inputs['Scale'].default_value[0] = 1 / aux_property[2]['value'] if aux_property[2]['value'] != 0 else 0.1 + layer0_mapping_n.inputs['Scale'].default_value[1] = 1 / aux_property[3]['value'] if aux_property[3]['value'] != 0 else 0.1 yaw = math.radians(aux_property[0]['value']) water_stream_n = node_tree.nodes[Water.WATER_STREAM_NODE] @@ -349,8 +349,8 @@ def set_aux4(node_tree, aux_property): _LAYER1_NMAP_MAPPING_NODE = Water.LAYER1_NMAP_UID + Water.POSTFIX_MAPPING_NODE layer1_mapping_n = node_tree.nodes[_LAYER1_NMAP_MAPPING_NODE] - layer1_mapping_n.inputs['Scale'].default_value[0] = 1 / aux_property[2]['value'] - layer1_mapping_n.inputs['Scale'].default_value[1] = 1 / aux_property[3]['value'] + layer1_mapping_n.inputs['Scale'].default_value[0] = 1 / aux_property[2]['value'] if aux_property[2]['value'] != 0 else 0.1 + layer1_mapping_n.inputs['Scale'].default_value[1] = 1 / aux_property[3]['value'] if aux_property[3]['value'] != 0 else 0.1 yaw = math.radians(aux_property[0]['value']) water_stream_n = node_tree.nodes[Water.WATER_STREAM_NODE] diff --git a/addon/io_scs_tools/properties/addon_preferences.py b/addon/io_scs_tools/properties/addon_preferences.py index 2ee88dc..992c94a 100644 --- a/addon/io_scs_tools/properties/addon_preferences.py +++ b/addon/io_scs_tools/properties/addon_preferences.py @@ -140,14 +140,26 @@ def retrieve_shader_presets_items(self, context): icon_str = 'MATERIAL' elif "glass" in preset_name: icon_str = 'MOD_LATTICE' + elif "grass" in preset_name: + icon_str = 'STRANDS' + elif "interior" in preset_name: + icon_str = 'OUTLINER_OB_LATTICE' elif "lamp" in preset_name: icon_str = 'LIGHT_SPOT' + elif "mlaa" in preset_name: + icon_str = 'GROUP_UVS' + elif "none" in preset_name and preset_name != "": + icon_str = 'MESH_CIRCLE' + elif "particle" in preset_name: + icon_str = 'PARTICLE_TIP' elif "shadowonly" in preset_name: icon_str = 'MAT_SPHERE_SKY' elif "truckpaint" in preset_name: icon_str = 'AUTO' - elif "mlaa" in preset_name: - icon_str = 'GROUP_UVS' + elif "water" in preset_name: + icon_str = 'MOD_OCEAN' + elif "window" in preset_name: + icon_str = 'OUTLINER_OB_LATTICE' elif preset_name == "": icon_str = 'X' else: diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index a1b3e9e..3dc51b3 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -2280,9 +2280,93 @@ Shader { TexCoord: ( 1 ) } } +Shader { + PresetName: "leaves" + Effect: "eut2.leaves" + Flavors: ( "NMAP_TS_CALC" "SHADOW" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 0.0 0.0 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "shininess" + Value: ( 4.0 ) + } + Attribute { + Format: FLOAT + Tag: "add_ambient" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT + Tag: "aux[0]" + FriendlyTag: "Shadow Offset" + Value: ( 0.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "aux[1]" + FriendlyTag: "LOD Selector" + Value: ( 0.0 1.0 0.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } + Texture { + Tag: "texture[X]:texture_mask" + Value: "" + TexCoord: ( 0 ) + } + Mapping { + Tag: "vertex_pos" + FriendlyTag: "Vertex Shift" + TexCoord: ( 1 ) + } + Mapping { + Tag: "unknown" + TexCoord: ( 2 ) + } +} +Shader { + PresetName: "light" + Effect: "eut2.light" + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "specular" + Value: ( 0.0 0.0 0.0 ) + } + Attribute { + Format: FLOAT3 + Tag: "env_factor" + Value: ( 0.0 0.0 0.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } +} Shader { PresetName: "light.tex" Effect: "eut2.light.tex" + Flavors: ( "LSBOUNCE|LSDIRECT|LSLUCENT" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2313,6 +2397,7 @@ Shader { } Texture { Tag: "texture[X]:texture_base" + FriendlyTag: "Mask" Value: "" TexCoord: ( 0 ) } @@ -2355,6 +2440,7 @@ Shader { TexCoord: ( 0 ) } } +# NOTE: TODO: mix00 shaders (can't recreate it cause it's not used anywhere and it's unique) Shader { PresetName: "mlaaweight" Effect: "eut2.mlaaweight" @@ -2366,6 +2452,28 @@ Shader { Effect: "eut2.none" Flags: 0 } +Shader { + PresetName: "particle" + Effect: "eut2.particle" + Flavors: ( "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flags: 0 + Attribute { + Format: FLOAT3 + Tag: "diffuse" + Value: ( 1.0 1.0 1.0 ) + } + Attribute { + Format: FLOAT2 + Tag: "aux[5]" + FriendlyTag: "Luminance Output (Day, Night) (nit)" + Value: ( 0.0 0.0 ) + } + Texture { + Tag: "texture[X]:texture_base" + Value: "" + TexCoord: ( 0 ) + } +} Shader { PresetName: "reflective" Effect: "eut2.reflective" @@ -2524,7 +2632,7 @@ Shader { Shader { PresetName: "truckpaint" Effect: "eut2.truckpaint" - Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "COLORMASK|AIRBRUSH" "FLIPFLAKE" "ALTUV" "ASAFEWA" ) + Flavors: ( "NMAP_TS|NMAP_TS_UV" "SHADOW" "COLORMASK|AIRBRUSH" "FLIPFLAKE|FLIPFLAKEUV" "ALTUV" "ASAFEWA" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2546,11 +2654,6 @@ Shader { Tag: "add_ambient" Value: ( 0.0 ) } - Attribute { - Format: FLOAT3 - Tag: "env_factor" - Value: ( 1.0 1.0 1.0 ) - } Attribute { Format: FLOAT2 Tag: "fresnel" @@ -2592,7 +2695,7 @@ Shader { Shader { PresetName: "unlit.vcol.tex" Effect: "eut2.unlit.vcol.tex" - Flavors: ( "FADESHEET|FLIPSHEET" "PAINT" "ALPHA" "AWHITE" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) + Flavors: ( "FADESHEET|FLIPSHEET" "LSBOUNCE|LSDIRECT|LSLUCENT" "PAINT" "NOZ" "ALPHA" "AWHITE" "DEPTH" "BLEND_OVER|BLEND_MULT|BLEND_ADD" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2614,6 +2717,7 @@ Shader { Shader { PresetName: "water" Effect: "eut2.water" + Flavors: ( "WATER_STREAM" "TOSTREAM|TOWATER" "MIRROR" ) Flags: 0 Attribute { Format: FLOAT3 @@ -2729,16 +2833,16 @@ Shader { Value: "/material/special/window_interior_01" TexCoord: ( 0 ) } - Texture { - Tag: "texture[X]:texture_reflection" - Value: "/material/environment/building_reflection/building_ref" - TexCoord: ( -1 ) - } Texture { Tag: "texture[X]:texture_lightmap" Value: "/material/special/window_interior_01" TexCoord: ( 1 ) } + Texture { + Tag: "texture[X]:texture_reflection" + Value: "/material/environment/building_reflection/building_ref" + TexCoord: ( -1 ) + } } Flavor { Type: "AIRBRUSH" @@ -3029,10 +3133,26 @@ Flavor { TexCoord: ( 0 ) } } +Flavor { + Type: "LSBOUNCE" + Name: "lsbounce" +} +Flavor { + Type: "LSDIRECT" + Name: "lsdirect" +} +Flavor { + Type: "LSLUCENT" + Name: "lslucent" +} Flavor { Type: "LUMMULVCOL" Name: "lvcol" } +Flavor { + Type: "MIRROR" + Name: "mirror" +} Flavor { Type: "NMAP_DETAIL" Name: "tsnmap" @@ -3289,3 +3409,15 @@ Flavor { Value: ( 8.0 8.0 0.0 0.0 ) } } +Flavor { + Type: "TOSTREAM" + Name: "tostream" +} +Flavor { + Type: "TOWATER" + Name: "towater" +} +Flavor { + Type: "WATER_STREAM" + Name: "stream" +} diff --git a/addon/io_scs_tools/supported_effects.bin b/addon/io_scs_tools/supported_effects.bin index f1d751c0e71293b02f751c41a06da6478211ba45..0bf92a2cf302c23f3f166485b3d74399e5dca8e4 100644 GIT binary patch literal 166152 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 literal 165827 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 diff --git a/addon/io_scs_tools/utils/info.py b/addon/io_scs_tools/utils/info.py index ff3373c..cde7413 100644 --- a/addon/io_scs_tools/utils/info.py +++ b/addon/io_scs_tools/utils/info.py @@ -132,7 +132,7 @@ def cmp_ver_str(version_str, version_str2): def cmp_ver_str_unofficial(version_str, version_str2): """Compares two version string of format "X.X.X..." where X is number. - :param version_str: version string to check (should be in format: "X" where X is unofficial update number) + :param version_str: version string to check (should be in format: "X.Y)" where X and Y are version numbers) :type version_str: str :param version_str2: version string to check (should be in format: "X" where X is unofficial update number) :type version_str2: str @@ -141,15 +141,18 @@ def cmp_ver_str_unofficial(version_str, version_str2): """ version_str = version_str.split(".") - version_str2 = version_str2.split(".") # Fix for users migrating from official BT (without unofficial update version at BT version) while len(version_str) <= 3: version_str.append('0') - if int(version_str[3]) < int(version_str2[0]): + # first version smaller than second + if int(version_str[3]) < int(version_str2): return -1 - elif int(version_str[3]) == int(version_str2[0]): + + # equal versions + if int(version_str[3]) == int(version_str2): return 0 - else: - return 1 \ No newline at end of file + + # otherwise we directly assume that first is greater + return 1 \ No newline at end of file From 50fba9ea8feb9d6445e917055d68a80d2858458d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sun, 6 Jul 2025 20:04:28 +0200 Subject: [PATCH 43/56] Replaced deprecated nodes + fixes * Changes in tool versioning (changed "MAJOR.MINOR.BUILD_HASH" format to "MAJOR.MINOR.PATH_UNOFFICIAL") * Fixed "over" flavor preview in "dif.lum" shadersa * Replaced deprecated shader nodes * Changed FriendlyTag for Luminance to better represent attribute from the newer material format --- addon/io_scs_tools/__init__.py | 2 +- .../internals/persistent/file_load.py | 23 ++++++------ .../shaders/eut2/dif_lum/__init__.py | 6 +++- .../shaders/eut2/dif_lum_spec/__init__.py | 6 +++- .../shaders/eut2/dif_spec_oclu/__init__.py | 8 ++--- .../eut2/dif_spec_oclu_add_env/__init__.py | 2 +- .../dif_spec_oclu_weight_add_env/__init__.py | 2 +- .../eut2/std_node_groups/lampmask_mixer_ng.py | 14 ++++---- .../eut2/std_node_groups/mult2_mix_ng.py | 6 ++-- .../eut2/std_node_groups/vcolor_input_ng.py | 20 +++++------ .../shaders/eut2/truckpaint/__init__.py | 10 +++--- .../shaders/flavors/nmap/dds16_ng.py | 18 +++++----- .../shaders/flavors/nmap/scale_ng.py | 8 ++--- addon/io_scs_tools/shader_presets.txt | 36 +++++++++---------- addon/io_scs_tools/utils/info.py | 20 +++++------ 15 files changed, 94 insertions(+), 87 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 0ea621f..7eeeed5 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 6), + "version": (2, 4, 6), # Replaced original "aeadde03" with unofficial version to fit with MAJOR.MINOR.PATCH scheme "blender": (4, 4, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 6184670..dba39f9 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -54,26 +54,25 @@ def post_load(scene): VERSIONS_LIST_UNOFFICIAL = ( ("4", apply_fixes_for_un_4), + ("7", apply_fixes_for_un_7), ) + v_parts = last_load_bt_ver.split(".") for version, func in VERSIONS_LIST: if _info_utils.cmp_ver_str(last_load_bt_ver, version) <= 0: - - # Apply fixes ONLY if your last loaded tools version is official to prevent unnecessary code execution. - while len(last_load_bt_ver.split(".")) <= 3: - + # OFFICIAL version: ( X.Y.ZZZZ or old unofficial X.Y.ZZZZ.U ) + if not v_parts[2].isdigit(): # try to add apply fixed function as callback, if failed execute fixes right now if not AsyncPathsInit.append_callback(func): func() + # UNOFFICIAL version: (X.Y.U) for version2, func2 in VERSIONS_LIST_UNOFFICIAL: if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) < 0: - # try to add apply fixed function as callback, if failed execute fixes right now if not AsyncPathsInit.append_callback(func2): func2() - # as last update "last load" Blender Tools version to current _get_scs_globals().last_load_bt_version = _info_utils.get_tools_version() @@ -369,7 +368,7 @@ def apply_fixes_for_un_4(): 3. Show welcome message """ - print("INFO\t- Applying fixes for unofficial version <= 4") + print("INFO\t- Applying fixes for (un)official versions < 4") # 1. do pre-reload changes and collect data @@ -407,12 +406,12 @@ def apply_fixes_for_un_4(): def apply_fixes_for_un_7(): """ - Applies fixes for unofficial 2.4.xxxxxx.7 or less: - 1. Pre-reload changes and collect data - 2. Reload materials since some got removed/restructed attributes + Applies fixes for unofficial 2.4.7 or less: + 1. Reload materials since some got removed/restructed attributes """ - print("INFO\t- Applying fixes for unofficial version <= 7") + print("INFO\t- Applying fixes for (un)official versions < 7") - # 2. reload all materials + # 1. reload all materials + # Some attributes got removed, in some cases attribute size changed and due to that we need to reload materials _reload_materials() diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py index 3b13d7a..22b539f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_lum/__init__.py @@ -69,14 +69,15 @@ def finalize(node_tree, material): material.surface_render_method = "DITHERED" compose_lighting_n = node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE] + lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # set proper blend method if alpha_test.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): - lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage if compose_lighting_n.inputs['Alpha'].links: @@ -92,12 +93,15 @@ def finalize(node_tree, material): if blend_add.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[Dif.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py index 0dd8eac..ac5af09 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_lum_spec/__init__.py @@ -69,14 +69,15 @@ def finalize(node_tree, material): material.surface_render_method = "DITHERED" compose_lighting_n = node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE] + lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # set proper blend method if alpha_test.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 0.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 0.0 # add alpha test pass if multiply blend enabled, where alphed pixels shouldn't be multiplied as they are discarded if blend_mult.is_set(node_tree): - lum_out_shader_n = node_tree.nodes[StdLum.LUM_OUT_SHADER_NODE] # alpha test pass has to get fully opaque input, thus remove transparency linkage if compose_lighting_n.inputs['Alpha'].links: @@ -92,12 +93,15 @@ def finalize(node_tree, material): if blend_add.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" if blend_mult.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" if blend_over.is_set(node_tree): compose_lighting_n.inputs["Alpha Type"].default_value = 1.0 + lum_out_shader_n.inputs["Alpha Type"].default_value = 1.0 material.surface_render_method = "BLENDED" if compose_lighting_n.inputs["Alpha Type"].default_value < 0.0 and node_tree.nodes[DifSpec.COMPOSE_LIGHTING_NODE].inputs['Alpha'].links: diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py index 34d9e17..9ebd362 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu/__init__.py @@ -72,7 +72,7 @@ def init(node_tree): oclu_tex_n.location = (start_pos_x + pos_x_shift, start_pos_y + 1200) oclu_tex_n.width = 140 - oclu_sep_rgb_n = node_tree.nodes.new("ShaderNodeSeparateRGB") + oclu_sep_rgb_n = node_tree.nodes.new("ShaderNodeSeparateColor") oclu_sep_rgb_n.name = oclu_sep_rgb_n.label = DifSpecOclu.OCLU_SEPARATE_RGB_NODE oclu_sep_rgb_n.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 1200) @@ -90,14 +90,14 @@ def init(node_tree): node_tree.links.new(oclu_tex_n.inputs["Vector"], sec_uvmap_n.outputs["UV"]) # pass 1 - node_tree.links.new(oclu_sep_rgb_n.inputs["Image"], oclu_tex_n.outputs["Color"]) + node_tree.links.new(oclu_sep_rgb_n.inputs["Color"], oclu_tex_n.outputs["Color"]) # pass 2 node_tree.links.new(oclu_a_mix_n.inputs[0], base_tex_n.outputs["Alpha"]) - node_tree.links.new(oclu_a_mix_n.inputs[1], oclu_sep_rgb_n.outputs["R"]) + node_tree.links.new(oclu_a_mix_n.inputs[1], oclu_sep_rgb_n.outputs["Red"]) node_tree.links.new(oclu_mix_n.inputs[0], base_tex_n.outputs["Color"]) - node_tree.links.new(oclu_mix_n.inputs[1], oclu_sep_rgb_n.outputs["R"]) + node_tree.links.new(oclu_mix_n.inputs[1], oclu_sep_rgb_n.outputs["Red"]) # pass 3 node_tree.links.new(spec_mult_n.inputs[1], oclu_a_mix_n.outputs["Value"]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_add_env/__init__.py index 73c2081..1d78520 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_add_env/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_add_env/__init__.py @@ -50,4 +50,4 @@ def init(node_tree): add_env_gn = node_tree.nodes[StdAddEnv.ADD_ENV_GROUP_NODE] # links creation - node_tree.links.new(add_env_gn.inputs['Strength Multiplier'], oclu_sep_n.outputs['R']) + node_tree.links.new(add_env_gn.inputs['Strength Multiplier'], oclu_sep_n.outputs['Red']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_weight_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_weight_add_env/__init__.py index e89d475..8d291a5 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_weight_add_env/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_oclu_weight_add_env/__init__.py @@ -71,4 +71,4 @@ def init(node_tree): # links creation node_tree.links.new(add_env_gn.inputs['Weighted Color'], vcol_scale_n.outputs[0]) - node_tree.links.new(add_env_gn.inputs['Strength Multiplier'], oclu_sep_n.outputs['R']) + node_tree.links.new(add_env_gn.inputs['Strength Multiplier'], oclu_sep_n.outputs['Red']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py index 93bb8ee..64c62cf 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py @@ -87,7 +87,7 @@ def __create_node_group__(): alpha_decode_n.operation = "POWER" alpha_decode_n.inputs[1].default_value = 2.2 - tex_col_sep_n = lampmask_g.nodes.new("ShaderNodeSeparateRGB") + tex_col_sep_n = lampmask_g.nodes.new("ShaderNodeSeparateColor") tex_col_sep_n.name = tex_col_sep_n.label = _TEX_COL_SEP_NODE tex_col_sep_n.location = (pos_x_shift, 400) @@ -105,7 +105,7 @@ def __create_node_group__(): # links creation lampmask_g.links.new(alpha_decode_n.inputs[0], input_n.outputs['Lampmask Tex Alpha']) - lampmask_g.links.new(tex_col_sep_n.inputs['Image'], input_n.outputs['Lampmask Tex Color']) + lampmask_g.links.new(tex_col_sep_n.inputs['Color'], input_n.outputs['Lampmask Tex Color']) lampmask_g.links.new(uv_x_dot_n.inputs[0], input_n.outputs['UV Vector']) lampmask_g.links.new(uv_y_dot_n.inputs[0], input_n.outputs['UV Vector']) @@ -153,9 +153,9 @@ def __create_node_group__(): __init_vehicle_switch_nodes__(lampmask_g, alpha_decode_n.outputs[0], - tex_col_sep_n.outputs["R"], - tex_col_sep_n.outputs["G"], - tex_col_sep_n.outputs["B"], + tex_col_sep_n.outputs["Red"], + tex_col_sep_n.outputs["Green"], + tex_col_sep_n.outputs["Blue"], vehicle_lamp_type, pos_x_shift * 5, pos_y, nodes_for_addition) @@ -167,8 +167,8 @@ def __create_node_group__(): __init_aux_switch_nodes__(lampmask_g, alpha_decode_n.outputs[0], - tex_col_sep_n.outputs["R"], - tex_col_sep_n.outputs["G"], + tex_col_sep_n.outputs["Red"], + tex_col_sep_n.outputs["Green"], aux_lamp_type, pos_x_shift * 5, pos_y, nodes_for_addition) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py index 83c5045..42ae88a 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/mult2_mix_ng.py @@ -75,7 +75,7 @@ def __create_node_group__(): output_n = mult2_mix_g.nodes.new("NodeGroupOutput") output_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y) - separate_mult_n = mult2_mix_g.nodes.new("ShaderNodeSeparateRGB") + separate_mult_n = mult2_mix_g.nodes.new("ShaderNodeSeparateColor") separate_mult_n.name = _SEPARATE_MULT_NODE separate_mult_n.label = _SEPARATE_MULT_NODE separate_mult_n.location = (start_pos_x + pos_x_shift, start_pos_y) @@ -109,9 +109,9 @@ def __create_node_group__(): alpha_mix_n.blend_type = "MIX" # links creation - mult2_mix_g.links.new(separate_mult_n.inputs["Image"], input_n.outputs["Mult Color"]) + mult2_mix_g.links.new(separate_mult_n.inputs["Color"], input_n.outputs["Mult Color"]) - mult2_mix_g.links.new(mult_green_scale_n.inputs[0], separate_mult_n.outputs["G"]) + mult2_mix_g.links.new(mult_green_scale_n.inputs[0], separate_mult_n.outputs["Green"]) mult2_mix_g.links.new(mult_green_mix_n.inputs["Factor"], input_n.outputs["Base Alpha"]) mult2_mix_g.links.new(mult_green_mix_n.inputs["A"], mult_green_scale_n.outputs["Value"]) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py index 10b26b2..01644f3 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/vcolor_input_ng.py @@ -84,7 +84,7 @@ def __create_vcolor_group__(): vcol_a_n.location = (pos_x_shift, -100) vcol_a_n.layer_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix - vcol_separate_rgb_n = vcol_g.nodes.new("ShaderNodeSeparateRGB") + vcol_separate_rgb_n = vcol_g.nodes.new("ShaderNodeSeparateColor") vcol_separate_rgb_n.name = vcol_separate_rgb_n.label = _VCOL_SEPARATE_NODE vcol_separate_rgb_n.location = (pos_x_shift * 2, 200) @@ -118,24 +118,24 @@ def __create_vcolor_group__(): alpha_extend_n.operation = "MULTIPLY" alpha_extend_n.inputs[1].default_value = 2.0 - vcol_combine_n = vcol_g.nodes.new("ShaderNodeCombineRGB") + vcol_combine_n = vcol_g.nodes.new("ShaderNodeCombineColor") vcol_combine_n.name = vcol_combine_n.label = _VCOL_COMBINE_NODE vcol_combine_n.location = (pos_x_shift * 4, 200) # group links - vcol_g.links.new(vcol_separate_rgb_n.inputs['Image'], vcol_n.outputs['Color']) + vcol_g.links.new(vcol_separate_rgb_n.inputs['Color'], vcol_n.outputs['Color']) vcol_g.links.new(alpha_to_bw_n.inputs["Color"], vcol_a_n.outputs['Color']) - vcol_g.links.new(vcol_r_lin_to_srgb_n.inputs['Value'], vcol_separate_rgb_n.outputs['R']) - vcol_g.links.new(vcol_g_lin_to_srgb_n.inputs['Value'], vcol_separate_rgb_n.outputs['G']) - vcol_g.links.new(vcol_b_lin_to_srgb_n.inputs['Value'], vcol_separate_rgb_n.outputs['B']) + vcol_g.links.new(vcol_r_lin_to_srgb_n.inputs['Value'], vcol_separate_rgb_n.outputs['Red']) + vcol_g.links.new(vcol_g_lin_to_srgb_n.inputs['Value'], vcol_separate_rgb_n.outputs['Green']) + vcol_g.links.new(vcol_b_lin_to_srgb_n.inputs['Value'], vcol_separate_rgb_n.outputs['Blue']) vcol_g.links.new(alpha_lin_to_srgb_n.inputs['Value'], alpha_to_bw_n.outputs['Val']) - vcol_g.links.new(vcol_combine_n.inputs['R'], vcol_r_lin_to_srgb_n.outputs["Value"]) - vcol_g.links.new(vcol_combine_n.inputs['G'], vcol_g_lin_to_srgb_n.outputs["Value"]) - vcol_g.links.new(vcol_combine_n.inputs['B'], vcol_b_lin_to_srgb_n.outputs["Value"]) + vcol_g.links.new(vcol_combine_n.inputs['Red'], vcol_r_lin_to_srgb_n.outputs["Value"]) + vcol_g.links.new(vcol_combine_n.inputs['Green'], vcol_g_lin_to_srgb_n.outputs["Value"]) + vcol_g.links.new(vcol_combine_n.inputs['Blue'], vcol_b_lin_to_srgb_n.outputs["Value"]) vcol_g.links.new(alpha_extend_n.inputs[0], alpha_lin_to_srgb_n.outputs['Value']) - vcol_g.links.new(output_n.inputs['Vertex Color'], vcol_combine_n.outputs['Image']) + vcol_g.links.new(output_n.inputs['Vertex Color'], vcol_combine_n.outputs['Color']) vcol_g.links.new(output_n.inputs['Vertex Color Alpha'], alpha_extend_n.outputs['Value']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py index 3937031..356ba12 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/truckpaint/__init__.py @@ -217,7 +217,7 @@ def init_colormask_or_airbrush(node_tree): paint_r_col_n.location = (start_pos_x + pos_x_shift, start_pos_y + 50) # node creation - level 2 - paint_tex_sep = node_tree.nodes.new("ShaderNodeSeparateRGB") + paint_tex_sep = node_tree.nodes.new("ShaderNodeSeparateColor") paint_tex_sep.name = paint_tex_sep.label = Truckpaint.PAINT_TEX_SEP_NODE paint_tex_sep.location = (start_pos_x + pos_x_shift * 3, start_pos_y + 650) @@ -258,22 +258,22 @@ def init_colormask_or_airbrush(node_tree): node_tree.links.new(paint_tex_n.inputs['Vector'], paint_uv_map_n.outputs['UV']) # make links - level 1 - node_tree.links.new(paint_tex_sep.inputs['Image'], paint_tex_n.outputs['Color']) + node_tree.links.new(paint_tex_sep.inputs['Color'], paint_tex_n.outputs['Color']) # make links - level 2 node_tree.links.new(airbrush_mix_n.inputs['Factor'], paint_tex_n.outputs['Alpha']) node_tree.links.new(airbrush_mix_n.inputs['A'], paint_base_col_n.outputs['Color']) node_tree.links.new(airbrush_mix_n.inputs['B'], paint_tex_n.outputs['Color']) - node_tree.links.new(col_mask_b_mix_n.inputs['Factor'], paint_tex_sep.outputs['B']) + node_tree.links.new(col_mask_b_mix_n.inputs['Factor'], paint_tex_sep.outputs['Blue']) node_tree.links.new(col_mask_b_mix_n.inputs['A'], paint_base_col_n.outputs['Color']) node_tree.links.new(col_mask_b_mix_n.inputs['B'], paint_b_col_n.outputs['Color']) - node_tree.links.new(col_mask_g_mix_n.inputs['Factor'], paint_tex_sep.outputs['G']) + node_tree.links.new(col_mask_g_mix_n.inputs['Factor'], paint_tex_sep.outputs['Green']) node_tree.links.new(col_mask_g_mix_n.inputs['A'], col_mask_b_mix_n.outputs['Result']) node_tree.links.new(col_mask_g_mix_n.inputs['B'], paint_g_col_n.outputs['Color']) - node_tree.links.new(col_mask_r_mix_n.inputs['Factor'], paint_tex_sep.outputs['R']) + node_tree.links.new(col_mask_r_mix_n.inputs['Factor'], paint_tex_sep.outputs['Red']) node_tree.links.new(col_mask_r_mix_n.inputs['A'], col_mask_g_mix_n.outputs['Result']) node_tree.links.new(col_mask_r_mix_n.inputs['B'], paint_r_col_n.outputs['Color']) diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py index 14b336c..06aebaf 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/dds16_ng.py @@ -73,7 +73,7 @@ def __create_node_group__(): output_n = nmap_dds16_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) - separate_rgb_n = nmap_dds16_g.nodes.new("ShaderNodeSeparateRGB") + separate_rgb_n = nmap_dds16_g.nodes.new("ShaderNodeSeparateColor") separate_rgb_n.name = separate_rgb_n.label = _NMAP_TEX_SEP_NODE separate_rgb_n.location = (185 * 1, 100) @@ -111,16 +111,16 @@ def __create_node_group__(): square_pow_n.inputs[1].default_value = 0.5 # 5. pass - combine_rgb_n = nmap_dds16_g.nodes.new("ShaderNodeCombineRGB") + combine_rgb_n = nmap_dds16_g.nodes.new("ShaderNodeCombineColor") combine_rgb_n.name = combine_rgb_n.label = _NMAP_TEX_COMBINE_NODE combine_rgb_n.location = (185 * 6, 100) - combine_rgb_n.inputs['B'].default_value = 1 + combine_rgb_n.inputs['Blue'].default_value = 1 # group links - nmap_dds16_g.links.new(separate_rgb_n.inputs['Image'], input_n.outputs['Color']) + nmap_dds16_g.links.new(separate_rgb_n.inputs['Color'], input_n.outputs['Color']) - nmap_dds16_g.links.new(red_pow_n.inputs[0], separate_rgb_n.outputs['R']) - nmap_dds16_g.links.new(green_pow_n.inputs[0], separate_rgb_n.outputs['G']) + nmap_dds16_g.links.new(red_pow_n.inputs[0], separate_rgb_n.outputs['Red']) + nmap_dds16_g.links.new(green_pow_n.inputs[0], separate_rgb_n.outputs['Green']) nmap_dds16_g.links.new(red_sub_n.inputs[1], red_pow_n.outputs[0]) @@ -129,8 +129,8 @@ def __create_node_group__(): nmap_dds16_g.links.new(square_pow_n.inputs[0], green_sub_n.outputs[0]) - nmap_dds16_g.links.new(combine_rgb_n.inputs['R'], separate_rgb_n.outputs['R']) - nmap_dds16_g.links.new(combine_rgb_n.inputs['G'], separate_rgb_n.outputs['G']) + nmap_dds16_g.links.new(combine_rgb_n.inputs['Red'], separate_rgb_n.outputs['Red']) + nmap_dds16_g.links.new(combine_rgb_n.inputs['Green'], separate_rgb_n.outputs['Green']) - nmap_dds16_g.links.new(output_n.inputs['Color'], combine_rgb_n.outputs['Image']) + nmap_dds16_g.links.new(output_n.inputs['Color'], combine_rgb_n.outputs['Color']) nmap_dds16_g.links.new(output_n.inputs['Strength'], square_pow_n.outputs[0]) diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py index ed24862..40deb3e 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/scale_ng.py @@ -87,7 +87,7 @@ def __create_nmap_scale_group__(): output_n = nmap_scale_g.nodes.new("NodeGroupOutput") output_n.location = (185 * 7, 0) - separate_rgb_n = nmap_scale_g.nodes.new("ShaderNodeSeparateRGB") + separate_rgb_n = nmap_scale_g.nodes.new("ShaderNodeSeparateColor") separate_rgb_n.name = separate_rgb_n.label = _NMAP_TEX_SEP_NODE separate_rgb_n.location = (185 * 1, 400) @@ -140,10 +140,10 @@ def __create_nmap_scale_group__(): green_mix_n.blend_type = "MIX" # group links - nmap_scale_g.links.new(separate_rgb_n.inputs['Image'], input_n.outputs['NMap Tex Color']) + nmap_scale_g.links.new(separate_rgb_n.inputs['Color'], input_n.outputs['NMap Tex Color']) - nmap_scale_g.links.new(red_math_sub_n.inputs[0], separate_rgb_n.outputs['R']) - nmap_scale_g.links.new(green_math_sub_n.inputs[0], separate_rgb_n.outputs['G']) + nmap_scale_g.links.new(red_math_sub_n.inputs[0], separate_rgb_n.outputs['Red']) + nmap_scale_g.links.new(green_math_sub_n.inputs[0], separate_rgb_n.outputs['Green']) nmap_scale_g.links.new(red_math_mult_n.inputs[0], red_math_sub_n.outputs[0]) nmap_scale_g.links.new(green_math_mult_n.inputs[0], green_math_sub_n.outputs[0]) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 3dc51b3..47663c1 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -141,7 +141,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -192,7 +192,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -234,7 +234,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -270,7 +270,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -306,7 +306,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -342,7 +342,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -479,7 +479,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -521,7 +521,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -1863,7 +1863,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -1990,7 +1990,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 1700.0 17.0 ) } Attribute { @@ -2083,7 +2083,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 1700.0 17.0 ) } Attribute { @@ -2182,7 +2182,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 1700.0 17.0 ) } Attribute { @@ -2359,7 +2359,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } } @@ -2392,7 +2392,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -2430,7 +2430,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -2465,7 +2465,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -2683,7 +2683,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { @@ -2705,7 +2705,7 @@ Shader { Attribute { Format: FLOAT2 Tag: "aux[5]" - FriendlyTag: "Luminance Output (Day, Night) (nit)" + FriendlyTag: "Luminance (Output, Night) (nit)" Value: ( 0.0 0.0 ) } Texture { diff --git a/addon/io_scs_tools/utils/info.py b/addon/io_scs_tools/utils/info.py index cde7413..c5a3adf 100644 --- a/addon/io_scs_tools/utils/info.py +++ b/addon/io_scs_tools/utils/info.py @@ -130,9 +130,9 @@ def cmp_ver_str(version_str, version_str2): return 1 def cmp_ver_str_unofficial(version_str, version_str2): - """Compares two version string of format "X.X.X..." where X is number. + """Compares two version string of format "X.X.X" where X is number. - :param version_str: version string to check (should be in format: "X.Y)" where X and Y are version numbers) + :param version_str: version string to check (should be in format: "X.Y.Z" where X and Y are version numbers, Z can be hash/unofficial version) :type version_str: str :param version_str2: version string to check (should be in format: "X" where X is unofficial update number) :type version_str2: str @@ -142,17 +142,17 @@ def cmp_ver_str_unofficial(version_str, version_str2): version_str = version_str.split(".") - # Fix for users migrating from official BT (without unofficial update version at BT version) - while len(version_str) <= 3: - version_str.append('0') + # Threat hash from official version as 0 + if not version_str[2].isdigit(): + version_str[2] = 0 - # first version smaller than second - if int(version_str[3]) < int(version_str2): + # First version smaller than second + if int(version_str[2]) < int(version_str2): return -1 - # equal versions - if int(version_str[3]) == int(version_str2): + # Equal versions + if int(version_str[2]) == int(version_str2): return 0 - # otherwise we directly assume that first is greater + # Otherwise we directly assume that first is greater return 1 \ No newline at end of file From 06058c5c68cd41a11ca24d3396292d33c2ce1110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 16 Jul 2025 06:28:38 +0200 Subject: [PATCH 44/56] fixes * "Fixed" some errors in console (skipped unnecessary code update to decrease errors count - setters in getters) * Fixed "SCS Blender Tools" ui window Y position (now it detects header panel and doesn't cover data) --- addon/io_scs_tools/internals/open_gl/core.py | 21 ++++++++++++--- addon/io_scs_tools/properties/object.py | 27 ++++++++++++++++++-- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/addon/io_scs_tools/internals/open_gl/core.py b/addon/io_scs_tools/internals/open_gl/core.py index b77ed45..e29e613 100644 --- a/addon/io_scs_tools/internals/open_gl/core.py +++ b/addon/io_scs_tools/internals/open_gl/core.py @@ -119,21 +119,36 @@ def _draw_3dview_report(window, area, region): return # calculate dynamic left and top margins + pos_y_shift = 190 if bpy.context.preferences.system.use_region_overlap: pos_x = 65 + header_height = 0 # try to find tools region and properly adopt position X + # TOOLS region for reg in area.regions: if reg.type == 'TOOLS': pos_x = reg.width + 10 break - pos_y = region.height - 105 + + # HEADER region + for reg in area.regions: + if reg.type == 'HEADER' and reg.height > 1: + # Check if header is on the top (!=) + header_height = reg.height if region.y != reg.y else 0 + break + else: + # Subtract size of reg.height if header is OFF + header_height = -38 + break + + pos_y = region.height - pos_y_shift - header_height else: pos_x = 10 - pos_y = region.height - 80 + pos_y = region.height - pos_y_shift + 38 for space in area.spaces: if space.type == 'VIEW_3D' and space.overlay.show_stats: - pos_y = pos_y - 105 + pos_y = pos_y - pos_y_shift - 10 break # draw BT banner diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 4bd83b8..9afb14d 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -379,27 +379,49 @@ def active_scs_part_get(self): with the index of part belonging to new active object. """ + debug = False + lprint("D ---------- GETTER ----------") if debug else None if "active_scs_part_old_active" not in self: + lprint("D ---- IF SELF 1 ENTERED ----") if debug else None self["active_scs_part_old_active"] = "" if "active_scs_part_value" not in self: + lprint("D ---- IF SELF 2 ENTERED ----") if debug else None self["active_scs_part_value"] = 0 scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) + if debug: + lprint("D --active_scs_part_old_active-- %r", (self["active_scs_part_old_active"],)) + lprint("D --active_scs_part_value-- %r", (self["active_scs_part_value"],)) + lprint("D --scs_root_object-- %r", (scs_root_object,)) + lprint("D --bpy.context.active_object-- %r", (bpy.context.active_object,)) + lprint("D --bpy.context.active_object.name-- %r", (bpy.context.active_object.name,)) if scs_root_object and bpy.context.active_object != scs_root_object: - + lprint("D ---- IF 1 ENTERED ----") if debug else None # if old active object is different than current # set the value for active part index from it if self["active_scs_part_old_active"] != bpy.context.active_object.name: + lprint("D ---- IF 2 ENTERED ----") if debug else None self["active_scs_part_value"] = _inventory.get_index(scs_root_object.scs_object_part_inventory, bpy.context.active_object.scs_props.scs_part) - self["active_scs_part_old_active"] = bpy.context.active_object.name + # TEMP: skip unnecessary 'active_scs_part_old_active' update to decrease errors count in console (setters in getters) + if self["active_scs_part_old_active"] != bpy.context.active_object.name: + lprint("D ---- TEMP IF ENTERED ----") if debug else None + self["active_scs_part_old_active"] = bpy.context.active_object.name + print("-------------------------\n") if debug else None return self["active_scs_part_value"] def active_scs_part_set(self, value): + debug = False + lprint("D ---------- SETTER ----------") if debug else None self["active_scs_part_value"] = value + print("-------------------------\n") if debug else None + + def active_scs_part_update(self, context): + lprint("D ---------- UPDATE ----------") + print("-------------------------\n") active_scs_part: IntProperty( name="Active SCS Part", @@ -411,6 +433,7 @@ def active_scs_part_set(self, value): subtype='NONE', get=active_scs_part_get, set=active_scs_part_set + # update=active_scs_part_update ) def get_active_scs_look(self): From 285b23ab6899bd8dcadd2e7468a04df80caea73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 19 Jul 2025 03:23:38 +0200 Subject: [PATCH 45/56] Vulkan support + checkbox tweaks * Added support for Vulkan backend (unfortunately line width of locators can't be changed on Vulkan) * Tweaks in some checkboxes layout in locators - Due to problems with readability at different panel sizes (e.g. "Parti..." instead of "Partial Activation" etc.), checkboxes are now displayed on separate lines. --- .../internals/open_gl/primitive.py | 28 +++- .../internals/open_gl/shaders/__init__.py | 139 +++++++++++++++--- .../shaders/smooth_color_clipped_3d_frag.glsl | 15 -- .../shaders/smooth_color_clipped_3d_vert.glsl | 22 --- .../smooth_color_stipple_clipped_3d_frag.glsl | 20 --- .../smooth_color_stipple_clipped_3d_vert.glsl | 17 --- addon/io_scs_tools/properties/object.py | 2 +- addon/io_scs_tools/ui/object.py | 13 +- 8 files changed, 139 insertions(+), 117 deletions(-) delete mode 100644 addon/io_scs_tools/internals/open_gl/shaders/smooth_color_clipped_3d_frag.glsl delete mode 100644 addon/io_scs_tools/internals/open_gl/shaders/smooth_color_clipped_3d_vert.glsl delete mode 100644 addon/io_scs_tools/internals/open_gl/shaders/smooth_color_stipple_clipped_3d_frag.glsl delete mode 100644 addon/io_scs_tools/internals/open_gl/shaders/smooth_color_stipple_clipped_3d_vert.glsl diff --git a/addon/io_scs_tools/internals/open_gl/primitive.py b/addon/io_scs_tools/internals/open_gl/primitive.py index 43b36ec..d9212c2 100644 --- a/addon/io_scs_tools/internals/open_gl/primitive.py +++ b/addon/io_scs_tools/internals/open_gl/primitive.py @@ -20,6 +20,7 @@ import blf import gpu +import struct from array import array from gpu_extras.batch import batch_for_shader from mathutils import Vector @@ -113,20 +114,33 @@ def draw(self, uniforms, space_3d): if self.__type == _Buffer.Types.TRIS and space_3d.shading.type == 'WIREFRAME': return + # gpu.state.point_size_set/line_width_set(size) self.__gpu_callback(self.__gpu_callback_param_before) # bind shader self.__shader.bind() # fill the uniforms to binded shader + clip_planes_data = None + num_clip_planes = 0 + for uniform_name, uniform_type, uniform_data, uniform_length, uniform_count in uniforms: - uniform_loc = self.__shader.uniform_from_name(uniform_name) - if uniform_type == float: - self.__shader.uniform_vector_float(uniform_loc, uniform_data, uniform_length, uniform_count) - elif uniform_type == int: - self.__shader.uniform_vector_int(uniform_loc, uniform_data, uniform_length, uniform_count) - else: - raise TypeError("Invalid uniform type: %s" % uniform_type) + if uniform_name == "clip_planes": + clip_planes_data = uniform_data + elif uniform_name == "num_clip_planes": + num_clip_planes = uniform_data[0] + + if clip_planes_data is None: + raise ValueError("Missing clip_planes in uniforms") + + pad_planes = b'\x00' * (24 * 4) # 96 bytes + padded_clip_planes = bytearray(pad_planes) + padded_clip_planes[:len(clip_planes_data)] = clip_planes_data + + ubo_data = struct.pack("24f 4i", *struct.unpack("24f", padded_clip_planes), num_clip_planes, 0, 0, 0) + ubo = gpu.types.GPUUniformBuf(ubo_data) + + self.__shader.uniform_block("clip_data", ubo) # create batch and dispatch draw batch = batch_for_shader(self.__shader, self.__draw_type, self.__data) diff --git a/addon/io_scs_tools/internals/open_gl/shaders/__init__.py b/addon/io_scs_tools/internals/open_gl/shaders/__init__.py index 081ce4a..33292fc 100644 --- a/addon/io_scs_tools/internals/open_gl/shaders/__init__.py +++ b/addon/io_scs_tools/internals/open_gl/shaders/__init__.py @@ -18,7 +18,6 @@ # Copyright (C) 2019: SCS Software -import os import gpu @@ -31,22 +30,6 @@ class ShaderTypes: """Simple dictonary holding shader instances by shader type. To prevent loading shader each time one requests it.""" -def __get_shader_data__(shader_filename): - """Loads and gets shader data from given filename. - - :param shader_filename: filename of shader file inside io_scs_tools/internals/open_gl/shaders - :type shader_filename: str - :return: shader data string from given glsl shader - :rtype: str - """ - shader_filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), shader_filename) - - with open(shader_filepath, "r") as file: - shader_data_str = file.read() - - return shader_data_str - - def get_shader(shader_type): """Get GPU shader for given type. @@ -58,19 +41,129 @@ def get_shader(shader_type): if shader_type == ShaderTypes.SMOOTH_COLOR_CLIPPED_3D: if shader_type not in __cache: - __cache[shader_type] = gpu.types.GPUShader( - __get_shader_data__("smooth_color_clipped_3d_vert.glsl"), - __get_shader_data__("smooth_color_clipped_3d_frag.glsl") + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out.smooth("VEC4", "finalColor") + vert_out.smooth("VEC4", "vertexPos") + + shader_info = gpu.types.GPUShaderCreateInfo() + shader_info.push_constant("MAT4", "ModelViewProjectionMatrix") + shader_info.push_constant("MAT4", "ModelMatrix") + + shader_info.typedef_source( + """ + struct ClipData { + vec4 clip_planes[6]; + int num_clip_planes; + int _pad1; + int _pad2; + int _pad3; + }; + """ + ) + shader_info.uniform_buf(0, "ClipData", "clip_data") + + shader_info.vertex_in(0, "VEC3", "pos") + shader_info.vertex_in(1, "VEC4", "color") + + shader_info.vertex_out(vert_out) + shader_info.fragment_out(0, "VEC4", "fragColor") + + shader_info.vertex_source( + """ + void main() + { + vertexPos = vec4(pos, 1.0); + gl_Position = ModelViewProjectionMatrix * vertexPos; + gl_PointSize = 12.0; + finalColor = color; + + #ifdef USE_WORLD_CLIP_PLANES + world_clip_planes_calc_clip_distance((ModelMatrix * vec4(pos, 1.0)).xyz); + #endif + } + """ ) + shader_info.fragment_source( + """ + void main() + { + for (int i=0; i Date: Tue, 22 Jul 2025 05:04:38 +0200 Subject: [PATCH 46/56] Custom spawn point settings, UI tweaks & version detection fix * Added new "Trailer" tab to settings where you can change colors for trailer (un)loading zones * Added preview of trailer type to (un)loading zones (with possibility to disable it in settings). Shapes represent models visible in ME (g_debug_parking) * Added trailer preview also for older Spawn Points associated with (un)loading zones. * Tweaks in UI - checkboxes now have labels * Fixes in unofficial version detection * --- addon/io_scs_tools/__init__.py | 2 +- .../internals/containers/config.py | 7 + .../internals/open_gl/locators/prefab.py | 327 +++++++++++++++--- .../internals/persistent/file_load.py | 24 +- .../properties/addon_preferences.py | 88 +++++ addon/io_scs_tools/ui/object.py | 117 +++++-- addon/io_scs_tools/ui/workspace.py | 28 ++ addon/io_scs_tools/utils/info.py | 3 +- 8 files changed, 498 insertions(+), 98 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 7eeeed5..8f3ea0d 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, 6), # Replaced original "aeadde03" with unofficial version to fit with MAJOR.MINOR.PATCH scheme + "version": (2, 4, 7), # Replaced original "aeadde03" with unofficial version to fit with MAJOR.MINOR.PATCH scheme "blender": (4, 4, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/containers/config.py b/addon/io_scs_tools/internals/containers/config.py index e2228a3..454bdb0 100644 --- a/addon/io_scs_tools/internals/containers/config.py +++ b/addon/io_scs_tools/internals/containers/config.py @@ -501,6 +501,7 @@ def __init__(self): "CurveSegments": (int, get_default(scs_globals, 'curve_segments'), 'curve_segments'), "DisplayTextInfo": (str, get_default(scs_globals, 'display_info'), 'display_info'), "IconTheme": (str, _ICONS_consts.default_icon_theme, 'icon_theme'), + "ShowTrailerType": (int, get_default(scs_globals, 'show_trailer_type'), 'show_trailer_type'), } @@ -523,6 +524,12 @@ def __init__(self): "TriggerLineBase": (tuple, get_default(scs_globals, 'tp_connection_base_color'), 'tp_connection_base_color'), "InfoText": (tuple, get_default(scs_globals, 'info_text_color'), 'info_text_color'), "BasePaint": (tuple, get_default(scs_globals, 'base_paint_color'), 'base_paint_color'), + "TrailerLoadEasy": (tuple, get_default(scs_globals, 'trailer_load_easy_color'), 'trailer_load_easy_color'), + "TrailerLoadMedium": (tuple, get_default(scs_globals, 'trailer_load_medium_color'), 'trailer_load_medium_color'), + "TrailerLoadHard": (tuple, get_default(scs_globals, 'trailer_load_hard_color'), 'trailer_load_hard_color'), + "TrailerUnloadEasy": (tuple, get_default(scs_globals, 'trailer_unload_easy_color'), 'trailer_unload_easy_color'), + "TrailerUnloadMedium": (tuple, get_default(scs_globals, 'trailer_unload_medium_color'), 'trailer_unload_medium_color'), + "TrailerUnloadHard": (tuple, get_default(scs_globals, 'trailer_unload_hard_color'), 'trailer_unload_hard_color'), } diff --git a/addon/io_scs_tools/internals/open_gl/locators/prefab.py b/addon/io_scs_tools/internals/open_gl/locators/prefab.py index 08a8d4b..c699b4f 100644 --- a/addon/io_scs_tools/internals/open_gl/locators/prefab.py +++ b/addon/io_scs_tools/internals/open_gl/locators/prefab.py @@ -108,74 +108,289 @@ def draw_shape_spawn_point_custom(mat, scs_globals, obj): :return: """ - color = ( - scs_globals.locator_prefab_wire_color.r, - scs_globals.locator_prefab_wire_color.g, - scs_globals.locator_prefab_wire_color.b, - 1.0 - ) + # Draw main Spawn Point shape + draw_shape_spawn_point(mat, scs_globals) + + # Get locator properties + depot_type = int(obj.scs_props.locator_prefab_custom_depot_type) >> 2 + cust_lenght = int(obj.scs_props.locator_prefab_custom_lenght) >> 4 + parking_diff = int(obj.scs_props.locator_prefab_custom_parking_difficulty) + cust_rule = int(obj.scs_props.locator_prefab_custom_rule) >> 16 + + # Matrix without "Locator Size" + mat_orig = obj.matrix_world # Load - if (int(obj.scs_props.locator_prefab_custom_depot_type) / 4) == 1: - match obj.scs_props.locator_prefab_custom_parking_difficulty: - case "1": # Easy - difficulty_color = (0.0, 1.0, 1.0, 1.0) - case "2": # Medium - difficulty_color = (0.0, 0.471, 1.0, 1.0) - case "3": # Hard - difficulty_color = (0.0, 0.0, 0.784, 1.0) + if depot_type == 1: + match parking_diff: + case 1: # Easy + difficulty_color = scs_globals.trailer_load_easy_color + case 2: # Medium + difficulty_color = scs_globals.trailer_load_medium_color + case 3: # Hard + difficulty_color = scs_globals.trailer_load_hard_color case _: - difficulty_color = (0.0, 1.0, 1.0, 1.0) + difficulty_color = (0.0, 1.0, 1.0) # Unload else: - match obj.scs_props.locator_prefab_custom_parking_difficulty: - case "1": # Easy - difficulty_color = (1.0, 1.0, 0.0, 1.0) - case "2": # Medium - difficulty_color = (0.706, 0.471, 0.0, 1.0) - case "3": # Hard - difficulty_color = (1.0, 0.0, 0.0, 1.0) + match parking_diff: + case 1: # Easy + difficulty_color = scs_globals.trailer_unload_easy_color + case 2: # Medium + difficulty_color = scs_globals.trailer_unload_medium_color + case 3: # Hard + difficulty_color = scs_globals.trailer_unload_hard_color case _: - difficulty_color = (1.0, 1.0, 0.0, 1.0) + difficulty_color = (1.0, 1.0, 0.0) - # Matrix without "Locator Size" - mat_orig = obj.matrix_world + color = ( + difficulty_color.r, + difficulty_color.g, + difficulty_color.b, + 1.0 + ) - width = 3.4 - height = 0.05 # Height above ground to prevent z-fight - lenght = (int(obj.scs_props.locator_prefab_custom_lenght) / 16) + 14 + # Local variables + width = 3.4 # Full width of depot shape + height = 0.05 # Height above ground to prevent z-fight + lenght = 14 + cust_lenght # Lenght of depo shape (we assume that cust_lenght (index) corresponds to additional lenght in meters eg. 14m + (idx) 0 = 14m) + lenght_t = 5.0/2 # Lenght of trailer type shape # Set diffrent shape for "Unlimited" lenght (max size of last fixed lenght) if lenght == 29.0: lenght = 20.0 - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), difficulty_color) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 1.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 8.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 10.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 8.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 1.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), difficulty_color) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 1.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 8.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 10.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 8.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 1.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), color) + + # Depot + _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, 0.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 2.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), color) + + # Set shape for "Trailer Type" + height = height + 0.20 # Height override to move shape a little bit above ground + if (cust_rule != 0) and scs_globals.show_trailer_type: + match cust_rule: + case 1: # Box Trailer + # Bottom rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + + # Top rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 2))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 2))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 2))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 2))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 2))), color) + + # Edges + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 2))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 2))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 2))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 2))), color) + + case 2: # Tank Trailer + # Rear hexagon + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 - lenght_t, height + 0.25))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 - lenght_t, height + 0.75))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 - lenght_t, height + 1.00))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 - lenght_t, height + 0.75))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 - lenght_t, height + 0.25))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.00, lenght/2 - lenght_t, height))), color) + + # Front hexagon + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 + lenght_t, height + 0.25))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 + lenght_t, height + 0.75))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 + lenght_t, height + 1.00))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 + lenght_t, height + 0.75))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 + lenght_t, height + 0.25))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.00, lenght/2 + lenght_t, height))), color) + + # Edges + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 + lenght_t, height))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 - lenght_t, height + 0.25))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 + lenght_t, height + 0.25))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 - lenght_t, height + 0.75))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.433, lenght/2 + lenght_t, height + 0.75))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 - lenght_t, height + 1.00))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.000, lenght/2 + lenght_t, height + 1.00))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 - lenght_t, height + 0.75))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 + lenght_t, height + 0.75))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 - lenght_t, height + 0.25))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.433, lenght/2 + lenght_t, height + 0.25))), color) + + case 3: # Dump & Bulk + # Top rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 1))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 1))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 1))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 1))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 1))), color) + + # Edges + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 1))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght/2, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 1))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 1))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght/2, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 1))), color) + + case 4: # Platform, Log & Container + # Bottom rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + + # Top rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 0.5))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 0.5))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 0.5))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 0.5))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 0.5))), color) + + # Edges + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 0.5))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 0.5))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 0.5))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 0.5))), color) + + case 5: # Livestock + # left-right + _primitive.append_line_vertex((mat_orig @ Vector((-1.0, lenght/2, height + 1.0))), color) + _primitive.append_line_vertex((mat_orig @ Vector((1.0, lenght/2, height + 1.0))), color) + + # front-back + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght/2 + 1.0, height + 1.0))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght/2 - 1.0, height + 1.0))), color) + + # up-down + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght/2, height + 2.0))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght/2, height))), color) + + case 6: # Log Trailer + # Bottom rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + + # Top rectangle + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 0.5))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 0.5))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 0.5))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 0.5))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 0.5))), color) + + # Edges + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 - lenght_t, height + 2))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.5, lenght/2 + lenght_t, height + 2))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 - lenght_t, height + 2))), color) + + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-0.5, lenght/2 + lenght_t, height + 2))), color) + + +def draw_shape_spawn_point_trailer(mat, scs_globals, obj, color_idx): + """ + Draws fixed shape for old trailer rails of "Spawn Point" type. + :param mat: + :param scs_globals: + :param obj: + :param color_idx: 0 - Load, 1 - Unload Easy, 2 - Unload Medium, 3 - Unload Hard + :type color_idx: int + :return: + """ - # Locator - _primitive.append_line_vertex((mat @ Vector((0.0, 0.0, scs_globals.locator_empty_size))), color) # , color - _primitive.append_line_vertex((mat @ Vector((0.0, 0.0, 0.75))), color) - _primitive.append_line_vertex((mat @ Vector((-0.1299, 0.0, 0.525))), color) - _primitive.append_line_vertex((mat @ Vector((0.1299, 0.0, 0.675))), color) - _primitive.append_line_vertex((mat @ Vector((0.1299, 0.0, 0.525))), color) - _primitive.append_line_vertex((mat @ Vector((-0.1299, 0.0, 0.675))), color) - _primitive.append_line_vertex((mat @ Vector((0.0, -0.1299, 0.525))), color) - _primitive.append_line_vertex((mat @ Vector((0.0, 0.1299, 0.675))), color) - _primitive.append_line_vertex((mat @ Vector((0.0, 0.1299, 0.525))), color) - _primitive.append_line_vertex((mat @ Vector((0.0, -0.1299, 0.675))), color) + # Draw main Spawn Point shape + draw_shape_spawn_point(mat, scs_globals) + + # Matrix without "Locator Size" + mat_orig = obj.matrix_world + + # Load colors from settings + match color_idx: + case 0: # Load Easy + difficulty_color = scs_globals.trailer_load_easy_color + case 1: # Unload Easy + difficulty_color = scs_globals.trailer_unload_easy_color + case 2: # Unload Medium + difficulty_color = scs_globals.trailer_unload_medium_color + case 3: # Unload Hard + difficulty_color = scs_globals.trailer_unload_hard_color + case _: + difficulty_color = (1.0, 1.0, 0.0) + + color = ( + difficulty_color.r, + difficulty_color.g, + difficulty_color.b, + 1.0 + ) + + # Local variables + width = 3.4 # Full width of depot shape + height = 0.05 # Height above ground to prevent z-fight + lenght = 20 # Lenght of depo shape (excluding "Unlimited" shape) + + # Shape for "Unlimited" lenght (default for old rail system) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 1.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 8.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 10.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 8.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 1.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), color) # Depot - _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), difficulty_color) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, 0.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 2.0, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght, height))), difficulty_color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), difficulty_color) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, 0.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 2.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), color) def draw_shape_traffic_light(mat, scs_globals): @@ -291,6 +506,14 @@ def draw_prefab_locator(obj, scs_globals): if not obj.scs_props.locator_preview_model_present or not scs_globals.show_preview_models: if obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.CUSTOM): draw_shape_spawn_point_custom(mat, scs_globals, obj) + elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.TRAILER_POS): # Load (Easy) OLD + draw_shape_spawn_point_trailer(mat, scs_globals, obj, 0) + elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.UNLOAD_EASY_POS): + draw_shape_spawn_point_trailer(mat, scs_globals, obj, 1) + elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.UNLOAD_MEDIUM_POS): + draw_shape_spawn_point_trailer(mat, scs_globals, obj, 2) + elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.UNLOAD_HARD_POS): + draw_shape_spawn_point_trailer(mat, scs_globals, obj, 3) else: draw_shape_spawn_point(mat, scs_globals) diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index dba39f9..befc973 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -61,17 +61,19 @@ def post_load(scene): for version, func in VERSIONS_LIST: if _info_utils.cmp_ver_str(last_load_bt_ver, version) <= 0: # OFFICIAL version: ( X.Y.ZZZZ or old unofficial X.Y.ZZZZ.U ) - if not v_parts[2].isdigit(): + if int(v_parts[2]) > 100: # try to add apply fixed function as callback, if failed execute fixes right now if not AsyncPathsInit.append_callback(func): func() - # UNOFFICIAL version: (X.Y.U) - for version2, func2 in VERSIONS_LIST_UNOFFICIAL: - if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) < 0: - # try to add apply fixed function as callback, if failed execute fixes right now - if not AsyncPathsInit.append_callback(func2): - func2() + # UNOFFICIAL version: (X.Y.U) (run only if 2.4 is applied) + if version == "2.4": + for version2, func2 in VERSIONS_LIST_UNOFFICIAL: + print(_info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2)) + if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) < 0: + # try to add apply fixed function as callback, if failed execute fixes right now + if not AsyncPathsInit.append_callback(func2): + func2() # as last update "last load" Blender Tools version to current _get_scs_globals().last_load_bt_version = _info_utils.get_tools_version() @@ -368,7 +370,7 @@ def apply_fixes_for_un_4(): 3. Show welcome message """ - print("INFO\t- Applying fixes for (un)official versions < 4") + print("INFO\t- Applying fixes for unofficial versions < 4") # 1. do pre-reload changes and collect data @@ -390,11 +392,11 @@ def apply_fixes_for_un_4(): _reload_materials() - # 3. Due to update from Blender 3.6, we let user know he is migrating to Blender 4.3 + # 3. Due to update from Blender 3.6, we let user know he is migrating to Blender 4.3+ windows = bpy.data.window_managers[0].windows if len(windows) > 0: msg = ( - "\nWelcome folks. You just migrated to Blender 4.3! Yey", + "\nWelcome folks. You just migrated to Blender 4.3+! Yey", "Big thanks, that you decided to try my unofficial BT update. I appreciate it.", "I hope you will enjoy new features and fixes I've added to this version.", "For full changelog and more details, visit official topic on: https://www.forum.scssoft.com" @@ -410,7 +412,7 @@ def apply_fixes_for_un_7(): 1. Reload materials since some got removed/restructed attributes """ - print("INFO\t- Applying fixes for (un)official versions < 7") + print("INFO\t- Applying fixes for unofficial versions < 7") # 1. reload all materials # Some attributes got removed, in some cases attribute size changed and due to that we need to reload materials diff --git a/addon/io_scs_tools/properties/addon_preferences.py b/addon/io_scs_tools/properties/addon_preferences.py index 992c94a..7dd00b6 100644 --- a/addon/io_scs_tools/properties/addon_preferences.py +++ b/addon/io_scs_tools/properties/addon_preferences.py @@ -559,6 +559,34 @@ def locator_coll_face_color_update(self, context): self.on_display_setting_update(context) _config_container.update_item_in_file('GlobalColors.ColliderLocatorsFace', tuple(self.locator_coll_face_color)) + def show_trailer_type_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalDisplay.ShowTrailerType', int(self.show_trailer_type)) + + def trailer_load_easy_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.TrailerLoadEasy', tuple(self.trailer_load_easy_color)) + + def trailer_load_medium_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.TrailerLoadMedium', tuple(self.trailer_load_medium_color)) + + def trailer_load_hard_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.TrailerLoadHard', tuple(self.trailer_load_hard_color)) + + def trailer_unload_easy_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.TrailerUnloadEasy', tuple(self.trailer_unload_easy_color)) + + def trailer_unload_medium_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.TrailerUnloadMedium', tuple(self.trailer_unload_medium_color)) + + def trailer_unload_hard_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.TrailerUnloadHard', tuple(self.trailer_unload_hard_color)) + def display_connections_update(self, context): self.on_display_setting_update(context) _config_container.update_item_in_file('GlobalDisplay.DisplayConnections', int(self.display_connections)) @@ -733,6 +761,66 @@ def get_icon_theme_item(self): # default=(0.065, 0.18, 0.3), update=locator_coll_face_color_update, ) + show_trailer_type: BoolProperty( + name="Show Trailer Type", + description="Show trailer type shape for locators", + default=True, + update=show_trailer_type_update + ) + trailer_load_easy_color: FloatVectorProperty( + name="Trailer Load (Easy)", + description="Color of trailer loading zone - easy difficulty", + options={'HIDDEN'}, + subtype='COLOR', + min=0, max=1, + default=(0.0, 1.0, 1.0), + update=trailer_load_easy_color_update, + ) + trailer_load_medium_color: FloatVectorProperty( + name="Trailer Load (Medium)", + description="Color of trailer loading zone - medium difficulty", + options={'HIDDEN'}, + subtype='COLOR', + min=0, max=1, + default=(0.0, 0.471, 1.0), + update=trailer_load_medium_color_update, + ) + trailer_load_hard_color: FloatVectorProperty( + name="Trailer Load (Hard)", + description="Color of trailer loading zone - hard difficulty", + options={'HIDDEN'}, + subtype='COLOR', + min=0, max=1, + default=(0.0, 0.0, 0.784), + update=trailer_load_hard_color_update, + ) + trailer_unload_easy_color: FloatVectorProperty( + name="Trailer Unload (Easy)", + description="Color of trailer unloading zone - easy difficulty", + options={'HIDDEN'}, + subtype='COLOR', + min=0, max=1, + default=(1.0, 1.0, 0.0), + update=trailer_unload_easy_color_update, + ) + trailer_unload_medium_color: FloatVectorProperty( + name="Trailer Unload (Medium)", + description="Color of trailer unloading zone - medium difficulty", + options={'HIDDEN'}, + subtype='COLOR', + min=0, max=1, + default=(0.706, 0.471, 0.0), + update=trailer_unload_medium_color_update, + ) + trailer_unload_hard_color: FloatVectorProperty( + name="Trailer Unload (Hard)", + description="Color of trailer unloading zone - hard difficulty", + options={'HIDDEN'}, + subtype='COLOR', + min=0, max=1, + default=(1.0, 0.0, 0.0), + update=trailer_unload_hard_color_update, + ) display_connections: BoolProperty( name="Display Connections", description="Display connections in 3D views", diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index 93df058..b2b3211 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -30,6 +30,7 @@ from io_scs_tools.utils import get_scs_inventories as _get_scs_inventories from io_scs_tools.ui import shared as _shared +_UI_SPLIT_PERC = 0.4 class _ObjectPanelBlDefs: bl_space_type = "PROPERTIES" @@ -282,17 +283,38 @@ def draw_model_locator(layout, obj): layout.prop(obj.scs_props, 'locator_model_hookup_lamp_height') @staticmethod - def draw_collision_locator(layout, obj): + def draw_collision_locator(layout, obj, split_perc): layout.use_property_split = True layout.use_property_decorate = False layout.prop(obj.scs_props, 'locator_collider_type') - col = layout.column() - col.prop(obj.scs_props, 'locator_collider_wires', text='Draw Wireframes') - col.prop(obj.scs_props, 'locator_collider_faces', text='Draw Faces') + # Temporary turning off to create correct split for checkbox labels + layout.use_property_split = False + + split = layout.split(factor=split_perc) + col_label = split.column() + col_label.alignment = 'RIGHT' + col_label.label(text="Visibility") + + col_checks = split.column() + check_col_wire = col_checks.row() + check_col_wire.prop(obj.scs_props, 'locator_collider_wires', text='Draw Wireframes') + check_col_face = col_checks.row() + check_col_face.prop(obj.scs_props, 'locator_collider_faces', text='Draw Faces') + if obj.scs_props.locator_collider_type != 'Convex': - layout.prop(obj.scs_props, 'locator_collider_centered') + + split = layout.split(factor=split_perc) + col_label = split.column() + col_label.alignment = 'RIGHT' + col_label.label(text="Properties") + + col_checks = split.column() + check_convex = col_checks.row() + check_convex.prop(obj.scs_props, 'locator_collider_centered') + + layout.use_property_split = True layout.prop(obj.scs_props, 'locator_collider_mass') @@ -418,16 +440,24 @@ def draw_prefab_semaphore(layout, obj): loc_set.prop(obj.scs_props, 'locator_prefab_tsem_cyc_delay') @staticmethod - def draw_prefab_navigation_point(layout, context, obj): - layout.use_property_split = True + def draw_prefab_navigation_point(layout, context, obj, split_perc): + layout.use_property_split = False layout.use_property_decorate = False - flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) + split = layout.split(factor=split_perc) + col_label = split.column() + col_label.alignment = 'RIGHT' + col_label.label(text="Properties") - col = flow.column() - col.prop(obj.scs_props, 'locator_prefab_np_low_probab') - col.prop(obj.scs_props, 'locator_prefab_np_add_priority') - col.prop(obj.scs_props, 'locator_prefab_np_limit_displace') + col_checks = split.column() + check_low_prob = col_checks.row() + check_low_prob.prop(obj.scs_props, 'locator_prefab_np_low_probab') + check_add_prio = col_checks.row() + check_add_prio.prop(obj.scs_props, 'locator_prefab_np_add_priority') + check_limit_disp = col_checks.row() + check_limit_disp.prop(obj.scs_props, 'locator_prefab_np_limit_displace') + + layout.use_property_split = True # allowed vehicles layout.prop(obj.scs_props, 'locator_prefab_np_allowed_veh') @@ -479,22 +509,31 @@ def draw_prefab_navigation_point(layout, context, obj): loc_set.operator('object.scs_tools_connect_prefab_locators', text="Connect / Disconnect Navigation Points", icon='LINKED') @staticmethod - def draw_prefab_map_point(layout, context, obj): + def draw_prefab_map_point(layout, context, obj, split_perc): # box_row_box = layout - layout.use_property_split = True + layout.use_property_split = False layout.use_property_decorate = False is_polygon = int(obj.scs_props.locator_prefab_mp_road_size) == _PL_consts.MPVF.ROAD_SIZE_MANUAL - flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) + split = layout.split(factor=split_perc) + col_label = split.column() + col_label.alignment = 'RIGHT' + col_label.label(text="Properties") + + col_checks = split.column() + check_road_ov = col_checks.row() + check_road_ov.prop(obj.scs_props, 'locator_prefab_mp_road_over') + check_no_out = col_checks.row() + check_no_out.prop(obj.scs_props, 'locator_prefab_mp_no_outline') + check_no_arr = col_checks.row() + check_no_arr.enabled = not is_polygon + check_no_arr.prop(obj.scs_props, 'locator_prefab_mp_no_arrow') + check_pr_exit = col_checks.row() + check_pr_exit.enabled = not is_polygon + check_pr_exit.prop(obj.scs_props, 'locator_prefab_mp_prefab_exit') - col = flow.column() - col.prop(obj.scs_props, 'locator_prefab_mp_road_over') - col.prop(obj.scs_props, 'locator_prefab_mp_no_outline') - col.enabled = not is_polygon - col.prop(obj.scs_props, 'locator_prefab_mp_no_arrow') - col.enabled = not is_polygon - col.prop(obj.scs_props, 'locator_prefab_mp_prefab_exit') + layout.use_property_split = True layout.prop(obj.scs_props, 'locator_prefab_mp_road_size') row = _shared.create_row(layout, use_split=True, enabled=not is_polygon) @@ -527,7 +566,7 @@ def draw_prefab_map_point(layout, context, obj): loc_set.operator('object.scs_tools_connect_prefab_locators', text="Connect / Disconnect Map Points", icon='LINKED') @staticmethod - def draw_prefab_trigger_point(layout, context, obj): + def draw_prefab_trigger_point(layout, context, obj, split_perc): layout.use_property_split = True layout.use_property_decorate = False @@ -539,13 +578,25 @@ def draw_prefab_trigger_point(layout, context, obj): layout.prop(obj.scs_props, 'locator_prefab_tp_range') layout.prop(obj.scs_props, 'locator_prefab_tp_reset_delay') - flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) + # Temporary turning off to create correct split for checkbox labels + layout.use_property_split = False - col = flow.column() - col.prop(obj.scs_props, 'locator_prefab_tp_sphere_trigger') - col.prop(obj.scs_props, 'locator_prefab_tp_partial_activ') - col.prop(obj.scs_props, 'locator_prefab_tp_onetime_activ') - col.prop(obj.scs_props, 'locator_prefab_tp_manual_activ') + split = layout.split(factor=split_perc) + col_label = split.column() + col_label.alignment = 'RIGHT' + col_label.label(text="Properties") + + col_checks = split.column() + check_sph_tr = col_checks.row() + check_sph_tr.prop(obj.scs_props, 'locator_prefab_tp_sphere_trigger') + check_part_act = col_checks.row() + check_part_act.prop(obj.scs_props, 'locator_prefab_tp_partial_activ') + check_one_tim_act = col_checks.row() + check_one_tim_act.prop(obj.scs_props, 'locator_prefab_tp_onetime_activ') + check_man_act = col_checks.row() + check_man_act.prop(obj.scs_props, 'locator_prefab_tp_manual_activ') + + layout.use_property_split = True loc_set = layout.row() if len(context.selected_objects) == 2: @@ -586,7 +637,7 @@ def draw(self, context): # COLLISION LOCATORS elif obj.scs_props.locator_type == 'Collision': - self.draw_collision_locator(layout, obj) + self.draw_collision_locator(layout, obj, _UI_SPLIT_PERC) # PREFAB LOCATORS elif obj.scs_props.locator_type == 'Prefab': @@ -602,11 +653,11 @@ def draw(self, context): elif obj.scs_props.locator_prefab_type == 'Traffic Semaphore': self.draw_prefab_semaphore(layout, obj) elif obj.scs_props.locator_prefab_type == 'Navigation Point': - self.draw_prefab_navigation_point(layout, context, obj) + self.draw_prefab_navigation_point(layout, context, obj, _UI_SPLIT_PERC) elif obj.scs_props.locator_prefab_type == 'Map Point': - self.draw_prefab_map_point(layout, context, obj) + self.draw_prefab_map_point(layout, context, obj, _UI_SPLIT_PERC) elif obj.scs_props.locator_prefab_type == 'Trigger Point': - self.draw_prefab_trigger_point(layout, context, obj) + self.draw_prefab_trigger_point(layout, context, obj, _UI_SPLIT_PERC) class SCS_TOOLS_PT_PreviewModel(_ObjectPanelBlDefs, Panel): diff --git a/addon/io_scs_tools/ui/workspace.py b/addon/io_scs_tools/ui/workspace.py index e9f3c2a..2c4c3b7 100644 --- a/addon/io_scs_tools/ui/workspace.py +++ b/addon/io_scs_tools/ui/workspace.py @@ -222,6 +222,33 @@ def draw(self, context): layout.prop(scs_globals, 'locator_coll_face_color') +class SCS_TOOLS_PT_TrailerDisplay(_WorkspacePanelBlDefs, Panel): + """Draw trailer display panel.""" + + bl_parent_id = SCS_TOOLS_PT_LocatorsDisplay.__name__ + bl_label = "Trailer" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.get_layout() + scs_globals = _get_scs_globals() + + # scs tools main panel if config is being updated + layout.enabled = not scs_globals.config_update_lock + + layout.use_property_split = True + layout.use_property_decorate = False + layout.enabled = scs_globals.display_locators and not scs_globals.config_update_lock + + layout.prop(scs_globals, 'show_trailer_type') + layout.prop(scs_globals, 'trailer_load_easy_color') + layout.prop(scs_globals, 'trailer_load_medium_color') + layout.prop(scs_globals, 'trailer_load_hard_color') + layout.prop(scs_globals, 'trailer_unload_easy_color') + layout.prop(scs_globals, 'trailer_unload_medium_color') + layout.prop(scs_globals, 'trailer_unload_hard_color') + + class SCS_TOOLS_PT_ConnectionsDisplay(_WorkspacePanelBlDefs, Panel): """Draw connections display panel.""" @@ -277,6 +304,7 @@ def draw(self, context): SCS_TOOLS_PT_PathSettings, SCS_TOOLS_PT_DisplaySettings, SCS_TOOLS_PT_LocatorsDisplay, + SCS_TOOLS_PT_TrailerDisplay, SCS_TOOLS_PT_ConnectionsDisplay, SCS_TOOLS_PT_OtherSetttings, ) diff --git a/addon/io_scs_tools/utils/info.py b/addon/io_scs_tools/utils/info.py index c5a3adf..6f67571 100644 --- a/addon/io_scs_tools/utils/info.py +++ b/addon/io_scs_tools/utils/info.py @@ -143,7 +143,8 @@ def cmp_ver_str_unofficial(version_str, version_str2): version_str = version_str.split(".") # Threat hash from official version as 0 - if not version_str[2].isdigit(): + # Because data from version_str string is returned as number, we comparing it with big number + if int(version_str[2]) > 100: version_str[2] = 0 # First version smaller than second From 49c5382ef051bd33fc3ba4bc93c7edf86ab3213d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 22 Jul 2025 22:32:37 +0200 Subject: [PATCH 47/56] zone preview for owned trailer + service station * Renamed new settings from "Trailer" to "Spawn Points" those setting tab. * Added zone preview for Owned Trailer and Service Station spawn points * Changed color settings for Spawn Points tab from RGB to RGBA. Now users can turn off specific zone color by setting Alpha to 0. --- .../internals/containers/config.py | 2 + .../internals/open_gl/locators/prefab.py | 100 +++++++++--------- .../properties/addon_preferences.py | 72 +++++++++---- addon/io_scs_tools/ui/workspace.py | 11 +- 4 files changed, 110 insertions(+), 75 deletions(-) diff --git a/addon/io_scs_tools/internals/containers/config.py b/addon/io_scs_tools/internals/containers/config.py index 454bdb0..2fdda47 100644 --- a/addon/io_scs_tools/internals/containers/config.py +++ b/addon/io_scs_tools/internals/containers/config.py @@ -530,6 +530,8 @@ def __init__(self): "TrailerUnloadEasy": (tuple, get_default(scs_globals, 'trailer_unload_easy_color'), 'trailer_unload_easy_color'), "TrailerUnloadMedium": (tuple, get_default(scs_globals, 'trailer_unload_medium_color'), 'trailer_unload_medium_color'), "TrailerUnloadHard": (tuple, get_default(scs_globals, 'trailer_unload_hard_color'), 'trailer_unload_hard_color'), + "OwnedTrailer": (tuple, get_default(scs_globals, 'owned_trailer_color'), 'owned_trailer_color'), + "ServiceStation": (tuple, get_default(scs_globals, 'service_station_color'), 'service_station_color'), } diff --git a/addon/io_scs_tools/internals/open_gl/locators/prefab.py b/addon/io_scs_tools/internals/open_gl/locators/prefab.py index c699b4f..0bcc6a6 100644 --- a/addon/io_scs_tools/internals/open_gl/locators/prefab.py +++ b/addon/io_scs_tools/internals/open_gl/locators/prefab.py @@ -120,41 +120,34 @@ def draw_shape_spawn_point_custom(mat, scs_globals, obj): # Matrix without "Locator Size" mat_orig = obj.matrix_world + # Local variables + width = 3.4 # Full width of depot shape + height = 0.05 # Height above ground to prevent z-fight + lenght = 14 + cust_lenght # Lenght of depo shape (we assume that cust_lenght (index) corresponds to additional lenght in meters eg. 14m + (idx) 0 = 14m) + lenght_t = 5.0/2 # Lenght of trailer type shape + # Load if depot_type == 1: match parking_diff: case 1: # Easy - difficulty_color = scs_globals.trailer_load_easy_color + color = scs_globals.trailer_load_easy_color case 2: # Medium - difficulty_color = scs_globals.trailer_load_medium_color + color = scs_globals.trailer_load_medium_color case 3: # Hard - difficulty_color = scs_globals.trailer_load_hard_color + color = scs_globals.trailer_load_hard_color case _: - difficulty_color = (0.0, 1.0, 1.0) + color = (0.0, 1.0, 1.0, 1.0) # Unload else: match parking_diff: case 1: # Easy - difficulty_color = scs_globals.trailer_unload_easy_color + color = scs_globals.trailer_unload_easy_color case 2: # Medium - difficulty_color = scs_globals.trailer_unload_medium_color + color = scs_globals.trailer_unload_medium_color case 3: # Hard - difficulty_color = scs_globals.trailer_unload_hard_color + color = scs_globals.trailer_unload_hard_color case _: - difficulty_color = (1.0, 1.0, 0.0) - - color = ( - difficulty_color.r, - difficulty_color.g, - difficulty_color.b, - 1.0 - ) - - # Local variables - width = 3.4 # Full width of depot shape - height = 0.05 # Height above ground to prevent z-fight - lenght = 14 + cust_lenght # Lenght of depo shape (we assume that cust_lenght (index) corresponds to additional lenght in meters eg. 14m + (idx) 0 = 14m) - lenght_t = 5.0/2 # Lenght of trailer type shape + color = (1.0, 1.0, 0.0, 1.0) # Set diffrent shape for "Unlimited" lenght (max size of last fixed lenght) if lenght == 29.0: @@ -339,7 +332,7 @@ def draw_shape_spawn_point_trailer(mat, scs_globals, obj, color_idx): :param mat: :param scs_globals: :param obj: - :param color_idx: 0 - Load, 1 - Unload Easy, 2 - Unload Medium, 3 - Unload Hard + :param color_idx: :type color_idx: int :return: """ @@ -350,47 +343,46 @@ def draw_shape_spawn_point_trailer(mat, scs_globals, obj, color_idx): # Matrix without "Locator Size" mat_orig = obj.matrix_world + # Local variables + width = 3.4 # Full width of depot shape + height = 0.05 # Height above ground to prevent z-fight + lenght = 20 # Lenght of depo shape (excluding "Unlimited" shape) + pos_0 = 0.0 # Default "0" position of shape (required when we need to move whole shape) + # Load colors from settings match color_idx: case 0: # Load Easy - difficulty_color = scs_globals.trailer_load_easy_color + color = scs_globals.trailer_load_easy_color case 1: # Unload Easy - difficulty_color = scs_globals.trailer_unload_easy_color + color = scs_globals.trailer_unload_easy_color case 2: # Unload Medium - difficulty_color = scs_globals.trailer_unload_medium_color + color = scs_globals.trailer_unload_medium_color case 3: # Unload Hard - difficulty_color = scs_globals.trailer_unload_hard_color + color = scs_globals.trailer_unload_hard_color + case 4: # Owned Trailer + color = scs_globals.owned_trailer_color + case 5: # Service Station + color = scs_globals.service_station_color + pos_0 = -29.0 # Move shape position 29m back, because service station uses truck point, not trailer rear as position 0. case _: - difficulty_color = (1.0, 1.0, 0.0) - - color = ( - difficulty_color.r, - difficulty_color.g, - difficulty_color.b, - 1.0 - ) - - # Local variables - width = 3.4 # Full width of depot shape - height = 0.05 # Height above ground to prevent z-fight - lenght = 20 # Lenght of depo shape (excluding "Unlimited" shape) + color = (1.0, 1.0, 0.0, 1.0) # Shape for "Unlimited" lenght (default for old rail system) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), color) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 1.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght + 8.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 10.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 8.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght + 1.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 3.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, pos_0 + lenght + 3.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, pos_0 + lenght + 1.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, pos_0 + lenght + 8.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, pos_0 + lenght + 10.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, pos_0 + lenght + 8.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, pos_0 + lenght + 1.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, pos_0 + lenght + 3.0, height))), color) # Depot - _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), color) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, 0.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((-width/2, lenght, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((0.0, lenght + 2.0, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, lenght, height))), color, is_strip=True) - _primitive.append_line_vertex((mat_orig @ Vector((width/2, 0.0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, pos_0, height))), color) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, pos_0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((-width/2, pos_0 + lenght, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((0.0, pos_0 + lenght + 2.0, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, pos_0 + lenght, height))), color, is_strip=True) + _primitive.append_line_vertex((mat_orig @ Vector((width/2, pos_0, height))), color) def draw_shape_traffic_light(mat, scs_globals): @@ -514,6 +506,10 @@ def draw_prefab_locator(obj, scs_globals): draw_shape_spawn_point_trailer(mat, scs_globals, obj, 2) elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.UNLOAD_HARD_POS): draw_shape_spawn_point_trailer(mat, scs_globals, obj, 3) + elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.TRAILER_SPAWN): + draw_shape_spawn_point_trailer(mat, scs_globals, obj, 4) + elif obj.scs_props.locator_prefab_spawn_type == str(_PL_consts.PSP.SERVICE_POS): + draw_shape_spawn_point_trailer(mat, scs_globals, obj, 5) else: draw_shape_spawn_point(mat, scs_globals) diff --git a/addon/io_scs_tools/properties/addon_preferences.py b/addon/io_scs_tools/properties/addon_preferences.py index 7dd00b6..d0d8ce1 100644 --- a/addon/io_scs_tools/properties/addon_preferences.py +++ b/addon/io_scs_tools/properties/addon_preferences.py @@ -587,6 +587,14 @@ def trailer_unload_hard_color_update(self, context): self.on_display_setting_update(context) _config_container.update_item_in_file('GlobalColors.TrailerUnloadHard', tuple(self.trailer_unload_hard_color)) + def owned_trailer_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.OwnedTrailer', tuple(self.owned_trailer_color)) + + def service_station_color_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalColors.ServiceStation', tuple(self.service_station_color)) + def display_connections_update(self, context): self.on_display_setting_update(context) _config_container.update_item_in_file('GlobalDisplay.DisplayConnections', int(self.display_connections)) @@ -763,64 +771,90 @@ def get_icon_theme_item(self): ) show_trailer_type: BoolProperty( name="Show Trailer Type", - description="Show trailer type shape for locators", + description="Show trailer type shape for custom spawn point locators", default=True, update=show_trailer_type_update ) trailer_load_easy_color: FloatVectorProperty( - name="Trailer Load (Easy)", - description="Color of trailer loading zone - easy difficulty", + name="(Easy) Trailer Load", + description="Color of trailer loading zone in 3D views - easy difficulty", options={'HIDDEN'}, subtype='COLOR', + size = 4, min=0, max=1, - default=(0.0, 1.0, 1.0), + default=(0.0, 1.0, 1.0, 1.0), update=trailer_load_easy_color_update, ) trailer_load_medium_color: FloatVectorProperty( - name="Trailer Load (Medium)", - description="Color of trailer loading zone - medium difficulty", + name="(Medium) Trailer Load", + description="Color of trailer loading zone in 3D views - medium difficulty", options={'HIDDEN'}, subtype='COLOR', + size = 4, min=0, max=1, - default=(0.0, 0.471, 1.0), + default=(0.0, 0.471, 1.0, 1.0), update=trailer_load_medium_color_update, ) trailer_load_hard_color: FloatVectorProperty( - name="Trailer Load (Hard)", - description="Color of trailer loading zone - hard difficulty", + name="(Hard) Trailer Load", + description="Color of trailer loading zone in 3D views - hard difficulty", options={'HIDDEN'}, subtype='COLOR', + size = 4, min=0, max=1, - default=(0.0, 0.0, 0.784), + default=(0.0, 0.0, 0.784, 1.0), update=trailer_load_hard_color_update, ) trailer_unload_easy_color: FloatVectorProperty( - name="Trailer Unload (Easy)", - description="Color of trailer unloading zone - easy difficulty", + name="(Easy) Trailer Unload", + description="Color of trailer unloading zone in 3D views - easy difficulty", options={'HIDDEN'}, subtype='COLOR', + size = 4, min=0, max=1, - default=(1.0, 1.0, 0.0), + default=(1.0, 1.0, 0.0, 1.0), update=trailer_unload_easy_color_update, ) trailer_unload_medium_color: FloatVectorProperty( - name="Trailer Unload (Medium)", - description="Color of trailer unloading zone - medium difficulty", + name="(Medium) Trailer Unload", + description="Color of trailer unloading zone in 3D views - medium difficulty", options={'HIDDEN'}, subtype='COLOR', + size = 4, min=0, max=1, - default=(0.706, 0.471, 0.0), + default=(0.706, 0.471, 0.0, 1.0), update=trailer_unload_medium_color_update, ) trailer_unload_hard_color: FloatVectorProperty( - name="Trailer Unload (Hard)", - description="Color of trailer unloading zone - hard difficulty", + name="(Hard) Trailer Unload", + description="Color of trailer unloading zone in 3D views - hard difficulty", options={'HIDDEN'}, subtype='COLOR', + size = 4, min=0, max=1, - default=(1.0, 0.0, 0.0), + default=(1.0, 0.0, 0.0, 1.0), update=trailer_unload_hard_color_update, ) + owned_trailer_color: FloatVectorProperty( + name="Owned Trailer Color", + description="Color for Owned Trailer zone in 3D views", + options={'HIDDEN'}, + subtype='COLOR', + size = 4, + min=0, max=1, + default=(1.0, 0.0, 1.0, 1.0), + update=owned_trailer_color_update, + ) + service_station_color: FloatVectorProperty( + name="Service Station Color", + description="Color for Service Station zone in 3D views", + options={'HIDDEN'}, + subtype='COLOR', + size = 4, + min=0, max=1, + default=(0.0, 0.706, 0.0, 1.0), + update=service_station_color_update, + ) display_connections: BoolProperty( name="Display Connections", description="Display connections in 3D views", diff --git a/addon/io_scs_tools/ui/workspace.py b/addon/io_scs_tools/ui/workspace.py index 2c4c3b7..a199a42 100644 --- a/addon/io_scs_tools/ui/workspace.py +++ b/addon/io_scs_tools/ui/workspace.py @@ -222,11 +222,11 @@ def draw(self, context): layout.prop(scs_globals, 'locator_coll_face_color') -class SCS_TOOLS_PT_TrailerDisplay(_WorkspacePanelBlDefs, Panel): - """Draw trailer display panel.""" +class SCS_TOOLS_PT_SpawnPointDisplay(_WorkspacePanelBlDefs, Panel): + """Draw spawn point display panel.""" bl_parent_id = SCS_TOOLS_PT_LocatorsDisplay.__name__ - bl_label = "Trailer" + bl_label = "Spawn Points" bl_options = {'DEFAULT_CLOSED'} def draw(self, context): @@ -248,6 +248,9 @@ def draw(self, context): layout.prop(scs_globals, 'trailer_unload_medium_color') layout.prop(scs_globals, 'trailer_unload_hard_color') + layout.prop(scs_globals, 'owned_trailer_color') + layout.prop(scs_globals, 'service_station_color') + class SCS_TOOLS_PT_ConnectionsDisplay(_WorkspacePanelBlDefs, Panel): """Draw connections display panel.""" @@ -304,7 +307,7 @@ def draw(self, context): SCS_TOOLS_PT_PathSettings, SCS_TOOLS_PT_DisplaySettings, SCS_TOOLS_PT_LocatorsDisplay, - SCS_TOOLS_PT_TrailerDisplay, + SCS_TOOLS_PT_SpawnPointDisplay, SCS_TOOLS_PT_ConnectionsDisplay, SCS_TOOLS_PT_OtherSetttings, ) From c44456d0b6f913ac9853491f275aa0f08ee52cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 24 Jul 2025 02:37:27 +0200 Subject: [PATCH 48/56] fixes + updated version * Fixed icons not loading on Vulkan * Restored previous versioning system for unofficial versions. --- addon/io_scs_tools/__init__.py | 2 +- .../io_scs_tools/internals/icons/__init__.py | 20 +++++++++++++++++++ .../internals/persistent/file_load.py | 7 +++---- addon/io_scs_tools/utils/info.py | 18 ++++++++--------- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 8f3ea0d..2feb399 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, 7), # Replaced original "aeadde03" with unofficial version to fit with MAJOR.MINOR.PATCH scheme + "version": (2, 4, "aeadde03", 7), "blender": (4, 4, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/icons/__init__.py b/addon/io_scs_tools/internals/icons/__init__.py index c260b4d..f57ebf5 100644 --- a/addon/io_scs_tools/internals/icons/__init__.py +++ b/addon/io_scs_tools/internals/icons/__init__.py @@ -19,6 +19,7 @@ # Copyright (C) 2013-2019: SCS Software import os +import bpy from bpy.utils import previews from io_scs_tools.consts import Icons as _ICON_consts from io_scs_tools.utils import path as _path @@ -90,6 +91,25 @@ def register(): set_theme(get_theme_name(0)) print("WARNING\t- Default icon theme doesn't exist, fallback to first available!") + # Forces icons to reload (Because of issues with Vulkan) + bpy.app.timers.register(reload_icons, first_interval=0.1) + +def reload_icons(): + """Forces icons to reload after a short delay (workaround for Vulkan issues).""" + + current_theme = _cache[CURRENT_THEME] + pcoll = _cache[PCOLLS].get(current_theme) + if not pcoll: + return + + icon_themes_dir = os.path.join(_path.get_addon_installation_paths()[0], 'ui', 'icons') + for icon_type in _ICON_consts.Types.as_list(): + icon_path = os.path.join(icon_themes_dir, current_theme, icon_type) + if os.path.isfile(icon_path): + if icon_type in pcoll: + del pcoll[icon_type] + pcoll.load(icon_type, icon_path, 'IMAGE', force_reload=True) + return None def unregister(): """Clearing preview collections for custom icons. Should be called on addon unregister. diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index befc973..9c98c42 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -60,16 +60,15 @@ def post_load(scene): v_parts = last_load_bt_ver.split(".") for version, func in VERSIONS_LIST: if _info_utils.cmp_ver_str(last_load_bt_ver, version) <= 0: - # OFFICIAL version: ( X.Y.ZZZZ or old unofficial X.Y.ZZZZ.U ) - if int(v_parts[2]) > 100: + # OFFICIAL version: ( X.Y.ZZZZ ) + if len(v_parts) <= 3: # try to add apply fixed function as callback, if failed execute fixes right now if not AsyncPathsInit.append_callback(func): func() - # UNOFFICIAL version: (X.Y.U) (run only if 2.4 is applied) + # UNOFFICIAL version: (X.Y.ZZZZ.U) if version == "2.4": for version2, func2 in VERSIONS_LIST_UNOFFICIAL: - print(_info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2)) if _info_utils.cmp_ver_str_unofficial(last_load_bt_ver, version2) < 0: # try to add apply fixed function as callback, if failed execute fixes right now if not AsyncPathsInit.append_callback(func2): diff --git a/addon/io_scs_tools/utils/info.py b/addon/io_scs_tools/utils/info.py index 6f67571..b6bdb45 100644 --- a/addon/io_scs_tools/utils/info.py +++ b/addon/io_scs_tools/utils/info.py @@ -130,29 +130,29 @@ def cmp_ver_str(version_str, version_str2): return 1 def cmp_ver_str_unofficial(version_str, version_str2): - """Compares two version string of format "X.X.X" where X is number. + """Compares two version string of format "X.X.X..." where X is number. - :param version_str: version string to check (should be in format: "X.Y.Z" where X and Y are version numbers, Z can be hash/unofficial version) + :param version_str: version string to check (should be in format: "X.Y.ZZZZ.[U]" where X and Y are version numbers, ZZZZ hash, and U is optional unofficial update version) :type version_str: str - :param version_str2: version string to check (should be in format: "X" where X is unofficial update number) + :param version_str2: version string to check (should be in format: "U" where U is unofficial update version) :type version_str2: str :return: -1 if first is smaller; 0 if equal; 1 if first is greater; :rtype: int """ version_str = version_str.split(".") + version_str2 = version_str2.split(".") - # Threat hash from official version as 0 - # Because data from version_str string is returned as number, we comparing it with big number - if int(version_str[2]) > 100: - version_str[2] = 0 + # Fix for users migrating from official BT (without unofficial update version at BT version) + while len(version_str) <= 3: + version_str.append('0') # First version smaller than second - if int(version_str[2]) < int(version_str2): + if int(version_str[3]) < int(version_str2[0]): return -1 # Equal versions - if int(version_str[2]) == int(version_str2): + if int(version_str[3]) == int(version_str2[0]): return 0 # Otherwise we directly assume that first is greater From 229c423859ee1d2eef72d222a99b16d210c78d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 27 Sep 2025 04:07:42 +0200 Subject: [PATCH 49/56] Lamp System update + small tweaks * Updated minimum required Blender version to 4.5 (LTS recommended) * Added support for new UV Tile (5) in Lamp System + changes in 4th tile * Changed error text when texture attribute is not set (From "Texture doesn't exists" to "Texture is missing") * Tweaks in custom icons interval time (icons should break less often) --- addon/io_scs_tools/__init__.py | 2 +- addon/io_scs_tools/consts.py | 3 +- addon/io_scs_tools/exp/pit.py | 7 ++- .../io_scs_tools/internals/icons/__init__.py | 2 +- .../eut2/std_node_groups/lampmask_mixer_ng.py | 48 +++++++++++++------ addon/io_scs_tools/operators/mesh.py | 4 +- addon/io_scs_tools/ui/tool_shelf.py | 7 ++- 7 files changed, 51 insertions(+), 22 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 2feb399..6396582 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -23,7 +23,7 @@ "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", "version": (2, 4, "aeadde03", 7), - "blender": (4, 4, 0), + "blender": (4, 5, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", "tracker_url": "http://forum.scssoft.com/viewforum.php?f=163", diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index 3b00c44..8cc8f21 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -194,7 +194,8 @@ class VehicleSides(Enum): FrontRight = 1 RearLeft = 2 RearRight = 3 - Middle = 4 + MiddleLeft = 4 + MiddleRight = 5 class VehicleLampTypes(Enum): """Defined lamp types for vehicles. diff --git a/addon/io_scs_tools/exp/pit.py b/addon/io_scs_tools/exp/pit.py index d153e92..233abd6 100644 --- a/addon/io_scs_tools/exp/pit.py +++ b/addon/io_scs_tools/exp/pit.py @@ -235,9 +235,14 @@ def get_texture_path_from_material(material, texture_type, export_path): return "" else: - lprint("E Texture file %r from material %r doesn't exists inside current Project Base Path.\n\t " + + if texture_raw_path: + lprint("E Texture file %r from material %r doesn't exists inside current Project Base Path.\n\t " + "TOBJ won't be exported and reference will remain empty, expect problems!", (texture_raw_path, material.name)) + else: + lprint("E Texture type %r on material %r is missing texture.\n\t " + + "TOBJ won't be exported and reference will remain empty, expect problems!", + (texture_type[8:], material.name)) return "" # CREATE TOBJ FILE diff --git a/addon/io_scs_tools/internals/icons/__init__.py b/addon/io_scs_tools/internals/icons/__init__.py index f57ebf5..1383c15 100644 --- a/addon/io_scs_tools/internals/icons/__init__.py +++ b/addon/io_scs_tools/internals/icons/__init__.py @@ -92,7 +92,7 @@ def register(): print("WARNING\t- Default icon theme doesn't exist, fallback to first available!") # Forces icons to reload (Because of issues with Vulkan) - bpy.app.timers.register(reload_icons, first_interval=0.1) + bpy.app.timers.register(reload_icons, first_interval=0.2) def reload_icons(): """Forces icons to reload after a short delay (workaround for Vulkan issues).""" diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py index 64c62cf..5542ca7 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py @@ -27,7 +27,7 @@ AUX_LAMP_TYPES = _LT_consts.AuxiliaryLampTypes TRAFFIC_LIGHT_TYPES = _LT_consts.TrafficLightTypes -UV_X_TILES = ["UV_X_0_", "UV_X_1_", "UV_X_2_", "UV_X_3_", "UV_X_4_"] +UV_X_TILES = ["UV_X_0_", "UV_X_1_", "UV_X_2_", "UV_X_3_", "UV_X_4_", "UV_X_5_"] UV_Y_TILES = ["UV_Y_0_", "UV_Y_1_", "UV_Y_2_", "UV_Y_3_"] LAMPMASK_MIX_G = _MAT_consts.node_group_prefix + "LampmaskMixerGroup" @@ -93,13 +93,13 @@ def __create_node_group__(): uv_x_dot_n = lampmask_g.nodes.new("ShaderNodeVectorMath") uv_x_dot_n.name = uv_x_dot_n.label = _UV_DOT_X_NODE - uv_x_dot_n.location = (pos_x_shift, -200) + uv_x_dot_n.location = (pos_x_shift, -400) uv_x_dot_n.operation = "DOT_PRODUCT" uv_x_dot_n.inputs[1].default_value = (1.0, 0, 0) uv_y_dot_n = lampmask_g.nodes.new("ShaderNodeVectorMath") uv_y_dot_n.name = uv_y_dot_n.label = _UV_DOT_Y_NODE - uv_y_dot_n.location = (pos_x_shift, -450) + uv_y_dot_n.location = (pos_x_shift, -750) uv_y_dot_n.operation = "DOT_PRODUCT" uv_y_dot_n.inputs[1].default_value = (0, 1.0, 0) @@ -112,7 +112,7 @@ def __create_node_group__(): nodes_for_addition = [] # init uv tilling mechanism - pos_y = -100 + pos_y = -300 max_x_uv = 1 for uv_x_tile in UV_X_TILES: @@ -122,7 +122,7 @@ def __create_node_group__(): max_x_uv += 1 max_y_uv = 1 - pos_y = -400 + pos_y = -700 for uv_y_tile in UV_Y_TILES: __init_uv_tile_bounding_nodes__(lampmask_g, uv_y_dot_n, uv_y_tile, pos_x_shift * 2, pos_y, max_y_uv) @@ -131,21 +131,21 @@ def __create_node_group__(): max_y_uv += 1 # init vehicle sides uv bounding mechanism - pos_y = -50 + pos_y = -250 for vehicle_side in VEHICLE_SIDES: __init_vehicle_uv_bounding_nodes__(lampmask_g, vehicle_side, pos_x_shift * 2, pos_y) pos_y -= 50 # init traffic light uv bounding mechanism - pos_y = -350 + pos_y = -600 for traffic_light_type in TRAFFIC_LIGHT_TYPES: __init_traffic_light_uv_bounding_nodes__(lampmask_g, traffic_light_type, pos_x_shift * 2, pos_y) pos_y -= 100 # init vehicle sides switches mechanism - pos_y = 1000 + pos_y = 1200 for vehicle_lamp_type in VEHICLE_LAMP_TYPES: if vehicle_lamp_type == VEHICLE_LAMP_TYPES.Positional: # make extra space for positional @@ -159,7 +159,7 @@ def __create_node_group__(): vehicle_lamp_type, pos_x_shift * 5, pos_y, nodes_for_addition) - pos_y -= 75 + pos_y -= 125 # init auxiliary lamp switches mechanism pos_y -= 60 @@ -197,7 +197,7 @@ def __create_node_group__(): add_n = lampmask_g.nodes.new("ShaderNodeMath") add_n.name = _ADD_NODE_PREFIX + str(i) add_n.label = _ADD_NODE_PREFIX + str(i) - add_n.location = (curr_node.location.x + 120, curr_node.location.y) + add_n.location = (curr_node.location.x + 180, curr_node.location.y) add_n.hide = True add_n.operation = "ADD" @@ -278,9 +278,12 @@ def __init_vehicle_uv_bounding_nodes__(node_tree, vehicle_side, pos_x, pos_y): elif vehicle_side == VEHICLE_SIDES.RearRight: min_uv_n = node_tree.nodes[UV_X_TILES[2] + _MAX_UV_SUFFIX] max_uv_n = node_tree.nodes[UV_X_TILES[3] + _MAX_UV_SUFFIX] - else: # fallback to middle + elif vehicle_side == VEHICLE_SIDES.MiddleLeft: min_uv_n = node_tree.nodes[UV_X_TILES[3] + _MAX_UV_SUFFIX] max_uv_n = node_tree.nodes[UV_X_TILES[4] + _MAX_UV_SUFFIX] + else: # fallback to MiddleRight + min_uv_n = node_tree.nodes[UV_X_TILES[4] + _MAX_UV_SUFFIX] + max_uv_n = node_tree.nodes[UV_X_TILES[5] + _MAX_UV_SUFFIX] uv_in_bounds_n = node_tree.nodes.new("ShaderNodeMath") uv_in_bounds_n.name = vehicle_side.name + _IN_BOUNDS_SUFFIX @@ -408,25 +411,33 @@ def __init_vehicle_switch_nodes__(node_tree, a_output, r_output, g_output, b_out elif lamp_type == VEHICLE_LAMP_TYPES.DRL: node_tree.links.new(switch_n.inputs[1], b_output) - node_name = lamp_type.name + VEHICLE_SIDES.Middle.name - position = (pos_x + 185 * 2, pos_y) - in_bounds_n = node_tree.nodes[VEHICLE_SIDES.Middle.name + _IN_BOUNDS_SUFFIX] + in_bounds_n = node_tree.nodes[VEHICLE_SIDES.MiddleLeft.name + _IN_BOUNDS_SUFFIX] + node_name = lamp_type.name + VEHICLE_SIDES.MiddleLeft.name + position = (pos_x + 185 * 2, pos_y + 18) + mult_n = __create_merging_node__(node_tree, node_name, position, in_bounds_n.outputs[0], switch_n.outputs[0]) + nodes_for_addition.append(mult_n) + + in_bounds_n = node_tree.nodes[VEHICLE_SIDES.MiddleRight.name + _IN_BOUNDS_SUFFIX] + node_name = lamp_type.name + VEHICLE_SIDES.MiddleRight.name + position = (pos_x + 185 * 2, pos_y - 18) mult_n = __create_merging_node__(node_tree, node_name, position, in_bounds_n.outputs[0], switch_n.outputs[0]) nodes_for_addition.append(mult_n) else: - color_output = veh_side_name1 = veh_side_name2 = None + color_output = veh_side_name1 = veh_side_name2 = veh_side_name3 = None if lamp_type == VEHICLE_LAMP_TYPES.LeftTurn: color_output = r_output veh_side_name1 = VEHICLE_SIDES.FrontLeft.name veh_side_name2 = VEHICLE_SIDES.RearLeft.name + veh_side_name3 = VEHICLE_SIDES.MiddleLeft.name elif lamp_type == VEHICLE_LAMP_TYPES.RightTurn: color_output = r_output veh_side_name1 = VEHICLE_SIDES.FrontRight.name veh_side_name2 = VEHICLE_SIDES.RearRight.name + veh_side_name3 = VEHICLE_SIDES.MiddleRight.name elif lamp_type == VEHICLE_LAMP_TYPES.Brake: @@ -467,6 +478,13 @@ def __init_vehicle_switch_nodes__(node_tree, a_output, r_output, g_output, b_out mult_n = __create_merging_node__(node_tree, node_name, node_pos, switch_n.outputs[0], in_bounds_n.outputs[0]) nodes_for_addition.append(mult_n) + if veh_side_name3: + in_bounds_n = node_tree.nodes[veh_side_name3 + _IN_BOUNDS_SUFFIX] + node_name = lamp_type.name + veh_side_name3 + node_pos = (pos_x + 185 * 2, pos_y - 54) + mult_n = __create_merging_node__(node_tree, node_name, node_pos, switch_n.outputs[0], in_bounds_n.outputs[0]) + nodes_for_addition.append(mult_n) + def __init_aux_switch_nodes__(node_tree, a_output, r_output, g_output, lamp_type, pos_x, pos_y, nodes_for_addition): """Creation and linking of switching nodes, covering all combinations for vehicle auxiliary lamp mask. diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index 3c7c0e3..563e144 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -79,8 +79,10 @@ def execute(self, context): offset_x = 2 elif _LT_consts.VehicleSides.RearRight.name == self.vehicle_side: offset_x = 3 - elif _LT_consts.VehicleSides.Middle.name == self.vehicle_side: + elif _LT_consts.VehicleSides.MiddleLeft.name == self.vehicle_side: offset_x = 4 + elif _LT_consts.VehicleSides.MiddleRight.name == self.vehicle_side: + offset_x = 5 elif _LT_consts.AuxiliaryLampColors.White.name == self.aux_color: # auxiliary lights checking offset_x = 0 elif _LT_consts.AuxiliaryLampColors.Orange.name == self.aux_color: diff --git a/addon/io_scs_tools/ui/tool_shelf.py b/addon/io_scs_tools/ui/tool_shelf.py index f4d3e76..0bf8458 100644 --- a/addon/io_scs_tools/ui/tool_shelf.py +++ b/addon/io_scs_tools/ui/tool_shelf.py @@ -472,8 +472,11 @@ def draw(self, context): props.vehicle_side = _LT_consts.VehicleSides.RearRight.name props.aux_color = props.traffic_light_color = "" body_row = body_col.row(align=True) - props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle") - props.vehicle_side = _LT_consts.VehicleSides.Middle.name + props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle Left") + props.vehicle_side = _LT_consts.VehicleSides.MiddleLeft.name + props.aux_color = props.traffic_light_color = "" + props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle Right") + props.vehicle_side = _LT_consts.VehicleSides.MiddleRight.name props.aux_color = props.traffic_light_color = "" body_col = layout.column(align=True) From 95a6855ff62e9d17293e19fb2ec85677cb89f9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 25 Nov 2025 01:09:11 +0100 Subject: [PATCH 50/56] Update to Blender 5.0 * Updated to Blender 5.0 (thanks to crisan21) * Changed button order in Lamp UV Tool (from front-rear-middle, to front-middle-rear) --- addon/io_scs_tools/__init__.py | 2 +- .../internals/callbacks/open_gl.py | 5 +- .../internals/containers/config.py | 5 +- addon/io_scs_tools/internals/open_gl/core.py | 2 +- .../internals/shaders/eut2/glass/__init__.py | 11 ++-- addon/io_scs_tools/operators/wm.py | 14 ++-- .../properties/dynamic/__init__.py | 14 ++-- addon/io_scs_tools/properties/object.py | 66 ++++++++----------- addon/io_scs_tools/ui/tool_shelf.py | 14 ++-- 9 files changed, 65 insertions(+), 68 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 6396582..c6540c8 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -23,7 +23,7 @@ "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", "version": (2, 4, "aeadde03", 7), - "blender": (4, 5, 0), + "blender": (5, 0, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", "tracker_url": "http://forum.scssoft.com/viewforum.php?f=163", diff --git a/addon/io_scs_tools/internals/callbacks/open_gl.py b/addon/io_scs_tools/internals/callbacks/open_gl.py index 07958cf..bdf6551 100644 --- a/addon/io_scs_tools/internals/callbacks/open_gl.py +++ b/addon/io_scs_tools/internals/callbacks/open_gl.py @@ -19,7 +19,6 @@ # Copyright (C) 2013-2019: SCS Software import bpy -import console_python from io_scs_tools.internals.open_gl import core as _gl_core from io_scs_tools.utils import view3d as _view3d_utils @@ -47,7 +46,7 @@ def enable(mode="Normal"): _callback_handle[:] = handle_post_pixel, handle_post_view - console_python.execute.hooks.append((_view3d_utils.tag_redraw_all_view3d, ())) + _view3d_utils.tag_redraw_all_view3d() def disable(): @@ -58,7 +57,7 @@ def disable(): if not _callback_handle: return - console_python.execute.hooks.remove((_view3d_utils.tag_redraw_all_view3d, ())) + _view3d_utils.tag_redraw_all_view3d() handle_post_pixel, handle_post_view = _callback_handle diff --git a/addon/io_scs_tools/internals/containers/config.py b/addon/io_scs_tools/internals/containers/config.py index 2fdda47..cf369f0 100644 --- a/addon/io_scs_tools/internals/containers/config.py +++ b/addon/io_scs_tools/internals/containers/config.py @@ -261,7 +261,10 @@ def apply_settings(self, settings_to_apply=None): if not attr: continue - setattr(scs_globals, attr, value) + # prevents the configuration from looping when starting Blender + current = getattr(scs_globals, attr, None) + if current != value: + setattr(scs_globals, attr, value) def fill_from_pix_section(self, section): """Fill config section with data from given pix section. diff --git a/addon/io_scs_tools/internals/open_gl/core.py b/addon/io_scs_tools/internals/open_gl/core.py index e29e613..350095d 100644 --- a/addon/io_scs_tools/internals/open_gl/core.py +++ b/addon/io_scs_tools/internals/open_gl/core.py @@ -155,7 +155,7 @@ def _draw_3dview_report(window, area, region): (texture, width, height) = _Show3DViewReport.get_scs_banner_img_data(window) gpu.state.blend_set("ALPHA") - draw_texture_2d(texture, (pos_x - 5, pos_y), width, height) + draw_texture_2d(texture, (pos_x - 5, pos_y), width, height, is_scene_linear_with_rec709_srgb_target=True) gpu.state.blend_set("NONE") # draw control buttons, if controls are enabled diff --git a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py index 6e78719..19055e3 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py @@ -205,9 +205,10 @@ def init(node_tree): lighting_eval_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1800) lighting_eval_n.node_tree = lighting_evaluator_ng.get_node_group() - fakeopac_hsv_n = node_tree.nodes.new("ShaderNodeSeparateHSV") + fakeopac_hsv_n = node_tree.nodes.new("ShaderNodeSeparateColor") fakeopac_hsv_n.name = fakeopac_hsv_n.label = Glass.FAKEOPAC_HSV_NODE fakeopac_hsv_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1500) + fakeopac_hsv_n.mode = "HSV" # pass 6 add_env_n = node_tree.nodes.new("ShaderNodeGroup") @@ -325,14 +326,14 @@ def init(node_tree): node_tree.links.new(fakeopac_spec_mix_n.inputs[0], final_spec_n.outputs[0]) node_tree.links.new(fakeopac_spec_mix_n.inputs[1], lighting_eval_n.outputs['Specular Lighting']) - node_tree.links.new(fakeopac_add_sv_n.inputs[0], fakeopac_hsv_n.outputs['S']) - node_tree.links.new(fakeopac_add_sv_n.inputs[1], fakeopac_hsv_n.outputs['V']) + node_tree.links.new(fakeopac_add_sv_n.inputs[0], fakeopac_hsv_n.outputs[1]) # Saturation + node_tree.links.new(fakeopac_add_sv_n.inputs[1], fakeopac_hsv_n.outputs[2]) # Value # pass 7 node_tree.links.new(fakeopac_sub_sv_n.inputs[0], fakeopac_add_sv_n.outputs['Value']) - node_tree.links.new(fakeopac_sub_sv_n.inputs[1], fakeopac_hsv_n.outputs['V']) + node_tree.links.new(fakeopac_sub_sv_n.inputs[1], fakeopac_hsv_n.outputs[2]) # Value - node_tree.links.new(fakeopac_v_inv_n.inputs[1], fakeopac_hsv_n.outputs['V']) + node_tree.links.new(fakeopac_v_inv_n.inputs[1], fakeopac_hsv_n.outputs[2]) # Value # pass 8 node_tree.links.new(fakeopac_add_spec_mix_n.inputs[0], add_env_n.outputs['Environment Addition Color']) diff --git a/addon/io_scs_tools/operators/wm.py b/addon/io_scs_tools/operators/wm.py index 76f2f4e..cd31240 100644 --- a/addon/io_scs_tools/operators/wm.py +++ b/addon/io_scs_tools/operators/wm.py @@ -152,22 +152,22 @@ def get_scs_banner_img_data(window): img_name = _OP_consts.View3DReport.BT_BANNER_IMG_NAME if img_name not in bpy.data.images: - img_path = os.path.join(_path_utils.get_addon_installation_paths()[0], "ui", "banners", img_name) img = bpy.data.images.load(img_path, check_existing=True) img.colorspace_settings.name = 'sRGB' img.alpha_mode = 'CHANNEL_PACKED' else: - img = bpy.data.images[img_name] - texture = gpu.texture.from_image(img) + if not img.has_data: + img.reload() - # ensure that image is loaded in GPU memory aka has proper bindcode, - # we have to that each time because if operator is shown for long time blender might free it on it's own - if img.bindcode == 0: - img.gl_load() + try: + texture = gpu.texture.from_image(img) + except RuntimeError: + img.reload() + texture = gpu.texture.from_image(img) return texture, img.size[0], img.size[1] diff --git a/addon/io_scs_tools/properties/dynamic/__init__.py b/addon/io_scs_tools/properties/dynamic/__init__.py index ece47ad..cdb8fb2 100644 --- a/addon/io_scs_tools/properties/dynamic/__init__.py +++ b/addon/io_scs_tools/properties/dynamic/__init__.py @@ -82,12 +82,12 @@ def getter(self): prefs = bpy.context.preferences.addons["io_scs_tools"].preferences - if scope not in prefs: + if not hasattr(prefs, scope): return default scoped_prefs = prefs[scope] - if property_name not in scoped_prefs: + if not hasattr(scoped_prefs, property_name): return default return scoped_prefs[property_name] @@ -105,10 +105,12 @@ def setter(self, value): prefs = bpy.context.preferences.addons["io_scs_tools"].preferences - if scope not in prefs: - prefs[scope] = {} - - prefs[scope][property_name] = value + if not hasattr(prefs, scope): + setattr(prefs, scope, {}) + + scope_dict = getattr(prefs, scope) + scope_dict[property_name] = value + setattr(prefs, scope, scope_dict) # check for default value type assert isinstance(default, property_type) diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 2a7042c..3bfe907 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -369,6 +369,10 @@ def active_scs_part_get_direct(self): # NOTE: case where this happens is if user imports SCS model, # duplicates prefab locator without part and then changes locator # type to model locator. + + if "active_scs_part_old_active" not in self: + self["active_scs_part_old_active"] = "" + if "active_scs_part_value" not in self: self["active_scs_part_value"] = 0 @@ -379,49 +383,22 @@ def active_scs_part_get(self): with the index of part belonging to new active object. """ - debug = False - lprint("D ---------- GETTER ----------") if debug else None - if "active_scs_part_old_active" not in self: - lprint("D ---- IF SELF 1 ENTERED ----") if debug else None - self["active_scs_part_old_active"] = "" - - if "active_scs_part_value" not in self: - lprint("D ---- IF SELF 2 ENTERED ----") if debug else None - self["active_scs_part_value"] = 0 + return self.active_scs_part_value + def active_scs_part_set(self, value): scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) - if debug: - lprint("D --active_scs_part_old_active-- %r", (self["active_scs_part_old_active"],)) - lprint("D --active_scs_part_value-- %r", (self["active_scs_part_value"],)) - lprint("D --scs_root_object-- %r", (scs_root_object,)) - lprint("D --bpy.context.active_object-- %r", (bpy.context.active_object,)) - lprint("D --bpy.context.active_object.name-- %r", (bpy.context.active_object.name,)) + self.active_scs_part_value = value + if scs_root_object and bpy.context.active_object != scs_root_object: - lprint("D ---- IF 1 ENTERED ----") if debug else None # if old active object is different than current # set the value for active part index from it - if self["active_scs_part_old_active"] != bpy.context.active_object.name: - lprint("D ---- IF 2 ENTERED ----") if debug else None - self["active_scs_part_value"] = _inventory.get_index(scs_root_object.scs_object_part_inventory, - bpy.context.active_object.scs_props.scs_part) - - # TEMP: skip unnecessary 'active_scs_part_old_active' update to decrease errors count in console (setters in getters) - if self["active_scs_part_old_active"] != bpy.context.active_object.name: - lprint("D ---- TEMP IF ENTERED ----") if debug else None - self["active_scs_part_old_active"] = bpy.context.active_object.name - print("-------------------------\n") if debug else None - return self["active_scs_part_value"] - - def active_scs_part_set(self, value): - debug = False - lprint("D ---------- SETTER ----------") if debug else None - self["active_scs_part_value"] = value - print("-------------------------\n") if debug else None + if self.active_scs_part_old_active != bpy.context.active_object.name: + self.active_scs_part_value = _inventory.get_index(scs_root_object.scs_object_part_inventory, + bpy.context.active_object.scs_props.scs_part) - def active_scs_part_update(self, context): - lprint("D ---------- UPDATE ----------") - print("-------------------------\n") + if self.active_scs_part_old_active != bpy.context.active_object.name: + self.active_scs_part_old_active = bpy.context.active_object.name active_scs_part: IntProperty( name="Active SCS Part", @@ -433,7 +410,22 @@ def active_scs_part_update(self, context): subtype='NONE', get=active_scs_part_get, set=active_scs_part_set - # update=active_scs_part_update + ) + + active_scs_part_old_active: bpy.props.StringProperty( + name="Old Active Part", + description="Stores previously active SCS part", + default="" + ) + + active_scs_part_value: IntProperty( + name="Active SCS Part Value", + description="Active SCS Part for current SCS Root Object Value", + default=0, + min=0, + step=1, + options={'HIDDEN'}, + subtype='NONE' ) def get_active_scs_look(self): diff --git a/addon/io_scs_tools/ui/tool_shelf.py b/addon/io_scs_tools/ui/tool_shelf.py index 0bf8458..a605c48 100644 --- a/addon/io_scs_tools/ui/tool_shelf.py +++ b/addon/io_scs_tools/ui/tool_shelf.py @@ -465,19 +465,19 @@ def draw(self, context): props.vehicle_side = _LT_consts.VehicleSides.FrontRight.name props.aux_color = props.traffic_light_color = "" body_row = body_col.row(align=True) - props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Rear Left") - props.vehicle_side = _LT_consts.VehicleSides.RearLeft.name - props.aux_color = props.traffic_light_color = "" - props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Rear Right") - props.vehicle_side = _LT_consts.VehicleSides.RearRight.name - props.aux_color = props.traffic_light_color = "" - body_row = body_col.row(align=True) props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle Left") props.vehicle_side = _LT_consts.VehicleSides.MiddleLeft.name props.aux_color = props.traffic_light_color = "" props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle Right") props.vehicle_side = _LT_consts.VehicleSides.MiddleRight.name props.aux_color = props.traffic_light_color = "" + body_row = body_col.row(align=True) + props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Rear Left") + props.vehicle_side = _LT_consts.VehicleSides.RearLeft.name + props.aux_color = props.traffic_light_color = "" + props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Rear Right") + props.vehicle_side = _LT_consts.VehicleSides.RearRight.name + props.aux_color = props.traffic_light_color = "" body_col = layout.column(align=True) body_row = body_col.row(align=True) From ae743652999761509a14182af68892a75bbd5155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sun, 30 Nov 2025 08:08:22 +0100 Subject: [PATCH 51/56] New options for parts and some fixes * Added 2 new settings to control automatic activation of parts (by default ON) when added, or when new variant is created. * Added auto remover for "diffuse_secondary" material attribute (not supported in current material format) * Fixed "defaultpart" part that was always being added to every imported root --- addon/io_scs_tools/imp/pit.py | 8 ++++++- addon/io_scs_tools/imp/pix.py | 1 + .../internals/containers/config.py | 15 +++++++++++++ addon/io_scs_tools/operators/object.py | 5 +++-- .../properties/addon_preferences.py | 22 +++++++++++++++++++ addon/io_scs_tools/properties/object.py | 10 +++++++-- addon/io_scs_tools/shader_presets.txt | 14 +++++------- addon/io_scs_tools/ui/shared.py | 2 ++ 8 files changed, 64 insertions(+), 13 deletions(-) diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index 54280a0..2b24cf5 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -200,7 +200,7 @@ def _get_look(section): mat_effect = mat_effect.replace(".night", ".day") lprint("W Night version of building shader detected in material %r, switching it to day!", (mat_alias,)) - # Extra treatment for deprecated/removed shaders and flavors + # Extra treatment for deprecated/removed/unsupported shaders and flavors # # If day/night version of "window" shader is detected, switch it to "lit". if mat_effect.startswith("eut2.window") and mat_effect.endswith((".day", ".night")): @@ -224,6 +224,12 @@ def _get_look(section): mat_effect = mat_effect.replace(".day", "") lprint("W Day version of billboard shader detected in material %r, removing it from effect!", (mat_alias,)) + # (temporary workaround for new attribute not supported in older material format used by BT) + # If "diffuse_secondary" attribute is detected, remove it + if attributes.pop("diffuse_secondary", None) is not None: + lprint("I Unsupported attribute: 'diffuse_secondary' in current material configuration inside material %r, ignoring it!", + (mat_alias,)) + look_mat_settings[mat_alias] = (mat_effect, mat_flags, attributes, textures, sec) return look_name, look_mat_settings diff --git a/addon/io_scs_tools/imp/pix.py b/addon/io_scs_tools/imp/pix.py index e499026..a3fc1b1 100644 --- a/addon/io_scs_tools/imp/pix.py +++ b/addon/io_scs_tools/imp/pix.py @@ -174,6 +174,7 @@ def _create_scs_root_object(name, loaded_variants, loaded_looks, mats_info, obje bpy.context.view_layer.active_layer_collection.collection.objects.link(scs_root_object) bpy.context.view_layer.objects.active = scs_root_object scs_root_object.scs_props.scs_root_object_export_enabled = True + scs_root_object["skip_default_part"] = True scs_root_object.scs_props.empty_object_type = 'SCS_Root' # print('LOD.pos: %s' % str(scs_root_object.location)) diff --git a/addon/io_scs_tools/internals/containers/config.py b/addon/io_scs_tools/internals/containers/config.py index cf369f0..93cd373 100644 --- a/addon/io_scs_tools/internals/containers/config.py +++ b/addon/io_scs_tools/internals/containers/config.py @@ -538,6 +538,20 @@ def __init__(self): } +class GlobalOther(_ConfigSection): + """Class for global other settings.""" + + def __init__(self): + """Constructor.""" + super().__init__("GlobalOther") + + scs_globals = _get_scs_globals() + self.props = { + "ActivateNewParts": (int, get_default(scs_globals, 'activate_new_parts'), 'activate_new_parts'), + "ActivateNewVariantParts": (int, get_default(scs_globals, 'activate_new_variant_parts'), 'activate_new_variant_parts'), + } + + class ConfigContainer: """Class implementing config container handler.""" @@ -550,6 +564,7 @@ def __init__(self): "Export": Export(), "GlobalDisplay": GlobalDisplay(), "GlobalColors": GlobalColors(), + "GlobalOther": GlobalOther(), } def set_property(self, section_type, prop_name, value): diff --git a/addon/io_scs_tools/operators/object.py b/addon/io_scs_tools/operators/object.py index eee6f9e..bfa6cc8 100755 --- a/addon/io_scs_tools/operators/object.py +++ b/addon/io_scs_tools/operators/object.py @@ -430,7 +430,7 @@ def execute(self, context): variant_part = _inventory.add_item(variant.parts, part.name) if variant_part: - variant_part.include = True + variant_part.include = True if _get_scs_globals().activate_new_parts else False else: lprint("W Part %r already in variant %r.", (part.name, variant.name)) @@ -778,7 +778,8 @@ def execute(self, context): for part in part_inventory: variant_part = _inventory.add_item(variant.parts, part.name) - variant_part.include = True + + variant_part.include = True if _get_scs_globals().activate_new_variant_parts else False scs_root_object.scs_props.active_scs_variant = len(variant_inventory) - 1 diff --git a/addon/io_scs_tools/properties/addon_preferences.py b/addon/io_scs_tools/properties/addon_preferences.py index d0d8ce1..ef8ed8a 100644 --- a/addon/io_scs_tools/properties/addon_preferences.py +++ b/addon/io_scs_tools/properties/addon_preferences.py @@ -1340,6 +1340,14 @@ def config_storage_place_update(self, context): return None + def activate_new_parts_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalOther.ActivateNewParts', int(self.activate_new_parts)) + + def activate_new_variant_parts_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalOther.ActivateNewVariantParts', int(self.activate_new_variant_parts)) + dump_level: EnumProperty( name="Printouts", items=( @@ -1364,6 +1372,20 @@ def config_storage_place_update(self, context): update=config_storage_place_update, ) + activate_new_parts: BoolProperty( + name="Activate New Parts", + description="Automatically activate new parts when added to SCS Game Object.", + default=True, + update=activate_new_parts_update + ) + + activate_new_variant_parts: BoolProperty( + name="Activate New Variant Parts", + description="Automatically activate parts when new variant is created for SCS Game Object.", + default=True, + update=activate_new_variant_parts_update + ) + # COMMON SETTINGS - NOT SAVED IN CONFIG preview_export_selection: BoolProperty( name="Preview selection", diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 3bfe907..da08882 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -295,9 +295,15 @@ def update_empty_object_type(self, context): obj.empty_display_type = "ARROWS" obj.show_name = True - # ensure default part + # ensure default part when it's needed part_inventory = obj.scs_object_part_inventory - _inventory.add_item(part_inventory, _PART_consts.default_name, conditional=True) + skip_default_part = obj.get("skip_default_part", False) + if not skip_default_part: + _inventory.add_item(part_inventory, _PART_consts.default_name, conditional=True) + + # remove flag after use + if "skip_default_part" in obj: + del obj["skip_default_part"] else: obj.empty_display_size = 1.0 obj.empty_display_type = "PLAIN_AXES" diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 47663c1..f0419b0 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1742,15 +1742,13 @@ Shader { Value: ( 1.0 1.0 1.0 ) } # NOTE: This shader use new secondary diffuse that is not supported in old material system by conversion tool 2.20 yet. - # To prevent being , I set it to previewonly and hide it. If newer version of conversion tool will support it, this should be changed. + # To prevent being , this attribute will be removed on import automatically. If newer version of conversion tool will support it, this should be changed. # This attribute will be probably named "aux[4]" in future. - Attribute { - Format: FLOAT3 - Tag: "diffuse_secondary" - Value: ( 1.0 1.0 1.0 ) - PreviewOnly: "True" - Hide: "True" - } + # Attribute { + # Format: FLOAT3 + # Tag: "diffuse_secondary" + # Value: ( 1.0 1.0 1.0 ) + # } Attribute { Format: FLOAT3 Tag: "specular" diff --git a/addon/io_scs_tools/ui/shared.py b/addon/io_scs_tools/ui/shared.py index 26d9518..5c6d2ea 100644 --- a/addon/io_scs_tools/ui/shared.py +++ b/addon/io_scs_tools/ui/shared.py @@ -194,6 +194,8 @@ def draw_common_settings(layout, log_level_only=False, without_box=False): if not log_level_only: sub_layout.prop(_get_scs_globals(), 'config_storage_place') + sub_layout.prop(_get_scs_globals(), 'activate_new_parts') + sub_layout.prop(_get_scs_globals(), 'activate_new_variant_parts') def draw_warning_operator(layout, title, message, text="", icon='ERROR'): From 760af2b165820b8541c3a037ccd9fcfe0f3deb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 17 Dec 2025 21:20:29 +0100 Subject: [PATCH 52/56] Fixes in nmaps and parts * Added "is_dynamic_road" and "is_terrain_material" to list of ignored attributes (not working in current legacy material format) * Tweaks in global nmap flavor (removed B channel support and forced 2-channel group node) * Added preview for "tsnmapuv2" flavor * Added preview for "tsnmapcalc" flavor (for dual texture) * Added few new FriendlyTags for nmaps * Fixed an issue where the currently assigned part was not displayed correctly --- addon/io_scs_tools/imp/pit.py | 17 +- .../shaders/eut2/dif_anim/__init__.py | 64 +++++++ .../shaders/eut2/dif_anim/over_nmap.py | 168 ++++++++++++++++++ .../eut2/dif_spec_weight_add_env/__init__.py | 62 +++++++ .../dif_spec_weight_add_env/detail_nmap.py | 167 +++++++++++++++++ .../__init__.py | 64 +++++++ .../over_nmap.py | 168 ++++++++++++++++++ .../shaders/flavors/nmap/__init__.py | 73 ++------ .../io_scs_tools/internals/shaders/shader.py | 2 +- addon/io_scs_tools/properties/object.py | 5 +- addon/io_scs_tools/shader_presets.txt | 5 + addon/io_scs_tools/ui/object.py | 7 +- 12 files changed, 741 insertions(+), 61 deletions(-) create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index 2b24cf5..552b46f 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -224,11 +224,22 @@ def _get_look(section): mat_effect = mat_effect.replace(".day", "") lprint("W Day version of billboard shader detected in material %r, removing it from effect!", (mat_alias,)) - # (temporary workaround for new attribute not supported in older material format used by BT) + # Extra temporary treatment for new attributes not supported in older material format used by BT + # # If "diffuse_secondary" attribute is detected, remove it if attributes.pop("diffuse_secondary", None) is not None: - lprint("I Unsupported attribute: 'diffuse_secondary' in current material configuration inside material %r, ignoring it!", - (mat_alias,)) + lprint("I Unsupported attribute: 'diffuse_secondary' in current material configuration inside material %r, ignoring it!", + (mat_alias,)) + + # If "is_dynamic_road" attribute is detected, remove it + if attributes.pop("is_dynamic_road", None) is not None: + lprint("W Unsupported attribute: 'is_dynamic_road' inside material %r, ignoring it! Material will not work as intended after export!", + (mat_alias,)) + + # If "is_terrain_material" attribute is detected, remove it + if attributes.pop("is_terrain_material", None) is not None: + lprint("W Unsupported attribute: 'is_terrain_material' inside material %r, ignoring it! Material will not work as intended after export!", + (mat_alias,)) look_mat_settings[mat_alias] = (mat_effect, mat_flags, attributes, textures, sec) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py index d70fcd4..6ad932d 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py @@ -21,6 +21,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif import Dif from io_scs_tools.internals.shaders.eut2.dif_anim import anim_blend_factor_ng +from io_scs_tools.internals.shaders.eut2.dif_anim import over_nmap from io_scs_tools.internals.shaders.flavors import fadesheet from io_scs_tools.internals.shaders.flavors import flipsheet from io_scs_tools.utils import material as _material_utils @@ -141,6 +142,69 @@ def finalize(node_tree, material): node_tree.links.new(node_tree.nodes[DifAnim.VCOLOR_MULT_NODE].inputs[1], node_tree.nodes[DifAnim.BASE_TEX_NODE].outputs['Color']) node_tree.links.new(node_tree.nodes[DifAnim.OPACITY_NODE].inputs[0], node_tree.nodes[DifAnim.BASE_TEX_NODE].outputs['Alpha']) + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[DifAnim.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[DifAnim.GEOM_NODE] + vcol_group_n = node_tree.nodes[DifAnim.VCOL_GROUP_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + over_nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal'], vcol_group_n.outputs["Vertex Color Alpha"]) + else: + over_nmap.delete(node_tree) + + @staticmethod + def set_nmap_over_uv(node_tree, uv_layer): + """Set UV layer to over normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over nmap texture + :type uv_layer: str + """ + + over_nmap.set_over_uv(node_tree, uv_layer) + + @staticmethod + def set_nmap_over_texture(node_tree, texture): + """Set over normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param texture: texture which should be assigned to over nmap texture node + :type texture: bpy.types.Texture + """ + + over_nmap.set_over_texture(node_tree, texture) + + @staticmethod + def set_nmap_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + over_nmap.set_over_texture_settings(node_tree, settings) + @staticmethod def set_base_texture(node_tree, image): """Set base texture to shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py new file mode 100644 index 0000000..659877c --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py @@ -0,0 +1,168 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import nmap + +OVER_NMAP_UVMAP_NODE = "OverNMapUVs" +OVER_NMAP_TEX_NODE = "OverNMapTex" +OVER_NMAP_COL_MIX_NODE = "OverNMapColMix" + + +def __create_nodes__(node_tree, location, factor_from): + """Create node for over normal maps. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + """ + + frame = node_tree.nodes[nmap.NMAP_FLAVOR_FRAME_NODE] + nmap_uvs_n = node_tree.nodes[nmap.NMAP_UVMAP_NODE] + nmap_tex_n = node_tree.nodes[nmap.NMAP_TEX_NODE] + nmap_dds16_n = node_tree.nodes[nmap.NMAP_DDS16_GNODE] + nmap_scale_n = node_tree.nodes[nmap.NMAP_SCALE_GNODE] + + # move existing + nmap_uvs_n.location.x -= 185 + nmap_tex_n.location.x -= 185 + + # nodes creation + over_nmap_uvs_n = node_tree.nodes.new("ShaderNodeUVMap") + over_nmap_uvs_n.parent = frame + over_nmap_uvs_n.name = over_nmap_uvs_n.label = OVER_NMAP_UVMAP_NODE + over_nmap_uvs_n.location = (location[0] - 185 * 5, location[1] - 400) + over_nmap_uvs_n.uv_map = _MESH_consts.none_uv + + over_nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_nmap_tex_n.parent = frame + over_nmap_tex_n.name = over_nmap_tex_n.label = OVER_NMAP_TEX_NODE + over_nmap_tex_n.location = (location[0] - 185 * 4, location[1] - 400) + over_nmap_tex_n.width = 140 + + over_nmap_col_mix_n = node_tree.nodes.new("ShaderNodeMix") + over_nmap_col_mix_n.parent = frame + over_nmap_col_mix_n.name = over_nmap_col_mix_n.label = OVER_NMAP_COL_MIX_NODE + over_nmap_col_mix_n.location = (location[0] - 185 * 3, location[1] - 110) + over_nmap_col_mix_n.data_type = "RGBA" + over_nmap_col_mix_n.blend_type = "MIX" + + # links creation + node_tree.links.new(factor_from, over_nmap_col_mix_n.inputs["Factor"]) + + node_tree.links.new(over_nmap_uvs_n.outputs["UV"], over_nmap_tex_n.inputs["Vector"]) + + node_tree.links.new(nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["A"]) + node_tree.links.new(over_nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["B"]) + + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_dds16_n.inputs["Color"]) + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_scale_n.inputs["NMap Tex Color"]) + + +def init(node_tree, location, normal_to, normal_from, factor_from): + """Initialize normal map nodes. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location: tuple[int, int] + :param normal_to: node socket to which result of normal map material should be send + :type normal_to: bpy.types.NodeSocket + :param normal_from: node socket from which original mesh normal should be taken + :type normal_from: bpy.types.NodeSocket + :param factor_from: node socket from which factor for over blend should be taken + :type factor_from: bpy.types.NodeSocket + """ + + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + nmap.init(node_tree, location, normal_to, normal_from) + __create_nodes__(node_tree, location, factor_from) + + +def set_over_texture(node_tree, image): + """Set over texture to normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Texture + """ + + # save currently active node to properly reset it on the end + # without reset of active node this material is marked as active which we don't want + old_active = node_tree.nodes.active + + # ignore empty texture + if image is None: + delete(node_tree, True) + return + + # create material node if not yet created + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + return + + # assign texture to texture node first + node_tree.nodes[OVER_NMAP_TEX_NODE].image = image + + node_tree.nodes.active = old_active + + +def set_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to flavor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[OVER_NMAP_TEX_NODE], settings) + +def set_over_uv(node_tree, uv_layer): + """Set UV layer to texture in normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + # set uv layer to texture node + node_tree.nodes[OVER_NMAP_UVMAP_NODE].uv_map = uv_layer + + +def delete(node_tree, preserve_node=False): + """Delete over normal map nodes from node tree. + + :param node_tree: node tree from which normal map should be deleted + :type node_tree: bpy.types.NodeTree + :param preserve_node: if true node won't be deleted + :type preserve_node: bool + """ + + if OVER_NMAP_TEX_NODE in node_tree.nodes and not preserve_node: + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_UVMAP_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_COL_MIX_NODE]) + + nmap.delete(node_tree, preserve_node=preserve_node) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py index e1b63dc..9e24a1f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py @@ -20,6 +20,7 @@ from io_scs_tools.internals.shaders.eut2.dif_spec_weight import DifSpecWeight from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv +from io_scs_tools.internals.shaders.eut2.dif_spec_weight_add_env import detail_nmap class DifSpecWeightAddEnv(DifSpecWeight, StdAddEnv): @@ -50,3 +51,64 @@ def init(node_tree): # links creation node_tree.links.new(add_env_gn.inputs['Weighted Color'], vcol_scale_n.outputs[0]) + + @staticmethod + def set_nmap2_flavor(node_tree, switch_on): + """Set secondary normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[DifSpecWeightAddEnv.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[DifSpecWeightAddEnv.GEOM_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + detail_nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal']) + else: + detail_nmap.delete(node_tree) + + @staticmethod + def set_nmap_detail_uv(node_tree, uv_layer): + """Set UV layer to detail normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for detail nmap texture + :type uv_layer: str + """ + + detail_nmap.set_detail_uv(node_tree, uv_layer) + + @staticmethod + def set_nmap_detail_texture(node_tree, texture): + """Set detail normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param texture: texture which should be assigned to detail nmap texture node + :type texture: bpy.types.Texture + """ + + detail_nmap.set_detail_texture(node_tree, texture) + + @staticmethod + def set_nmap_detail_texture_settings(node_tree, settings): + """Set detail normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + detail_nmap.set_detail_texture_settings(node_tree, settings) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py new file mode 100644 index 0000000..1c340d8 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py @@ -0,0 +1,167 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import nmap + +DET_NMAP_UVMAP_NODE = "DetailNMapUVs" +DET_NMAP_TEX_NODE = "DetailNMapTex" +DET_NMAP_COMBINE_NODE = "DetailNMapCombine" + + +def __create_nodes__(node_tree, location): + """Create node for detail normal maps. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + """ + + frame = node_tree.nodes[nmap.NMAP_FLAVOR_FRAME_NODE] + nmap_uvs_n = node_tree.nodes[nmap.NMAP_UVMAP_NODE] + nmap_tex_n = node_tree.nodes[nmap.NMAP_TEX_NODE] + nmap_dds16_n = node_tree.nodes[nmap.NMAP_DDS16_GNODE] + nmap_scale_n = node_tree.nodes[nmap.NMAP_SCALE_GNODE] + + # move existing + nmap_uvs_n.location.x -= 185 + nmap_tex_n.location.x -= 185 + + # nodes creation + det_nmap_uvs_n = node_tree.nodes.new("ShaderNodeUVMap") + det_nmap_uvs_n.parent = frame + det_nmap_uvs_n.name = det_nmap_uvs_n.label = DET_NMAP_UVMAP_NODE + det_nmap_uvs_n.location = (location[0] - 185 * 5, location[1] - 400) + det_nmap_uvs_n.uv_map = _MESH_consts.none_uv + + det_nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + det_nmap_tex_n.parent = frame + det_nmap_tex_n.name = det_nmap_tex_n.label = DET_NMAP_TEX_NODE + det_nmap_tex_n.location = (location[0] - 185 * 4, location[1] - 400) + det_nmap_tex_n.width = 140 + + det_nmap_combine_n = node_tree.nodes.new("ShaderNodeMix") + det_nmap_combine_n.parent = frame + det_nmap_combine_n.name = det_nmap_combine_n.label = DET_NMAP_COMBINE_NODE + det_nmap_combine_n.location = (location[0] - 185 * 3, location[1] - 110) + det_nmap_combine_n.data_type = "RGBA" + det_nmap_combine_n.blend_type = "OVERLAY" + det_nmap_combine_n.inputs["Factor"].default_value = 1.0 + + # links creation + node_tree.links.new(nmap_uvs_n.outputs["UV"], det_nmap_tex_n.inputs["Vector"]) + node_tree.links.new(det_nmap_uvs_n.outputs["UV"], nmap_tex_n.inputs["Vector"]) + + node_tree.links.new(nmap_tex_n.outputs["Color"], det_nmap_combine_n.inputs["A"]) + node_tree.links.new(det_nmap_tex_n.outputs["Color"], det_nmap_combine_n.inputs["B"]) + + node_tree.links.new(det_nmap_combine_n.outputs["Result"], nmap_dds16_n.inputs["Color"]) + node_tree.links.new(det_nmap_combine_n.outputs["Result"], nmap_scale_n.inputs["NMap Tex Color"]) + + +def init(node_tree, location, normal_to, normal_from): + """Initialize normal map nodes. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location: tuple[int, int] + :param normal_to: node socket to which result of normal map material should be send + :type normal_to: bpy.types.NodeSocket + :param normal_from: node socket from which original mesh normal should be taken + :type normal_from: bpy.types.NodeSocket + """ + + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + nmap.init(node_tree, location, normal_to, normal_from) + __create_nodes__(node_tree, location) + + +def set_detail_texture(node_tree, image): + """Set texture to normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Texture + """ + + # save currently active node to properly reset it on the end + # without reset of active node this material is marked as active which we don't want + old_active = node_tree.nodes.active + + # ignore empty texture + if image is None: + delete(node_tree, True) + return + + # create material node if not yet created + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + return + + # assign texture to texture node first + node_tree.nodes[DET_NMAP_TEX_NODE].image = image + + node_tree.nodes.active = old_active + + +def set_detail_texture_settings(node_tree, settings): + """Set detail normal map texture settings to flavor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DET_NMAP_TEX_NODE], settings) + +def set_detail_uv(node_tree, uv_layer): + """Set UV layer to texture in normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + # set uv layer to texture node and normal map node + node_tree.nodes[DET_NMAP_UVMAP_NODE].uv_map = uv_layer + node_tree.nodes[nmap.NMAP_NODE].uv_map = uv_layer + + +def delete(node_tree, preserve_node=False): + """Delete normal map nodes from node tree. + + :param node_tree: node tree from which normal map should be deleted + :type node_tree: bpy.types.NodeTree + :param preserve_node: if true node won't be deleted + :type preserve_node: bool + """ + + if DET_NMAP_TEX_NODE in node_tree.nodes and not preserve_node: + node_tree.nodes.remove(node_tree.nodes[DET_NMAP_UVMAP_NODE]) + node_tree.nodes.remove(node_tree.nodes[DET_NMAP_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[DET_NMAP_COMBINE_NODE]) + + nmap.delete(node_tree, preserve_node=preserve_node) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py index f0d4d6a..b7b7d17 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py @@ -20,6 +20,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.internals.shaders.eut2.dif_spec_weight_weight_dif_spec_weight import over_nmap from io_scs_tools.internals.shaders.flavors import tg1 from io_scs_tools.utils import material as _material_utils @@ -154,6 +155,69 @@ def init(node_tree): node_tree.links.new(compose_lighting_n.inputs["Specular Color"], vcol_spec_mul_n.outputs[0]) node_tree.links.new(compose_lighting_n.inputs['Alpha'], base_tex_n.outputs['Alpha']) + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[DifSpecWeightWeightDifSpecWeight.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[DifSpecWeightWeightDifSpecWeight.GEOM_NODE] + vcol_group_n = node_tree.nodes[DifSpecWeightWeightDifSpecWeight.VCOL_GROUP_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + over_nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal'], vcol_group_n.outputs["Vertex Color Alpha"]) + else: + over_nmap.delete(node_tree) + + @staticmethod + def set_nmap_over_uv(node_tree, uv_layer): + """Set UV layer to over normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over nmap texture + :type uv_layer: str + """ + + over_nmap.set_over_uv(node_tree, uv_layer) + + @staticmethod + def set_nmap_over_texture(node_tree, texture): + """Set over normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param texture: texture which should be assigned to over nmap texture node + :type texture: bpy.types.Texture + """ + + over_nmap.set_over_texture(node_tree, texture) + + @staticmethod + def set_nmap_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + over_nmap.set_over_texture_settings(node_tree, settings) + @staticmethod def set_shininess(node_tree, factor): """Set shininess factor to shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py new file mode 100644 index 0000000..659877c --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py @@ -0,0 +1,168 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import nmap + +OVER_NMAP_UVMAP_NODE = "OverNMapUVs" +OVER_NMAP_TEX_NODE = "OverNMapTex" +OVER_NMAP_COL_MIX_NODE = "OverNMapColMix" + + +def __create_nodes__(node_tree, location, factor_from): + """Create node for over normal maps. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + """ + + frame = node_tree.nodes[nmap.NMAP_FLAVOR_FRAME_NODE] + nmap_uvs_n = node_tree.nodes[nmap.NMAP_UVMAP_NODE] + nmap_tex_n = node_tree.nodes[nmap.NMAP_TEX_NODE] + nmap_dds16_n = node_tree.nodes[nmap.NMAP_DDS16_GNODE] + nmap_scale_n = node_tree.nodes[nmap.NMAP_SCALE_GNODE] + + # move existing + nmap_uvs_n.location.x -= 185 + nmap_tex_n.location.x -= 185 + + # nodes creation + over_nmap_uvs_n = node_tree.nodes.new("ShaderNodeUVMap") + over_nmap_uvs_n.parent = frame + over_nmap_uvs_n.name = over_nmap_uvs_n.label = OVER_NMAP_UVMAP_NODE + over_nmap_uvs_n.location = (location[0] - 185 * 5, location[1] - 400) + over_nmap_uvs_n.uv_map = _MESH_consts.none_uv + + over_nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_nmap_tex_n.parent = frame + over_nmap_tex_n.name = over_nmap_tex_n.label = OVER_NMAP_TEX_NODE + over_nmap_tex_n.location = (location[0] - 185 * 4, location[1] - 400) + over_nmap_tex_n.width = 140 + + over_nmap_col_mix_n = node_tree.nodes.new("ShaderNodeMix") + over_nmap_col_mix_n.parent = frame + over_nmap_col_mix_n.name = over_nmap_col_mix_n.label = OVER_NMAP_COL_MIX_NODE + over_nmap_col_mix_n.location = (location[0] - 185 * 3, location[1] - 110) + over_nmap_col_mix_n.data_type = "RGBA" + over_nmap_col_mix_n.blend_type = "MIX" + + # links creation + node_tree.links.new(factor_from, over_nmap_col_mix_n.inputs["Factor"]) + + node_tree.links.new(over_nmap_uvs_n.outputs["UV"], over_nmap_tex_n.inputs["Vector"]) + + node_tree.links.new(nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["A"]) + node_tree.links.new(over_nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["B"]) + + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_dds16_n.inputs["Color"]) + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_scale_n.inputs["NMap Tex Color"]) + + +def init(node_tree, location, normal_to, normal_from, factor_from): + """Initialize normal map nodes. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location: tuple[int, int] + :param normal_to: node socket to which result of normal map material should be send + :type normal_to: bpy.types.NodeSocket + :param normal_from: node socket from which original mesh normal should be taken + :type normal_from: bpy.types.NodeSocket + :param factor_from: node socket from which factor for over blend should be taken + :type factor_from: bpy.types.NodeSocket + """ + + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + nmap.init(node_tree, location, normal_to, normal_from) + __create_nodes__(node_tree, location, factor_from) + + +def set_over_texture(node_tree, image): + """Set over texture to normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Texture + """ + + # save currently active node to properly reset it on the end + # without reset of active node this material is marked as active which we don't want + old_active = node_tree.nodes.active + + # ignore empty texture + if image is None: + delete(node_tree, True) + return + + # create material node if not yet created + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + return + + # assign texture to texture node first + node_tree.nodes[OVER_NMAP_TEX_NODE].image = image + + node_tree.nodes.active = old_active + + +def set_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to flavor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[OVER_NMAP_TEX_NODE], settings) + +def set_over_uv(node_tree, uv_layer): + """Set UV layer to texture in normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + # set uv layer to texture node + node_tree.nodes[OVER_NMAP_UVMAP_NODE].uv_map = uv_layer + + +def delete(node_tree, preserve_node=False): + """Delete over normal map nodes from node tree. + + :param node_tree: node tree from which normal map should be deleted + :type node_tree: bpy.types.NodeTree + :param preserve_node: if true node won't be deleted + :type preserve_node: bool + """ + + if OVER_NMAP_TEX_NODE in node_tree.nodes and not preserve_node: + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_UVMAP_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_COL_MIX_NODE]) + + nmap.delete(node_tree, preserve_node=preserve_node) diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py index 3d5abfb..cbbe6f6 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py @@ -68,6 +68,11 @@ def __create_nodes__(node_tree, location=None, normal_to=None, normal_from=None) nmap_tex_n.name = nmap_tex_n.label = NMAP_TEX_NODE nmap_tex_n.width = 140 + nmap_dds16_n = node_tree.nodes.new("ShaderNodeGroup") + nmap_dds16_n.parent = frame + nmap_dds16_n.name = nmap_dds16_n.label = NMAP_DDS16_GNODE + nmap_dds16_n.node_tree = dds16_ng.get_node_group() + nmap_n = node_tree.nodes.new("ShaderNodeNormalMap") nmap_n.parent = frame nmap_n.name = nmap_n.label = NMAP_NODE @@ -81,8 +86,9 @@ def __create_nodes__(node_tree, location=None, normal_to=None, normal_from=None) # position nodes if location: - nmap_uvs_n.location = (location[0] - 185 * 3, location[1]) - nmap_tex_n.location = (location[0] - 185 * 2, location[1]) + nmap_uvs_n.location = (location[0] - 185 * 4, location[1]) + nmap_tex_n.location = (location[0] - 185 * 3, location[1]) + nmap_dds16_n.location = (location[0] - 185 * 2, location[1] - 220) nmap_n.location = (location[0] - 185, location[1] - 200) nmap_scale_n.location = (location[0], location[1]) @@ -91,58 +97,21 @@ def __create_nodes__(node_tree, location=None, normal_to=None, normal_from=None) node_tree.links.new(nodes[NMAP_UVMAP_NODE].outputs["UV"], nodes[NMAP_TEX_NODE].inputs["Vector"]) - node_tree.links.new(nodes[NMAP_NODE].inputs["Color"], nodes[NMAP_TEX_NODE].outputs["Color"]) + node_tree.links.new(nodes[NMAP_TEX_NODE].outputs["Color"], nodes[NMAP_DDS16_GNODE].inputs["Color"]) + + node_tree.links.new(nodes[NMAP_DDS16_GNODE].outputs["Color"], nodes[NMAP_NODE].inputs["Color"]) + # Commented because default 1.0 strength gives better visualization. There must be something wrong with calculating strenght? (R channel?) + # Probably SCS changed formula a little bit when they abandoned 3-channel normal maps to 2-channel ones. + # node_tree.links.new(nodes[NMAP_DDS16_GNODE].outputs["Strength"], nodes[NMAP_NODE].inputs["Strength"]) + + node_tree.links.new(nodes[NMAP_NODE].outputs["Normal"], nodes[NMAP_SCALE_GNODE].inputs["Modified Normal"]) - node_tree.links.new(nodes[NMAP_SCALE_GNODE].inputs["NMap Tex Color"], nodes[NMAP_TEX_NODE].outputs["Color"]) if normal_from: - node_tree.links.new(nodes[NMAP_SCALE_GNODE].inputs["Original Normal"], normal_from) - node_tree.links.new(nodes[NMAP_SCALE_GNODE].inputs["Modified Normal"], nodes[NMAP_NODE].outputs["Normal"]) + node_tree.links.new(normal_from, nodes[NMAP_SCALE_GNODE].inputs["Original Normal"]) # set normal only if we know where to if normal_to: - node_tree.links.new(normal_to, nodes[NMAP_SCALE_GNODE].outputs["Normal"]) - - -def __check_and_create_dds16_node__(node_tree, image): - """Checks if given texture is composed '16-bit DDS' texture and properly create extra node for it's representation. - On the contrary if texture is not 16-bit DDS and node exists clean that node and restore old connections. - - :param node_tree: node tree on which normal map will be used - :type node_tree: bpy.types.NodeTree - :param image: texture image which should be assigned to nmap texture node - :type image: bpy.types.Image - """ - - # in case of DDS simulating 16-bit normal maps create it's group and properly connect it, - # on the other hand if group exists but shouldn't delete group and restore old connections - - is_dds16 = image and image.filepath.endswith(".dds") and image.pixels[2] == 0.0 - if is_dds16 and NMAP_DDS16_GNODE not in node_tree.nodes: - - nmap_dds16_n = node_tree.nodes.new("ShaderNodeGroup") - nmap_dds16_n.parent = node_tree.nodes[NMAP_FLAVOR_FRAME_NODE] - nmap_dds16_n.name = nmap_dds16_n.label = NMAP_DDS16_GNODE - nmap_dds16_n.node_tree = dds16_ng.get_node_group() - - location = node_tree.nodes[NMAP_NODE].location - - node_tree.nodes[NMAP_TEX_NODE].location[0] -= 185 - node_tree.nodes[NMAP_UVMAP_NODE].location[0] -= 185 - nmap_dds16_n.location = (location[0] - 185, location[1]) - - node_tree.links.new(node_tree.nodes[NMAP_DDS16_GNODE].inputs["Color"], node_tree.nodes[NMAP_TEX_NODE].outputs["Color"]) - - node_tree.links.new(node_tree.nodes[NMAP_NODE].inputs["Strength"], node_tree.nodes[NMAP_DDS16_GNODE].outputs["Strength"]) - node_tree.links.new(node_tree.nodes[NMAP_NODE].inputs["Color"], node_tree.nodes[NMAP_DDS16_GNODE].outputs["Color"]) - - elif not is_dds16 and NMAP_DDS16_GNODE in node_tree.nodes: - - node_tree.nodes.remove(node_tree.nodes[NMAP_DDS16_GNODE]) - - node_tree.nodes[NMAP_TEX_NODE].location[0] += 185 - node_tree.nodes[NMAP_UVMAP_NODE].location[0] += 185 - - node_tree.links.new(node_tree.nodes[NMAP_NODE].inputs["Color"], node_tree.nodes[NMAP_TEX_NODE].outputs["Color"]) + node_tree.links.new(nodes[NMAP_SCALE_GNODE].outputs["Normal"], normal_to) def init(node_tree, location, normal_to, normal_from): @@ -184,9 +153,6 @@ def set_texture(node_tree, image): if NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: __create_nodes__(node_tree) - # in case of DDS simulating 16-bit normal maps create it's group and properly connect it - __check_and_create_dds16_node__(node_tree, image) - # assign texture to texture node first node_tree.nodes[NMAP_TEX_NODE].image = image @@ -237,7 +203,6 @@ def delete(node_tree, preserve_node=False): if NMAP_NODE in node_tree.nodes and not preserve_node: node_tree.nodes.remove(node_tree.nodes[NMAP_TEX_NODE]) node_tree.nodes.remove(node_tree.nodes[NMAP_NODE]) - if NMAP_DDS16_GNODE in node_tree.nodes: - node_tree.nodes.remove(node_tree.nodes[NMAP_DDS16_GNODE]) + node_tree.nodes.remove(node_tree.nodes[NMAP_DDS16_GNODE]) node_tree.nodes.remove(node_tree.nodes[NMAP_SCALE_GNODE]) node_tree.nodes.remove(node_tree.nodes[NMAP_FLAVOR_FRAME_NODE]) diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 6e64764..259453f 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -63,7 +63,7 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea flavors["nmap"] = True if effect.endswith(".tsnmapuv2") or ".tsnmapuv2." in effect: - flavors["nmap"] = True + flavors["nmap2"] = True if effect.endswith(".tsnmap") or ".tsnmap." in effect: flavors["nmap"] = True diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index da08882..d9064e3 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -389,8 +389,11 @@ def active_scs_part_get(self): with the index of part belonging to new active object. """ + scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) + active_scs_part = _inventory.get_index(scs_root_object.scs_object_part_inventory, bpy.context.active_object.scs_props.scs_part) - return self.active_scs_part_value + # return active_scs_part # Fixes auto-selecting part for mesh, but breaks ability to change part + return self.active_scs_part_value # Fixes ability to change part, but breaks auto-selecting part for mesh def active_scs_part_set(self, value): scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index f0419b0..7e8a16b 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -3156,11 +3156,13 @@ Flavor { Name: "tsnmap" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal map (detail)" Value: "" TexCoord: ( 0 ) } @@ -3196,6 +3198,7 @@ Flavor { } Texture { Tag: "texture[X]:texture_nmap_over" + FriendlyTag: "Normal map (over)" Value: "" TexCoord: ( 0 ) } @@ -3211,6 +3214,7 @@ Flavor { } Texture { Tag: "texture[X]:texture_nmap_over" + FriendlyTag: "Normal map (over)" Value: "" TexCoord: ( 1 ) } @@ -3236,6 +3240,7 @@ Flavor { } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal map (detail)" Value: "" TexCoord: ( 6 ) } diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index b2b3211..8ea291b 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -778,12 +778,15 @@ def draw(self, context): else: # more roots or active object is root object + # NOTE: Due to problems with showing actual active part in the list after chanes made in getters/setters, we show it temporarily by default skiping DEBUG check. # DEBUG - if int(_get_scs_globals().dump_level) > 2 and not active_object is scs_root_object: + # if int(_get_scs_globals().dump_level) > 2 and not active_object is scs_root_object: + if not active_object is scs_root_object: row = layout.row(align=True) row.enabled = False - row.label(text="DEBUG - active obj part:") + #row.label(text="DEBUG - active obj part:") + row.label(text="Active object part:") row.prop(active_object.scs_props, 'scs_part', text="") # PART LIST From abecf6ace354b9d15ef556088d67a4f1942f2a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 19 Dec 2025 05:45:41 +0100 Subject: [PATCH 53/56] preview model fix + other fixes * Fixed Preview Model not working correctly with some paths * Fixed Preview Model not reloading preview if type was changed * Tweaks in Factor vcol for piko.alldir - changed default 0.0 color to 0.5 --- addon/io_scs_tools/exp/pim/exporter.py | 2 +- addon/io_scs_tools/exp/pim_ef/exporter.py | 2 +- addon/io_scs_tools/internals/preview_models/__init__.py | 2 +- addon/io_scs_tools/operators/mesh.py | 8 +++----- addon/io_scs_tools/properties/object.py | 2 +- addon/io_scs_tools/ui/object.py | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/addon/io_scs_tools/exp/pim/exporter.py b/addon/io_scs_tools/exp/pim/exporter.py index 6b271cb..8e85616 100644 --- a/addon/io_scs_tools/exp/pim/exporter.py +++ b/addon/io_scs_tools/exp/pim/exporter.py @@ -431,7 +431,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name if vfactor_shader: if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component - vfcol = (0.0,) * 4 + vfcol = (1.0,) * 4 missing_vfcolor = True else: vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] diff --git a/addon/io_scs_tools/exp/pim_ef/exporter.py b/addon/io_scs_tools/exp/pim_ef/exporter.py index 98d5397..ab3b959 100644 --- a/addon/io_scs_tools/exp/pim_ef/exporter.py +++ b/addon/io_scs_tools/exp/pim_ef/exporter.py @@ -320,7 +320,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name if vfactor_shader: if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component - vfcol = (0.0,) * 4 + vfcol = (1.0,) * 4 missing_vfcolor = True else: vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] diff --git a/addon/io_scs_tools/internals/preview_models/__init__.py b/addon/io_scs_tools/internals/preview_models/__init__.py index f08d803..c80365b 100644 --- a/addon/io_scs_tools/internals/preview_models/__init__.py +++ b/addon/io_scs_tools/internals/preview_models/__init__.py @@ -168,7 +168,7 @@ def load(locator, deep_reload=False): new_mesh = obj.data # set preview model path to mesh, so it can be reused next time user requests same preview model - new_mesh.scs_props.locator_preview_model_path = locator.scs_props.locator_preview_model_path + new_mesh.scs_props.locator_preview_model_path = _path_utils.readable_norm(abs_filepath) # now remove imported object, as we need only mesh bpy.data.objects.remove(obj, do_unlink=True) diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index 563e144..531e0b1 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -392,7 +392,6 @@ def poll(cls, context): def execute(self, context): default_color = tuple(Color((0.5,) * 3).from_srgb_to_scene_linear()) + (1.0,) - default_factor = tuple((0.0,) * 4) layer_name = _MESH_consts.default_vcol layer_a_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix @@ -418,9 +417,9 @@ def execute(self, context): vfcolor = context.object.data.color_attributes.new(name=layer_factor_name, type='BYTE_COLOR', domain='CORNER') vfcolor.name = layer_factor_name # repeat naming step to make sure it's properly named - # setting neutral value (0.0) to all factors + # setting neutral value (0.5) to all factors for vertex_fac_col_data in context.object.data.color_attributes[layer_factor_name].data: - vertex_fac_col_data.color = default_factor + vertex_fac_col_data.color = default_color # restore active or set to default vcol if there was none if old_active_col_i is None: @@ -442,7 +441,6 @@ def poll(cls, context): def execute(self, context): default_color = tuple(Color((0.5,) * 3).from_srgb_to_scene_linear()) + (1.0,) - default_factor = tuple((0.0,) * 4) layer_name = _MESH_consts.default_vcol layer_a_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix @@ -487,7 +485,7 @@ def execute(self, context): # setting neutral value (0.0) to all factors for vertex_fac_col_data in obj.data.color_attributes[layer_factor_name].data: - vertex_fac_col_data.color = default_factor + vertex_fac_col_data.color = default_color # restore active or set to default vcol if there was none if old_active_col_i is None: diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index d9064e3..31b4c35 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -615,7 +615,7 @@ def locator_preview_model_type_update(self, context): for child in obj.children: - if "scs_props" in child.data and child.data.scs_props.locator_preview_model_path != "": + if hasattr(child.data, "scs_props") and child.data.scs_props.locator_preview_model_path != "": child.display_type = obj.scs_props.locator_preview_model_type diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index 8ea291b..c8e5ae0 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -778,7 +778,7 @@ def draw(self, context): else: # more roots or active object is root object - # NOTE: Due to problems with showing actual active part in the list after chanes made in getters/setters, we show it temporarily by default skiping DEBUG check. + # NOTE: Due to problems with showing actual active part in the list after changes made in getters/setters, we show it temporarily by default skiping DEBUG (dump_level) check. # DEBUG # if int(_get_scs_globals().dump_level) > 2 and not active_object is scs_root_object: if not active_object is scs_root_object: From f362a53a3f6fa05260126a71f3b25d8b736af9a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Sat, 20 Dec 2025 05:12:58 +0100 Subject: [PATCH 54/56] Changes in SCS Part * Changed version + forced material reload * Increased reload_icons interval a little bit (for faster systems to prevent not loading icons on first start) * Fixed auto selecting currently assigned part * Added interactive icons to SCS Parts to inform which part the object is assigned to. Clicking on such an icon will act as an Assign button. --- addon/io_scs_tools/__init__.py | 2 +- .../internals/callbacks/persistent.py | 4 ++ .../io_scs_tools/internals/icons/__init__.py | 4 +- .../internals/persistent/active_part.py | 39 +++++++++++++++++++ .../internals/persistent/file_load.py | 24 ++++++++++++ addon/io_scs_tools/operators/object.py | 14 ++++++- addon/io_scs_tools/properties/object.py | 20 ++-------- addon/io_scs_tools/ui/object.py | 30 ++++++++++---- 8 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 addon/io_scs_tools/internals/persistent/active_part.py diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index c6540c8..8fc8e5a 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 7), + "version": (2, 4, "aeadde03", 8), "blender": (5, 0, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/internals/callbacks/persistent.py b/addon/io_scs_tools/internals/callbacks/persistent.py index 30ea300..9f9ec5e 100644 --- a/addon/io_scs_tools/internals/callbacks/persistent.py +++ b/addon/io_scs_tools/internals/callbacks/persistent.py @@ -25,6 +25,7 @@ from io_scs_tools.internals.persistent import file_load as _persistent_file_load from io_scs_tools.internals.persistent import open_gl as _persistent_open_gl from io_scs_tools.internals.persistent import shaders_update as _persistent_shaders_update +from io_scs_tools.internals.persistent import active_part as _persistent_active_part def enable(): @@ -51,6 +52,7 @@ def enable(): bpy.app.handlers.frame_change_post.append(_persistent_shaders_update.post_frame_change) bpy.app.handlers.undo_post.append(_persistent_open_gl.post_undo) bpy.app.handlers.redo_post.append(_persistent_open_gl.post_redo) + bpy.app.handlers.depsgraph_update_post.append(_persistent_active_part.sync_part) bpy.app.handlers.load_post.append(_persistent_file_load.post_load) @@ -79,3 +81,5 @@ def disable(): bpy.app.handlers.load_post.remove(_persistent_file_load.post_load) if _persistent_file_save.pre_save in bpy.app.handlers.save_pre: bpy.app.handlers.save_pre.remove(_persistent_file_save.pre_save) + if _persistent_active_part.sync_part in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(_persistent_active_part.sync_part) diff --git a/addon/io_scs_tools/internals/icons/__init__.py b/addon/io_scs_tools/internals/icons/__init__.py index 1383c15..771f59c 100644 --- a/addon/io_scs_tools/internals/icons/__init__.py +++ b/addon/io_scs_tools/internals/icons/__init__.py @@ -91,8 +91,8 @@ def register(): set_theme(get_theme_name(0)) print("WARNING\t- Default icon theme doesn't exist, fallback to first available!") - # Forces icons to reload (Because of issues with Vulkan) - bpy.app.timers.register(reload_icons, first_interval=0.2) + # Forces icons to reload (Because of issues with Vulkan on faster systems) + bpy.app.timers.register(reload_icons, first_interval=0.5) def reload_icons(): """Forces icons to reload after a short delay (workaround for Vulkan issues).""" diff --git a/addon/io_scs_tools/internals/persistent/active_part.py b/addon/io_scs_tools/internals/persistent/active_part.py new file mode 100644 index 0000000..14649cf --- /dev/null +++ b/addon/io_scs_tools/internals/persistent/active_part.py @@ -0,0 +1,39 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.internals import inventory as _inventory +from io_scs_tools.utils import object as _object_utils +from bpy.app.handlers import persistent + +@persistent +def sync_part(scene, depsgraph): + scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) + if scs_root_object: + scs_props = scs_root_object.scs_props + if scs_root_object and bpy.context.active_object != scs_root_object: + # if old active object is different than current + # set the value for active part index from it + if scs_props.active_scs_part_old_active != bpy.context.active_object.name: + scs_props.active_scs_part_value = _inventory.get_index(scs_root_object.scs_object_part_inventory, + bpy.context.active_object.scs_props.scs_part) + + if scs_props.active_scs_part_old_active != bpy.context.active_object.name: + scs_props.active_scs_part_old_active = bpy.context.active_object.name diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 9c98c42..65b75ad 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -55,6 +55,7 @@ def post_load(scene): VERSIONS_LIST_UNOFFICIAL = ( ("4", apply_fixes_for_un_4), ("7", apply_fixes_for_un_7), + ("8", apply_fixes_for_un_8), ) v_parts = last_load_bt_ver.split(".") @@ -416,3 +417,26 @@ def apply_fixes_for_un_7(): # 1. reload all materials # Some attributes got removed, in some cases attribute size changed and due to that we need to reload materials _reload_materials() + +def apply_fixes_for_un_8(): + """ + Applies fixes for unofficial 2.4.8 or less: + 1. Reload materials since some got restructed nodes + 2. Show welcome message + """ + + print("INFO\t- Applying fixes for unofficial versions < 8") + + # 1. reload all materials + # Some shaders like nmaps got restructured nodes and due to that we need to reload materials + _reload_materials() + + # 2. Due to update to Blender 5.0+, we let user know + windows = bpy.data.window_managers[0].windows + if len(windows) > 0: + msg = ( + "\nWelcome folks. You just migrated to Blender 5.0+! Yey", + ) + + with bpy.context.temp_override(window=windows[0]): + bpy.ops.wm.scs_tools_show_3dview_report('INVOKE_DEFAULT', message="\n".join(msg)) diff --git a/addon/io_scs_tools/operators/object.py b/addon/io_scs_tools/operators/object.py index bfa6cc8..1c371d2 100755 --- a/addon/io_scs_tools/operators/object.py +++ b/addon/io_scs_tools/operators/object.py @@ -604,6 +604,12 @@ class SCS_TOOLS_OT_AssignPart(bpy.types.Operator): bl_idname = "object.scs_tools_assign_part" bl_description = "Assign active SCS Part to selected objects" + part_index: IntProperty( + name="Part Index", + description="Index of the part to assign. If negative, uses active_scs_part.", + default=-1 + ) + @classmethod def poll(cls, context): if _object_utils.get_scs_root(context.active_object): @@ -616,7 +622,13 @@ def execute(self, context): active_object = context.active_object scs_root_object = _object_utils.get_scs_root(active_object) part_inventory = scs_root_object.scs_object_part_inventory - active_part_index = scs_root_object.scs_props.active_scs_part + + # Use part_index if provided, else use active_part_index + if self.part_index >= 0: + active_part_index = self.part_index + self.part_index = -1 # Reset to default after use + else: + active_part_index = scs_root_object.scs_props.active_scs_part scs_roots_count = len(_object_utils.gather_scs_roots(bpy.context.selected_objects)) if scs_roots_count == 1: diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 31b4c35..a4aa2c5 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -375,7 +375,6 @@ def active_scs_part_get_direct(self): # NOTE: case where this happens is if user imports SCS model, # duplicates prefab locator without part and then changes locator # type to model locator. - if "active_scs_part_old_active" not in self: self["active_scs_part_old_active"] = "" @@ -389,25 +388,14 @@ def active_scs_part_get(self): with the index of part belonging to new active object. """ - scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) - active_scs_part = _inventory.get_index(scs_root_object.scs_object_part_inventory, bpy.context.active_object.scs_props.scs_part) - # return active_scs_part # Fixes auto-selecting part for mesh, but breaks ability to change part - return self.active_scs_part_value # Fixes ability to change part, but breaks auto-selecting part for mesh + return self.active_scs_part_value def active_scs_part_set(self, value): - scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) - self.active_scs_part_value = value - - if scs_root_object and bpy.context.active_object != scs_root_object: - # if old active object is different than current - # set the value for active part index from it - if self.active_scs_part_old_active != bpy.context.active_object.name: - self.active_scs_part_value = _inventory.get_index(scs_root_object.scs_object_part_inventory, - bpy.context.active_object.scs_props.scs_part) + """Store the index selected by the user. + """ - if self.active_scs_part_old_active != bpy.context.active_object.name: - self.active_scs_part_old_active = bpy.context.active_object.name + self.active_scs_part_value = value active_scs_part: IntProperty( name="Active SCS Part", diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index c8e5ae0..4f2b71c 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -73,9 +73,26 @@ def draw_icon_part_tools(layout, index): def draw_item(self, context, layout, data, item, icon, active_data, active_property, index): if self.layout_type in {'DEFAULT', 'COMPACT'}: if item: - line = layout.split(factor=0.6, align=False) - line.prop(item, "name", text="", emboss=False, icon_value=icon) - tools = line.row(align=True) + active_object = context.active_object + is_not_root = getattr(active_object.scs_props, 'empty_object_type', None) != 'SCS_Root' + row = layout.row(align=True) + if is_not_root: + current_part_name = getattr(active_object.scs_props, 'scs_part', None) + is_assigned = (item.name == current_part_name) + icon_col = row.column() + + if is_assigned: + icon_col.label(icon='RESTRICT_INSTANCED_OFF') + else: + op = icon_col.operator('object.scs_tools_assign_part', text="", icon='RESTRICT_INSTANCED_ON', emboss=False) + op.part_index = index + + spacer_col = row.column() + spacer_col.scale_x = 0.15 + spacer_col.label(text="") + + row.prop(item, "name", text="", emboss=False, icon_value=icon) + tools = row.row(align=True) tools.alignment = 'RIGHT' self.draw_icon_part_tools(tools, index) else: @@ -778,15 +795,12 @@ def draw(self, context): else: # more roots or active object is root object - # NOTE: Due to problems with showing actual active part in the list after changes made in getters/setters, we show it temporarily by default skiping DEBUG (dump_level) check. # DEBUG - # if int(_get_scs_globals().dump_level) > 2 and not active_object is scs_root_object: - if not active_object is scs_root_object: + if int(_get_scs_globals().dump_level) > 2 and not active_object is scs_root_object: row = layout.row(align=True) row.enabled = False - #row.label(text="DEBUG - active obj part:") - row.label(text="Active object part:") + row.label(text="DEBUG - active obj part:") row.prop(active_object.scs_props, 'scs_part', text="") # PART LIST From 66cb3b17fe4bfad2e9234ec4786b384363682fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 22 Dec 2025 21:17:22 +0100 Subject: [PATCH 55/56] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit f362a53a3f6fa05260126a71f3b25d8b736af9a0 Author: Michał Date: Sat Dec 20 05:12:58 2025 +0100 Changes in SCS Part * Changed version + forced material reload * Increased reload_icons interval a little bit (for faster systems to prevent not loading icons on first start) * Fixed auto selecting currently assigned part * Added interactive icons to SCS Parts to inform which part the object is assigned to. Clicking on such an icon will act as an Assign button. commit abecf6ace354b9d15ef556088d67a4f1942f2a38 Author: Michał Date: Fri Dec 19 05:45:41 2025 +0100 preview model fix + other fixes * Fixed Preview Model not working correctly with some paths * Fixed Preview Model not reloading preview if type was changed * Tweaks in Factor vcol for piko.alldir - changed default 0.0 color to 0.5 commit 760af2b165820b8541c3a037ccd9fcfe0f3deb23 Author: Michał Date: Wed Dec 17 21:20:29 2025 +0100 Fixes in nmaps and parts * Added "is_dynamic_road" and "is_terrain_material" to list of ignored attributes (not working in current legacy material format) * Tweaks in global nmap flavor (removed B channel support and forced 2-channel group node) * Added preview for "tsnmapuv2" flavor * Added preview for "tsnmapcalc" flavor (for dual texture) * Added few new FriendlyTags for nmaps * Fixed an issue where the currently assigned part was not displayed correctly commit ae743652999761509a14182af68892a75bbd5155 Author: Michał Date: Sun Nov 30 08:08:22 2025 +0100 New options for parts and some fixes * Added 2 new settings to control automatic activation of parts (by default ON) when added, or when new variant is created. * Added auto remover for "diffuse_secondary" material attribute (not supported in current material format) * Fixed "defaultpart" part that was always being added to every imported root commit 95a6855ff62e9d17293e19fb2ec85677cb89f9d5 Author: Michał Date: Tue Nov 25 01:09:11 2025 +0100 Update to Blender 5.0 * Updated to Blender 5.0 (thanks to crisan21) * Changed button order in Lamp UV Tool (from front-rear-middle, to front-middle-rear) commit 229c423859ee1d2eef72d222a99b16d210c78d81 Author: Michał Date: Sat Sep 27 04:07:42 2025 +0200 Lamp System update + small tweaks * Updated minimum required Blender version to 4.5 (LTS recommended) * Added support for new UV Tile (5) in Lamp System + changes in 4th tile * Changed error text when texture attribute is not set (From "Texture doesn't exists" to "Texture is missing") * Tweaks in custom icons interval time (icons should break less often) --- addon/io_scs_tools/__init__.py | 4 +- addon/io_scs_tools/consts.py | 3 +- addon/io_scs_tools/exp/pim/exporter.py | 2 +- addon/io_scs_tools/exp/pim_ef/exporter.py | 2 +- addon/io_scs_tools/exp/pit.py | 7 +- addon/io_scs_tools/imp/pit.py | 19 +- addon/io_scs_tools/imp/pix.py | 1 + .../internals/callbacks/open_gl.py | 5 +- .../internals/callbacks/persistent.py | 4 + .../internals/containers/config.py | 20 ++- .../io_scs_tools/internals/icons/__init__.py | 4 +- addon/io_scs_tools/internals/open_gl/core.py | 2 +- .../internals/persistent/active_part.py | 39 ++++ .../internals/persistent/file_load.py | 24 +++ .../internals/preview_models/__init__.py | 2 +- .../shaders/eut2/dif_anim/__init__.py | 64 +++++++ .../shaders/eut2/dif_anim/over_nmap.py | 168 ++++++++++++++++++ .../eut2/dif_spec_weight_add_env/__init__.py | 62 +++++++ .../dif_spec_weight_add_env/detail_nmap.py | 167 +++++++++++++++++ .../__init__.py | 64 +++++++ .../over_nmap.py | 168 ++++++++++++++++++ .../internals/shaders/eut2/glass/__init__.py | 11 +- .../eut2/std_node_groups/lampmask_mixer_ng.py | 48 +++-- .../shaders/flavors/nmap/__init__.py | 73 ++------ .../io_scs_tools/internals/shaders/shader.py | 2 +- addon/io_scs_tools/operators/mesh.py | 12 +- addon/io_scs_tools/operators/object.py | 19 +- addon/io_scs_tools/operators/wm.py | 14 +- .../properties/addon_preferences.py | 22 +++ .../properties/dynamic/__init__.py | 14 +- addon/io_scs_tools/properties/object.py | 75 ++++---- addon/io_scs_tools/shader_presets.txt | 19 +- addon/io_scs_tools/ui/object.py | 23 ++- addon/io_scs_tools/ui/shared.py | 2 + addon/io_scs_tools/ui/tool_shelf.py | 11 +- 35 files changed, 1006 insertions(+), 170 deletions(-) create mode 100644 addon/io_scs_tools/internals/persistent/active_part.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py create mode 100644 addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 2feb399..8fc8e5a 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,8 +22,8 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 7), - "blender": (4, 4, 0), + "version": (2, 4, "aeadde03", 8), + "blender": (5, 0, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", "tracker_url": "http://forum.scssoft.com/viewforum.php?f=163", diff --git a/addon/io_scs_tools/consts.py b/addon/io_scs_tools/consts.py index 3b00c44..8cc8f21 100644 --- a/addon/io_scs_tools/consts.py +++ b/addon/io_scs_tools/consts.py @@ -194,7 +194,8 @@ class VehicleSides(Enum): FrontRight = 1 RearLeft = 2 RearRight = 3 - Middle = 4 + MiddleLeft = 4 + MiddleRight = 5 class VehicleLampTypes(Enum): """Defined lamp types for vehicles. diff --git a/addon/io_scs_tools/exp/pim/exporter.py b/addon/io_scs_tools/exp/pim/exporter.py index 6b271cb..8e85616 100644 --- a/addon/io_scs_tools/exp/pim/exporter.py +++ b/addon/io_scs_tools/exp/pim/exporter.py @@ -431,7 +431,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name if vfactor_shader: if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component - vfcol = (0.0,) * 4 + vfcol = (1.0,) * 4 missing_vfcolor = True else: vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] diff --git a/addon/io_scs_tools/exp/pim_ef/exporter.py b/addon/io_scs_tools/exp/pim_ef/exporter.py index 98d5397..ab3b959 100644 --- a/addon/io_scs_tools/exp/pim_ef/exporter.py +++ b/addon/io_scs_tools/exp/pim_ef/exporter.py @@ -320,7 +320,7 @@ def execute(dirpath, name_suffix, root_object, armature_object, skeleton_filepat vfactor_shader = ("piko.alldir") in material.scs_props.mat_effect_name if vfactor_shader: if _MESH_consts.default_vfactor not in mesh.color_attributes: # get FACTOR component - vfcol = (0.0,) * 4 + vfcol = (1.0,) * 4 missing_vfcolor = True else: vfcolors = mesh.color_attributes[_MESH_consts.default_vfactor] diff --git a/addon/io_scs_tools/exp/pit.py b/addon/io_scs_tools/exp/pit.py index d153e92..233abd6 100644 --- a/addon/io_scs_tools/exp/pit.py +++ b/addon/io_scs_tools/exp/pit.py @@ -235,9 +235,14 @@ def get_texture_path_from_material(material, texture_type, export_path): return "" else: - lprint("E Texture file %r from material %r doesn't exists inside current Project Base Path.\n\t " + + if texture_raw_path: + lprint("E Texture file %r from material %r doesn't exists inside current Project Base Path.\n\t " + "TOBJ won't be exported and reference will remain empty, expect problems!", (texture_raw_path, material.name)) + else: + lprint("E Texture type %r on material %r is missing texture.\n\t " + + "TOBJ won't be exported and reference will remain empty, expect problems!", + (texture_type[8:], material.name)) return "" # CREATE TOBJ FILE diff --git a/addon/io_scs_tools/imp/pit.py b/addon/io_scs_tools/imp/pit.py index 54280a0..552b46f 100644 --- a/addon/io_scs_tools/imp/pit.py +++ b/addon/io_scs_tools/imp/pit.py @@ -200,7 +200,7 @@ def _get_look(section): mat_effect = mat_effect.replace(".night", ".day") lprint("W Night version of building shader detected in material %r, switching it to day!", (mat_alias,)) - # Extra treatment for deprecated/removed shaders and flavors + # Extra treatment for deprecated/removed/unsupported shaders and flavors # # If day/night version of "window" shader is detected, switch it to "lit". if mat_effect.startswith("eut2.window") and mat_effect.endswith((".day", ".night")): @@ -224,6 +224,23 @@ def _get_look(section): mat_effect = mat_effect.replace(".day", "") lprint("W Day version of billboard shader detected in material %r, removing it from effect!", (mat_alias,)) + # Extra temporary treatment for new attributes not supported in older material format used by BT + # + # If "diffuse_secondary" attribute is detected, remove it + if attributes.pop("diffuse_secondary", None) is not None: + lprint("I Unsupported attribute: 'diffuse_secondary' in current material configuration inside material %r, ignoring it!", + (mat_alias,)) + + # If "is_dynamic_road" attribute is detected, remove it + if attributes.pop("is_dynamic_road", None) is not None: + lprint("W Unsupported attribute: 'is_dynamic_road' inside material %r, ignoring it! Material will not work as intended after export!", + (mat_alias,)) + + # If "is_terrain_material" attribute is detected, remove it + if attributes.pop("is_terrain_material", None) is not None: + lprint("W Unsupported attribute: 'is_terrain_material' inside material %r, ignoring it! Material will not work as intended after export!", + (mat_alias,)) + look_mat_settings[mat_alias] = (mat_effect, mat_flags, attributes, textures, sec) return look_name, look_mat_settings diff --git a/addon/io_scs_tools/imp/pix.py b/addon/io_scs_tools/imp/pix.py index e499026..a3fc1b1 100644 --- a/addon/io_scs_tools/imp/pix.py +++ b/addon/io_scs_tools/imp/pix.py @@ -174,6 +174,7 @@ def _create_scs_root_object(name, loaded_variants, loaded_looks, mats_info, obje bpy.context.view_layer.active_layer_collection.collection.objects.link(scs_root_object) bpy.context.view_layer.objects.active = scs_root_object scs_root_object.scs_props.scs_root_object_export_enabled = True + scs_root_object["skip_default_part"] = True scs_root_object.scs_props.empty_object_type = 'SCS_Root' # print('LOD.pos: %s' % str(scs_root_object.location)) diff --git a/addon/io_scs_tools/internals/callbacks/open_gl.py b/addon/io_scs_tools/internals/callbacks/open_gl.py index 07958cf..bdf6551 100644 --- a/addon/io_scs_tools/internals/callbacks/open_gl.py +++ b/addon/io_scs_tools/internals/callbacks/open_gl.py @@ -19,7 +19,6 @@ # Copyright (C) 2013-2019: SCS Software import bpy -import console_python from io_scs_tools.internals.open_gl import core as _gl_core from io_scs_tools.utils import view3d as _view3d_utils @@ -47,7 +46,7 @@ def enable(mode="Normal"): _callback_handle[:] = handle_post_pixel, handle_post_view - console_python.execute.hooks.append((_view3d_utils.tag_redraw_all_view3d, ())) + _view3d_utils.tag_redraw_all_view3d() def disable(): @@ -58,7 +57,7 @@ def disable(): if not _callback_handle: return - console_python.execute.hooks.remove((_view3d_utils.tag_redraw_all_view3d, ())) + _view3d_utils.tag_redraw_all_view3d() handle_post_pixel, handle_post_view = _callback_handle diff --git a/addon/io_scs_tools/internals/callbacks/persistent.py b/addon/io_scs_tools/internals/callbacks/persistent.py index 30ea300..9f9ec5e 100644 --- a/addon/io_scs_tools/internals/callbacks/persistent.py +++ b/addon/io_scs_tools/internals/callbacks/persistent.py @@ -25,6 +25,7 @@ from io_scs_tools.internals.persistent import file_load as _persistent_file_load from io_scs_tools.internals.persistent import open_gl as _persistent_open_gl from io_scs_tools.internals.persistent import shaders_update as _persistent_shaders_update +from io_scs_tools.internals.persistent import active_part as _persistent_active_part def enable(): @@ -51,6 +52,7 @@ def enable(): bpy.app.handlers.frame_change_post.append(_persistent_shaders_update.post_frame_change) bpy.app.handlers.undo_post.append(_persistent_open_gl.post_undo) bpy.app.handlers.redo_post.append(_persistent_open_gl.post_redo) + bpy.app.handlers.depsgraph_update_post.append(_persistent_active_part.sync_part) bpy.app.handlers.load_post.append(_persistent_file_load.post_load) @@ -79,3 +81,5 @@ def disable(): bpy.app.handlers.load_post.remove(_persistent_file_load.post_load) if _persistent_file_save.pre_save in bpy.app.handlers.save_pre: bpy.app.handlers.save_pre.remove(_persistent_file_save.pre_save) + if _persistent_active_part.sync_part in bpy.app.handlers.depsgraph_update_post: + bpy.app.handlers.depsgraph_update_post.remove(_persistent_active_part.sync_part) diff --git a/addon/io_scs_tools/internals/containers/config.py b/addon/io_scs_tools/internals/containers/config.py index 2fdda47..93cd373 100644 --- a/addon/io_scs_tools/internals/containers/config.py +++ b/addon/io_scs_tools/internals/containers/config.py @@ -261,7 +261,10 @@ def apply_settings(self, settings_to_apply=None): if not attr: continue - setattr(scs_globals, attr, value) + # prevents the configuration from looping when starting Blender + current = getattr(scs_globals, attr, None) + if current != value: + setattr(scs_globals, attr, value) def fill_from_pix_section(self, section): """Fill config section with data from given pix section. @@ -535,6 +538,20 @@ def __init__(self): } +class GlobalOther(_ConfigSection): + """Class for global other settings.""" + + def __init__(self): + """Constructor.""" + super().__init__("GlobalOther") + + scs_globals = _get_scs_globals() + self.props = { + "ActivateNewParts": (int, get_default(scs_globals, 'activate_new_parts'), 'activate_new_parts'), + "ActivateNewVariantParts": (int, get_default(scs_globals, 'activate_new_variant_parts'), 'activate_new_variant_parts'), + } + + class ConfigContainer: """Class implementing config container handler.""" @@ -547,6 +564,7 @@ def __init__(self): "Export": Export(), "GlobalDisplay": GlobalDisplay(), "GlobalColors": GlobalColors(), + "GlobalOther": GlobalOther(), } def set_property(self, section_type, prop_name, value): diff --git a/addon/io_scs_tools/internals/icons/__init__.py b/addon/io_scs_tools/internals/icons/__init__.py index f57ebf5..771f59c 100644 --- a/addon/io_scs_tools/internals/icons/__init__.py +++ b/addon/io_scs_tools/internals/icons/__init__.py @@ -91,8 +91,8 @@ def register(): set_theme(get_theme_name(0)) print("WARNING\t- Default icon theme doesn't exist, fallback to first available!") - # Forces icons to reload (Because of issues with Vulkan) - bpy.app.timers.register(reload_icons, first_interval=0.1) + # Forces icons to reload (Because of issues with Vulkan on faster systems) + bpy.app.timers.register(reload_icons, first_interval=0.5) def reload_icons(): """Forces icons to reload after a short delay (workaround for Vulkan issues).""" diff --git a/addon/io_scs_tools/internals/open_gl/core.py b/addon/io_scs_tools/internals/open_gl/core.py index e29e613..350095d 100644 --- a/addon/io_scs_tools/internals/open_gl/core.py +++ b/addon/io_scs_tools/internals/open_gl/core.py @@ -155,7 +155,7 @@ def _draw_3dview_report(window, area, region): (texture, width, height) = _Show3DViewReport.get_scs_banner_img_data(window) gpu.state.blend_set("ALPHA") - draw_texture_2d(texture, (pos_x - 5, pos_y), width, height) + draw_texture_2d(texture, (pos_x - 5, pos_y), width, height, is_scene_linear_with_rec709_srgb_target=True) gpu.state.blend_set("NONE") # draw control buttons, if controls are enabled diff --git a/addon/io_scs_tools/internals/persistent/active_part.py b/addon/io_scs_tools/internals/persistent/active_part.py new file mode 100644 index 0000000..14649cf --- /dev/null +++ b/addon/io_scs_tools/internals/persistent/active_part.py @@ -0,0 +1,39 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.internals import inventory as _inventory +from io_scs_tools.utils import object as _object_utils +from bpy.app.handlers import persistent + +@persistent +def sync_part(scene, depsgraph): + scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) + if scs_root_object: + scs_props = scs_root_object.scs_props + if scs_root_object and bpy.context.active_object != scs_root_object: + # if old active object is different than current + # set the value for active part index from it + if scs_props.active_scs_part_old_active != bpy.context.active_object.name: + scs_props.active_scs_part_value = _inventory.get_index(scs_root_object.scs_object_part_inventory, + bpy.context.active_object.scs_props.scs_part) + + if scs_props.active_scs_part_old_active != bpy.context.active_object.name: + scs_props.active_scs_part_old_active = bpy.context.active_object.name diff --git a/addon/io_scs_tools/internals/persistent/file_load.py b/addon/io_scs_tools/internals/persistent/file_load.py index 9c98c42..65b75ad 100644 --- a/addon/io_scs_tools/internals/persistent/file_load.py +++ b/addon/io_scs_tools/internals/persistent/file_load.py @@ -55,6 +55,7 @@ def post_load(scene): VERSIONS_LIST_UNOFFICIAL = ( ("4", apply_fixes_for_un_4), ("7", apply_fixes_for_un_7), + ("8", apply_fixes_for_un_8), ) v_parts = last_load_bt_ver.split(".") @@ -416,3 +417,26 @@ def apply_fixes_for_un_7(): # 1. reload all materials # Some attributes got removed, in some cases attribute size changed and due to that we need to reload materials _reload_materials() + +def apply_fixes_for_un_8(): + """ + Applies fixes for unofficial 2.4.8 or less: + 1. Reload materials since some got restructed nodes + 2. Show welcome message + """ + + print("INFO\t- Applying fixes for unofficial versions < 8") + + # 1. reload all materials + # Some shaders like nmaps got restructured nodes and due to that we need to reload materials + _reload_materials() + + # 2. Due to update to Blender 5.0+, we let user know + windows = bpy.data.window_managers[0].windows + if len(windows) > 0: + msg = ( + "\nWelcome folks. You just migrated to Blender 5.0+! Yey", + ) + + with bpy.context.temp_override(window=windows[0]): + bpy.ops.wm.scs_tools_show_3dview_report('INVOKE_DEFAULT', message="\n".join(msg)) diff --git a/addon/io_scs_tools/internals/preview_models/__init__.py b/addon/io_scs_tools/internals/preview_models/__init__.py index f08d803..c80365b 100644 --- a/addon/io_scs_tools/internals/preview_models/__init__.py +++ b/addon/io_scs_tools/internals/preview_models/__init__.py @@ -168,7 +168,7 @@ def load(locator, deep_reload=False): new_mesh = obj.data # set preview model path to mesh, so it can be reused next time user requests same preview model - new_mesh.scs_props.locator_preview_model_path = locator.scs_props.locator_preview_model_path + new_mesh.scs_props.locator_preview_model_path = _path_utils.readable_norm(abs_filepath) # now remove imported object, as we need only mesh bpy.data.objects.remove(obj, do_unlink=True) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py index d70fcd4..6ad932d 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/__init__.py @@ -21,6 +21,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif import Dif from io_scs_tools.internals.shaders.eut2.dif_anim import anim_blend_factor_ng +from io_scs_tools.internals.shaders.eut2.dif_anim import over_nmap from io_scs_tools.internals.shaders.flavors import fadesheet from io_scs_tools.internals.shaders.flavors import flipsheet from io_scs_tools.utils import material as _material_utils @@ -141,6 +142,69 @@ def finalize(node_tree, material): node_tree.links.new(node_tree.nodes[DifAnim.VCOLOR_MULT_NODE].inputs[1], node_tree.nodes[DifAnim.BASE_TEX_NODE].outputs['Color']) node_tree.links.new(node_tree.nodes[DifAnim.OPACITY_NODE].inputs[0], node_tree.nodes[DifAnim.BASE_TEX_NODE].outputs['Alpha']) + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[DifAnim.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[DifAnim.GEOM_NODE] + vcol_group_n = node_tree.nodes[DifAnim.VCOL_GROUP_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + over_nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal'], vcol_group_n.outputs["Vertex Color Alpha"]) + else: + over_nmap.delete(node_tree) + + @staticmethod + def set_nmap_over_uv(node_tree, uv_layer): + """Set UV layer to over normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over nmap texture + :type uv_layer: str + """ + + over_nmap.set_over_uv(node_tree, uv_layer) + + @staticmethod + def set_nmap_over_texture(node_tree, texture): + """Set over normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param texture: texture which should be assigned to over nmap texture node + :type texture: bpy.types.Texture + """ + + over_nmap.set_over_texture(node_tree, texture) + + @staticmethod + def set_nmap_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + over_nmap.set_over_texture_settings(node_tree, settings) + @staticmethod def set_base_texture(node_tree, image): """Set base texture to shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py new file mode 100644 index 0000000..659877c --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_anim/over_nmap.py @@ -0,0 +1,168 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import nmap + +OVER_NMAP_UVMAP_NODE = "OverNMapUVs" +OVER_NMAP_TEX_NODE = "OverNMapTex" +OVER_NMAP_COL_MIX_NODE = "OverNMapColMix" + + +def __create_nodes__(node_tree, location, factor_from): + """Create node for over normal maps. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + """ + + frame = node_tree.nodes[nmap.NMAP_FLAVOR_FRAME_NODE] + nmap_uvs_n = node_tree.nodes[nmap.NMAP_UVMAP_NODE] + nmap_tex_n = node_tree.nodes[nmap.NMAP_TEX_NODE] + nmap_dds16_n = node_tree.nodes[nmap.NMAP_DDS16_GNODE] + nmap_scale_n = node_tree.nodes[nmap.NMAP_SCALE_GNODE] + + # move existing + nmap_uvs_n.location.x -= 185 + nmap_tex_n.location.x -= 185 + + # nodes creation + over_nmap_uvs_n = node_tree.nodes.new("ShaderNodeUVMap") + over_nmap_uvs_n.parent = frame + over_nmap_uvs_n.name = over_nmap_uvs_n.label = OVER_NMAP_UVMAP_NODE + over_nmap_uvs_n.location = (location[0] - 185 * 5, location[1] - 400) + over_nmap_uvs_n.uv_map = _MESH_consts.none_uv + + over_nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_nmap_tex_n.parent = frame + over_nmap_tex_n.name = over_nmap_tex_n.label = OVER_NMAP_TEX_NODE + over_nmap_tex_n.location = (location[0] - 185 * 4, location[1] - 400) + over_nmap_tex_n.width = 140 + + over_nmap_col_mix_n = node_tree.nodes.new("ShaderNodeMix") + over_nmap_col_mix_n.parent = frame + over_nmap_col_mix_n.name = over_nmap_col_mix_n.label = OVER_NMAP_COL_MIX_NODE + over_nmap_col_mix_n.location = (location[0] - 185 * 3, location[1] - 110) + over_nmap_col_mix_n.data_type = "RGBA" + over_nmap_col_mix_n.blend_type = "MIX" + + # links creation + node_tree.links.new(factor_from, over_nmap_col_mix_n.inputs["Factor"]) + + node_tree.links.new(over_nmap_uvs_n.outputs["UV"], over_nmap_tex_n.inputs["Vector"]) + + node_tree.links.new(nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["A"]) + node_tree.links.new(over_nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["B"]) + + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_dds16_n.inputs["Color"]) + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_scale_n.inputs["NMap Tex Color"]) + + +def init(node_tree, location, normal_to, normal_from, factor_from): + """Initialize normal map nodes. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location: tuple[int, int] + :param normal_to: node socket to which result of normal map material should be send + :type normal_to: bpy.types.NodeSocket + :param normal_from: node socket from which original mesh normal should be taken + :type normal_from: bpy.types.NodeSocket + :param factor_from: node socket from which factor for over blend should be taken + :type factor_from: bpy.types.NodeSocket + """ + + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + nmap.init(node_tree, location, normal_to, normal_from) + __create_nodes__(node_tree, location, factor_from) + + +def set_over_texture(node_tree, image): + """Set over texture to normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Texture + """ + + # save currently active node to properly reset it on the end + # without reset of active node this material is marked as active which we don't want + old_active = node_tree.nodes.active + + # ignore empty texture + if image is None: + delete(node_tree, True) + return + + # create material node if not yet created + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + return + + # assign texture to texture node first + node_tree.nodes[OVER_NMAP_TEX_NODE].image = image + + node_tree.nodes.active = old_active + + +def set_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to flavor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[OVER_NMAP_TEX_NODE], settings) + +def set_over_uv(node_tree, uv_layer): + """Set UV layer to texture in normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + # set uv layer to texture node + node_tree.nodes[OVER_NMAP_UVMAP_NODE].uv_map = uv_layer + + +def delete(node_tree, preserve_node=False): + """Delete over normal map nodes from node tree. + + :param node_tree: node tree from which normal map should be deleted + :type node_tree: bpy.types.NodeTree + :param preserve_node: if true node won't be deleted + :type preserve_node: bool + """ + + if OVER_NMAP_TEX_NODE in node_tree.nodes and not preserve_node: + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_UVMAP_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_COL_MIX_NODE]) + + nmap.delete(node_tree, preserve_node=preserve_node) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py index e1b63dc..9e24a1f 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/__init__.py @@ -20,6 +20,7 @@ from io_scs_tools.internals.shaders.eut2.dif_spec_weight import DifSpecWeight from io_scs_tools.internals.shaders.eut2.std_passes.add_env import StdAddEnv +from io_scs_tools.internals.shaders.eut2.dif_spec_weight_add_env import detail_nmap class DifSpecWeightAddEnv(DifSpecWeight, StdAddEnv): @@ -50,3 +51,64 @@ def init(node_tree): # links creation node_tree.links.new(add_env_gn.inputs['Weighted Color'], vcol_scale_n.outputs[0]) + + @staticmethod + def set_nmap2_flavor(node_tree, switch_on): + """Set secondary normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[DifSpecWeightAddEnv.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[DifSpecWeightAddEnv.GEOM_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + detail_nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal']) + else: + detail_nmap.delete(node_tree) + + @staticmethod + def set_nmap_detail_uv(node_tree, uv_layer): + """Set UV layer to detail normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for detail nmap texture + :type uv_layer: str + """ + + detail_nmap.set_detail_uv(node_tree, uv_layer) + + @staticmethod + def set_nmap_detail_texture(node_tree, texture): + """Set detail normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param texture: texture which should be assigned to detail nmap texture node + :type texture: bpy.types.Texture + """ + + detail_nmap.set_detail_texture(node_tree, texture) + + @staticmethod + def set_nmap_detail_texture_settings(node_tree, settings): + """Set detail normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + detail_nmap.set_detail_texture_settings(node_tree, settings) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py new file mode 100644 index 0000000..1c340d8 --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_add_env/detail_nmap.py @@ -0,0 +1,167 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import nmap + +DET_NMAP_UVMAP_NODE = "DetailNMapUVs" +DET_NMAP_TEX_NODE = "DetailNMapTex" +DET_NMAP_COMBINE_NODE = "DetailNMapCombine" + + +def __create_nodes__(node_tree, location): + """Create node for detail normal maps. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + """ + + frame = node_tree.nodes[nmap.NMAP_FLAVOR_FRAME_NODE] + nmap_uvs_n = node_tree.nodes[nmap.NMAP_UVMAP_NODE] + nmap_tex_n = node_tree.nodes[nmap.NMAP_TEX_NODE] + nmap_dds16_n = node_tree.nodes[nmap.NMAP_DDS16_GNODE] + nmap_scale_n = node_tree.nodes[nmap.NMAP_SCALE_GNODE] + + # move existing + nmap_uvs_n.location.x -= 185 + nmap_tex_n.location.x -= 185 + + # nodes creation + det_nmap_uvs_n = node_tree.nodes.new("ShaderNodeUVMap") + det_nmap_uvs_n.parent = frame + det_nmap_uvs_n.name = det_nmap_uvs_n.label = DET_NMAP_UVMAP_NODE + det_nmap_uvs_n.location = (location[0] - 185 * 5, location[1] - 400) + det_nmap_uvs_n.uv_map = _MESH_consts.none_uv + + det_nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + det_nmap_tex_n.parent = frame + det_nmap_tex_n.name = det_nmap_tex_n.label = DET_NMAP_TEX_NODE + det_nmap_tex_n.location = (location[0] - 185 * 4, location[1] - 400) + det_nmap_tex_n.width = 140 + + det_nmap_combine_n = node_tree.nodes.new("ShaderNodeMix") + det_nmap_combine_n.parent = frame + det_nmap_combine_n.name = det_nmap_combine_n.label = DET_NMAP_COMBINE_NODE + det_nmap_combine_n.location = (location[0] - 185 * 3, location[1] - 110) + det_nmap_combine_n.data_type = "RGBA" + det_nmap_combine_n.blend_type = "OVERLAY" + det_nmap_combine_n.inputs["Factor"].default_value = 1.0 + + # links creation + node_tree.links.new(nmap_uvs_n.outputs["UV"], det_nmap_tex_n.inputs["Vector"]) + node_tree.links.new(det_nmap_uvs_n.outputs["UV"], nmap_tex_n.inputs["Vector"]) + + node_tree.links.new(nmap_tex_n.outputs["Color"], det_nmap_combine_n.inputs["A"]) + node_tree.links.new(det_nmap_tex_n.outputs["Color"], det_nmap_combine_n.inputs["B"]) + + node_tree.links.new(det_nmap_combine_n.outputs["Result"], nmap_dds16_n.inputs["Color"]) + node_tree.links.new(det_nmap_combine_n.outputs["Result"], nmap_scale_n.inputs["NMap Tex Color"]) + + +def init(node_tree, location, normal_to, normal_from): + """Initialize normal map nodes. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location: tuple[int, int] + :param normal_to: node socket to which result of normal map material should be send + :type normal_to: bpy.types.NodeSocket + :param normal_from: node socket from which original mesh normal should be taken + :type normal_from: bpy.types.NodeSocket + """ + + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + nmap.init(node_tree, location, normal_to, normal_from) + __create_nodes__(node_tree, location) + + +def set_detail_texture(node_tree, image): + """Set texture to normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Texture + """ + + # save currently active node to properly reset it on the end + # without reset of active node this material is marked as active which we don't want + old_active = node_tree.nodes.active + + # ignore empty texture + if image is None: + delete(node_tree, True) + return + + # create material node if not yet created + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + return + + # assign texture to texture node first + node_tree.nodes[DET_NMAP_TEX_NODE].image = image + + node_tree.nodes.active = old_active + + +def set_detail_texture_settings(node_tree, settings): + """Set detail normal map texture settings to flavor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[DET_NMAP_TEX_NODE], settings) + +def set_detail_uv(node_tree, uv_layer): + """Set UV layer to texture in normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + # set uv layer to texture node and normal map node + node_tree.nodes[DET_NMAP_UVMAP_NODE].uv_map = uv_layer + node_tree.nodes[nmap.NMAP_NODE].uv_map = uv_layer + + +def delete(node_tree, preserve_node=False): + """Delete normal map nodes from node tree. + + :param node_tree: node tree from which normal map should be deleted + :type node_tree: bpy.types.NodeTree + :param preserve_node: if true node won't be deleted + :type preserve_node: bool + """ + + if DET_NMAP_TEX_NODE in node_tree.nodes and not preserve_node: + node_tree.nodes.remove(node_tree.nodes[DET_NMAP_UVMAP_NODE]) + node_tree.nodes.remove(node_tree.nodes[DET_NMAP_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[DET_NMAP_COMBINE_NODE]) + + nmap.delete(node_tree, preserve_node=preserve_node) diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py index f0d4d6a..b7b7d17 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/__init__.py @@ -20,6 +20,7 @@ from io_scs_tools.consts import Mesh as _MESH_consts from io_scs_tools.internals.shaders.eut2.dif_spec import DifSpec +from io_scs_tools.internals.shaders.eut2.dif_spec_weight_weight_dif_spec_weight import over_nmap from io_scs_tools.internals.shaders.flavors import tg1 from io_scs_tools.utils import material as _material_utils @@ -154,6 +155,69 @@ def init(node_tree): node_tree.links.new(compose_lighting_n.inputs["Specular Color"], vcol_spec_mul_n.outputs[0]) node_tree.links.new(compose_lighting_n.inputs['Alpha'], base_tex_n.outputs['Alpha']) + + @staticmethod + def set_nmap_flavor(node_tree, switch_on): + """Set normal map flavor to this shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param switch_on: flag indication if normal map should be switched on or off + :type switch_on: bool + """ + + if switch_on: + + # find minimal y position for input nodes and position flavor beneath it + min_y = None + for node in node_tree.nodes: + if node.location.x <= 185 and (min_y is None or min_y > node.location.y): + min_y = node.location.y + + lighting_eval_n = node_tree.nodes[DifSpecWeightWeightDifSpecWeight.LIGHTING_EVAL_NODE] + geom_n = node_tree.nodes[DifSpecWeightWeightDifSpecWeight.GEOM_NODE] + vcol_group_n = node_tree.nodes[DifSpecWeightWeightDifSpecWeight.VCOL_GROUP_NODE] + location = (lighting_eval_n.location.x - 185, min_y - 400) + + over_nmap.init(node_tree, location, lighting_eval_n.inputs['Normal Vector'], geom_n.outputs['Normal'], vcol_group_n.outputs["Vertex Color Alpha"]) + else: + over_nmap.delete(node_tree) + + @staticmethod + def set_nmap_over_uv(node_tree, uv_layer): + """Set UV layer to over normal map texture in shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for over nmap texture + :type uv_layer: str + """ + + over_nmap.set_over_uv(node_tree, uv_layer) + + @staticmethod + def set_nmap_over_texture(node_tree, texture): + """Set over normal map texture to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param texture: texture which should be assigned to over nmap texture node + :type texture: bpy.types.Texture + """ + + over_nmap.set_over_texture(node_tree, texture) + + @staticmethod + def set_nmap_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to shader. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + over_nmap.set_over_texture_settings(node_tree, settings) + @staticmethod def set_shininess(node_tree, factor): """Set shininess factor to shader. diff --git a/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py new file mode 100644 index 0000000..659877c --- /dev/null +++ b/addon/io_scs_tools/internals/shaders/eut2/dif_spec_weight_weight_dif_spec_weight/over_nmap.py @@ -0,0 +1,168 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# Copyright (C) 2025: SCS Software + +import bpy +from io_scs_tools.consts import Mesh as _MESH_consts +from io_scs_tools.utils import material as _material_utils +from io_scs_tools.internals.shaders.flavors import nmap + +OVER_NMAP_UVMAP_NODE = "OverNMapUVs" +OVER_NMAP_TEX_NODE = "OverNMapTex" +OVER_NMAP_COL_MIX_NODE = "OverNMapColMix" + + +def __create_nodes__(node_tree, location, factor_from): + """Create node for over normal maps. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + """ + + frame = node_tree.nodes[nmap.NMAP_FLAVOR_FRAME_NODE] + nmap_uvs_n = node_tree.nodes[nmap.NMAP_UVMAP_NODE] + nmap_tex_n = node_tree.nodes[nmap.NMAP_TEX_NODE] + nmap_dds16_n = node_tree.nodes[nmap.NMAP_DDS16_GNODE] + nmap_scale_n = node_tree.nodes[nmap.NMAP_SCALE_GNODE] + + # move existing + nmap_uvs_n.location.x -= 185 + nmap_tex_n.location.x -= 185 + + # nodes creation + over_nmap_uvs_n = node_tree.nodes.new("ShaderNodeUVMap") + over_nmap_uvs_n.parent = frame + over_nmap_uvs_n.name = over_nmap_uvs_n.label = OVER_NMAP_UVMAP_NODE + over_nmap_uvs_n.location = (location[0] - 185 * 5, location[1] - 400) + over_nmap_uvs_n.uv_map = _MESH_consts.none_uv + + over_nmap_tex_n = node_tree.nodes.new("ShaderNodeTexImage") + over_nmap_tex_n.parent = frame + over_nmap_tex_n.name = over_nmap_tex_n.label = OVER_NMAP_TEX_NODE + over_nmap_tex_n.location = (location[0] - 185 * 4, location[1] - 400) + over_nmap_tex_n.width = 140 + + over_nmap_col_mix_n = node_tree.nodes.new("ShaderNodeMix") + over_nmap_col_mix_n.parent = frame + over_nmap_col_mix_n.name = over_nmap_col_mix_n.label = OVER_NMAP_COL_MIX_NODE + over_nmap_col_mix_n.location = (location[0] - 185 * 3, location[1] - 110) + over_nmap_col_mix_n.data_type = "RGBA" + over_nmap_col_mix_n.blend_type = "MIX" + + # links creation + node_tree.links.new(factor_from, over_nmap_col_mix_n.inputs["Factor"]) + + node_tree.links.new(over_nmap_uvs_n.outputs["UV"], over_nmap_tex_n.inputs["Vector"]) + + node_tree.links.new(nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["A"]) + node_tree.links.new(over_nmap_tex_n.outputs["Color"], over_nmap_col_mix_n.inputs["B"]) + + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_dds16_n.inputs["Color"]) + node_tree.links.new(over_nmap_col_mix_n.outputs["Result"], nmap_scale_n.inputs["NMap Tex Color"]) + + +def init(node_tree, location, normal_to, normal_from, factor_from): + """Initialize normal map nodes. + + :param node_tree: node tree on which normal map will be used + :type node_tree: bpy.types.NodeTree + :param location: x position in node tree + :type location: tuple[int, int] + :param normal_to: node socket to which result of normal map material should be send + :type normal_to: bpy.types.NodeSocket + :param normal_from: node socket from which original mesh normal should be taken + :type normal_from: bpy.types.NodeSocket + :param factor_from: node socket from which factor for over blend should be taken + :type factor_from: bpy.types.NodeSocket + """ + + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + nmap.init(node_tree, location, normal_to, normal_from) + __create_nodes__(node_tree, location, factor_from) + + +def set_over_texture(node_tree, image): + """Set over texture to normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param image: texture image which should be assignet to nmap texture node + :type image: bpy.types.Texture + """ + + # save currently active node to properly reset it on the end + # without reset of active node this material is marked as active which we don't want + old_active = node_tree.nodes.active + + # ignore empty texture + if image is None: + delete(node_tree, True) + return + + # create material node if not yet created + if nmap.NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: + return + + # assign texture to texture node first + node_tree.nodes[OVER_NMAP_TEX_NODE].image = image + + node_tree.nodes.active = old_active + + +def set_over_texture_settings(node_tree, settings): + """Set over normal map texture settings to flavor. + + :param node_tree: node tree of current shader + :type node_tree: bpy.types.NodeTree + :param settings: binary string of TOBJ settings gotten from tobj import + :type settings: str + """ + _material_utils.set_texture_settings_to_node(node_tree.nodes[OVER_NMAP_TEX_NODE], settings) + +def set_over_uv(node_tree, uv_layer): + """Set UV layer to texture in normal map flavor. + + :param node_tree: node tree on which normal map is used + :type node_tree: bpy.types.NodeTree + :param uv_layer: uv layer string used for nmap texture + :type uv_layer: str + """ + + if uv_layer is None or uv_layer == "": + uv_layer = _MESH_consts.none_uv + + # set uv layer to texture node + node_tree.nodes[OVER_NMAP_UVMAP_NODE].uv_map = uv_layer + + +def delete(node_tree, preserve_node=False): + """Delete over normal map nodes from node tree. + + :param node_tree: node tree from which normal map should be deleted + :type node_tree: bpy.types.NodeTree + :param preserve_node: if true node won't be deleted + :type preserve_node: bool + """ + + if OVER_NMAP_TEX_NODE in node_tree.nodes and not preserve_node: + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_UVMAP_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_TEX_NODE]) + node_tree.nodes.remove(node_tree.nodes[OVER_NMAP_COL_MIX_NODE]) + + nmap.delete(node_tree, preserve_node=preserve_node) diff --git a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py index 6e78719..19055e3 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py +++ b/addon/io_scs_tools/internals/shaders/eut2/glass/__init__.py @@ -205,9 +205,10 @@ def init(node_tree): lighting_eval_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1800) lighting_eval_n.node_tree = lighting_evaluator_ng.get_node_group() - fakeopac_hsv_n = node_tree.nodes.new("ShaderNodeSeparateHSV") + fakeopac_hsv_n = node_tree.nodes.new("ShaderNodeSeparateColor") fakeopac_hsv_n.name = fakeopac_hsv_n.label = Glass.FAKEOPAC_HSV_NODE fakeopac_hsv_n.location = (start_pos_x + pos_x_shift * 6, start_pos_y + 1500) + fakeopac_hsv_n.mode = "HSV" # pass 6 add_env_n = node_tree.nodes.new("ShaderNodeGroup") @@ -325,14 +326,14 @@ def init(node_tree): node_tree.links.new(fakeopac_spec_mix_n.inputs[0], final_spec_n.outputs[0]) node_tree.links.new(fakeopac_spec_mix_n.inputs[1], lighting_eval_n.outputs['Specular Lighting']) - node_tree.links.new(fakeopac_add_sv_n.inputs[0], fakeopac_hsv_n.outputs['S']) - node_tree.links.new(fakeopac_add_sv_n.inputs[1], fakeopac_hsv_n.outputs['V']) + node_tree.links.new(fakeopac_add_sv_n.inputs[0], fakeopac_hsv_n.outputs[1]) # Saturation + node_tree.links.new(fakeopac_add_sv_n.inputs[1], fakeopac_hsv_n.outputs[2]) # Value # pass 7 node_tree.links.new(fakeopac_sub_sv_n.inputs[0], fakeopac_add_sv_n.outputs['Value']) - node_tree.links.new(fakeopac_sub_sv_n.inputs[1], fakeopac_hsv_n.outputs['V']) + node_tree.links.new(fakeopac_sub_sv_n.inputs[1], fakeopac_hsv_n.outputs[2]) # Value - node_tree.links.new(fakeopac_v_inv_n.inputs[1], fakeopac_hsv_n.outputs['V']) + node_tree.links.new(fakeopac_v_inv_n.inputs[1], fakeopac_hsv_n.outputs[2]) # Value # pass 8 node_tree.links.new(fakeopac_add_spec_mix_n.inputs[0], add_env_n.outputs['Environment Addition Color']) diff --git a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py index 64c62cf..5542ca7 100644 --- a/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py +++ b/addon/io_scs_tools/internals/shaders/eut2/std_node_groups/lampmask_mixer_ng.py @@ -27,7 +27,7 @@ AUX_LAMP_TYPES = _LT_consts.AuxiliaryLampTypes TRAFFIC_LIGHT_TYPES = _LT_consts.TrafficLightTypes -UV_X_TILES = ["UV_X_0_", "UV_X_1_", "UV_X_2_", "UV_X_3_", "UV_X_4_"] +UV_X_TILES = ["UV_X_0_", "UV_X_1_", "UV_X_2_", "UV_X_3_", "UV_X_4_", "UV_X_5_"] UV_Y_TILES = ["UV_Y_0_", "UV_Y_1_", "UV_Y_2_", "UV_Y_3_"] LAMPMASK_MIX_G = _MAT_consts.node_group_prefix + "LampmaskMixerGroup" @@ -93,13 +93,13 @@ def __create_node_group__(): uv_x_dot_n = lampmask_g.nodes.new("ShaderNodeVectorMath") uv_x_dot_n.name = uv_x_dot_n.label = _UV_DOT_X_NODE - uv_x_dot_n.location = (pos_x_shift, -200) + uv_x_dot_n.location = (pos_x_shift, -400) uv_x_dot_n.operation = "DOT_PRODUCT" uv_x_dot_n.inputs[1].default_value = (1.0, 0, 0) uv_y_dot_n = lampmask_g.nodes.new("ShaderNodeVectorMath") uv_y_dot_n.name = uv_y_dot_n.label = _UV_DOT_Y_NODE - uv_y_dot_n.location = (pos_x_shift, -450) + uv_y_dot_n.location = (pos_x_shift, -750) uv_y_dot_n.operation = "DOT_PRODUCT" uv_y_dot_n.inputs[1].default_value = (0, 1.0, 0) @@ -112,7 +112,7 @@ def __create_node_group__(): nodes_for_addition = [] # init uv tilling mechanism - pos_y = -100 + pos_y = -300 max_x_uv = 1 for uv_x_tile in UV_X_TILES: @@ -122,7 +122,7 @@ def __create_node_group__(): max_x_uv += 1 max_y_uv = 1 - pos_y = -400 + pos_y = -700 for uv_y_tile in UV_Y_TILES: __init_uv_tile_bounding_nodes__(lampmask_g, uv_y_dot_n, uv_y_tile, pos_x_shift * 2, pos_y, max_y_uv) @@ -131,21 +131,21 @@ def __create_node_group__(): max_y_uv += 1 # init vehicle sides uv bounding mechanism - pos_y = -50 + pos_y = -250 for vehicle_side in VEHICLE_SIDES: __init_vehicle_uv_bounding_nodes__(lampmask_g, vehicle_side, pos_x_shift * 2, pos_y) pos_y -= 50 # init traffic light uv bounding mechanism - pos_y = -350 + pos_y = -600 for traffic_light_type in TRAFFIC_LIGHT_TYPES: __init_traffic_light_uv_bounding_nodes__(lampmask_g, traffic_light_type, pos_x_shift * 2, pos_y) pos_y -= 100 # init vehicle sides switches mechanism - pos_y = 1000 + pos_y = 1200 for vehicle_lamp_type in VEHICLE_LAMP_TYPES: if vehicle_lamp_type == VEHICLE_LAMP_TYPES.Positional: # make extra space for positional @@ -159,7 +159,7 @@ def __create_node_group__(): vehicle_lamp_type, pos_x_shift * 5, pos_y, nodes_for_addition) - pos_y -= 75 + pos_y -= 125 # init auxiliary lamp switches mechanism pos_y -= 60 @@ -197,7 +197,7 @@ def __create_node_group__(): add_n = lampmask_g.nodes.new("ShaderNodeMath") add_n.name = _ADD_NODE_PREFIX + str(i) add_n.label = _ADD_NODE_PREFIX + str(i) - add_n.location = (curr_node.location.x + 120, curr_node.location.y) + add_n.location = (curr_node.location.x + 180, curr_node.location.y) add_n.hide = True add_n.operation = "ADD" @@ -278,9 +278,12 @@ def __init_vehicle_uv_bounding_nodes__(node_tree, vehicle_side, pos_x, pos_y): elif vehicle_side == VEHICLE_SIDES.RearRight: min_uv_n = node_tree.nodes[UV_X_TILES[2] + _MAX_UV_SUFFIX] max_uv_n = node_tree.nodes[UV_X_TILES[3] + _MAX_UV_SUFFIX] - else: # fallback to middle + elif vehicle_side == VEHICLE_SIDES.MiddleLeft: min_uv_n = node_tree.nodes[UV_X_TILES[3] + _MAX_UV_SUFFIX] max_uv_n = node_tree.nodes[UV_X_TILES[4] + _MAX_UV_SUFFIX] + else: # fallback to MiddleRight + min_uv_n = node_tree.nodes[UV_X_TILES[4] + _MAX_UV_SUFFIX] + max_uv_n = node_tree.nodes[UV_X_TILES[5] + _MAX_UV_SUFFIX] uv_in_bounds_n = node_tree.nodes.new("ShaderNodeMath") uv_in_bounds_n.name = vehicle_side.name + _IN_BOUNDS_SUFFIX @@ -408,25 +411,33 @@ def __init_vehicle_switch_nodes__(node_tree, a_output, r_output, g_output, b_out elif lamp_type == VEHICLE_LAMP_TYPES.DRL: node_tree.links.new(switch_n.inputs[1], b_output) - node_name = lamp_type.name + VEHICLE_SIDES.Middle.name - position = (pos_x + 185 * 2, pos_y) - in_bounds_n = node_tree.nodes[VEHICLE_SIDES.Middle.name + _IN_BOUNDS_SUFFIX] + in_bounds_n = node_tree.nodes[VEHICLE_SIDES.MiddleLeft.name + _IN_BOUNDS_SUFFIX] + node_name = lamp_type.name + VEHICLE_SIDES.MiddleLeft.name + position = (pos_x + 185 * 2, pos_y + 18) + mult_n = __create_merging_node__(node_tree, node_name, position, in_bounds_n.outputs[0], switch_n.outputs[0]) + nodes_for_addition.append(mult_n) + + in_bounds_n = node_tree.nodes[VEHICLE_SIDES.MiddleRight.name + _IN_BOUNDS_SUFFIX] + node_name = lamp_type.name + VEHICLE_SIDES.MiddleRight.name + position = (pos_x + 185 * 2, pos_y - 18) mult_n = __create_merging_node__(node_tree, node_name, position, in_bounds_n.outputs[0], switch_n.outputs[0]) nodes_for_addition.append(mult_n) else: - color_output = veh_side_name1 = veh_side_name2 = None + color_output = veh_side_name1 = veh_side_name2 = veh_side_name3 = None if lamp_type == VEHICLE_LAMP_TYPES.LeftTurn: color_output = r_output veh_side_name1 = VEHICLE_SIDES.FrontLeft.name veh_side_name2 = VEHICLE_SIDES.RearLeft.name + veh_side_name3 = VEHICLE_SIDES.MiddleLeft.name elif lamp_type == VEHICLE_LAMP_TYPES.RightTurn: color_output = r_output veh_side_name1 = VEHICLE_SIDES.FrontRight.name veh_side_name2 = VEHICLE_SIDES.RearRight.name + veh_side_name3 = VEHICLE_SIDES.MiddleRight.name elif lamp_type == VEHICLE_LAMP_TYPES.Brake: @@ -467,6 +478,13 @@ def __init_vehicle_switch_nodes__(node_tree, a_output, r_output, g_output, b_out mult_n = __create_merging_node__(node_tree, node_name, node_pos, switch_n.outputs[0], in_bounds_n.outputs[0]) nodes_for_addition.append(mult_n) + if veh_side_name3: + in_bounds_n = node_tree.nodes[veh_side_name3 + _IN_BOUNDS_SUFFIX] + node_name = lamp_type.name + veh_side_name3 + node_pos = (pos_x + 185 * 2, pos_y - 54) + mult_n = __create_merging_node__(node_tree, node_name, node_pos, switch_n.outputs[0], in_bounds_n.outputs[0]) + nodes_for_addition.append(mult_n) + def __init_aux_switch_nodes__(node_tree, a_output, r_output, g_output, lamp_type, pos_x, pos_y, nodes_for_addition): """Creation and linking of switching nodes, covering all combinations for vehicle auxiliary lamp mask. diff --git a/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py b/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py index 3d5abfb..cbbe6f6 100644 --- a/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py +++ b/addon/io_scs_tools/internals/shaders/flavors/nmap/__init__.py @@ -68,6 +68,11 @@ def __create_nodes__(node_tree, location=None, normal_to=None, normal_from=None) nmap_tex_n.name = nmap_tex_n.label = NMAP_TEX_NODE nmap_tex_n.width = 140 + nmap_dds16_n = node_tree.nodes.new("ShaderNodeGroup") + nmap_dds16_n.parent = frame + nmap_dds16_n.name = nmap_dds16_n.label = NMAP_DDS16_GNODE + nmap_dds16_n.node_tree = dds16_ng.get_node_group() + nmap_n = node_tree.nodes.new("ShaderNodeNormalMap") nmap_n.parent = frame nmap_n.name = nmap_n.label = NMAP_NODE @@ -81,8 +86,9 @@ def __create_nodes__(node_tree, location=None, normal_to=None, normal_from=None) # position nodes if location: - nmap_uvs_n.location = (location[0] - 185 * 3, location[1]) - nmap_tex_n.location = (location[0] - 185 * 2, location[1]) + nmap_uvs_n.location = (location[0] - 185 * 4, location[1]) + nmap_tex_n.location = (location[0] - 185 * 3, location[1]) + nmap_dds16_n.location = (location[0] - 185 * 2, location[1] - 220) nmap_n.location = (location[0] - 185, location[1] - 200) nmap_scale_n.location = (location[0], location[1]) @@ -91,58 +97,21 @@ def __create_nodes__(node_tree, location=None, normal_to=None, normal_from=None) node_tree.links.new(nodes[NMAP_UVMAP_NODE].outputs["UV"], nodes[NMAP_TEX_NODE].inputs["Vector"]) - node_tree.links.new(nodes[NMAP_NODE].inputs["Color"], nodes[NMAP_TEX_NODE].outputs["Color"]) + node_tree.links.new(nodes[NMAP_TEX_NODE].outputs["Color"], nodes[NMAP_DDS16_GNODE].inputs["Color"]) + + node_tree.links.new(nodes[NMAP_DDS16_GNODE].outputs["Color"], nodes[NMAP_NODE].inputs["Color"]) + # Commented because default 1.0 strength gives better visualization. There must be something wrong with calculating strenght? (R channel?) + # Probably SCS changed formula a little bit when they abandoned 3-channel normal maps to 2-channel ones. + # node_tree.links.new(nodes[NMAP_DDS16_GNODE].outputs["Strength"], nodes[NMAP_NODE].inputs["Strength"]) + + node_tree.links.new(nodes[NMAP_NODE].outputs["Normal"], nodes[NMAP_SCALE_GNODE].inputs["Modified Normal"]) - node_tree.links.new(nodes[NMAP_SCALE_GNODE].inputs["NMap Tex Color"], nodes[NMAP_TEX_NODE].outputs["Color"]) if normal_from: - node_tree.links.new(nodes[NMAP_SCALE_GNODE].inputs["Original Normal"], normal_from) - node_tree.links.new(nodes[NMAP_SCALE_GNODE].inputs["Modified Normal"], nodes[NMAP_NODE].outputs["Normal"]) + node_tree.links.new(normal_from, nodes[NMAP_SCALE_GNODE].inputs["Original Normal"]) # set normal only if we know where to if normal_to: - node_tree.links.new(normal_to, nodes[NMAP_SCALE_GNODE].outputs["Normal"]) - - -def __check_and_create_dds16_node__(node_tree, image): - """Checks if given texture is composed '16-bit DDS' texture and properly create extra node for it's representation. - On the contrary if texture is not 16-bit DDS and node exists clean that node and restore old connections. - - :param node_tree: node tree on which normal map will be used - :type node_tree: bpy.types.NodeTree - :param image: texture image which should be assigned to nmap texture node - :type image: bpy.types.Image - """ - - # in case of DDS simulating 16-bit normal maps create it's group and properly connect it, - # on the other hand if group exists but shouldn't delete group and restore old connections - - is_dds16 = image and image.filepath.endswith(".dds") and image.pixels[2] == 0.0 - if is_dds16 and NMAP_DDS16_GNODE not in node_tree.nodes: - - nmap_dds16_n = node_tree.nodes.new("ShaderNodeGroup") - nmap_dds16_n.parent = node_tree.nodes[NMAP_FLAVOR_FRAME_NODE] - nmap_dds16_n.name = nmap_dds16_n.label = NMAP_DDS16_GNODE - nmap_dds16_n.node_tree = dds16_ng.get_node_group() - - location = node_tree.nodes[NMAP_NODE].location - - node_tree.nodes[NMAP_TEX_NODE].location[0] -= 185 - node_tree.nodes[NMAP_UVMAP_NODE].location[0] -= 185 - nmap_dds16_n.location = (location[0] - 185, location[1]) - - node_tree.links.new(node_tree.nodes[NMAP_DDS16_GNODE].inputs["Color"], node_tree.nodes[NMAP_TEX_NODE].outputs["Color"]) - - node_tree.links.new(node_tree.nodes[NMAP_NODE].inputs["Strength"], node_tree.nodes[NMAP_DDS16_GNODE].outputs["Strength"]) - node_tree.links.new(node_tree.nodes[NMAP_NODE].inputs["Color"], node_tree.nodes[NMAP_DDS16_GNODE].outputs["Color"]) - - elif not is_dds16 and NMAP_DDS16_GNODE in node_tree.nodes: - - node_tree.nodes.remove(node_tree.nodes[NMAP_DDS16_GNODE]) - - node_tree.nodes[NMAP_TEX_NODE].location[0] += 185 - node_tree.nodes[NMAP_UVMAP_NODE].location[0] += 185 - - node_tree.links.new(node_tree.nodes[NMAP_NODE].inputs["Color"], node_tree.nodes[NMAP_TEX_NODE].outputs["Color"]) + node_tree.links.new(nodes[NMAP_SCALE_GNODE].outputs["Normal"], normal_to) def init(node_tree, location, normal_to, normal_from): @@ -184,9 +153,6 @@ def set_texture(node_tree, image): if NMAP_FLAVOR_FRAME_NODE not in node_tree.nodes: __create_nodes__(node_tree) - # in case of DDS simulating 16-bit normal maps create it's group and properly connect it - __check_and_create_dds16_node__(node_tree, image) - # assign texture to texture node first node_tree.nodes[NMAP_TEX_NODE].image = image @@ -237,7 +203,6 @@ def delete(node_tree, preserve_node=False): if NMAP_NODE in node_tree.nodes and not preserve_node: node_tree.nodes.remove(node_tree.nodes[NMAP_TEX_NODE]) node_tree.nodes.remove(node_tree.nodes[NMAP_NODE]) - if NMAP_DDS16_GNODE in node_tree.nodes: - node_tree.nodes.remove(node_tree.nodes[NMAP_DDS16_GNODE]) + node_tree.nodes.remove(node_tree.nodes[NMAP_DDS16_GNODE]) node_tree.nodes.remove(node_tree.nodes[NMAP_SCALE_GNODE]) node_tree.nodes.remove(node_tree.nodes[NMAP_FLAVOR_FRAME_NODE]) diff --git a/addon/io_scs_tools/internals/shaders/shader.py b/addon/io_scs_tools/internals/shaders/shader.py index 6e64764..259453f 100644 --- a/addon/io_scs_tools/internals/shaders/shader.py +++ b/addon/io_scs_tools/internals/shaders/shader.py @@ -63,7 +63,7 @@ def setup_nodes(material, effect, attr_dict, tex_dict, tex_settings_dict, recrea flavors["nmap"] = True if effect.endswith(".tsnmapuv2") or ".tsnmapuv2." in effect: - flavors["nmap"] = True + flavors["nmap2"] = True if effect.endswith(".tsnmap") or ".tsnmap." in effect: flavors["nmap"] = True diff --git a/addon/io_scs_tools/operators/mesh.py b/addon/io_scs_tools/operators/mesh.py index 3c7c0e3..531e0b1 100644 --- a/addon/io_scs_tools/operators/mesh.py +++ b/addon/io_scs_tools/operators/mesh.py @@ -79,8 +79,10 @@ def execute(self, context): offset_x = 2 elif _LT_consts.VehicleSides.RearRight.name == self.vehicle_side: offset_x = 3 - elif _LT_consts.VehicleSides.Middle.name == self.vehicle_side: + elif _LT_consts.VehicleSides.MiddleLeft.name == self.vehicle_side: offset_x = 4 + elif _LT_consts.VehicleSides.MiddleRight.name == self.vehicle_side: + offset_x = 5 elif _LT_consts.AuxiliaryLampColors.White.name == self.aux_color: # auxiliary lights checking offset_x = 0 elif _LT_consts.AuxiliaryLampColors.Orange.name == self.aux_color: @@ -390,7 +392,6 @@ def poll(cls, context): def execute(self, context): default_color = tuple(Color((0.5,) * 3).from_srgb_to_scene_linear()) + (1.0,) - default_factor = tuple((0.0,) * 4) layer_name = _MESH_consts.default_vcol layer_a_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix @@ -416,9 +417,9 @@ def execute(self, context): vfcolor = context.object.data.color_attributes.new(name=layer_factor_name, type='BYTE_COLOR', domain='CORNER') vfcolor.name = layer_factor_name # repeat naming step to make sure it's properly named - # setting neutral value (0.0) to all factors + # setting neutral value (0.5) to all factors for vertex_fac_col_data in context.object.data.color_attributes[layer_factor_name].data: - vertex_fac_col_data.color = default_factor + vertex_fac_col_data.color = default_color # restore active or set to default vcol if there was none if old_active_col_i is None: @@ -440,7 +441,6 @@ def poll(cls, context): def execute(self, context): default_color = tuple(Color((0.5,) * 3).from_srgb_to_scene_linear()) + (1.0,) - default_factor = tuple((0.0,) * 4) layer_name = _MESH_consts.default_vcol layer_a_name = _MESH_consts.default_vcol + _MESH_consts.vcol_a_suffix @@ -485,7 +485,7 @@ def execute(self, context): # setting neutral value (0.0) to all factors for vertex_fac_col_data in obj.data.color_attributes[layer_factor_name].data: - vertex_fac_col_data.color = default_factor + vertex_fac_col_data.color = default_color # restore active or set to default vcol if there was none if old_active_col_i is None: diff --git a/addon/io_scs_tools/operators/object.py b/addon/io_scs_tools/operators/object.py index eee6f9e..1c371d2 100755 --- a/addon/io_scs_tools/operators/object.py +++ b/addon/io_scs_tools/operators/object.py @@ -430,7 +430,7 @@ def execute(self, context): variant_part = _inventory.add_item(variant.parts, part.name) if variant_part: - variant_part.include = True + variant_part.include = True if _get_scs_globals().activate_new_parts else False else: lprint("W Part %r already in variant %r.", (part.name, variant.name)) @@ -604,6 +604,12 @@ class SCS_TOOLS_OT_AssignPart(bpy.types.Operator): bl_idname = "object.scs_tools_assign_part" bl_description = "Assign active SCS Part to selected objects" + part_index: IntProperty( + name="Part Index", + description="Index of the part to assign. If negative, uses active_scs_part.", + default=-1 + ) + @classmethod def poll(cls, context): if _object_utils.get_scs_root(context.active_object): @@ -616,7 +622,13 @@ def execute(self, context): active_object = context.active_object scs_root_object = _object_utils.get_scs_root(active_object) part_inventory = scs_root_object.scs_object_part_inventory - active_part_index = scs_root_object.scs_props.active_scs_part + + # Use part_index if provided, else use active_part_index + if self.part_index >= 0: + active_part_index = self.part_index + self.part_index = -1 # Reset to default after use + else: + active_part_index = scs_root_object.scs_props.active_scs_part scs_roots_count = len(_object_utils.gather_scs_roots(bpy.context.selected_objects)) if scs_roots_count == 1: @@ -778,7 +790,8 @@ def execute(self, context): for part in part_inventory: variant_part = _inventory.add_item(variant.parts, part.name) - variant_part.include = True + + variant_part.include = True if _get_scs_globals().activate_new_variant_parts else False scs_root_object.scs_props.active_scs_variant = len(variant_inventory) - 1 diff --git a/addon/io_scs_tools/operators/wm.py b/addon/io_scs_tools/operators/wm.py index 76f2f4e..cd31240 100644 --- a/addon/io_scs_tools/operators/wm.py +++ b/addon/io_scs_tools/operators/wm.py @@ -152,22 +152,22 @@ def get_scs_banner_img_data(window): img_name = _OP_consts.View3DReport.BT_BANNER_IMG_NAME if img_name not in bpy.data.images: - img_path = os.path.join(_path_utils.get_addon_installation_paths()[0], "ui", "banners", img_name) img = bpy.data.images.load(img_path, check_existing=True) img.colorspace_settings.name = 'sRGB' img.alpha_mode = 'CHANNEL_PACKED' else: - img = bpy.data.images[img_name] - texture = gpu.texture.from_image(img) + if not img.has_data: + img.reload() - # ensure that image is loaded in GPU memory aka has proper bindcode, - # we have to that each time because if operator is shown for long time blender might free it on it's own - if img.bindcode == 0: - img.gl_load() + try: + texture = gpu.texture.from_image(img) + except RuntimeError: + img.reload() + texture = gpu.texture.from_image(img) return texture, img.size[0], img.size[1] diff --git a/addon/io_scs_tools/properties/addon_preferences.py b/addon/io_scs_tools/properties/addon_preferences.py index d0d8ce1..ef8ed8a 100644 --- a/addon/io_scs_tools/properties/addon_preferences.py +++ b/addon/io_scs_tools/properties/addon_preferences.py @@ -1340,6 +1340,14 @@ def config_storage_place_update(self, context): return None + def activate_new_parts_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalOther.ActivateNewParts', int(self.activate_new_parts)) + + def activate_new_variant_parts_update(self, context): + self.on_display_setting_update(context) + _config_container.update_item_in_file('GlobalOther.ActivateNewVariantParts', int(self.activate_new_variant_parts)) + dump_level: EnumProperty( name="Printouts", items=( @@ -1364,6 +1372,20 @@ def config_storage_place_update(self, context): update=config_storage_place_update, ) + activate_new_parts: BoolProperty( + name="Activate New Parts", + description="Automatically activate new parts when added to SCS Game Object.", + default=True, + update=activate_new_parts_update + ) + + activate_new_variant_parts: BoolProperty( + name="Activate New Variant Parts", + description="Automatically activate parts when new variant is created for SCS Game Object.", + default=True, + update=activate_new_variant_parts_update + ) + # COMMON SETTINGS - NOT SAVED IN CONFIG preview_export_selection: BoolProperty( name="Preview selection", diff --git a/addon/io_scs_tools/properties/dynamic/__init__.py b/addon/io_scs_tools/properties/dynamic/__init__.py index ece47ad..cdb8fb2 100644 --- a/addon/io_scs_tools/properties/dynamic/__init__.py +++ b/addon/io_scs_tools/properties/dynamic/__init__.py @@ -82,12 +82,12 @@ def getter(self): prefs = bpy.context.preferences.addons["io_scs_tools"].preferences - if scope not in prefs: + if not hasattr(prefs, scope): return default scoped_prefs = prefs[scope] - if property_name not in scoped_prefs: + if not hasattr(scoped_prefs, property_name): return default return scoped_prefs[property_name] @@ -105,10 +105,12 @@ def setter(self, value): prefs = bpy.context.preferences.addons["io_scs_tools"].preferences - if scope not in prefs: - prefs[scope] = {} - - prefs[scope][property_name] = value + if not hasattr(prefs, scope): + setattr(prefs, scope, {}) + + scope_dict = getattr(prefs, scope) + scope_dict[property_name] = value + setattr(prefs, scope, scope_dict) # check for default value type assert isinstance(default, property_type) diff --git a/addon/io_scs_tools/properties/object.py b/addon/io_scs_tools/properties/object.py index 2a7042c..a4aa2c5 100644 --- a/addon/io_scs_tools/properties/object.py +++ b/addon/io_scs_tools/properties/object.py @@ -295,9 +295,15 @@ def update_empty_object_type(self, context): obj.empty_display_type = "ARROWS" obj.show_name = True - # ensure default part + # ensure default part when it's needed part_inventory = obj.scs_object_part_inventory - _inventory.add_item(part_inventory, _PART_consts.default_name, conditional=True) + skip_default_part = obj.get("skip_default_part", False) + if not skip_default_part: + _inventory.add_item(part_inventory, _PART_consts.default_name, conditional=True) + + # remove flag after use + if "skip_default_part" in obj: + del obj["skip_default_part"] else: obj.empty_display_size = 1.0 obj.empty_display_type = "PLAIN_AXES" @@ -369,6 +375,9 @@ def active_scs_part_get_direct(self): # NOTE: case where this happens is if user imports SCS model, # duplicates prefab locator without part and then changes locator # type to model locator. + if "active_scs_part_old_active" not in self: + self["active_scs_part_old_active"] = "" + if "active_scs_part_value" not in self: self["active_scs_part_value"] = 0 @@ -379,49 +388,14 @@ def active_scs_part_get(self): with the index of part belonging to new active object. """ - debug = False - lprint("D ---------- GETTER ----------") if debug else None - - if "active_scs_part_old_active" not in self: - lprint("D ---- IF SELF 1 ENTERED ----") if debug else None - self["active_scs_part_old_active"] = "" - if "active_scs_part_value" not in self: - lprint("D ---- IF SELF 2 ENTERED ----") if debug else None - self["active_scs_part_value"] = 0 - - scs_root_object = _object_utils.get_scs_root(bpy.context.active_object) - if debug: - lprint("D --active_scs_part_old_active-- %r", (self["active_scs_part_old_active"],)) - lprint("D --active_scs_part_value-- %r", (self["active_scs_part_value"],)) - lprint("D --scs_root_object-- %r", (scs_root_object,)) - lprint("D --bpy.context.active_object-- %r", (bpy.context.active_object,)) - lprint("D --bpy.context.active_object.name-- %r", (bpy.context.active_object.name,)) - if scs_root_object and bpy.context.active_object != scs_root_object: - lprint("D ---- IF 1 ENTERED ----") if debug else None - # if old active object is different than current - # set the value for active part index from it - if self["active_scs_part_old_active"] != bpy.context.active_object.name: - lprint("D ---- IF 2 ENTERED ----") if debug else None - self["active_scs_part_value"] = _inventory.get_index(scs_root_object.scs_object_part_inventory, - bpy.context.active_object.scs_props.scs_part) - - # TEMP: skip unnecessary 'active_scs_part_old_active' update to decrease errors count in console (setters in getters) - if self["active_scs_part_old_active"] != bpy.context.active_object.name: - lprint("D ---- TEMP IF ENTERED ----") if debug else None - self["active_scs_part_old_active"] = bpy.context.active_object.name - print("-------------------------\n") if debug else None - return self["active_scs_part_value"] + return self.active_scs_part_value def active_scs_part_set(self, value): - debug = False - lprint("D ---------- SETTER ----------") if debug else None - self["active_scs_part_value"] = value - print("-------------------------\n") if debug else None + """Store the index selected by the user. + """ - def active_scs_part_update(self, context): - lprint("D ---------- UPDATE ----------") - print("-------------------------\n") + self.active_scs_part_value = value active_scs_part: IntProperty( name="Active SCS Part", @@ -433,7 +407,22 @@ def active_scs_part_update(self, context): subtype='NONE', get=active_scs_part_get, set=active_scs_part_set - # update=active_scs_part_update + ) + + active_scs_part_old_active: bpy.props.StringProperty( + name="Old Active Part", + description="Stores previously active SCS part", + default="" + ) + + active_scs_part_value: IntProperty( + name="Active SCS Part Value", + description="Active SCS Part for current SCS Root Object Value", + default=0, + min=0, + step=1, + options={'HIDDEN'}, + subtype='NONE' ) def get_active_scs_look(self): @@ -614,7 +603,7 @@ def locator_preview_model_type_update(self, context): for child in obj.children: - if "scs_props" in child.data and child.data.scs_props.locator_preview_model_path != "": + if hasattr(child.data, "scs_props") and child.data.scs_props.locator_preview_model_path != "": child.display_type = obj.scs_props.locator_preview_model_type diff --git a/addon/io_scs_tools/shader_presets.txt b/addon/io_scs_tools/shader_presets.txt index 47663c1..7e8a16b 100644 --- a/addon/io_scs_tools/shader_presets.txt +++ b/addon/io_scs_tools/shader_presets.txt @@ -1742,15 +1742,13 @@ Shader { Value: ( 1.0 1.0 1.0 ) } # NOTE: This shader use new secondary diffuse that is not supported in old material system by conversion tool 2.20 yet. - # To prevent being , I set it to previewonly and hide it. If newer version of conversion tool will support it, this should be changed. + # To prevent being , this attribute will be removed on import automatically. If newer version of conversion tool will support it, this should be changed. # This attribute will be probably named "aux[4]" in future. - Attribute { - Format: FLOAT3 - Tag: "diffuse_secondary" - Value: ( 1.0 1.0 1.0 ) - PreviewOnly: "True" - Hide: "True" - } + # Attribute { + # Format: FLOAT3 + # Tag: "diffuse_secondary" + # Value: ( 1.0 1.0 1.0 ) + # } Attribute { Format: FLOAT3 Tag: "specular" @@ -3158,11 +3156,13 @@ Flavor { Name: "tsnmap" Texture { Tag: "texture[X]:texture_nmap" + FriendlyTag: "Normal map" Value: "" TexCoord: ( 0 ) } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal map (detail)" Value: "" TexCoord: ( 0 ) } @@ -3198,6 +3198,7 @@ Flavor { } Texture { Tag: "texture[X]:texture_nmap_over" + FriendlyTag: "Normal map (over)" Value: "" TexCoord: ( 0 ) } @@ -3213,6 +3214,7 @@ Flavor { } Texture { Tag: "texture[X]:texture_nmap_over" + FriendlyTag: "Normal map (over)" Value: "" TexCoord: ( 1 ) } @@ -3238,6 +3240,7 @@ Flavor { } Texture { Tag: "texture[X]:texture_nmap_detail" + FriendlyTag: "Normal map (detail)" Value: "" TexCoord: ( 6 ) } diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index b2b3211..4f2b71c 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -73,9 +73,26 @@ def draw_icon_part_tools(layout, index): def draw_item(self, context, layout, data, item, icon, active_data, active_property, index): if self.layout_type in {'DEFAULT', 'COMPACT'}: if item: - line = layout.split(factor=0.6, align=False) - line.prop(item, "name", text="", emboss=False, icon_value=icon) - tools = line.row(align=True) + active_object = context.active_object + is_not_root = getattr(active_object.scs_props, 'empty_object_type', None) != 'SCS_Root' + row = layout.row(align=True) + if is_not_root: + current_part_name = getattr(active_object.scs_props, 'scs_part', None) + is_assigned = (item.name == current_part_name) + icon_col = row.column() + + if is_assigned: + icon_col.label(icon='RESTRICT_INSTANCED_OFF') + else: + op = icon_col.operator('object.scs_tools_assign_part', text="", icon='RESTRICT_INSTANCED_ON', emboss=False) + op.part_index = index + + spacer_col = row.column() + spacer_col.scale_x = 0.15 + spacer_col.label(text="") + + row.prop(item, "name", text="", emboss=False, icon_value=icon) + tools = row.row(align=True) tools.alignment = 'RIGHT' self.draw_icon_part_tools(tools, index) else: diff --git a/addon/io_scs_tools/ui/shared.py b/addon/io_scs_tools/ui/shared.py index 26d9518..5c6d2ea 100644 --- a/addon/io_scs_tools/ui/shared.py +++ b/addon/io_scs_tools/ui/shared.py @@ -194,6 +194,8 @@ def draw_common_settings(layout, log_level_only=False, without_box=False): if not log_level_only: sub_layout.prop(_get_scs_globals(), 'config_storage_place') + sub_layout.prop(_get_scs_globals(), 'activate_new_parts') + sub_layout.prop(_get_scs_globals(), 'activate_new_variant_parts') def draw_warning_operator(layout, title, message, text="", icon='ERROR'): diff --git a/addon/io_scs_tools/ui/tool_shelf.py b/addon/io_scs_tools/ui/tool_shelf.py index f4d3e76..a605c48 100644 --- a/addon/io_scs_tools/ui/tool_shelf.py +++ b/addon/io_scs_tools/ui/tool_shelf.py @@ -465,16 +465,19 @@ def draw(self, context): props.vehicle_side = _LT_consts.VehicleSides.FrontRight.name props.aux_color = props.traffic_light_color = "" body_row = body_col.row(align=True) + props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle Left") + props.vehicle_side = _LT_consts.VehicleSides.MiddleLeft.name + props.aux_color = props.traffic_light_color = "" + props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle Right") + props.vehicle_side = _LT_consts.VehicleSides.MiddleRight.name + props.aux_color = props.traffic_light_color = "" + body_row = body_col.row(align=True) props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Rear Left") props.vehicle_side = _LT_consts.VehicleSides.RearLeft.name props.aux_color = props.traffic_light_color = "" props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Rear Right") props.vehicle_side = _LT_consts.VehicleSides.RearRight.name props.aux_color = props.traffic_light_color = "" - body_row = body_col.row(align=True) - props = body_row.operator("mesh.scs_tools_set_lampmask_uv", text="Middle") - props.vehicle_side = _LT_consts.VehicleSides.Middle.name - props.aux_color = props.traffic_light_color = "" body_col = layout.column(align=True) body_row = body_col.row(align=True) From cb43456765fa02d76b7344b8e69819eddf64bf26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 29 Dec 2025 21:11:36 +0100 Subject: [PATCH 56/56] Animation import/export fix * Fixed error when importing/exporting .pia files * Replaced old context property "user_properties" by "properties" --- addon/io_scs_tools/__init__.py | 2 +- addon/io_scs_tools/exp/pia.py | 15 +++-- addon/io_scs_tools/imp/pia.py | 56 +++++++++++-------- .../internals/containers/config.py | 2 +- addon/io_scs_tools/ui/object.py | 2 +- addon/io_scs_tools/unused/pim.py | 2 +- 6 files changed, 47 insertions(+), 32 deletions(-) diff --git a/addon/io_scs_tools/__init__.py b/addon/io_scs_tools/__init__.py index 8fc8e5a..4934c1f 100644 --- a/addon/io_scs_tools/__init__.py +++ b/addon/io_scs_tools/__init__.py @@ -22,7 +22,7 @@ "name": "SCS Tools", "description": "Setup models, Import-Export SCS data format", "author": "Simon Lusenc (50keda), Milos Zajic (4museman), Michal (Michaleczeq)", - "version": (2, 4, "aeadde03", 8), + "version": (2, 4, "aeadde03", 8, 1), "blender": (5, 0, 0), "location": "File > Import-Export", "doc_url": "http://modding.scssoft.com/wiki/Documentation/Tools/SCS_Blender_Tools", diff --git a/addon/io_scs_tools/exp/pia.py b/addon/io_scs_tools/exp/pia.py index 93b83e9..1fd6689 100644 --- a/addon/io_scs_tools/exp/pia.py +++ b/addon/io_scs_tools/exp/pia.py @@ -21,6 +21,7 @@ import os import bpy +from bpy_extras import anim_utils from collections import OrderedDict from mathutils import Vector, Matrix, Euler, Quaternion from io_scs_tools.utils import convert as _convert_utils @@ -31,7 +32,7 @@ from io_scs_tools.internals.containers import pix as _pix_container -def _get_custom_channels(scs_animation, action): +def _get_custom_channels(armature, scs_animation, action): custom_channels = [] frame_start = scs_animation.anim_start frame_end = scs_animation.anim_end @@ -40,8 +41,11 @@ def _get_custom_channels(scs_animation, action): loc_curves = {} # dictionary for storing "location" curves of action + action_slot = armature.animation_data.action_slot + channelbag = anim_utils.action_get_channelbag_for_slot(action, action_slot) + # get curves which are related to moving of armature object - for fcurve in action.fcurves: + for fcurve in channelbag.fcurves: if fcurve.data_path == 'location': loc_curves[fcurve.array_index] = fcurve @@ -103,7 +107,10 @@ def _get_bone_channels(scs_root_obj, armature, scs_animation, action, export_sca curves_per_bone = OrderedDict() # store all the curves we are interested in per bone names for bone in armature.data.bones: - for fcurve in action.fcurves: + action_slot = armature.animation_data.action_slot + channelbag = anim_utils.action_get_channelbag_for_slot(action, action_slot) + + for fcurve in channelbag.fcurves: # check if curve belongs to bone if '["' + bone.name + '"]' in fcurve.data_path: @@ -331,7 +338,7 @@ def export(scs_root_obj, armature, scs_animation, dirpath, name_suffix, skeleton total_time = scs_animation.length action = bpy.data.actions[scs_animation.action] bone_channels = _get_bone_channels(scs_root_obj, armature, scs_animation, action, scs_globals.export_scale) - custom_channels = _get_custom_channels(scs_animation, action) + custom_channels = _get_custom_channels(armature, scs_animation, action) # DATA CREATION header_section = _fill_header_section(scs_animation.name, scs_globals.export_write_signature) diff --git a/addon/io_scs_tools/imp/pia.py b/addon/io_scs_tools/imp/pia.py index 5adb334..4118512 100644 --- a/addon/io_scs_tools/imp/pia.py +++ b/addon/io_scs_tools/imp/pia.py @@ -124,44 +124,44 @@ def _get_anim_channels(pia_container, section_name="BoneChannel"): return channels -def _create_fcurves(anim_action, anim_group, anim_curve, rot_euler=True, types='LocRotSca'): - """Creates animation curves for provided Action / Group (Bone). +def _create_fcurves(channelbag, anim_group, anim_curve, rot_euler=True, types='LocRotSca'): + """Creates animation curves for provided Channelbag / Group (Bone). :return: Tuple of position vector, rotation quaternion and scaling vector :rtype (fcurve, fcurve, fcurve) """ pos_fcurves = rot_fcurves = sca_fcurves = None if 'Loc' in types: - fcurve_pos_x = anim_action.fcurves.new(str(anim_curve + '.location'), index=0) - fcurve_pos_y = anim_action.fcurves.new(str(anim_curve + '.location'), index=1) - fcurve_pos_z = anim_action.fcurves.new(str(anim_curve + '.location'), index=2) + fcurve_pos_x = channelbag.fcurves.new(str(anim_curve + '.location'), index=0) + fcurve_pos_y = channelbag.fcurves.new(str(anim_curve + '.location'), index=1) + fcurve_pos_z = channelbag.fcurves.new(str(anim_curve + '.location'), index=2) fcurve_pos_x.group = anim_group fcurve_pos_y.group = anim_group fcurve_pos_z.group = anim_group pos_fcurves = (fcurve_pos_x, fcurve_pos_y, fcurve_pos_z) if 'Rot' in types: if rot_euler: - fcurve_rot_x = anim_action.fcurves.new(str(anim_curve + '.rotation_euler'), index=0) - fcurve_rot_y = anim_action.fcurves.new(str(anim_curve + '.rotation_euler'), index=1) - fcurve_rot_z = anim_action.fcurves.new(str(anim_curve + '.rotation_euler'), index=2) + fcurve_rot_x = channelbag.fcurves.new(str(anim_curve + '.rotation_euler'), index=0) + fcurve_rot_y = channelbag.fcurves.new(str(anim_curve + '.rotation_euler'), index=1) + fcurve_rot_z = channelbag.fcurves.new(str(anim_curve + '.rotation_euler'), index=2) fcurve_rot_x.group = anim_group fcurve_rot_y.group = anim_group fcurve_rot_z.group = anim_group rot_fcurves = (fcurve_rot_x, fcurve_rot_y, fcurve_rot_z) else: - fcurve_rot_w = anim_action.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=0) - fcurve_rot_x = anim_action.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=1) - fcurve_rot_y = anim_action.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=2) - fcurve_rot_z = anim_action.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=3) + fcurve_rot_w = channelbag.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=0) + fcurve_rot_x = channelbag.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=1) + fcurve_rot_y = channelbag.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=2) + fcurve_rot_z = channelbag.fcurves.new(str(anim_curve + '.rotation_quaternion'), index=3) fcurve_rot_w.group = anim_group fcurve_rot_x.group = anim_group fcurve_rot_y.group = anim_group fcurve_rot_z.group = anim_group rot_fcurves = (fcurve_rot_w, fcurve_rot_x, fcurve_rot_y, fcurve_rot_z) if 'Sca' in types: - fcurve_sca_x = anim_action.fcurves.new(str(anim_curve + '.scale'), index=0) - fcurve_sca_y = anim_action.fcurves.new(str(anim_curve + '.scale'), index=1) - fcurve_sca_z = anim_action.fcurves.new(str(anim_curve + '.scale'), index=2) + fcurve_sca_x = channelbag.fcurves.new(str(anim_curve + '.scale'), index=0) + fcurve_sca_y = channelbag.fcurves.new(str(anim_curve + '.scale'), index=1) + fcurve_sca_z = channelbag.fcurves.new(str(anim_curve + '.scale'), index=2) fcurve_sca_x.group = anim_group fcurve_sca_y.group = anim_group fcurve_sca_z.group = anim_group @@ -259,8 +259,16 @@ def load(root_object, pia_files, armature, pis_filepath=None, bones=None): # CREATE ANIMATION ACTIONS anim_action = bpy.data.actions.new(animation_name + "_action") anim_action.use_fake_user = True + + anim_slot = anim_action.slots.new(id_type='OBJECT', name="SCS Slot") + anim_layer = anim_action.layers.new("AnimLayer") + strip = anim_layer.strips.new(type='KEYFRAME') + channelbag = strip.channelbag(anim_slot, ensure=True) + anim_data = armature.animation_data if armature.animation_data else armature.animation_data_create() anim_data.action = anim_action + anim_data.action_slot = anim_action.slots[0] + # LOAD BONE CHANNELS bone_channels = _get_anim_channels(pia_container, section_name="BoneChannel") @@ -277,7 +285,7 @@ def load(root_object, pia_files, armature, pis_filepath=None, bones=None): streams = bone_channels[bone_name][2] # CREATE ANIMATION GROUP - anim_group = anim_action.groups.new(bone_name) + anim_group = channelbag.groups.new(bone_name) armature.pose.bones[bone_name].rotation_mode = 'XYZ' # Set rotation mode. # use pose bone scale set on PIS import @@ -288,7 +296,7 @@ def load(root_object, pia_files, armature, pis_filepath=None, bones=None): # CREATE FCURVES (pos_fcurves, rot_fcurves, - sca_fcurves) = _create_fcurves(anim_action, anim_group, str('pose.bones["' + bone_name + '"]'), rot_euler=True) + sca_fcurves) = _create_fcurves(channelbag, anim_group, str('pose.bones["' + bone_name + '"]'), rot_euler=True) # GET BONE REST POSITION MATRIX bone_rest_matrix_scs = bones[bone_name][1].transposed() @@ -368,19 +376,19 @@ def load(root_object, pia_files, armature, pis_filepath=None, bones=None): # print(' channel %r - streams %s - keyframes %s' % (channel_name, stream_count, keyframe_count)) # CREATE ANIMATION GROUP - # anim_group = anim_action.groups.new(channel_name) - anim_group = anim_action.groups.new('Location') + # anim_group = channelbag.groups.new(channel_name) + anim_group = channelbag.groups.new('Location') # armature.[channel_name].rotation_mode = 'XYZ' ## Set rotation mode. # active_bone = armature.data.bones[channel_name] # parent_bone = active_bone.parent # CREATE FCURVES - # pos_fcurves, rot_fcurves, sca_fcurves = _create_fcurves(anim_action, anim_group, anim_curve, rot_euler=True, + # pos_fcurves, rot_fcurves, sca_fcurves = _create_fcurves(channelbag, anim_group, anim_curve, rot_euler=True, # types='LocRotSca') - # pos_fcurves, rot_fcurves, sca_fcurves = _create_fcurves(anim_action, anim_group, anim_curve, types='Loc') - fcurve_pos_x = anim_action.fcurves.new('location', index=0) - fcurve_pos_y = anim_action.fcurves.new('location', index=1) - fcurve_pos_z = anim_action.fcurves.new('location', index=2) + # pos_fcurves, rot_fcurves, sca_fcurves = _create_fcurves(channelbag, anim_group, anim_curve, types='Loc') + fcurve_pos_x = channelbag.fcurves.new('location', index=0) + fcurve_pos_y = channelbag.fcurves.new('location', index=1) + fcurve_pos_z = channelbag.fcurves.new('location', index=2) fcurve_pos_x.group = anim_group fcurve_pos_y.group = anim_group fcurve_pos_z.group = anim_group diff --git a/addon/io_scs_tools/internals/containers/config.py b/addon/io_scs_tools/internals/containers/config.py index 93cd373..def0feb 100644 --- a/addon/io_scs_tools/internals/containers/config.py +++ b/addon/io_scs_tools/internals/containers/config.py @@ -316,7 +316,7 @@ def __init__(self): "Source": (str, get_combined_ver_str(), None), "Type": (str, "Configuration", None), "Note": (str, "User settings of SCS Blender Tools", None), - # "Author": (str, bpy.context.user_preferences.system.author, None), + # "Author": (str, bpy.context.preferences.system.author, None), "ConfigStoragePlace": (str, get_default(scs_globals, 'config_storage_place'), 'config_storage_place'), "DumpLevel": (str, get_default(scs_globals, 'dump_level'), 'dump_level') } diff --git a/addon/io_scs_tools/ui/object.py b/addon/io_scs_tools/ui/object.py index 4f2b71c..f0bb0d0 100644 --- a/addon/io_scs_tools/ui/object.py +++ b/addon/io_scs_tools/ui/object.py @@ -1209,7 +1209,7 @@ def draw(self, context): sub = layout_row.row(align=True) sub.scale_x = 10 if not screen.is_animation_playing: - if scene.sync_mode == 'AUDIO_SYNC' and context.user_preferences.system.audio_device == 'JACK': + if scene.sync_mode == 'AUDIO_SYNC' and context.preferences.system.audio_device == 'JACK': sub.operator("screen.animation_play", text="", icon='PLAY') else: sub.operator("screen.animation_play", text="", icon='PLAY_REVERSE').reverse = True diff --git a/addon/io_scs_tools/unused/pim.py b/addon/io_scs_tools/unused/pim.py index 79f04fb..af57a03 100644 --- a/addon/io_scs_tools/unused/pim.py +++ b/addon/io_scs_tools/unused/pim.py @@ -52,7 +52,7 @@ def _fill_header_section(file_name, output_type): if source_filename == "": source_filename = "Unsaved" header_section.props.append(("SourceFilename", source_filename)) - author = bpy.context.user_preferences.system.author + author = bpy.context.preferences.system.author if author: header_section.props.append(("Author", str(author))) return header_section