Reorg + use generation operator when initially creating Blender materials + make all texture fields in SWBF materials properties paths
This commit is contained in:
parent
8974131550
commit
9bd8479e31
@ -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)
|
||||
|
||||
|
||||
|
||||
|
@ -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'}
|
||||
|
||||
|
||||
|
||||
|
137
addons/io_scene_swbf_msh/msh_material_operators.py
Normal file
137
addons/io_scene_swbf_msh/msh_material_operators.py
Normal 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'}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user