diff --git a/src/bonsai/bonsai/bim/module/style/operator.py b/src/bonsai/bonsai/bim/module/style/operator.py index 918b06175a..4a036652f2 100644 --- a/src/bonsai/bonsai/bim/module/style/operator.py +++ b/src/bonsai/bonsai/bim/module/style/operator.py @@ -455,7 +455,7 @@ class ActivateExternalStyle(bpy.types.Operator): return {"CANCELLED"} self.copy_material_attributes(db["data_block"], material) - if material.use_nodes: + if tool.Style.get_use_nodes(material): tool.Blender.copy_node_graph(material, db["data_block"]) bpy.data.materials.remove(db["data_block"]) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py index 8e0e45f02f..517bfe34f1 100644 --- a/src/bonsai/bonsai/bim/operator.py +++ b/src/bonsai/bonsai/bim/operator.py @@ -786,7 +786,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator): material = bpy.data.materials.get("Section Override") if not material: material = bpy.data.materials.new("Section Override") - material.use_nodes = True + tool.Style.set_use_nodes(material, True) props = tool.Blender.get_bim_props() if props.should_section_selected_objects: @@ -809,7 +809,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator): def override_materials(self): override = bpy.data.node_groups.get("Section Override") for material in bpy.data.materials: - material.use_nodes = True + tool.Style.set_use_nodes(material, True) if material.node_tree.nodes.get("Section Override"): continue # In EEVEE rendering engine, `blend_mode` is deprecated and replaced by `surface_render_method` diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index ee54a2a7fc..d128101317 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -100,6 +100,7 @@ class Blender(bonsai.core.tool.Blender): - (identifier, name, description, icon, number) """ BLENDER_ENUM_ITEMS = Iterable[BLENDER_ENUM_ITEM] + BLENDER_5 = bpy.app.version >= (5, 0, 0) @classmethod def activate_camera(cls, obj: bpy.types.Object) -> None: @@ -416,7 +417,7 @@ class Blender(bonsai.core.tool.Blender): cls, blender_material: bpy.types.Material, node_type: str, kwargs: Optional[dict] = {} ) -> Union[bpy.types.ShaderNode, None]: """returns first node from the `blender_material` shader graph with type `node_type`""" - if not blender_material.use_nodes: + if not tool.Style.get_use_nodes(blender_material): return nodes = blender_material.node_tree.nodes for node in nodes: diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index b7aa4ff15b..879df2adf0 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -1262,7 +1262,7 @@ class Geometry(bonsai.core.tool.Geometry): if tool.Ifc.get().schema == "IFC2X3": return False for slot in obj.material_slots: - if slot.material and slot.material.use_nodes: + if slot.material and tool.Style.get_use_nodes(slot.material): for node in slot.material.node_tree.nodes: if node.type == "TEX_COORD" and node.outputs["UV"].links: return True @@ -2021,7 +2021,7 @@ class Geometry(bonsai.core.tool.Geometry): continue if not (style := tool.Ifc.get_entity(material)): style = ifcopenshell.api.style.add_style(ifc_file, name=material.name) - if material.use_nodes: + if tool.Style.get_use_nodes(material): ifc_class = "IfcSurfaceStyleRendering" attributes = tool.Style.get_surface_rendering_attributes(material) else: diff --git a/src/bonsai/bonsai/tool/loader.py b/src/bonsai/bonsai/tool/loader.py index f748c88616..e28ceacaec 100644 --- a/src/bonsai/bonsai/tool/loader.py +++ b/src/bonsai/bonsai/tool/loader.py @@ -116,7 +116,7 @@ class Loader(bonsai.core.tool.Loader): if transparency := surface_style.get("Transparency", None): alpha = 1 - transparency blender_material.diffuse_color = surface_style["SurfaceColour"] + (alpha,) - blender_material.use_nodes = False + tool.Style.set_use_nodes(blender_material, False) @classmethod def restart_material_node_tree(cls, blender_material: bpy.types.Material) -> None: @@ -204,7 +204,7 @@ class Loader(bonsai.core.tool.Loader): # TODO: reset pins to default values if no values passed if reflectance_method in ["PHYSICAL", "NOTDEFINED"]: - blender_material.use_nodes = True + tool.Style.set_use_nodes(blender_material, True) cls.restart_material_node_tree(blender_material) bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") assert bsdf @@ -231,7 +231,7 @@ class Loader(bonsai.core.tool.Loader): blender_material.blend_method = "BLEND" elif reflectance_method == "FLAT": - blender_material.use_nodes = True + tool.Style.set_use_nodes(blender_material, True) cls.restart_material_node_tree(blender_material) output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL") diff --git a/src/bonsai/bonsai/tool/sequence.py b/src/bonsai/bonsai/tool/sequence.py index 14c88dd251..4e3144dc26 100644 --- a/src/bonsai/bonsai/tool/sequence.py +++ b/src/bonsai/bonsai/tool/sequence.py @@ -1020,7 +1020,7 @@ class Sequence(bonsai.core.tool.Sequence): def set_material(name, r, g, b): material = bpy.data.materials.new(name) - material.use_nodes = True + tool.Style.set_use_nodes(material, True) tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs[0].default_value = (r, g, b, 1.0) return material diff --git a/src/bonsai/bonsai/tool/style.py b/src/bonsai/bonsai/tool/style.py index 35399e7811..e6861b53dc 100644 --- a/src/bonsai/bonsai/tool/style.py +++ b/src/bonsai/bonsai/tool/style.py @@ -63,9 +63,23 @@ class Style(bonsai.core.tool.Style): def get_material_style_props(cls, material: bpy.types.Material) -> BIMStyleProperties: return material.BIMStyleProperties + @classmethod + def get_use_nodes(cls, obj: bpy.types.Material) -> bool: + """Since Blender 5.0 ``use_nodes`` are always ``True`` and considered deprecated.""" + if tool.Blender.BLENDER_5: + return True + return obj.use_nodes + + @classmethod + def set_use_nodes(cls, obj: bpy.types.Material, use_nodes: bool) -> None: + """Since Blender 5.0 ``use_nodes`` are always ``True`` and considered deprecated.""" + if tool.Blender.BLENDER_5: + return + obj.use_nodes = use_nodes + @classmethod def can_support_rendering_style(cls, obj: bpy.types.Material) -> bool: - return obj.use_nodes and hasattr(obj.node_tree, "nodes") + return tool.Blender.BLENDER_5 or (obj.use_nodes and hasattr(obj.node_tree, "nodes")) @classmethod def delete_object(cls, obj: bpy.types.Material) -> None: diff --git a/src/bonsai/scripts/standalone_section_shader.py b/src/bonsai/scripts/standalone_section_shader.py index 7d732dad2a..970e58105b 100644 --- a/src/bonsai/scripts/standalone_section_shader.py +++ b/src/bonsai/scripts/standalone_section_shader.py @@ -89,10 +89,17 @@ class SectionCutawayManager: SectionCutawayManager.offset_previous_nodes(previous_section_compare, offset_x, offset_y) @staticmethod - def override_materials(): + def set_use_nodes(obj: bpy.types.Material, use_nodes: bool) -> None: + if bpy.app.version >= (5, 0, 0): + # Since Blender 5.0 `use_nodes` are always `True` and considered deprecated. + return + obj.use_nodes = use_nodes + + @classmethod + def override_materials(cls): override = SectionCutawayManager.get_section_tree() for material in bpy.data.materials: - material.use_nodes = True + cls.set_use_nodes(material, True) if material.node_tree.nodes.get(override.name): continue material.blend_method = "HASHED" @@ -261,12 +268,12 @@ class SectionCutawayManager: section_compare.name = "Last Section Compare" - @staticmethod - def add_default_material_if_none_exists(context): + @classmethod + def add_default_material_if_none_exists(cls, context): material = bpy.data.materials.get("Section Override") if not material: material = bpy.data.materials.new("Section Override") - material.use_nodes = True + cls.set_use_nodes(material, True) if context.scene.SectionProperties.should_section_selected_objects: objects = list(context.selected_objects) diff --git a/src/bonsai/test/tool/test_blender.py b/src/bonsai/test/tool/test_blender.py index 7df17e00f9..2db244abe1 100644 --- a/src/bonsai/test/tool/test_blender.py +++ b/src/bonsai/test/tool/test_blender.py @@ -37,7 +37,7 @@ class TestImplementsTool(NewFile): class TestCopyNodeGraph(NewFile): def test_run(self): material_to = bpy.data.materials.new("material_to") - material_to.use_nodes = True + tool.Style.set_use_nodes(material_to, True) assert material_to.node_tree material_to_nodes = material_to.node_tree.nodes assert len(material_to_nodes) == 2 @@ -46,7 +46,7 @@ class TestCopyNodeGraph(NewFile): assert len(material_to_nodes) == 0 material_from = bpy.data.materials.new("material_from") - material_from.use_nodes = True + tool.Style.set_use_nodes(material_from, True) subject.copy_node_graph(material_to, material_from) assert len(material_to_nodes) == 2 diff --git a/src/bonsai/test/tool/test_geometry.py b/src/bonsai/test/tool/test_geometry.py index af7670275d..a2107da12f 100644 --- a/src/bonsai/test/tool/test_geometry.py +++ b/src/bonsai/test/tool/test_geometry.py @@ -488,7 +488,7 @@ class TestShouldGenerateUVs(NewFile): obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) material = bpy.data.materials.new("Material") obj.data.materials.append(material) - material.use_nodes = False + tool.Style.set_use_nodes(material, False) assert subject.should_generate_uvs(obj) is False def test_needs_texture_coordinates_with_a_uv_output(self): @@ -497,7 +497,7 @@ class TestShouldGenerateUVs(NewFile): obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) material = bpy.data.materials.new("Material") obj.data.materials.append(material) - material.use_nodes = True + tool.Style.set_use_nodes(material, True) bsdf = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED") node = material.node_tree.nodes.new(type="ShaderNodeTexImage") @@ -513,7 +513,7 @@ class TestShouldGenerateUVs(NewFile): obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) material = bpy.data.materials.new("Material") obj.data.materials.append(material) - material.use_nodes = True + tool.Style.set_use_nodes(material, True) bsdf = tool.Blender.get_material_node(material, "BSDF_PRINCIPLED") node = material.node_tree.nodes.new(type="ShaderNodeTexImage") diff --git a/src/bonsai/test/tool/test_style.py b/src/bonsai/test/tool/test_style.py index e0c82a4ead..23c70461ee 100644 --- a/src/bonsai/test/tool/test_style.py +++ b/src/bonsai/test/tool/test_style.py @@ -22,6 +22,7 @@ import ifcopenshell import ifcopenshell.api import ifcopenshell.api.root import ifcopenshell.util.representation +import pytest import bonsai.core.tool import bonsai.tool as tool from test.bim.bootstrap import NewFile @@ -37,12 +38,16 @@ class TestImplementsTool(NewFile): class TestCanSupportRenderingStyle(NewFile): def test_anything_with_nodes_can_support_a_rendering_style(self): obj = bpy.data.materials.new("Material") - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) assert subject.can_support_rendering_style(obj) is True + @pytest.mark.skipif( + tool.Blender.BLENDER_5, + reason="Since Blender 5.0 `use_nodes` are always `True` and deprecated.", + ) def test_without_nodes_we_do_not_support_rendering(self): obj = bpy.data.materials.new("Material") - obj.use_nodes = False + tool.Style.set_use_nodes(obj, False) assert subject.can_support_rendering_style(obj) is False @@ -125,7 +130,7 @@ class TestGetSurfaceRenderingAttributes(NewFile): def test_get_different_surface_and_diffuse_colours_from_a_principled_bsdf(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) node = tool.Blender.get_material_node(obj, "BSDF_PRINCIPLED") assert node node.inputs["Alpha"].default_value = 0.8 @@ -153,7 +158,7 @@ class TestGetSurfaceRenderingAttributes(NewFile): def test_get_rendering_styles_from_a_glossy_bsdf(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL") node = tool.Blender.get_material_node(obj, "BSDF_PRINCIPLED") assert obj.node_tree and node @@ -185,7 +190,7 @@ class TestGetSurfaceRenderingAttributes(NewFile): def test_get_rendering_styles_from_a_diffuse_bsdf(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL") node = tool.Blender.get_material_node(obj, "BSDF_PRINCIPLED") assert obj.node_tree and node and output @@ -217,7 +222,7 @@ class TestGetSurfaceRenderingAttributes(NewFile): def test_get_rendering_styles_from_a_glass_bsdf(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL") node = tool.Blender.get_material_node(obj, "BSDF_PRINCIPLED") assert obj.node_tree and node and output @@ -249,7 +254,7 @@ class TestGetSurfaceRenderingAttributes(NewFile): def test_get_rendering_styles_from_a_emission_bsdf(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL") node = tool.Blender.get_material_node(obj, "BSDF_PRINCIPLED") assert obj.node_tree and node and output @@ -280,7 +285,7 @@ class TestGetSurfaceRenderingAttributes(NewFile): def test_other_unsupported_bsdfs_copy_the_rendering_style_from_the_shading_colours_as_a_fallback(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] - obj.use_nodes = True + tool.Style.set_use_nodes(obj, True) output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL") node = tool.Blender.get_material_node(obj, "BSDF_PRINCIPLED") assert obj.node_tree and node and output diff --git a/src/ifcblender/io_import_scene_ifc/__init__.py b/src/ifcblender/io_import_scene_ifc/__init__.py index b005b15564..29834b2356 100644 --- a/src/ifcblender/io_import_scene_ifc/__init__.py +++ b/src/ifcblender/io_import_scene_ifc/__init__.py @@ -176,7 +176,8 @@ def import_ifc(filename, use_names, process_relations, blender_booleans): mat.blend_method = "HASHED" mat.use_screen_refraction = True mat.refraction_depth = 0.1 - mat.use_nodes = True + if bpy.app.version >= (5, 0, 0): + mat.use_nodes = True bsdf = next(n for n in mat.node_tree.nodes if n.type == "BSDF_PRINCIPLED") bsdf.inputs[15].default_value = v else: