Files
test/scripts/startup/bl_operators/world.py
Habib Gahbiche 1b4daf9d2e Nodes: remove "Use Nodes" in Shader Editor for Object Materials
"Use Nodes" was removed in the compositor to simplify the compositing
workflow. This introduced a slight inconsistency with the Shader Node
Editor.

This PR removes "Use Nodes" for object materials.

For Line Style, no changes are planned (not sure how to preserve
compatibility yet).
This simplifies the state of objects; either they have a material or
they don't.

Backward compatibility:
- If Use Nodes is turned Off, new nodes are added to the node tree to
simulate the same material:
- DNA: Only `use_nodes` is marked deprecated
- Python API:
  - `material.use_nodes` is marked deprecated and will be removed in
6.0. Reading it always returns `True` and setting it has no effect.
  - `material.diffuse_color`, `material.specular` etc.. Are not used by
EEVEE anymore but are kept because they are used by Workbench.

Forward compatibility:
Always enable 'Use Nodes' when writing blend files.

Known Issues:
Some UI tests are failing on macOS

Pull Request: https://projects.blender.org/blender/blender/pulls/141278
2025-09-14 17:53:54 +02:00

168 lines
5.4 KiB
Python

# SPDX-FileCopyrightText: 2024 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
import bmesh
class WORLD_OT_convert_volume_to_mesh(bpy.types.Operator):
"""Convert the volume of a world to a mesh. """ \
"""The world's volume used to be rendered by EEVEE Legacy. Conversion is needed for it to render properly"""
bl_label = "Convert Volume"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "world.convert_volume_to_mesh"
@classmethod
def poll(cls, context):
world = cls._world_get(context)
if not world:
return False
ntree = world.node_tree
node = ntree.get_output_node('EEVEE')
return bool(node.inputs["Volume"].links)
def execute(self, context):
cls = self.__class__
world = cls._world_get(context)
view_layer = context.view_layer
world_tree = world.node_tree
world_output = world_tree.get_output_node('EEVEE')
name = "{:s}_volume".format(world.name)
collection = bpy.data.collections.new(name)
view_layer.layer_collection.collection.children.link(collection)
# Add World Volume Mesh object to scene
mesh = bpy.data.meshes.new(name)
object = bpy.data.objects.new(name, mesh)
object.display.show_shadows = False
bm = bmesh.new()
bmesh.ops.create_icosphere(bm, subdivisions=0, radius=1e5)
bm.to_mesh(mesh)
bm.free()
# Remove all non-essential attributes
for attribute in mesh.attributes:
if attribute.is_internal or attribute.is_required:
continue
mesh.attributes.remove(attribute)
material = bpy.data.materials.new(name)
mesh.materials.append(material)
volume_tree = material.node_tree
volume_tree.nodes.new("ShaderNodeOutputMaterial")
volume_output = volume_tree.get_output_node('EEVEE')
links_to_add = []
self._sync_rna_properties(volume_output, world_output)
self._sync_node_input(
volume_tree,
volume_output,
volume_output.inputs["Volume"],
world_output,
world_output.inputs["Volume"],
links_to_add,
)
self._sync_links(volume_tree, links_to_add)
# Add transparent volume for other render engines
if volume_output.target == 'EEVEE':
all_output = volume_tree.nodes.new(type="ShaderNodeOutputMaterial")
transparent = volume_tree.nodes.new(type="ShaderNodeBsdfTransparent")
volume_tree.links.new(transparent.outputs[0], all_output.inputs[0])
# Remove all volume links from the world node tree.
for link in world_output.inputs["Volume"].links:
world_tree.links.remove(link)
collection.objects.link(object)
object.select_set(True)
view_layer.objects.active = object
world.use_eevee_finite_volume = False
return {'FINISHED'}
@staticmethod
def _world_get(context):
if world := getattr(context, "world", None):
return world
return context.scene.world
def _sync_node_input(
self,
dst_tree, # bpy.types.NodeTree
dst_node, # bpy.types.Node
dst_socket, # bpy.types.NodeSocket
src_node, # bpy.types.Node
src_socket, # bpy.types.NodeSocket
links_to_add,
): # -> None
self._sync_rna_properties(dst_socket, src_socket)
for src_link in src_socket.links:
src_linked_node = src_link.from_node
dst_linked_node = self._sync_node(dst_tree, src_linked_node, links_to_add)
from_socket_index = src_node.outputs.find(src_link.from_socket.name)
dst_tree.links.new(
dst_linked_node.outputs[from_socket_index],
dst_socket,
)
def _sync_node(
self,
dst_tree, # bpy.types.NodeTree
src_node, # bpy.types.Node
links_to_add,
): # -> bpy.types.Node
"""
Find the counter part of the src_node in dst_tree. When found return the counter part. When not found
create the counter part, sync it and return the created node.
"""
if src_node.name in dst_tree.nodes:
return dst_tree.nodes[src_node.name]
dst_node = dst_tree.nodes.new(src_node.bl_idname)
self._sync_rna_properties(dst_node, src_node)
self._sync_node_inputs(dst_tree, dst_node, src_node, links_to_add)
return dst_node
def _sync_rna_properties(self, dst, src): # -> None
for rna_prop in src.bl_rna.properties:
if rna_prop.is_readonly:
continue
attr_name = rna_prop.identifier
if attr_name in {"bl_idname", "bl_static_type"}:
continue
setattr(dst, attr_name, getattr(src, attr_name))
def _sync_node_inputs(
self,
dst_tree, # bpy.types.NodeTree
dst_node, # bpy.types.Node
src_node, # bpy.types.Node
links_to_add,
): # -> None
for index in range(len(src_node.inputs)):
src_socket = src_node.inputs[index]
dst_socket = dst_node.inputs[index]
self._sync_node_input(dst_tree, dst_node, dst_socket, src_node, src_socket, links_to_add)
def _sync_links(
self,
dst_tree, # bpy.types.NodeTree
links_to_add,
): # -> None
pass
classes = (
WORLD_OT_convert_volume_to_mesh,
)