Skip to content

Instantly share code, notes, and snippets.

@Polynominal
Last active September 29, 2020 17:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Polynominal/2236e2b28d9114e35fcce50eb4dcff54 to your computer and use it in GitHub Desktop.
Save Polynominal/2236e2b28d9114e35fcce50eb4dcff54 to your computer and use it in GitHub Desktop.
simple blender material conversion to urho3d. Currently supports basic shaders only.
// Texture node.py
import bpy
import os
ACCEPTED_FORMATS = [
"BMP",
"PNG",
"TGA",
"JPG",
]
def RepresentsInt(s):
try:
print("try: ",s)
int(s)
return True
except ValueError:
return False
class TextureNode():
def __init__(self,node,cache):
self.node = node #ShaderNodeTexImage
self.cache = cache
image = self.node.image
if image: #save the image
dir = self.cache.absolute_paths["textures"]
if image in self.cache.textures:
pass
else:
extensions = os.path.splitext(image.name)
name = extensions[0]
last = extensions[len(extensions)-1]
if len(last) == 4 and RepresentsInt(last[1]) and RepresentsInt(last[2]) and RepresentsInt(last[3]):
extensions = os.path.splitext(name)
name = extensions[0]
if not (image.file_format in ACCEPTED_FORMATS):
image.file_format = "PNG"
#in the future sort this out by using cache unique name thing.
name = name + "." + image.file_format.lower()
self.name = name
self.filepath = dir + "/" + name
def getRelativePath(self):
return self.cache.relative_paths["textures"] + "/" + self.name
def getAbsolutePath(self):
return self.filepath
def save(self):
image = self.node.image
print("Saving image: ",self.name)
old = image.colorspace_settings.name
image.colorspace_settings.name = "Raw"
image.save_render(self.filepath)
image.colorspace_settings.name = old
//material.py
import bpy
import numbers
import os
from node.texture_node import TextureNode
MAT_TYPES = [
"Principled BSDF",
"Diffuse BSDF",
"Mix Shader"
]
# int TU_DIFFUSE
# int TU_ALBEDOBUFFER
# int TU_NORMAL
# int TU_NORMALBUFFER
# int TU_SPECULAR
# int TU_EMISSIVE
# int TU_ENVIRONMENT
NAME_ORDER = [
"Diffuse",
"Specular",
"Normal",
"Emissive",
]
NAME_MAPPING = {
"Base Color" : "Diffuse",
"Emission" : "Emissive",
"Normal" : "Normal",
"Specular": "Specular"
}
NAME_MAPPING_SHORT = {
"Diffuse" : "Diff",
"Emissive" : "Emissive",
"Specular" : "Spec"
}
# <material>
# <technique name="Techniques/DiffUnlitAlphaGlow.xml" />
# <parameter name="MatDiffColor" value="1 1 1 1"/>
# <textures>
# <TU_DIFFUSE>arcade.png</TU_DIFFUSE>
# </textures>
# <cull value="none" />
# </material>
class Material():
def __init__(self, bpy_mat, cache):
self.cache = cache
self.mat = bpy_mat
self.hasAlpha = False
self.filename = None
self.isSerialized = False
self.surfaceName = None
def getBlendMethod(self): #urho3d blend method
blend_method = self.mat.blend_method
if blend_method == "OPAQUE":
return None
elif blend_method == "CLIP":
return "AlphaMask"
else:
return "Alpha"
#trasparent
def getNodeFromLink(self, dif, socket):
try:
#Get the link that input to 'dif' and 'socket'
link = next( link for link in self.mat.node_tree.links if link.to_node == dif and link.to_socket == socket )
return link.from_node
except:
return None
def getName(self):
return self.mat.name
def parse(self):
textures = {}
colors = {}
#print("--- nodes for " + self.mat.name + " ---")
material_node = None
for name,node in self.mat.node_tree.nodes.items():
if name in MAT_TYPES:
material_node = node
self.surfaceName = name
if material_node:
inputs = material_node.inputs
if self.surfaceName == "Mix Shader":
fac = inputs["Fac"]
link = fac.links[0]
node = link.from_node #ShaderNodeTexImage
textures["Diffuse"] = TextureNode(node, self.cache)
self.hasAlpha = True
self.isUnlit = True
else:
#print("- inputs -")
#for name, model in inputs.items():
# print(name,model)
#print("- -")
for input_name,remaped_name in NAME_MAPPING.items():
target = inputs.get(input_name)
if target:
node = self.getNodeFromLink(material_node,target)
if isinstance(node, bpy.types.ShaderNodeTexImage):
textures[remaped_name] = TextureNode(node, self.cache)
else:
colors[remaped_name] = inputs[input_name].default_value
node = self.getNodeFromLink(material_node,inputs["Alpha"])
if isinstance(node, bpy.types.ShaderNodeTexImage):
self.hasAlpha = True
self.isUnlit = self.mat.shadow_method == "NONE"
self.textures = textures
self.colors = colors
self.surfaceName == "Mix Shader"
self.ambientOcclusion = False
def getTechniqueName(self):
technique = ""
if "Diffuse" in self.textures:
technique = "Diff"
if not self.isUnlit:
if "Normal" in self.textures:
technique = technique + "Normal"
if "Specular" in self.textures:
technique = technique + "Spec"
elif "Specular" in self.textures:
technique = technique + "Spec"
else:
technique = "NoTexture"
if self.isUnlit:
technique = technique + "Unlit"
elif self.ambientOcclusion:
technique = technique + "AO"
if self.hasAlpha:
technique = technique + self.getBlendMethod()
return technique
def convertColorToString(self, color):
if isinstance(color, numbers.Number):
color = [color,color,color,1]
return " ".join(str(v) for v in color)
def getRelativeFileName(self):
dir = self.cache.relative_paths["materials"]
return dir + "/" + self.filename
def serialize(self):
if self.isSerialized:
return
print("serializing material: ",self.getName())
self.isSerialized = True
self.parse()
out = []
out.append("<?xml version=\"1.0\"?>")
out.append("<material>")
out.append(" <technique name=\"Techniques/"+self.getTechniqueName()+".xml\" />")
if not ("Diffuse" in self.colors):
out.append(" <parameter name=\"MatDiffColor\" value=\"1 1 1 1\"/>")
for full_name in NAME_ORDER:
#print(full_name)
#found in colors and in short name mapping, cuz if its not then dont bother.
if full_name in self.colors and full_name in NAME_MAPPING_SHORT:
#print(full_name)
#for name,color in self.colors.items():
# print(name,color)
color = self.colors[full_name]
name = NAME_MAPPING_SHORT[full_name]
color = self.convertColorToString(color)
out.append(" <parameter name=\"Mat"+name+"Color\" value=\""+color+"\"/>")
if self.cache.exportSettings["Textures"]:
out.append(" <textures>")
for name in NAME_ORDER:
if name in self.textures:
textureNode = self.textures[name]
unit = "TU_"+name.upper()
path = textureNode.getRelativePath()
out.append(" <" + unit + ">" + path + "</" + unit + ">")
textureNode.save()
out.append(" </textures>")
#out.append(" <cull value=\"none\" />")
out.append("</material>")
dir = self.cache.absolute_paths["materials"]
name = self.mat.name + ".xml"
fname = dir + "/" + name
self.filename = name
with open(fname, "w") as f:
f.write("\n".join(out))
return out
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment