I put together a short script that scans a folder and creates a mesh for each image. It links the image to a material and assigns the material to the mesh. It stacks up your images in z-space.
# Images As Planes, for the Unreal Editor
# Author: Atom
# This script will scan a folder for image files with a specific file extenstion.
# A companion mesh will be generated for each file found in the folder.
# A material will be assigned to the mesh linking the image.
# (c) 2020 Atom
# 10/17/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
# Crude width height detection for now.
# Only supports GIFs, PNGs, and JPGs.
# Alternately install PIL or dimensions.
# Or maybe someone can demonstrate how to get the width/height out of an image map?
def get_image_size(file_path):
"""
Return (width, height) for a given img file content - no external
dependencies except the os and struct modules from core
"""
size = os.path.getsize(file_path)
#print size
with open(file_path) as input:
height = -1
width = -1
data = input.read(25)
#print data
if (size >= 10) and data:6] in ('GIF87a', 'GIF89a'):
# GIFs
w, h = struct.unpack("<HH", data[6:10])
width = int(w)
height = int(h)
elif ((size >= 24) and data.startswith('\211PNG
\032
')
and (data[12:16] == 'IHDR')):
# PNGs
w, h = struct.unpack(">LL", data[16:24])
width = int(w)
size = os.path.getsize(file_path)
# Weird?
with open(file_path) as input:
height = -1
width = -1
data = input.read(25)
print data
height = int(h)
elif (size >= 16) and data.startswith('\211PNG
\032
'):
# older PNGs?
w, h = struct.unpack(">LL", data[8:16])
width = int(w)
height = int(h)
elif (size >= 2) and data.startswith('\377\330'):
# JPEG
msg = " raised while trying to decode as JPEG."
input.seek(0)
input.read(2)
b = input.read(1)
try:
while (b and ord(b) != 0xDA):
while (ord(b) != 0xFF): b = input.read(1)
while (ord(b) == 0xFF): b = input.read(1)
if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
input.read(3)
h, w = struct.unpack(">HH", input.read(4))
break
else:
input.read(int(struct.unpack(">H", input.read(2))[0])-2)
b = input.read(1)
width = int(w)
height = int(h)
except:
# Unsupported jpeg file type.
print "Unsupported JPG type."
pass
else:
# Unsupported file extension, extend to suport more file types.
print "Unsupported file extension."
pass
return width, height
def returnFilesLike(passedFolderName, passedFileExtension = ".jpg"):
result = ]
#print passedFolderName
for file in os.listdir(passedFolderName):
if file.endswith(passedFileExtension):
result.append(os.path.join(passedFolderName,file))
return result
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 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)
return mat_closure
def imagesToPlanes (passedPath, passedType, passedMaterialPath, passedTexturePath):
lst_files = returnFilesLike(passedPath, passedType)
if len(lst_files):
as_cube = True
# Collect list of image files associated with this folder.
lst_textures = importListOfImageMaps(lst_files, texture_path) #should match the length of list_files.
for i,file in enumerate(lst_files):
depth = i * 25.0
thickness = 0.002
w,h = get_image_size(file)
# Create a map file asset for each image in our list.
local_name = os.path.basename(file)
local_name_only = os.path.splitext(local_name)[0]
shader_name = returnValidUnrealMaterialName("%s" % local_name_only)
mesh_name = returnValidUnrealMaterialName("SM_%s" % local_name_only)
# Defaults for new material.
reflection_weight = 0.1
reflection_roughness = 0.23
diffuse_color = [0.18,0.18,0.18]
temp_material = createNewImageMapOpaqueMaterial(passedMaterialPath, shader_name, lst_textures*, None, diffuse_color, reflection_weight, reflection_roughness)
# Make a static mesh plane.
ell = unreal.EditorLevelLibrary
eal = unreal.EditorAssetLibrary
mesh_actor = ell.spawn_actor_from_class(unreal.StaticMeshActor.static_class(), unreal.Vector(depth, 0, 100), unreal.Rotator(0, 0, 0))
mesh_actor.set_actor_label(mesh_name)
mesh_comp = mesh_actor.get_component_by_class(unreal.StaticMeshComponent.static_class())
if as_cube:
temp_mesh = eal.load_asset("StaticMesh'/Engine/BasicShapes/Cube.Cube'") # Assume asset is created at default scale of 1.0.
else:
temp_mesh = eal.load_asset("StaticMesh'/Engine/BasicShapes/Plane.Plane'") # Assume asset is created at default scale of 1.0.
mesh_comp.set_static_mesh(temp_mesh)
mesh_comp.set_editor_property("override_materials", [temp_material])
if w != -1:
# Guestimate from brief file header review.
mesh_comp.set_editor_property("relative_scale3d", unreal.Vector(thickness, w*0.001, h*0.001))
else:
# Default to 16:9.
mesh_comp.set_editor_property("relative_scale3d", unreal.Vector(thickness, 1.6, 0.9)) # Set the aspect ratio of image here
else:
print"This combination %s].%s] produces no results." % (passedPath, passedType)
# Program begins here.
asset_path = "/Game"
# Select which disk folder to scan.
n = 2
if n==1:
folder_path = r'F:\Keep\Maps\Bullet_Holes'
extension = ".png"
material_path = "%s/Bullet_Holes/Mats" % asset_path
texture_path = "%s/Bullet_Holes/Tex" % asset_path
if n==2:
folder_path = r'F:\Keep\Maps\fur'
extension = ".jpg"
material_path = "%s/Fur/Mats" % asset_path
texture_path = "%s/Fur/Tex" % asset_path
imagesToPlanes (folder_path,extension,material_path,texture_path)
print "Images To Planes complete."