Fix T54504: Cycles wrong backwards compatibility with linked libraries.

The code assumed all datablocks were read from .blend files saved with the
same version. This restructures the Cycles versioning code to take into
account libraries.
This commit is contained in:
Brecht Van Lommel 2019-02-17 12:27:07 +01:00
parent d8293fd6be
commit 8a97b85555
2 changed files with 194 additions and 232 deletions

@ -22,89 +22,39 @@ import math
from bpy.app.handlers import persistent
def check_is_new_shading_ntree(node_tree):
for node in node_tree.nodes:
# If material has any node with ONLY new shading system
# compatibility then it's considered a Cycles material
# and versioning code would need to perform on it.
#
# We can not check for whether NEW_SHADING in compatibility
# because some nodes could have compatibility with both old
# and new shading system and they can't be used for any
# decision here.
if node.shading_compatibility == {'NEW_SHADING'}:
return True
# If node is only compatible with old shading system
# then material can not be Cycles material and we
# can stopiterating nodes now.
if node.shading_compatibility == {'OLD_SHADING'}:
return False
return False
def check_is_new_shading_material(material):
if not material.node_tree:
return False
return check_is_new_shading_ntree(material.node_tree)
def check_is_new_shading_world(world):
if not world.node_tree:
return False
return check_is_new_shading_ntree(world.node_tree)
def check_is_new_shading_lamp(lamp):
if not lamp.node_tree:
return False
return check_is_new_shading_ntree(lamp.node_tree)
def foreach_notree_node(nodetree, callback, traversed):
if nodetree in traversed:
return
traversed.add(nodetree)
def foreach_cycles_nodetree_group(nodetree, traversed):
for node in nodetree.nodes:
callback(node)
if node.bl_idname == 'ShaderNodeGroup':
foreach_notree_node(node.node_tree, callback, traversed)
group = node.node_tree
if group and group not in traversed:
traversed.add(group)
yield group, group.library
yield from foreach_cycles_nodetree_group(group, traversed)
def foreach_cycles_node(callback):
def foreach_cycles_nodetree():
traversed = set()
for material in bpy.data.materials:
if check_is_new_shading_material(material):
foreach_notree_node(
material.node_tree,
callback,
traversed,
)
nodetree = material.node_tree
if nodetree:
yield nodetree, material.library
yield from foreach_cycles_nodetree_group(nodetree, traversed)
for world in bpy.data.worlds:
if check_is_new_shading_world(world):
foreach_notree_node(
world.node_tree,
callback,
traversed,
)
nodetree = world.node_tree
if nodetree:
yield nodetree, world.library
foreach_cycles_nodetree_group(nodetree, traversed)
for lamp in bpy.data.lamps:
if check_is_new_shading_world(lamp):
foreach_notree_node(
lamp.node_tree,
callback,
traversed,
)
nodetree = lamp.node_tree
if nodetree:
yield nodetree, lamp.library
foreach_cycles_nodetree_group(nodetree, traversed)
def displacement_node_insert(material, nodetree, traversed):
if nodetree in traversed:
return
traversed.add(nodetree)
for node in nodetree.nodes:
if node.bl_idname == 'ShaderNodeGroup':
displacement_node_insert(material, node.node_tree, traversed)
def displacement_node_insert(nodetree):
# Gather links to replace
displacement_links = []
for link in nodetree.links:
@ -134,12 +84,6 @@ def displacement_node_insert(material, nodetree, traversed):
nodetree.links.new(node.outputs['Displacement'], to_socket)
def displacement_nodes_insert():
traversed = set()
for material in bpy.data.materials:
if check_is_new_shading_material(material):
displacement_node_insert(material, material.node_tree, traversed)
def displacement_principled_nodes(node):
if node.bl_idname == 'ShaderNodeDisplacement':
@ -150,11 +94,7 @@ def displacement_principled_nodes(node):
node.subsurface_method = 'BURLEY'
def square_roughness_node_insert(material, nodetree, traversed):
if nodetree in traversed:
return
traversed.add(nodetree)
def square_roughness_node_insert(nodetree):
roughness_node_types = {
'ShaderNodeBsdfAnisotropic',
'ShaderNodeBsdfGlass',
@ -163,9 +103,7 @@ def square_roughness_node_insert(material, nodetree, traversed):
# Update default values
for node in nodetree.nodes:
if node.bl_idname == 'ShaderNodeGroup':
square_roughness_node_insert(material, node.node_tree, traversed)
elif node.bl_idname in roughness_node_types:
if node.bl_idname in roughness_node_types:
roughness_input = node.inputs['Roughness']
roughness_input.default_value = math.sqrt(max(roughness_input.default_value, 0.0))
@ -195,13 +133,6 @@ def square_roughness_node_insert(material, nodetree, traversed):
nodetree.links.new(node.outputs['Value'], to_socket)
def square_roughness_nodes_insert():
traversed = set()
for material in bpy.data.materials:
if check_is_new_shading_material(material):
square_roughness_node_insert(material, material.node_tree, traversed)
def mapping_node_order_flip(node):
"""
Flip euler order of mapping shader node
@ -283,18 +214,12 @@ def custom_bake_remap(scene):
scene.render.bake.use_pass_indirect = False
def ambient_occlusion_node_relink(material, nodetree, traversed):
if nodetree in traversed:
return
traversed.add(nodetree)
def ambient_occlusion_node_relink(nodetree):
for node in nodetree.nodes:
if node.bl_idname == 'ShaderNodeAmbientOcclusion':
node.samples = 1
node.only_local = False
node.inputs['Distance'].default_value = 0.0
elif node.bl_idname == 'ShaderNodeGroup':
ambient_occlusion_node_relink(material, node.node_tree, traversed)
# Gather links to replace
ao_links = []
@ -311,13 +236,6 @@ def ambient_occlusion_node_relink(material, nodetree, traversed):
nodetree.links.new(from_node.outputs['Color'], to_socket)
def ambient_occlusion_nodes_relink():
traversed = set()
for material in bpy.data.materials:
if check_is_new_shading_material(material):
ambient_occlusion_node_relink(material, material.node_tree, traversed)
@persistent
def do_versions(self):
if bpy.context.user_preferences.version <= (2, 78, 1):
@ -343,9 +261,25 @@ def do_versions(self):
if not bpy.data.is_saved:
return
# Clamp Direct/Indirect separation in 270
if bpy.data.version <= (2, 70, 0):
# Map of versions used by libraries.
library_versions = {}
library_versions[bpy.data.version] = [None]
for library in bpy.data.libraries:
library_versions.setdefault(library.version, []).append(library)
# Do versioning per library, since they might have different versions.
max_need_versioning = (2, 79, 6)
for version, libraries in library_versions.items():
if version > max_need_versioning:
continue
# Scenes
for scene in bpy.data.scenes:
if scene.library not in libraries:
continue
# Clamp Direct/Indirect separation in 270
if version <= (2, 70, 0):
cscene = scene.cycles
sample_clamp = cscene.get("sample_clamp", False)
if (
@ -358,15 +292,13 @@ def do_versions(self):
cscene.sample_clamp_indirect = sample_clamp
# Change of Volume Bounces in 271
if bpy.data.version <= (2, 71, 0):
for scene in bpy.data.scenes:
if version <= (2, 71, 0):
cscene = scene.cycles
if not cscene.is_property_set("volume_bounces"):
cscene.volume_bounces = 1
# Caustics Reflective/Refractive separation in 272
if bpy.data.version <= (2, 72, 0):
for scene in bpy.data.scenes:
if version <= (2, 72, 0):
cscene = scene.cycles
if (
cscene.get("no_caustics", False) and
@ -376,21 +308,12 @@ def do_versions(self):
cscene.caustics_reflective = False
cscene.caustics_refractive = False
# Euler order was ZYX in previous versions.
if bpy.data.version <= (2, 73, 4):
foreach_cycles_node(mapping_node_order_flip)
if bpy.data.version <= (2, 76, 5):
foreach_cycles_node(vector_curve_node_remap)
# Baking types changed
if bpy.data.version <= (2, 76, 6):
for scene in bpy.data.scenes:
if version <= (2, 76, 6):
custom_bake_remap(scene)
# Several default changes for 2.77
if bpy.data.version <= (2, 76, 8):
for scene in bpy.data.scenes:
if version <= (2, 76, 8):
cscene = scene.cycles
# Samples
@ -409,34 +332,7 @@ def do_versions(self):
if not cscene.is_property_set("tile_order"):
cscene.tile_order = 'CENTER'
for lamp in bpy.data.lamps:
clamp = lamp.cycles
# MIS
if not clamp.is_property_set("use_multiple_importance_sampling"):
clamp.use_multiple_importance_sampling = False
for mat in bpy.data.materials:
cmat = mat.cycles
# Volume Sampling
if not cmat.is_property_set("volume_sampling"):
cmat.volume_sampling = 'DISTANCE'
if bpy.data.version <= (2, 76, 9):
for world in bpy.data.worlds:
cworld = world.cycles
# World MIS Samples
if not cworld.is_property_set("samples"):
cworld.samples = 4
# World MIS Resolution
if not cworld.is_property_set("sample_map_resolution"):
cworld.sample_map_resolution = 256
if bpy.data.version <= (2, 76, 10):
for scene in bpy.data.scenes:
if version <= (2, 76, 10):
cscene = scene.cycles
if cscene.is_property_set("filter_type"):
if not cscene.is_property_set("pixel_filter_type"):
@ -444,14 +340,12 @@ def do_versions(self):
if cscene.filter_type == 'BLACKMAN_HARRIS':
cscene.filter_type = 'GAUSSIAN'
if bpy.data.version <= (2, 78, 2):
for scene in bpy.data.scenes:
if version <= (2, 78, 2):
cscene = scene.cycles
if not cscene.is_property_set("light_sampling_threshold"):
cscene.light_sampling_threshold = 0.0
if bpy.data.version <= (2, 79, 0):
for scene in bpy.data.scenes:
if version <= (2, 79, 0):
cscene = scene.cycles
# Default changes
if not cscene.is_property_set("aa_samples"):
@ -463,23 +357,35 @@ def do_versions(self):
if not cscene.is_property_set("sample_clamp_indirect"):
cscene.sample_clamp_indirect = 0.0
if bpy.data.version <= (2, 79, 1):
displacement_nodes_insert()
# Lamps
for lamp in bpy.data.lamps:
if lamp.library not in libraries:
continue
if bpy.data.version <= (2, 79, 2):
for mat in bpy.data.materials:
cmat = mat.cycles
if not cmat.is_property_set("displacement_method"):
cmat.displacement_method = 'BUMP'
if version <= (2, 76, 5):
clamp = lamp.cycles
foreach_cycles_node(displacement_principled_nodes)
# MIS
if not clamp.is_property_set("use_multiple_importance_sampling"):
clamp.use_multiple_importance_sampling = False
if bpy.data.version <= (2, 79, 3):
# Switch to squared roughness convention
square_roughness_nodes_insert()
if bpy.data.version <= (2, 79, 4):
# Worlds
for world in bpy.data.worlds:
if world.library not in libraries:
continue
if version <= (2, 76, 9):
cworld = world.cycles
# World MIS Samples
if not cworld.is_property_set("samples"):
cworld.samples = 4
# World MIS Resolution
if not cworld.is_property_set("sample_map_resolution"):
cworld.sample_map_resolution = 256
if version <= (2, 79, 4):
cworld = world.cycles
# World MIS
if not cworld.is_property_set("sampling_method"):
@ -488,11 +394,53 @@ def do_versions(self):
else:
cworld.sampling_method = 'NONE'
ambient_occlusion_nodes_relink()
if bpy.data.version <= (2, 79, 6):
# Change default to bump again.
# Materials
for mat in bpy.data.materials:
if mat.library not in libraries:
continue
if version <= (2, 76, 5):
cmat = mat.cycles
# Volume Sampling
if not cmat.is_property_set("volume_sampling"):
cmat.volume_sampling = 'DISTANCE'
if version <= (2, 79, 2):
cmat = mat.cycles
if not cmat.is_property_set("displacement_method"):
cmat.displacement_method = 'BUMP'
# Change default to bump again.
if version <= (2, 79, 6):
cmat = mat.cycles
if not cmat.is_property_set("displacement_method"):
cmat.displacement_method = 'DISPLACEMENT'
# Nodes
for nodetree, library in foreach_cycles_nodetree():
if library not in libraries:
continue
# Euler order was ZYX in previous versions.
if version <= (2, 73, 4):
for node in nodetree.nodes:
mapping_node_order_flip(node)
if version <= (2, 76, 5):
for node in nodetree.nodes:
vector_curve_node_remap(node)
if version <= (2, 79, 1):
displacement_node_insert(nodetree)
if version <= (2, 79, 2):
for node in nodetree.nodes:
displacement_principled_nodes(node)
if version <= (2, 79, 3):
# Switch to squared roughness convention
square_roughness_node_insert(nodetree)
if version <= (2, 79, 4):
ambient_occlusion_node_relink(nodetree)

@ -762,6 +762,14 @@ static IDProperty *rna_IDPropertyWrapPtr_idprops(PointerRNA *ptr, bool UNUSED(cr
return ptr->data;
}
static void rna_Library_version_get(PointerRNA *ptr, int *value)
{
Library *lib = (Library *)ptr->data;
value[0] = lib->versionfile / 100;
value[1] = lib->versionfile % 100;
value[2] = lib->subversionfile;
}
#else
static void rna_def_ID_properties(BlenderRNA *brna)
@ -1110,6 +1118,12 @@ static void rna_def_library(BlenderRNA *brna)
RNA_def_property_pointer_sdna(prop, NULL, "packedfile");
RNA_def_property_ui_text(prop, "Packed File", "");
prop = RNA_def_int_vector(srna, "version", 3, NULL, 0, INT_MAX,
"Version", "Version of Blender the library .blend was saved with", 0, INT_MAX);
RNA_def_property_int_funcs(prop, "rna_Library_version_get", NULL, NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_flag(prop, PROP_THICK_WRAP);
func = RNA_def_function(srna, "reload", "WM_lib_reload");
RNA_def_function_flag(func, FUNC_USE_REPORTS | FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Reload this library and all its linked data-blocks");