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