]> git.tdb.fi Git - libs/gl.git/blob - blender/io_mspgl/material.py
Remove default sampler from Texture
[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                 self.image_based_lighting = material.image_based_lighting
100
101                 if self.render_mode=='EXTERNAL' and not self.technique:
102                         raise Exception("Invalid configuration on material {}: No technique for external rendering".format(self.name))
103                 elif self.render_mode=='CUSTOM' and not self.shader:
104                         raise Exception("Invalid configuration on material {}: No shader for custom rendering".format(self.name))
105
106                 out_node = next((n for n in material.node_tree.nodes if n.type=='OUTPUT_MATERIAL'), None)
107                 if not out_node:
108                         raise Exception("No output node found on material {}".format(self.name))
109
110                 from .util import get_linked_node_and_socket
111
112                 surface_node, _ = get_linked_node_and_socket(material.node_tree, out_node.inputs["Surface"])
113                 if not surface_node:
114                         if self.render_mode=='BUILTIN':
115                                 raise Exception("Invalid configuration on material {}: Empty material with builtin rendering".format(self.name))
116                         return
117                 elif surface_node.type=='BSDF_PRINCIPLED':
118                         self.type = "pbr"
119
120                         base_color = self.create_property("base_color", (0.8, 0.8, 0.8, 1.0))
121                         metalness = self.create_property("metalness", 0.0)
122                         roughness = self.create_property("roughness", 0.5)
123                         normal = self.create_property("normal_map")
124                         emission = self.create_property("emission", (0.0, 0.0, 0.0))
125
126                         base_color.set_from_input(material.node_tree, surface_node.inputs["Base Color"], surface_node.inputs["Alpha"])
127                         metalness.set_from_input(material.node_tree, surface_node.inputs["Metallic"])
128                         roughness.set_from_input(material.node_tree, surface_node.inputs["Roughness"])
129                         normal.set_from_input(material.node_tree, surface_node.inputs["Normal"])
130                         emission.set_from_input(material.node_tree, surface_node.inputs["Emission"])
131                 elif surface_node.type=='EMISSION':
132                         self.type = "unlit"
133
134                         color = self.create_property("color", "texture", (1.0, 1.0, 1.0, 1.0))
135
136                         color.set_from_input(material.node_tree, surface_node.inputs["Color"])
137                 else:
138                         raise Exception("Unsupported surface node type {} on material {}".format(surface_node.type, self.name))
139
140                 sampler_settings = None
141                 for p in self.properties:
142                         if p.texture:
143                                 settings = (p.texture.interpolation, p.texture.use_mipmap, p.texture.max_anisotropy)
144                                 if sampler_settings is None:
145                                         sampler_settings = settings
146                                 elif settings!=sampler_settings:
147                                         raise Exception("Material {} has conflicting texture sampler settings".format(self.name))
148
149         def create_property(self, *args):
150                 prop = None
151                 if len(args)==1:
152                         prop = MaterialProperty(None, args[0], None)
153                 elif len(args)==2:
154                         prop = MaterialProperty(args[0], args[0]+"_map", args[1])
155                 else:
156                         prop = MaterialProperty(*args)
157                 self.properties.append(prop)
158                 return prop
159
160
161 class MaterialAtlas:
162         def __init__(self, materials):
163                 self.render_mode = materials[0].render_mode
164                 if self.render_mode=='EXTERNAL':
165                         raise Exception("Material atlas with external render mode does not make sense")
166
167                 self.shader = materials[0].shader
168                 if self.shader:
169                         self.name = "material_atlas_"+os.path.splitext(self.shader)[0]
170                 else:
171                         self.name = "material_atlas"
172                 self.receive_shadows = materials[0].receive_shadows
173                 self.cast_shadows = (materials[0].shadow_method!='NONE')
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                         if m.receive_shadows!=self.receive_shadows or m.shadow_method!=materials[0].shadow_method:
182                                 raise Exception("Conflicting shadow settings in MaterialAtlas constructor")
183
184                 count = len(self.materials)
185                 size = 1
186                 while size*size*2<count:
187                         size *= 2
188                 if size*size>=count:
189                         self.size = (size, size)
190                 else:
191                         self.size = (size*2, size)
192
193                 from .util import get_colormap
194
195                 cm = get_colormap(True)
196                 self.base_color_data = ""
197                 for m in map(Material, self.materials):
198                         if any(p.texture for p in m.properties):
199                                 raise Exception("Texturing is incompatible with material atlas")
200                         base_color = [int(cm(c)*255) for c in m.base_color.value]
201                         self.base_color_data += "\\x{:02X}\\x{:02X}\\x{:02X}\\xFF".format(*base_color)
202                 self.base_color_data += "\\x00\\x00\\x00\\x00"*(self.size[0]*self.size[1]-count)
203
204         def get_material_uv(self, material):
205                 index = self.material_names.index(material.name)
206                 x = index%self.size[0]
207                 y = index//self.size[0]
208                 return ((x+0.5)/self.size[0], (y+0.5)/self.size[1])
209
210 def create_material_atlas(context, material):
211         if not material.material_atlas:
212                 raise Exception("Material is not part of a material atlas")
213
214         shader = material.shader
215         materials = []
216         for m in context.blend_data.materials:
217                 if m.material_atlas and m.shader==shader:
218                         materials.append(m)
219
220         return MaterialAtlas(materials)