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