diff --git a/addons/io_scene_swbf_msh/__init__.py b/addons/io_scene_swbf_msh/__init__.py index 1bb8331..3ba0636 100644 --- a/addons/io_scene_swbf_msh/__init__.py +++ b/addons/io_scene_swbf_msh/__init__.py @@ -63,8 +63,8 @@ from .msh_material_properties import * from .msh_skeleton_properties import * from .msh_collision_prim_properties import * from .msh_to_blend import * +from .msh_material_operators import * from .zaa_to_blend import * -from .material_props_to_nodes_op import GenerateMaterialFromSWBFProperties class ExportMSH(Operator, ExportHelper): @@ -203,63 +203,6 @@ def menu_func_import(self, context): -class FillSWBFMaterialProperties(bpy.types.Operator): - bl_idname = "swbf_msh.fill_mat_props" - bl_label = "Fill SWBF Material Properties" - bl_description = ("Fill in SWBF properties of all materials used by selected objects.\n" - "Only considers materials that use nodes.\n" - "Please see 'Materials > Materials Operators' in the docs for more details.") - - def execute(self, context): - - slots = sum([list(ob.material_slots) for ob in bpy.context.selected_objects if ob.type == 'MESH'],[]) - mats = [slot.material for slot in slots if (slot.material and slot.material.node_tree)] - - for mat in mats: - try: - for BSDF_node in [n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED']: - base_col = BSDF_node.inputs['Base Color'] - - for link in base_col.links : - link_node = link.from_node - - if link_node.type != 'TEX_IMAGE': - continue - - tex_name = link_node.image.name - - i = tex_name.find(".tga") - - # Get rid of trailing number in case one is present - if i > 0: - tex_name = tex_name[0:i+4] - - mat.swbf_msh_mat.rendertype = 'NORMAL_BF2' - mat.swbf_msh_mat.diffuse_map = tex_name - break - except: - # Many chances for null ref exceptions. None if user reads doc section... - pass - - return {'FINISHED'} - - -class VIEW3D_MT_SWBF(bpy.types.Menu): - bl_label = "SWBF" - - def draw(self, _context): - layout = self.layout - layout.operator("swbf_msh.fill_mat_props", text="Fill SWBF Material Properties") - - -def draw_matfill_menu(self, context): - layout = self.layout - layout.separator() - layout.menu("VIEW3D_MT_SWBF") - - - - def register(): bpy.utils.register_class(CollisionPrimitiveProperties) @@ -283,7 +226,7 @@ def register(): bpy.utils.register_class(VIEW3D_MT_SWBF) bpy.types.VIEW3D_MT_object_context_menu.append(draw_matfill_menu) - bpy.utils.register_class(GenerateMaterialFromSWBFProperties) + bpy.utils.register_class(GenerateMaterialNodesFromSWBFProperties) @@ -307,7 +250,7 @@ def unregister(): bpy.utils.unregister_class(VIEW3D_MT_SWBF) bpy.types.VIEW3D_MT_object_context_menu.remove(draw_matfill_menu) - bpy.utils.unregister_class(GenerateMaterialFromSWBFProperties) + bpy.utils.unregister_class(GenerateMaterialNodesFromSWBFProperties) diff --git a/addons/io_scene_swbf_msh/material_props_to_nodes_op.py b/addons/io_scene_swbf_msh/material_props_to_nodes_op.py deleted file mode 100644 index e863396..0000000 --- a/addons/io_scene_swbf_msh/material_props_to_nodes_op.py +++ /dev/null @@ -1,78 +0,0 @@ -""" Operator for creating/modifying nodes to approximate appearance of SWBF material. - Only relevant if the builtin Eevee renderer is being used. """ - -import bpy -from typing import Dict -from .msh_material import * -from .msh_material_gather import * -from .msh_material_properties import * - -from .msh_material_utilities import _REVERSE_RENDERTYPES_MAPPING - -from math import sqrt - - -from bpy.props import BoolProperty, EnumProperty, StringProperty -from bpy.types import Operator, Menu - - - - - - -class GenerateMaterialFromSWBFProperties(bpy.types.Operator): - - bl_idname = "swbf_msh.generate_material" - bl_label = "Generate Nodes" - bl_description= """Generate Cycles shader nodes from SWBF material properties. - -The nodes generated are meant to give one a general idea -of how the material would look ingame. They cannot -to provide an exact emulation""" - - - material_name: StringProperty( - name = "Material Name", - description = "Name of material whose SWBF properties the generated nodes will emulate." - ) - - - def execute(self, context): - - material = bpy.data.materials[self.material_name] - - - if material and material.swbf_msh_mat: - - mat_props = material.swbf_msh_mat - - material.node_tree.nodes.clear() - - bsdf = material.node_tree.nodes.new("ShaderNodeBsdfPrincipled") - - diffuse_texture_path = mat_props.diffuse_map - if diffuse_texture_path: - texImage = material.node_tree.nodes.new('ShaderNodeTexImage') - texImage.image = bpy.data.images.load(diffuse_texture_path) - texImage.image.alpha_mode = 'CHANNEL_PACKED' - material.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) - - bsdf.inputs["Roughness"].default_value = 1.0 - bsdf.inputs["Specular"].default_value = 0.0 - - if mat_props.hardedged_transparency: - material.blend_method = "CLIP" - material.node_tree.links.new(bsdf.inputs['Alpha'], texImage.outputs['Alpha']) - - material.use_backface_culling = not bool(mat_props.doublesided) - - - output = material.node_tree.nodes.new("ShaderNodeOutputMaterial") - material.node_tree.links.new(output.inputs['Surface'], bsdf.outputs['BSDF']) - - - return {'FINISHED'} - - - - diff --git a/addons/io_scene_swbf_msh/msh_material_operators.py b/addons/io_scene_swbf_msh/msh_material_operators.py new file mode 100644 index 0000000..cd2d8e3 --- /dev/null +++ b/addons/io_scene_swbf_msh/msh_material_operators.py @@ -0,0 +1,137 @@ +""" Operators for basic emulation and mapping of SWBF material system in Blender. + Only relevant if the builtin Eevee renderer is being used! """ + +import bpy + +from .msh_material_properties import * + +from math import sqrt + +from bpy.props import BoolProperty, EnumProperty, StringProperty +from bpy.types import Operator, Menu + + + +# FillSWBFMaterialProperties + +# Iterates through all material slots of all selected +# objects and fills basic SWBF material properties +# from any Principled BSDF nodes it finds. + + +class FillSWBFMaterialProperties(bpy.types.Operator): + bl_idname = "swbf_msh.fill_mat_props" + bl_label = "Fill SWBF Material Properties" + bl_description = ("Fill in SWBF properties of all materials used by selected objects.\n" + "Only considers materials that use nodes.\n" + "Please see 'Materials > Materials Operators' in the docs for more details.") + + def execute(self, context): + + slots = sum([list(ob.material_slots) for ob in bpy.context.selected_objects if ob.type == 'MESH'],[]) + mats = [slot.material for slot in slots if (slot.material and slot.material.node_tree)] + + for mat in mats: + try: + for BSDF_node in [n for n in mat.node_tree.nodes if n.type == 'BSDF_PRINCIPLED']: + base_col = BSDF_node.inputs['Base Color'] + + for link in base_col.links : + link_node = link.from_node + + if link_node.type != 'TEX_IMAGE': + continue + + tex_name = link_node.image.name + + i = tex_name.find(".tga") + + # Get rid of trailing number in case one is present + if i > 0: + tex_name = tex_name[0:i+4] + + mat.swbf_msh_mat.rendertype = 'NORMAL_BF2' + mat.swbf_msh_mat.diffuse_map = tex_name + break + except: + # Many chances for null ref exceptions. None if user reads doc section... + pass + + return {'FINISHED'} + + +class VIEW3D_MT_SWBF(bpy.types.Menu): + bl_label = "SWBF" + + def draw(self, _context): + layout = self.layout + layout.operator("swbf_msh.fill_mat_props", text="Fill SWBF Material Properties") + + +def draw_matfill_menu(self, context): + layout = self.layout + layout.separator() + layout.menu("VIEW3D_MT_SWBF") + + + + +# GenerateMaterialNodesFromSWBFProperties + +# Creates shader nodes to emulate SWBF material properties. +# Will probably only support for a narrow subset of properties... + +class GenerateMaterialNodesFromSWBFProperties(bpy.types.Operator): + + bl_idname = "swbf_msh.generate_material_nodes" + bl_label = "Generate Nodes" + bl_description= """Generate Cycles shader nodes from SWBF material properties. + +The nodes generated are meant to give one a general idea +of how the material would look ingame. They cannot +to provide an exact emulation""" + + + material_name: StringProperty( + name = "Material Name", + description = "Name of material whose SWBF properties the generated nodes will emulate." + ) + + + def execute(self, context): + + material = bpy.data.materials[self.material_name] + + + if material and material.swbf_msh_mat: + + material.use_nodes = True + mat_props = material.swbf_msh_mat + + material.node_tree.nodes.clear() + + bsdf = material.node_tree.nodes.new("ShaderNodeBsdfPrincipled") + + diffuse_texture_path = mat_props.diffuse_map + if diffuse_texture_path: + texImage = material.node_tree.nodes.new('ShaderNodeTexImage') + texImage.image = bpy.data.images.load(diffuse_texture_path) + texImage.image.alpha_mode = 'CHANNEL_PACKED' + material.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) + + bsdf.inputs["Roughness"].default_value = 1.0 + bsdf.inputs["Specular"].default_value = 0.0 + + if mat_props.hardedged_transparency: + material.blend_method = "CLIP" + material.node_tree.links.new(bsdf.inputs['Alpha'], texImage.outputs['Alpha']) + + material.use_backface_culling = not bool(mat_props.doublesided) + + + output = material.node_tree.nodes.new("ShaderNodeOutputMaterial") + material.node_tree.links.new(output.inputs['Surface'], bsdf.outputs['BSDF']) + + + return {'FINISHED'} + diff --git a/addons/io_scene_swbf_msh/msh_material_properties.py b/addons/io_scene_swbf_msh/msh_material_properties.py index a817e61..81d1e06 100644 --- a/addons/io_scene_swbf_msh/msh_material_properties.py +++ b/addons/io_scene_swbf_msh/msh_material_properties.py @@ -9,7 +9,7 @@ from .msh_material_ui_strings import * from .msh_material_utilities import _REVERSE_RENDERTYPES_MAPPING -from .material_props_to_nodes_op import GenerateMaterialFromSWBFProperties +from .msh_material_operators import GenerateMaterialNodesFromSWBFProperties @@ -177,21 +177,25 @@ class MaterialProperties(PropertyGroup): detail_map: StringProperty(name="Detail Map", description="Detail maps allow you to add in 'detail' to the diffuse " "map at runtime. Or they can be used as fake ambient occlusion " - "maps or even wacky emissive maps. See docs for more details.") + "maps or even wacky emissive maps. See docs for more details.", + subtype='FILE_PATH') normal_map: StringProperty(name="Normal Map", description="Normal maps can provide added detail from lighting. " "If Specular is enabled the alpha channel will be " - "the Gloss Map.") + "the Gloss Map.", + subtype='FILE_PATH') environment_map: StringProperty(name="Environment Map", description="Environment map for the material. Provides static " - "reflections around the surface. Must be a cubemap.") + "reflections around the surface. Must be a cubemap.", + subtype='FILE_PATH') distortion_map: StringProperty(name="Distortion Map", description="Distortion maps control how Refractive materials " "distort the scene behind them. Should be a normal map " - "with '-forceformat v8u8' in it's '.tga.option' file.") + "with '-forceformat v8u8' in it's '.tga.option' file.", + subtype='FILE_PATH') # Below props are for yet unsupported render types data_value_0: IntProperty(name="", description="First data value") @@ -199,10 +203,10 @@ class MaterialProperties(PropertyGroup): rendertype_value: IntProperty(name="Rendertype Value", description="Raw number value of rendertype.", min=0, max=31) - texture_0: StringProperty(name="1", description="First texture slot") - texture_1: StringProperty(name="2", description="Second texture slot") - texture_2: StringProperty(name="3", description="Third texture slot") - texture_3: StringProperty(name="4", description="Fourth texture slot") + texture_0: StringProperty(name="1", description="First texture slot", subtype='FILE_PATH', default="white.tga") + texture_1: StringProperty(name="2", description="Second texture slot", subtype='FILE_PATH') + texture_2: StringProperty(name="3", description="Third texture slot", subtype='FILE_PATH') + texture_3: StringProperty(name="4", description="Fourth texture slot", subtype='FILE_PATH') class MaterialPropertiesPanel(bpy.types.Panel): @@ -291,6 +295,6 @@ class MaterialPropertiesPanel(bpy.types.Panel): layout.prop(material_props, "texture_3") - op_props = layout.operator("swbf_msh.generate_material", text="Generate Nodes") + op_props = layout.operator("swbf_msh.generate_material_nodes", text="Generate Nodes") op_props.material_name = context.material.name diff --git a/addons/io_scene_swbf_msh/msh_material_to_blend.py b/addons/io_scene_swbf_msh/msh_material_to_blend.py index 5400953..2e1a73f 100644 --- a/addons/io_scene_swbf_msh/msh_material_to_blend.py +++ b/addons/io_scene_swbf_msh/msh_material_to_blend.py @@ -5,6 +5,7 @@ from typing import Dict from .msh_material import * from .msh_material_gather import * from .msh_material_properties import * +from .msh_material_operators import * from .msh_material_utilities import _REVERSE_RENDERTYPES_MAPPING @@ -33,38 +34,20 @@ def find_texture_path(folder_path : str, name : str) -> str: return "" + def swbf_material_to_blend(material_name : str, material : Material, folder_path : str) -> bpy.types.Material: new_mat = bpy.data.materials.new(name=material_name) - new_mat.use_nodes = True - bsdf = new_mat.node_tree.nodes["Principled BSDF"] - - diffuse_texture_path = find_texture_path(folder_path, material.texture0) - - if diffuse_texture_path: - texImage = new_mat.node_tree.nodes.new('ShaderNodeTexImage') - texImage.image = bpy.data.images.load(diffuse_texture_path) - texImage.image.alpha_mode = 'CHANNEL_PACKED' - new_mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) - - bsdf.inputs["Roughness"].default_value = 1.0 - bsdf.inputs["Specular"].default_value = 0.0 - - if material.flags & MaterialFlags.HARDEDGED_TRANSPARENCY: - new_mat.blend_method = "CLIP" - new_mat.node_tree.links.new(bsdf.inputs['Alpha'], texImage.outputs['Alpha']) - - - new_mat.use_backface_culling = not bool(material.flags & MaterialFlags.DOUBLESIDED) - - - fill_material_props(material, new_mat.swbf_msh_mat, folder_path) + + fill_material_props(material, new_mat.swbf_msh_mat, folder_path) + + bpy.ops.swbf_msh.generate_material_nodes('EXEC_DEFAULT', material_name=new_mat.name) return new_mat -def fill_material_props(material : Material, material_properties, folder_path): +def fill_material_props(material, material_properties, folder_path): """ Fills MaterialProperties from Material instance """ if material_properties is None or material is None: @@ -135,14 +118,19 @@ def _fill_material_props_data(material, material_properties): def _fill_material_props_texture_maps(material, material_properties, folder_path): t0path = find_texture_path(folder_path, material.texture0) + t1path = find_texture_path(folder_path, material.texture1) + t2path = find_texture_path(folder_path, material.texture2) + t3path = find_texture_path(folder_path, material.texture3) material_properties.texture_0 = t0path if t0path else material.texture0 - material_properties.texture_1 = material.texture1 - material_properties.texture_2 = material.texture2 - material_properties.texture_3 = material.texture3 + material_properties.texture_1 = t1path if t1path else material.texture1 + material_properties.texture_2 = t2path if t2path else material.texture2 + material_properties.texture_3 = t3path if t3path else material.texture3 material_properties.diffuse_map = t0path - material_properties.distortion_map = material.texture1 - material_properties.normal_map = material.texture1 - material_properties.detail_map = material.texture2 - material_properties.environment_map = material.texture3 + material_properties.distortion_map = t1path + material_properties.normal_map = t1path + material_properties.detail_map = t2path + material_properties.environment_map = t3path + +