Cycles: add random walk subsurface scattering to Principled BSDF.
[blender.git] / intern / cycles / blender / addon / version_update.py
1 #
2 # Copyright 2011-2014 Blender Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16
17 # <pep8 compliant>
18
19 import bpy
20
21 from bpy.app.handlers import persistent
22
23
24 def check_is_new_shading_ntree(node_tree):
25     for node in node_tree.nodes:
26         # If material has any node with ONLY new shading system
27         # compatibility then it's considered a Cycles material
28         # and versioning code would need to perform on it.
29         #
30         # We can not check for whether NEW_SHADING in compatibility
31         # because some nodes could have compatibility with both old
32         # and new shading system and they can't be used for any
33         # decision here.
34         if node.shading_compatibility == {'NEW_SHADING'}:
35             return True
36
37         # If node is only compatible with old shading system
38         # then material can not be Cycles material and we
39         # can stopiterating nodes now.
40         if node.shading_compatibility == {'OLD_SHADING'}:
41             return False
42     return False
43
44
45 def check_is_new_shading_material(material):
46     if not material.node_tree:
47         return False
48     return check_is_new_shading_ntree(material.node_tree)
49
50
51 def check_is_new_shading_world(world):
52     if not world.node_tree:
53         return False
54     return check_is_new_shading_ntree(world.node_tree)
55
56
57 def check_is_new_shading_lamp(lamp):
58     if not lamp.node_tree:
59         return False
60     return check_is_new_shading_ntree(lamp.node_tree)
61
62
63 def foreach_notree_node(nodetree, callback, traversed):
64     if nodetree in traversed:
65         return
66     traversed.add(nodetree)
67     for node in nodetree.nodes:
68         callback(node)
69         if node.bl_idname == 'ShaderNodeGroup':
70             foreach_notree_node(node.node_tree, callback, traversed)
71
72
73 def foreach_cycles_node(callback):
74     traversed = set()
75     for material in bpy.data.materials:
76         if check_is_new_shading_material(material):
77                 foreach_notree_node(material.node_tree,
78                                     callback,
79                                     traversed)
80     for world in bpy.data.worlds:
81         if check_is_new_shading_world(world):
82                 foreach_notree_node(world.node_tree,
83                                     callback,
84                                     traversed)
85     for lamp in bpy.data.lamps:
86         if check_is_new_shading_world(lamp):
87                 foreach_notree_node(lamp.node_tree,
88                                     callback,
89                                     traversed)
90
91
92 def displacement_node_insert(material, nodetree, traversed):
93     if nodetree in traversed:
94         return
95     traversed.add(nodetree)
96
97     for node in nodetree.nodes:
98         if node.bl_idname == 'ShaderNodeGroup':
99             displacement_node_insert(material, node.node_tree, traversed)
100
101     # Gather links to replace
102     displacement_links = []
103     for link in nodetree.links:
104         if link.to_node.bl_idname == 'ShaderNodeOutputMaterial' and \
105            link.from_node.bl_idname != 'ShaderNodeDisplacement' and \
106            link.to_socket.identifier == 'Displacement':
107            displacement_links.append(link)
108
109     # Replace links with displacement node
110     for link in displacement_links:
111         from_node = link.from_node
112         from_socket = link.from_socket
113         to_node = link.to_node
114         to_socket = link.to_socket
115
116         nodetree.links.remove(link)
117
118         node = nodetree.nodes.new(type='ShaderNodeDisplacement')
119         node.location[0] = 0.5 * (from_node.location[0] + to_node.location[0]);
120         node.location[1] = 0.5 * (from_node.location[1] + to_node.location[1]);
121         node.inputs['Scale'].default_value = 0.1
122         node.inputs['Midlevel'].default_value = 0.0
123
124         nodetree.links.new(from_socket, node.inputs['Height'])
125         nodetree.links.new(node.outputs['Displacement'], to_socket)
126
127 def displacement_nodes_insert():
128     traversed = set()
129     for material in bpy.data.materials:
130         if check_is_new_shading_material(material):
131             displacement_node_insert(material, material.node_tree, traversed)
132
133 def displacement_principled_nodes(node):
134     if node.bl_idname == 'ShaderNodeDisplacement':
135         if node.space != 'WORLD':
136             node.space = 'OBJECT'
137     if node.bl_idname == 'ShaderNodeBsdfPrincipled':
138         if node.subsurface_method != 'RANDOM_WALK':
139             node.subsurface_method = 'BURLEY'
140
141
142 def mapping_node_order_flip(node):
143     """
144     Flip euler order of mapping shader node
145     """
146     if node.bl_idname == 'ShaderNodeMapping':
147         rot = node.rotation.copy()
148         rot.order = 'ZYX'
149         quat = rot.to_quaternion()
150         node.rotation = quat.to_euler('XYZ')
151
152
153 def vector_curve_node_remap(node):
154     """
155     Remap values of vector curve node from normalized to absolute values
156     """
157     if node.bl_idname == 'ShaderNodeVectorCurve':
158         node.mapping.use_clip = False
159         for curve in node.mapping.curves:
160             for point in curve.points:
161                 point.location.x = (point.location.x * 2.0) - 1.0
162                 point.location.y = (point.location.y - 0.5) * 2.0
163         node.mapping.update()
164
165
166 def custom_bake_remap(scene):
167     """
168     Remap bake types into the new types and set the flags accordingly
169     """
170     bake_lookup = (
171         'COMBINED',
172         'AO',
173         'SHADOW',
174         'NORMAL',
175         'UV',
176         'EMIT',
177         'ENVIRONMENT',
178         'DIFFUSE_DIRECT',
179         'DIFFUSE_INDIRECT',
180         'DIFFUSE_COLOR',
181         'GLOSSY_DIRECT',
182         'GLOSSY_INDIRECT',
183         'GLOSSY_COLOR',
184         'TRANSMISSION_DIRECT',
185         'TRANSMISSION_INDIRECT',
186         'TRANSMISSION_COLOR',
187         'SUBSURFACE_DIRECT',
188         'SUBSURFACE_INDIRECT',
189         'SUBSURFACE_COLOR')
190
191     diffuse_direct_idx = bake_lookup.index('DIFFUSE_DIRECT')
192
193     cscene = scene.cycles
194
195     # Old bake type
196     bake_type_idx = cscene.get("bake_type")
197
198     if bake_type_idx is None:
199         cscene.bake_type = 'COMBINED'
200         return
201
202     # File doesn't need versioning
203     if bake_type_idx < diffuse_direct_idx:
204         return
205
206     # File needs versioning
207     bake_type = bake_lookup[bake_type_idx]
208     cscene.bake_type, end = bake_type.split('_')
209
210     if end == 'DIRECT':
211         scene.render.bake.use_pass_indirect = False
212         scene.render.bake.use_pass_color = False
213
214     elif end == 'INDIRECT':
215         scene.render.bake.use_pass_direct = False
216         scene.render.bake.use_pass_color = False
217
218     elif end == 'COLOR':
219         scene.render.bake.use_pass_direct = False
220         scene.render.bake.use_pass_indirect = False
221
222
223 @persistent
224 def do_versions(self):
225     if bpy.context.user_preferences.version <= (2, 78, 1):
226         prop = bpy.context.user_preferences.addons[__package__].preferences
227         system = bpy.context.user_preferences.system
228         if not prop.is_property_set("compute_device_type"):
229             # Device might not currently be available so this can fail
230             try:
231                 if system.legacy_compute_device_type == 1:
232                     prop.compute_device_type = 'OPENCL'
233                 elif system.legacy_compute_device_type == 2:
234                     prop.compute_device_type = 'CUDA'
235                 else:
236                     prop.compute_device_type = 'NONE'
237             except:
238                 pass
239
240             # Init device list for UI
241             prop.get_devices()
242
243     # We don't modify startup file because it assumes to
244     # have all the default values only.
245     if not bpy.data.is_saved:
246         return
247
248     # Clamp Direct/Indirect separation in 270
249     if bpy.data.version <= (2, 70, 0):
250         for scene in bpy.data.scenes:
251             cscene = scene.cycles
252             sample_clamp = cscene.get("sample_clamp", False)
253             if (sample_clamp and
254                 not cscene.is_property_set("sample_clamp_direct") and
255                 not cscene.is_property_set("sample_clamp_indirect")):
256
257                 cscene.sample_clamp_direct = sample_clamp
258                 cscene.sample_clamp_indirect = sample_clamp
259
260     # Change of Volume Bounces in 271
261     if bpy.data.version <= (2, 71, 0):
262         for scene in bpy.data.scenes:
263             cscene = scene.cycles
264             if not cscene.is_property_set("volume_bounces"):
265                 cscene.volume_bounces = 1
266
267     # Caustics Reflective/Refractive separation in 272
268     if bpy.data.version <= (2, 72, 0):
269         for scene in bpy.data.scenes:
270             cscene = scene.cycles
271             if (cscene.get("no_caustics", False) and
272                 not cscene.is_property_set("caustics_reflective") and
273                 not cscene.is_property_set("caustics_refractive")):
274
275                 cscene.caustics_reflective = False
276                 cscene.caustics_refractive = False
277
278     # Euler order was ZYX in previous versions.
279     if bpy.data.version <= (2, 73, 4):
280         foreach_cycles_node(mapping_node_order_flip)
281
282     if bpy.data.version <= (2, 76, 5):
283         foreach_cycles_node(vector_curve_node_remap)
284
285     # Baking types changed
286     if bpy.data.version <= (2, 76, 6):
287         for scene in bpy.data.scenes:
288             custom_bake_remap(scene)
289
290     # Several default changes for 2.77
291     if bpy.data.version <= (2, 76, 8):
292         for scene in bpy.data.scenes:
293             cscene = scene.cycles
294
295             # Samples
296             if not cscene.is_property_set("samples"):
297                 cscene.samples = 10
298
299             # Preview Samples
300             if not cscene.is_property_set("preview_samples"):
301                 cscene.preview_samples = 10
302
303             # Filter
304             if not cscene.is_property_set("filter_type"):
305                 cscene.pixel_filter_type = 'GAUSSIAN'
306
307             # Tile Order
308             if not cscene.is_property_set("tile_order"):
309                 cscene.tile_order = 'CENTER'
310
311         for lamp in bpy.data.lamps:
312             clamp = lamp.cycles
313
314             # MIS
315             if not clamp.is_property_set("use_multiple_importance_sampling"):
316                 clamp.use_multiple_importance_sampling = False
317
318         for mat in bpy.data.materials:
319             cmat = mat.cycles
320
321             # Volume Sampling
322             if not cmat.is_property_set("volume_sampling"):
323                 cmat.volume_sampling = 'DISTANCE'
324
325     if bpy.data.version <= (2, 76, 9):
326         for world in bpy.data.worlds:
327             cworld = world.cycles
328
329             # World MIS
330             if not cworld.is_property_set("sample_as_light"):
331                 cworld.sample_as_light = False
332
333             # World MIS Samples
334             if not cworld.is_property_set("samples"):
335                 cworld.samples = 4
336
337             # World MIS Resolution
338             if not cworld.is_property_set("sample_map_resolution"):
339                 cworld.sample_map_resolution = 256
340
341     if bpy.data.version <= (2, 76, 10):
342         for scene in bpy.data.scenes:
343             cscene = scene.cycles
344             if cscene.is_property_set("filter_type"):
345                 if not cscene.is_property_set("pixel_filter_type"):
346                     cscene.pixel_filter_type = cscene.filter_type
347                 if cscene.filter_type == 'BLACKMAN_HARRIS':
348                     cscene.filter_type = 'GAUSSIAN'
349
350     if bpy.data.version <= (2, 78, 2):
351         for scene in bpy.data.scenes:
352             cscene = scene.cycles
353             if not cscene.is_property_set("light_sampling_threshold"):
354                 cscene.light_sampling_threshold = 0.0
355
356     if bpy.data.version <= (2, 79, 0):
357         for scene in bpy.data.scenes:
358             cscene = scene.cycles
359             # Default changes
360             if not cscene.is_property_set("aa_samples"):
361                 cscene.aa_samples = 4
362             if not cscene.is_property_set("preview_aa_samples"):
363                 cscene.preview_aa_samples = 4
364             if not cscene.is_property_set("blur_glossy"):
365                 cscene.blur_glossy = 0.0
366             if not cscene.is_property_set("sample_clamp_indirect"):
367                 cscene.sample_clamp_indirect = 0.0
368
369     if bpy.data.version <= (2, 79, 1):
370         displacement_nodes_insert()
371
372     if bpy.data.version <= (2, 79, 2):
373         for mat in bpy.data.materials:
374             cmat = mat.cycles
375             if not cmat.is_property_set("displacement_method"):
376                 cmat.displacement_method = 'BUMP'
377
378         foreach_cycles_node(displacement_principled_nodes)