+ if self.tex_keyword:
+ from .util import get_linked_node_and_socket
+
+ from_node, from_sock = get_linked_node_and_socket(node_tree, input_socket)
+ alpha_from = None
+ if from_node:
+ channels = None
+ top_node = PropertyNode(node_tree, from_node, from_sock)
+ n = top_node
+ while n:
+ if n.type=='NORMAL':
+ channels = ['R', 'G', 'B']
+ elif n.type=='GRAY':
+ channels = ['Y']
+ elif n.type=='EXTRACT':
+ channels = [n.data]
+ elif n.type=='INVERT':
+ channels = ['~'+c if c in n.data else c for c in channels]
+ elif n.type=='SCALE':
+ self.scale = n.data
+ elif n.type=='TEXTURE':
+ self.texture = n.node
+ n = n.input
+
+ if alpha_socket:
+ alpha_from, _ = get_linked_node_and_socket(node_tree, alpha_socket)
+ if alpha_from and alpha_from!=self.texture:
+ raise Exception("Separate textures for color and alpha are not supported")
+
+ if self.scale==0.0 and self.keyword and type(self.value)!=tuple:
+ self.texture = None
+ self.value = self.scale
+ elif self.scale!=1.0:
+ raise Exception("Unsupported material property scale {}".format(self.scale))
+ elif self.texture:
+ if channels:
+ self.tex_channels = channels
+ elif alpha_from:
+ self.tex_channels = ['R', 'G', 'B', 'A']
+ elif type(self.value)==tuple:
+ self.tex_channels = ['R', 'G', 'B']
+ else:
+ self.tex_channels = ['Y']
+ else:
+ raise Exception("Unsupported property input node type "+from_node.type)
+
+class Material:
+ def __init__(self, material):
+ self.name = material.name
+ self.type = None
+ self.properties = []
+
+ self.render_mode = material.render_mode
+ self.technique = material.technique
+ self.render_methods = material.render_methods[:]
+ self.uniforms = material.uniforms[:]
+ self.face_cull = 'BACK' if material.use_backface_culling else 'NONE'
+ self.receive_shadows = material.receive_shadows
+ self.cast_shadows = (material.shadow_method!='NONE')
+ self.blend_type = 'ALPHA' if material.blend_method=='BLEND' else 'NONE'
+ self.alpha_cutoff = material.alpha_threshold if material.blend_method=='CLIP' else 0.0
+ self.image_based_lighting = material.image_based_lighting
+ self.instancing = material.instancing
+
+ if self.render_mode=='EXTERNAL' and not self.technique:
+ raise Exception("Invalid configuration on material {}: No technique for external rendering".format(self.name))
+ elif self.render_mode=='CUSTOM' and not self.render_methods:
+ raise Exception("Invalid configuration on material {}: No render methods for custom rendering".format(self.name))
+
+ out_node = next((n for n in material.node_tree.nodes if n.type=='OUTPUT_MATERIAL'), None)
+ if not out_node:
+ raise Exception("No output node found on material {}".format(self.name))
+
+ from .util import get_linked_node_and_socket
+
+ from_node, from_sock = get_linked_node_and_socket(material.node_tree, out_node.inputs["Surface"])
+ if not from_node:
+ if self.render_mode=='BUILTIN':
+ raise Exception("Invalid configuration on material {}: Empty material with builtin rendering".format(self.name))
+ return
+
+ surface_node = PropertyNode(material.node_tree, from_node, from_sock)
+ if surface_node.type=='ADDITIVE':
+ self.blend_type = 'ADDITIVE'
+ from_node = surface_node.input.node
+
+ if from_node.type=='BSDF_PRINCIPLED':
+ self.type = "pbr"
+
+ base_color = self.create_property("base_color", (0.8, 0.8, 0.8, 1.0))
+ metalness = self.create_property("metalness", 0.0)
+ roughness = self.create_property("roughness", 0.5)
+ normal = self.create_property("normal_map")
+ emission = self.create_property("emission", (0.0, 0.0, 0.0))
+
+ base_color.set_from_input(material.node_tree, from_node.inputs["Base Color"], from_node.inputs["Alpha"])
+ metalness.set_from_input(material.node_tree, from_node.inputs["Metallic"])
+ roughness.set_from_input(material.node_tree, from_node.inputs["Roughness"])
+ normal.set_from_input(material.node_tree, from_node.inputs["Normal"])
+ emission.set_from_input(material.node_tree, from_node.inputs["Emission"])
+ elif from_node.type=='EMISSION' or from_node.type=='MIX_SHADER':
+ color_input, alpha_input = get_unlit_inputs(material.node_tree, from_node, self.blend_type=='ADDITIVE')
+ if not color_input:
+ raise Exception("Unsupported configuration for unlit material {}".format(self.name))
+
+ self.type = "unlit"
+
+ color = self.create_property("color", "texture", (1.0, 1.0, 1.0, 1.0))
+
+ color.set_from_input(material.node_tree, color_input, alpha_input)
+ if self.blend_type=='ADDITIVE' and alpha_input:
+ self.blend_type = 'ADDITIVE_ALPHA'
+ else:
+ raise Exception("Unsupported surface node type {} on material {}".format(from_node.type, self.name))
+
+ sampler_settings = None
+ for p in self.properties:
+ if p.texture:
+ settings = (p.texture.interpolation, p.texture.use_mipmap, p.texture.max_anisotropy)
+ if sampler_settings is None:
+ sampler_settings = settings
+ elif settings!=sampler_settings:
+ raise Exception("Material {} has conflicting texture sampler settings".format(self.name))
+
+ def create_property(self, *args):
+ prop = None
+ if len(args)==1:
+ prop = MaterialProperty(None, args[0], None)
+ elif len(args)==2:
+ prop = MaterialProperty(args[0], args[0]+"_map", args[1])
+ else:
+ prop = MaterialProperty(*args)
+ self.properties.append(prop)
+ return prop