File: world.py

package info (click to toggle)
blender 4.3.2%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 309,564 kB
  • sloc: cpp: 2,385,210; python: 330,236; ansic: 280,972; xml: 2,446; sh: 972; javascript: 317; makefile: 170
file content (169 lines) | stat: -rw-r--r-- 5,674 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# 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 or not world.use_nodes:
            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)
        material.use_nodes = True
        volume_tree = material.node_tree
        for node in volume_tree.nodes:
            if node.type != 'OUTPUT_MATERIAL':
                volume_tree.nodes.remove(node)
        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,
)