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