Cycles: change default displacement method to bump mapping again.
[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 import math
21
22 from bpy.app.handlers import persistent
23
24
25 def check_is_new_shading_ntree(node_tree):
26     for node in node_tree.nodes:
27         # If material has any node with ONLY new shading system
28         # compatibility then it's considered a Cycles material
29         # and versioning code would need to perform on it.
30         #
31         # We can not check for whether NEW_SHADING in compatibility
32         # because some nodes could have compatibility with both old
33         # and new shading system and they can't be used for any
34         # decision here.
35         if node.shading_compatibility == {'NEW_SHADING'}:
36             return True
37
38         # If node is only compatible with old shading system
39         # then material can not be Cycles material and we
40         # can stopiterating nodes now.
41         if node.shading_compatibility == {'OLD_SHADING'}:
42             return False
43     return False
44
45
46 def check_is_new_shading_material(material):
47     if not material.node_tree:
48         return False
49     return check_is_new_shading_ntree(material.node_tree)
50
51
52 def check_is_new_shading_world(world):
53     if not world.node_tree:
54         return False
55     return check_is_new_shading_ntree(world.node_tree)
56
57
58 def check_is_new_shading_lamp(lamp):
59     if not lamp.node_tree:
60         return False
61     return check_is_new_shading_ntree(lamp.node_tree)
62
63
64 def foreach_notree_node(nodetree, callback, traversed):
65     if nodetree in traversed:
66         return
67     traversed.add(nodetree)
68     for node in nodetree.nodes:
69         callback(node)
70         if node.bl_idname == 'ShaderNodeGroup':
71             foreach_notree_node(node.node_tree, callback, traversed)
72
73
74 def foreach_cycles_node(callback):
75     traversed = set()
76     for material in bpy.data.materials:
77         if check_is_new_shading_material(material):
78             foreach_notree_node(
79                 material.node_tree,
80                 callback,
81                 traversed,
82             )
83     for world in bpy.data.worlds:
84         if check_is_new_shading_world(world):
85             foreach_notree_node(
86                 world.node_tree,
87                 callback,
88                 traversed,
89             )
90     for lamp in bpy.data.lamps:
91         if check_is_new_shading_world(lamp):
92             foreach_notree_node(
93                 lamp.node_tree,
94                 callback,
95                 traversed,
96             )
97
98
99 def displacement_node_insert(material, nodetree, traversed):
100     if nodetree in traversed:
101         return
102     traversed.add(nodetree)
103
104     for node in nodetree.nodes:
105         if node.bl_idname == 'ShaderNodeGroup':
106             displacement_node_insert(material, node.node_tree, traversed)
107
108     # Gather links to replace
109     displacement_links = []
110     for link in nodetree.links:
111         if (
112            link.to_node.bl_idname == 'ShaderNodeOutputMaterial' and
113            link.from_node.bl_idname != 'ShaderNodeDisplacement' and
114            link.to_socket.identifier == 'Displacement'
115            ):
116             displacement_links.append(link)
117
118     # Replace links with displacement node
119     for link in displacement_links:
120         from_node = link.from_node
121         from_socket = link.from_socket
122         to_node = link.to_node
123         to_socket = link.to_socket
124
125         nodetree.links.remove(link)
126
127         node = nodetree.nodes.new(type='ShaderNodeDisplacement')
128         node.location[0] = 0.5 * (from_node.location[0] + to_node.location[0])
129         node.location[1] = 0.5 * (from_node.location[1] + to_node.location[1])
130         node.inputs['Scale'].default_value = 0.1
131         node.inputs['Midlevel'].default_value = 0.0
132
133         nodetree.links.new(from_socket, node.inputs['Height'])
134         nodetree.links.new(node.outputs['Displacement'], to_socket)
135
136
137 def displacement_nodes_insert():
138     traversed = set()
139     for material in bpy.data.materials:
140         if check_is_new_shading_material(material):
141             displacement_node_insert(material, material.node_tree, traversed)
142
143
144 def displacement_principled_nodes(node):
145     if node.bl_idname == 'ShaderNodeDisplacement':
146         if node.space != 'WORLD':
147             node.space = 'OBJECT'
148     if node.bl_idname == 'ShaderNodeBsdfPrincipled':
149         if node.subsurface_method != 'RANDOM_WALK':
150             node.subsurface_method = 'BURLEY'
151
152
153 def square_roughness_node_insert(material, nodetree, traversed):
154     if nodetree in traversed:
155         return
156     traversed.add(nodetree)
157
158     roughness_node_types = {
159         'ShaderNodeBsdfAnisotropic',
160         'ShaderNodeBsdfGlass',
161         'ShaderNodeBsdfGlossy',
162         'ShaderNodeBsdfRefraction'}
163
164     # Update default values
165     for node in nodetree.nodes:
166         if node.bl_idname == 'ShaderNodeGroup':
167             square_roughness_node_insert(material, node.node_tree, traversed)
168         elif node.bl_idname in roughness_node_types:
169             roughness_input = node.inputs['Roughness']
170             roughness_input.default_value = math.sqrt(max(roughness_input.default_value, 0.0))
171
172     # Gather roughness links to replace
173     roughness_links = []
174     for link in nodetree.links:
175         if link.to_node.bl_idname in roughness_node_types and \
176            link.to_socket.identifier == 'Roughness':
177             roughness_links.append(link)
178
179     # Replace links with sqrt node
180     for link in roughness_links:
181         from_node = link.from_node
182         from_socket = link.from_socket
183         to_node = link.to_node
184         to_socket = link.to_socket
185
186         nodetree.links.remove(link)
187
188         node = nodetree.nodes.new(type='ShaderNodeMath')
189         node.operation = 'POWER'
190         node.location[0] = 0.5 * (from_node.location[0] + to_node.location[0])
191         node.location[1] = 0.5 * (from_node.location[1] + to_node.location[1])
192
193         nodetree.links.new(from_socket, node.inputs[0])
194         node.inputs[1].default_value = 0.5
195         nodetree.links.new(node.outputs['Value'], to_socket)
196
197
198 def square_roughness_nodes_insert():
199     traversed = set()
200     for material in bpy.data.materials:
201         if check_is_new_shading_material(material):
202             square_roughness_node_insert(material, material.node_tree, traversed)
203
204
205 def mapping_node_order_flip(node):
206     """
207     Flip euler order of mapping shader node
208     """
209     if node.bl_idname == 'ShaderNodeMapping':
210         rot = node.rotation.copy()
211         rot.order = 'ZYX'
212         quat = rot.to_quaternion()
213         node.rotation = quat.to_euler('XYZ')
214
215
216 def vector_curve_node_remap(node):
217     """
218     Remap values of vector curve node from normalized to absolute values
219     """
220     if node.bl_idname == 'ShaderNodeVectorCurve':
221         node.mapping.use_clip = False
222         for curve in node.mapping.curves:
223             for point in curve.points:
224                 point.location.x = (point.location.x * 2.0) - 1.0
225                 point.location.y = (point.location.y - 0.5) * 2.0
226         node.mapping.update()
227
228
229 def custom_bake_remap(scene):
230     """
231     Remap bake types into the new types and set the flags accordingly
232     """
233     bake_lookup = (
234         'COMBINED',
235         'AO',
236         'SHADOW',
237         'NORMAL',
238         'UV',
239         'EMIT',
240         'ENVIRONMENT',
241         'DIFFUSE_DIRECT',
242         'DIFFUSE_INDIRECT',
243         'DIFFUSE_COLOR',
244         'GLOSSY_DIRECT',
245         'GLOSSY_INDIRECT',
246         'GLOSSY_COLOR',
247         'TRANSMISSION_DIRECT',
248         'TRANSMISSION_INDIRECT',
249         'TRANSMISSION_COLOR',
250         'SUBSURFACE_DIRECT',
251         'SUBSURFACE_INDIRECT',
252         'SUBSURFACE_COLOR')
253
254     diffuse_direct_idx = bake_lookup.index('DIFFUSE_DIRECT')
255
256     cscene = scene.cycles
257
258     # Old bake type
259     bake_type_idx = cscene.get("bake_type")
260
261     if bake_type_idx is None:
262         cscene.bake_type = 'COMBINED'
263         return
264
265     # File doesn't need versioning
266     if bake_type_idx < diffuse_direct_idx:
267         return
268
269     # File needs versioning
270     bake_type = bake_lookup[bake_type_idx]
271     cscene.bake_type, end = bake_type.split('_')
272
273     if end == 'DIRECT':
274         scene.render.bake.use_pass_indirect = False
275         scene.render.bake.use_pass_color = False
276
277     elif end == 'INDIRECT':
278         scene.render.bake.use_pass_direct = False
279         scene.render.bake.use_pass_color = False
280
281     elif end == 'COLOR':
282         scene.render.bake.use_pass_direct = False
283         scene.render.bake.use_pass_indirect = False
284
285
286 def ambient_occlusion_node_relink(material, nodetree, traversed):
287     if nodetree in traversed:
288         return
289     traversed.add(nodetree)
290
291     for node in nodetree.nodes:
292         if node.bl_idname == 'ShaderNodeAmbientOcclusion':
293             node.samples = 1
294             node.only_local = False
295             node.inputs['Distance'].default_value = 0.0
296         elif node.bl_idname == 'ShaderNodeGroup':
297             ambient_occlusion_node_relink(material, node.node_tree, traversed)
298
299     # Gather links to replace
300     ao_links = []
301     for link in nodetree.links:
302         if link.from_node.bl_idname == 'ShaderNodeAmbientOcclusion':
303             ao_links.append(link)
304
305     # Replace links
306     for link in ao_links:
307         from_node = link.from_node
308         to_socket = link.to_socket
309
310         nodetree.links.remove(link)
311         nodetree.links.new(from_node.outputs['Color'], to_socket)
312
313
314 def ambient_occlusion_nodes_relink():
315     traversed = set()
316     for material in bpy.data.materials:
317         if check_is_new_shading_material(material):
318             ambient_occlusion_node_relink(material, material.node_tree, traversed)
319
320
321 @persistent
322 def do_versions(self):
323     if bpy.context.user_preferences.version <= (2, 78, 1):
324         prop = bpy.context.user_preferences.addons[__package__].preferences
325         system = bpy.context.user_preferences.system
326         if not prop.is_property_set("compute_device_type"):
327             # Device might not currently be available so this can fail
328             try:
329                 if system.legacy_compute_device_type == 1:
330                     prop.compute_device_type = 'OPENCL'
331                 elif system.legacy_compute_device_type == 2:
332                     prop.compute_device_type = 'CUDA'
333                 else:
334                     prop.compute_device_type = 'NONE'
335             except:
336                 pass
337
338             # Init device list for UI
339             prop.get_devices()
340
341     # We don't modify startup file because it assumes to
342     # have all the default values only.
343     if not bpy.data.is_saved:
344         return
345
346     # Clamp Direct/Indirect separation in 270
347     if bpy.data.version <= (2, 70, 0):
348         for scene in bpy.data.scenes:
349             cscene = scene.cycles
350             sample_clamp = cscene.get("sample_clamp", False)
351             if (
352                 sample_clamp and
353                 not cscene.is_property_set("sample_clamp_direct") and
354                     not cscene.is_property_set("sample_clamp_indirect")
355             ):
356
357                 cscene.sample_clamp_direct = sample_clamp
358                 cscene.sample_clamp_indirect = sample_clamp
359
360     # Change of Volume Bounces in 271
361     if bpy.data.version <= (2, 71, 0):
362         for scene in bpy.data.scenes:
363             cscene = scene.cycles
364             if not cscene.is_property_set("volume_bounces"):
365                 cscene.volume_bounces = 1
366
367     # Caustics Reflective/Refractive separation in 272
368     if bpy.data.version <= (2, 72, 0):
369         for scene in bpy.data.scenes:
370             cscene = scene.cycles
371             if (
372                 cscene.get("no_caustics", False) and
373                 not cscene.is_property_set("caustics_reflective") and
374                 not cscene.is_property_set("caustics_refractive")
375             ):
376                 cscene.caustics_reflective = False
377                 cscene.caustics_refractive = False
378
379     # Euler order was ZYX in previous versions.
380     if bpy.data.version <= (2, 73, 4):
381         foreach_cycles_node(mapping_node_order_flip)
382
383     if bpy.data.version <= (2, 76, 5):
384         foreach_cycles_node(vector_curve_node_remap)
385
386     # Baking types changed
387     if bpy.data.version <= (2, 76, 6):
388         for scene in bpy.data.scenes:
389             custom_bake_remap(scene)
390
391     # Several default changes for 2.77
392     if bpy.data.version <= (2, 76, 8):
393         for scene in bpy.data.scenes:
394             cscene = scene.cycles
395
396             # Samples
397             if not cscene.is_property_set("samples"):
398                 cscene.samples = 10
399
400             # Preview Samples
401             if not cscene.is_property_set("preview_samples"):
402                 cscene.preview_samples = 10
403
404             # Filter
405             if not cscene.is_property_set("filter_type"):
406                 cscene.pixel_filter_type = 'GAUSSIAN'
407
408             # Tile Order
409             if not cscene.is_property_set("tile_order"):
410                 cscene.tile_order = 'CENTER'
411
412         for lamp in bpy.data.lamps:
413             clamp = lamp.cycles
414
415             # MIS
416             if not clamp.is_property_set("use_multiple_importance_sampling"):
417                 clamp.use_multiple_importance_sampling = False
418
419         for mat in bpy.data.materials:
420             cmat = mat.cycles
421
422             # Volume Sampling
423             if not cmat.is_property_set("volume_sampling"):
424                 cmat.volume_sampling = 'DISTANCE'
425
426     if bpy.data.version <= (2, 76, 9):
427         for world in bpy.data.worlds:
428             cworld = world.cycles
429
430             # World MIS Samples
431             if not cworld.is_property_set("samples"):
432                 cworld.samples = 4
433
434             # World MIS Resolution
435             if not cworld.is_property_set("sample_map_resolution"):
436                 cworld.sample_map_resolution = 256
437
438     if bpy.data.version <= (2, 76, 10):
439         for scene in bpy.data.scenes:
440             cscene = scene.cycles
441             if cscene.is_property_set("filter_type"):
442                 if not cscene.is_property_set("pixel_filter_type"):
443                     cscene.pixel_filter_type = cscene.filter_type
444                 if cscene.filter_type == 'BLACKMAN_HARRIS':
445                     cscene.filter_type = 'GAUSSIAN'
446
447     if bpy.data.version <= (2, 78, 2):
448         for scene in bpy.data.scenes:
449             cscene = scene.cycles
450             if not cscene.is_property_set("light_sampling_threshold"):
451                 cscene.light_sampling_threshold = 0.0
452
453     if bpy.data.version <= (2, 79, 0):
454         for scene in bpy.data.scenes:
455             cscene = scene.cycles
456             # Default changes
457             if not cscene.is_property_set("aa_samples"):
458                 cscene.aa_samples = 4
459             if not cscene.is_property_set("preview_aa_samples"):
460                 cscene.preview_aa_samples = 4
461             if not cscene.is_property_set("blur_glossy"):
462                 cscene.blur_glossy = 0.0
463             if not cscene.is_property_set("sample_clamp_indirect"):
464                 cscene.sample_clamp_indirect = 0.0
465
466     if bpy.data.version <= (2, 79, 1):
467         displacement_nodes_insert()
468
469     if bpy.data.version <= (2, 79, 2):
470         for mat in bpy.data.materials:
471             cmat = mat.cycles
472             if not cmat.is_property_set("displacement_method"):
473                 cmat.displacement_method = 'BUMP'
474
475         foreach_cycles_node(displacement_principled_nodes)
476
477     if bpy.data.version <= (2, 79, 3):
478         # Switch to squared roughness convention
479         square_roughness_nodes_insert()
480
481     if bpy.data.version <= (2, 79, 4):
482         for world in bpy.data.worlds:
483             cworld = world.cycles
484             # World MIS
485             if not cworld.is_property_set("sampling_method"):
486                 if cworld.get("sample_as_light", True):
487                     cworld.sampling_method = 'MANUAL'
488                 else:
489                     cworld.sampling_method = 'NONE'
490
491         ambient_occlusion_nodes_relink()
492
493     if bpy.data.version <= (2, 79, 6):
494         # Change default to bump again.
495         for mat in bpy.data.materials:
496             cmat = mat.cycles
497             if not cmat.is_property_set("displacement_method"):
498                 cmat.displacement_method = 'DISPLACEMENT'