]> git.tdb.fi Git - libs/gl.git/blob - blender/io_mspgl/material.py
Recognize a certain configuration of nodes as inverting Y for normals
[libs/gl.git] / blender / io_mspgl / material.py
1 import os
2
3 def get_linked_node_and_socket(node_tree, socket):
4         for l in node_tree.links:
5                 if socket==l.to_socket:
6                         return (l.from_node, l.from_socket)
7                 elif socket==l.from_socket:
8                         return (l.to_node, l.to_socket)
9         return (None, None)
10
11 def check_group(node_tree, group, func):
12         output = group.node_tree.nodes["Group Output"]
13         from_node, _ = get_linked_node_and_socket(group.node_tree, output.inputs[0])
14         if from_node:
15                 from_node, _ = func(group.node_tree, from_node)
16                 if from_node and from_node.type=='GROUP_INPUT':
17                         return get_linked_node_and_socket(node_tree, group.inputs[0])
18         return (None, None)
19
20 def check_invert_green(node_tree, node):
21         if node.type=='GROUP':
22                 return check_group(node_tree, node, check_invert_green)
23         elif node.type!='COMBRGB':
24                 return (None, None)
25
26         green, g_sock = get_linked_node_and_socket(node_tree, node.inputs["G"])
27         if not green or green.type!='MATH' or green.operation!='SUBTRACT':
28                 return (None, None)
29         green, g_sock = get_linked_node_and_socket(node_tree, green.inputs[1])
30
31         red, r_sock = get_linked_node_and_socket(node_tree, node.inputs["R"])
32         blue, b_sock = get_linked_node_and_socket(node_tree, node.inputs["B"])
33         if not red or red.type!='SEPRGB' or blue!=red or green!=red:
34                 return (None, None)
35
36         return get_linked_node_and_socket(node_tree, red.inputs["Image"])
37
38 class MaterialProperty:
39         def __init__(self, keyword, tex_keyword, value):
40                 self.keyword = keyword
41                 self.tex_keyword = tex_keyword
42                 self.value = value
43                 self.texture = None
44                 self.tex_usage = None
45                 self.invert_green = False
46
47         def set_from_input(self, node_tree, input_socket, alpha_socket=None):
48                 if self.keyword:
49                         if type(self.value)==tuple:
50                                 if alpha_socket:
51                                         self.value = input_socket.default_value[:len(self.value)-1]+(alpha_socket.default_value,)
52                                 else:
53                                         self.value = input_socket.default_value[:len(self.value)]
54                         else:
55                                 self.value = input_socket.default_value
56
57                 if self.tex_keyword:
58                         from_node, _ = get_linked_node_and_socket(node_tree, input_socket)
59                         alpha_from = None
60                         if from_node:
61                                 usage = None
62                                 if from_node.type=='NORMAL_MAP':
63                                         from_node, _ = get_linked_node_and_socket(node_tree, from_node.inputs["Color"])
64                                         invert, _ = check_invert_green(node_tree, from_node)
65                                         if invert:
66                                                 from_node = invert
67                                                 self.invert_green = True
68                                         usage = 'RGB'
69                                 elif from_node.type=='RGBTOBW':
70                                         from_node, _ = get_linked_node_and_socket(node_tree, from_node.inputs["Color"])
71
72                                 if alpha_socket:
73                                         alpha_from, _ = get_linked_node_and_socket(node_tree, alpha_socket)
74                                         if alpha_from and alpha_from!=from_node:
75                                                 raise Exception("Separate textures for color and alpha are not supported")
76
77                                 if from_node.type=='TEX_IMAGE':
78                                         self.texture = from_node
79                                         if usage:
80                                                 self.tex_usage = usage
81                                         elif alpha_from:
82                                                 self.tex_usage = 'RGBA'
83                                         elif type(self.value)==tuple:
84                                                 self.tex_usage = 'RGB'
85                                         else:
86                                                 self.tex_usage = 'GRAY'
87                                 else:
88                                         raise Exception("Unsupported property input node type "+from_node.type)
89
90 class Material:
91         def __init__(self, material):
92                 self.name = material.name
93                 self.type = None
94                 self.properties = []
95
96                 self.render_mode = material.render_mode
97                 self.technique = material.technique
98                 self.shader = material.shader
99
100                 if self.render_mode=='EXTERNAL' and not self.technique:
101                         raise Exception("Missing technique with external rendering mode")
102                 elif self.render_mode=='CUSTOM' and not self.shader:
103                         raise Exception("Missing shader with custom rendering mode")
104
105                 out_node = None
106                 for n in material.node_tree.nodes:
107                         if n.type=='OUTPUT_MATERIAL':
108                                 out_node = n
109                                 break
110
111                 if not out_node:
112                         raise Exception("No material output node found")
113
114                 surface_node, _ = get_linked_node_and_socket(material.node_tree, out_node.inputs["Surface"])
115                 if not surface_node:
116                         if self.render_mode=='BUILTIN':
117                                 raise Exception("Empty material can't use builtin rendering mode")
118                         return
119                 elif surface_node.type=='BSDF_PRINCIPLED':
120                         self.type = "pbr"
121
122                         base_color = self.create_property("base_color", (0.8, 0.8, 0.8, 1.0))
123                         metalness = self.create_property("metalness", 0.0)
124                         roughness = self.create_property("roughness", 0.5)
125                         normal = self.create_property("normal_map")
126                         emission = self.create_property("emission", (0.0, 0.0, 0.0))
127
128                         base_color.set_from_input(material.node_tree, surface_node.inputs["Base Color"], surface_node.inputs["Alpha"])
129                         metalness.set_from_input(material.node_tree, surface_node.inputs["Metallic"])
130                         roughness.set_from_input(material.node_tree, surface_node.inputs["Roughness"])
131                         normal.set_from_input(material.node_tree, surface_node.inputs["Normal"])
132                         emission.set_from_input(material.node_tree, surface_node.inputs["Emission"])
133                 elif surface_node.type=='EMISSION':
134                         self.type = "unlit"
135
136                         color = self.create_property("color", "texture", (1.0, 1.0, 1.0, 1.0))
137
138                         color.set_from_input(material.node_tree, surface_node.inputs["Color"])
139                 else:
140                         raise Exception("Unsupported surface node type "+surface_node.type)
141
142                 sampler_settings = None
143                 for p in self.properties:
144                         if p.texture:
145                                 settings = (p.texture.default_filter, p.texture.interpolation, p.texture.use_mipmap, p.texture.max_anisotropy)
146                                 if sampler_settings is None:
147                                         sampler_settings = settings
148                                 elif settings!=sampler_settings:
149                                         raise Exception("Conflicting sampler settings in material textures")
150
151         def create_property(self, *args):
152                 prop = None
153                 if len(args)==1:
154                         prop = MaterialProperty(None, args[0], None)
155                 elif len(args)==2:
156                         prop = MaterialProperty(args[0], args[0]+"_map", args[1])
157                 else:
158                         prop = MaterialProperty(*args)
159                 self.properties.append(prop)
160                 return prop
161
162
163 class MaterialAtlas:
164         def __init__(self, materials):
165                 self.render_mode = materials[0].render_mode
166                 if self.render_mode=='EXTERNAL':
167                         raise Exception("Material atlas with external render mode does not make sense")
168
169                 self.shader = materials[0].shader
170                 if self.shader:
171                         self.name = "material_atlas_"+os.path.splitext(self.shader)[0]
172                 else:
173                         self.name = "material_atlas"
174                 self.materials = materials
175                 self.material_names = [m.name for m in self.materials]
176                 for m in self.materials:
177                         if m.render_mode!=self.render_mode:
178                                 raise Exception("Conflicting render modes in MaterialAtlas constructor")
179                         if self.render_mode=='CUSTOM' and m.shader!=self.shader:
180                                 raise Exception("Conflicting shaders in MaterialAtlas constructor")
181
182                 count = len(self.materials)
183                 size = 1
184                 while size*size*2<count:
185                         size *= 2
186                 if size*size>=count:
187                         self.size = (size, size)
188                 else:
189                         self.size = (size*2, size)
190
191                 from .util import get_colormap
192
193                 cm = get_colormap(True)
194                 self.base_color_data = ""
195                 for m in map(Material, self.materials):
196                         if any(p.texture for p in m.properties):
197                                 raise Exception("Texturing is incompatible with material atlas")
198                         base_color = [int(cm(c)*255) for c in m.base_color.value]
199                         self.base_color_data += "\\x{:02X}\\x{:02X}\\x{:02X}\\xFF".format(*base_color)
200                 self.base_color_data += "\\x00\\x00\\x00\\x00"*(self.size[0]*self.size[1]-count)
201
202         def get_material_uv(self, material):
203                 index = self.material_names.index(material.name)
204                 x = index%self.size[0]
205                 y = index//self.size[0]
206                 return ((x+0.5)/self.size[0], (y+0.5)/self.size[1])
207
208 def create_material_atlas(context, material):
209         if not material.material_atlas:
210                 raise Exception("Material is not part of a material atlas")
211
212         shader = material.shader
213         materials = []
214         for m in context.blend_data.materials:
215                 if m.material_atlas and m.shader==shader:
216                         materials.append(m)
217
218         mat_map = MaterialAtlas(materials)
219
220         return mat_map