Reorg + use generation operator when initially creating Blender materials + make all texture fields in SWBF materials properties paths

This commit is contained in:
William Herald Snyder 2022-09-28 08:47:43 -04:00
parent 8974131550
commit 9bd8479e31
5 changed files with 173 additions and 179 deletions

View File

@ -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)

View File

@ -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'}

View File

@ -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'}

View File

@ -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

View File

@ -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