I have put together a basic MTL file scanning script. (Python 2.7, UE 4.2523).
It will import the images referenced by the newmtl entry, make materials, and connect all nodes. If no image is detected, the diffuse color will be used as the base color.
# MTL File Reader
# (c) 2020 Atom
# Reads the entries of an .MTL file and generates a material linked to the image maps.
# 10/01/20
import unreal
import os, re, platform
def returnValidUnrealMaterialName(passedItem):
# Replace any illegal characters for names here.
s = re.sub("^0-9a-zA-Z\.]+", "_", passedItem)
s = s.replace(".","_")
return s
def importListOfImageMaps(passedList, passedTargetFolder):
lst_texture2D = ]
data = unreal.AutomatedAssetImportData()
data.set_editor_property('destination_path', passedTargetFolder) # Unreal game folder.
data.set_editor_property('filenames', passedList)
lst_texture2D = unreal.AssetToolsHelpers.get_asset_tools().import_assets_automated(data)
return lst_texture2D
def returnImageListByToken(passedLines, passedToken, passedTexturePath=""):
# Collect list of image files associated with this MTL file.
lst_images = ]
for line in lines:
s = line.lstrip() # Remove leading TAB or spacing if present.
ary = s.split(' ') # Split by spaces.
if ary[0] == passedToken:
s = os.path.join(passedTexturePath, ary[1])
lst_images.append(s)
return lst_images
def createNewImageMapOpaqueMaterial(passedAssetPath, passedMaterialName, passedDiffuseTexture, passedNormalTexture, passedDiffuseColor, passedSpec, passedRough):
# Create a material.
assetTools = unreal.AssetToolsHelpers.get_asset_tools()
mf = unreal.MaterialFactoryNew()
mat_closure = assetTools.create_asset("M_%s" % passedMaterialName, passedAssetPath, unreal.Material, mf)
# Make a texture diffuse node.
if passedDiffuseTexture!=None:
# Add an image node.
ts_node_diffuse = unreal.MaterialEditingLibrary.create_material_expression(mat_closure,unreal.MaterialExpressionTextureSample,-384,-200)
ts_node_diffuse.texture = passedDiffuseTexture
unreal.MaterialEditingLibrary.connect_material_property(ts_node_diffuse, "RGBA", unreal.MaterialProperty.MP_BASE_COLOR)
else:
# Add a color constant node.
ts_node_diffuse = unreal.MaterialEditingLibrary.create_material_expression(mat_closure,unreal.MaterialExpressionConstant3Vector,-384,-200)
value = unreal.LinearColor(float(passedDiffuseColor[0]),float(passedDiffuseColor[1]),float(passedDiffuseColor[2]),1.0)
ts_node_diffuse.set_editor_property("constant", value)
unreal.MaterialEditingLibrary.connect_material_property(ts_node_diffuse, "", unreal.MaterialProperty.MP_BASE_COLOR)
'''
# Make a texture normal node.
if passedNormalTexture!=None:
ts_node_normal = unreal.MaterialEditingLibrary.create_material_expression(mat_closure,unreal.MaterialExpressionTextureSample,-384,200)
unreal.MaterialEditingLibrary.connect_material_property(ts_node_normal, "RGB", unreal.MaterialProperty.MP_NORMAL)
# Change this sampler color sample type to work with normal types.
ts_node_normal.sampler_type = unreal.MaterialSamplerType.SAMPLERTYPE_NORMAL
ts_node_normal.texture = passedNormalTexture
'''
# Make a constant float node.
ts_node_roughness = unreal.MaterialEditingLibrary.create_material_expression(mat_closure,unreal.MaterialExpressionConstant,-125,150)
ts_node_roughness.set_editor_property('R', passedRough)
unreal.MaterialEditingLibrary.connect_material_property(ts_node_roughness, "", unreal.MaterialProperty.MP_ROUGHNESS)
# Make a constant float node.
ts_node_specular = unreal.MaterialEditingLibrary.create_material_expression(mat_closure,unreal.MaterialExpressionConstant,-125,50)
ts_node_specular.set_editor_property('R', passedSpec)
unreal.MaterialEditingLibrary.connect_material_property(ts_node_specular, "", unreal.MaterialProperty.MP_SPECULAR)
unreal.MaterialEditingLibrary.recompile_material(mat_closure)
# Program begins here.
asset_path = "/Game"
# Select which disk file to scan.
n = 2
if n==1:
file_path = r'F:\Keep\Models\Blendswap\cc0_medieval_village_seaport'
file_name = "%s\%s" % (file_path, "\27_cc0_medieval_seaport.mtl")
material_path = "%s/MedievalSeaPort/Mats" % asset_path
texture_path = "%s/MedievalSeaPort/Tex" % asset_path
if n==2:
file_path = r'F:\Keep\Models\Blendswap\cc0_14_building_set\obj'
file_name = "%s\%s" % (file_path, "\Building_A02.mtl")
material_path = "%s/BuildingSet/Mats" % asset_path
texture_path = "%s/BuildingSet/Tex" % asset_path
with open(file_name, 'r') as f:
lines = f.read().splitlines() # Remove slash n character at the end of each line.
f.close()
# Collect list of image files associated with this MTL file.
lst_result = returnImageListByToken(lines, "map_Kd", file_path)
lst_diffuse_maps = list(dict.fromkeys(lst_result)) #Remove doubles.
lst_textures_diffuse = importListOfImageMaps(lst_diffuse_maps, texture_path)
lst_result = returnImageListByToken(lines, "map_Kn")
lst_normal_maps = list(dict.fromkeys(lst_result)) #Remove doubles.
lst_textures_normal = importListOfImageMaps(lst_normal_maps, texture_path)
# Defaults
mat_name = ""
material_pending = False
opacity = 1.0
ior = 1.0
reflection_weight = 0.1
reflection_roughness = 0.23
diffuse_color = [0,0,0]
specular_color = [0,0,0]
map_diffuse = ""
map_normal = ""
for line in lines:
s = line.lstrip() # Remove leading TAB or spacing if present.
ary = s.split(' ') # Split by spaces.
if ary[0] == 'newmtl':
if material_pending:
# We encountered a new material, but have a pending one we need to generate.
shader_name = returnValidUnrealMaterialName("M_%s" % mat_name)
print "Detected material %s]" % shader_name
print "diff color%s]." % diffuse_color
print "map diff%s]." % map_diffuse
print "map nor%s]." % map_normal
txture_diffuse = None # If there is no map, a solid color will be used instead.
if len(map_diffuse) > 0:
# Search for map_diffuse texture.
for i,entry in enumerate(lst_diffuse_maps):
if entry.find(map_diffuse)!=-1:
# Found a match, pass texture to material creation routine.
txture_diffuse = lst_textures_diffuse*
break
createNewImageMapOpaqueMaterial(material_path, shader_name, txture_diffuse, None, diffuse_color, 0.92, 0.26)
# Reset defaults.
material_pending = False
opacity = 1.0
ior = 1.0
reflection_weight = 0.1
reflection_roughness = 0.23
diffuse_color = [0,0,0]
specular_color = [0,0,0]
map_diffuse = ""
map_normal = ""
mat_name = ary[1] # Kind of assuming that only one space will be between the token and the value.
material_pending = True
if ary[0] == 'Ks':
specular_color = [ary[1],ary[2],ary[3]]
if ary[0] == 'Kd':
diffuse_color = [ary[1],ary[2],ary[3]]
if ary[0] == 'Ns':
reflection_weight = float(ary[1])/100
if ary[0] == 'Ni':
ior = ary[1]
if ary[0] == 'd':
opacity = ary[1]
if ary[0] == 'map_Kd':
# Found a diffuse map.
map_diffuse = ary[1]
if ary[0] == 'map_Kn':
# Found a normal map.
map_normal = ary[1]
# Write out the last material if it is still pending.
if material_pending:
# We encountered a new material, but have a pending one we need to generate.
shader_name = returnValidUnrealMaterialName("M_%s" % mat_name)
print "Detected material %s]" % shader_name
print "diff color%s]." % diffuse_color
print "map diff%s]." % map_diffuse
print "map nor%s]." % map_normal
txture_diffuse = None # If there is no map, a solid color will be used instead.
if len(map_diffuse) > 0:
# Search for map_diffuse texture.
for i,entry in enumerate(lst_diffuse_maps):
if entry.find(map_diffuse)!=-1:
# Found a match, pass texture to material creation routine.
txture_diffuse = lst_textures_diffuse*
break
createNewImageMapOpaqueMaterial(material_path, shader_name, txture_diffuse, None, diffuse_color, 0.92, 0.26)
print "MTL file scanned."
How quaint, a code board that does not support indentation :<(