Files
test2/scripts/startup/bl_ui/properties_world.py
Habib Gahbiche 445eceb02a Nodes: Remove "Use Nodes" in Shader Editor for World
Part of https://projects.blender.org/blender/blender/pulls/141278

Blend files compatibility:
If a World exists and "Use Nodes" is disabled, we add new nodes to the
existing node tree (or create one if it doesn't) that emulates the
behavior of a world without a node tree. This ensures backward and
forward compatibility.

Python API compatibility:
- `world.use_nodes` was removed from Python API => **Breaking change**
- `world.color` is still being used by Workbench, so it stays there,
although it has no effect anymore when using Cycles or EEVEE.

Python API changes:
Creating a World using `bpy.data.worlds.new()` now creates a World with
 an empty (embedded) node tree. This was necessary to enable Python
scripts to add nodes without having to create a node tree (which is
currently not possible, because World node trees are embedded).

Pull Request: https://projects.blender.org/blender/blender/pulls/142342
2025-07-28 14:06:08 +02:00

283 lines
7.6 KiB
Python

# SPDX-FileCopyrightText: 2009-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Panel
from bpy.app.translations import contexts as i18n_contexts
from rna_prop_ui import PropertyPanel
from bpy_extras.node_utils import find_node_input
from bl_ui.space_properties import PropertiesAnimationMixin
class WorldButtonsPanel:
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "world"
# COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
@classmethod
def poll(cls, context):
return (context.world and context.engine in cls.COMPAT_ENGINES)
class WORLD_PT_context_world(WorldButtonsPanel, Panel):
bl_label = ""
bl_options = {'HIDE_HEADER'}
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE',
'BLENDER_WORKBENCH',
}
@classmethod
def poll(cls, context):
return (context.engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
scene = context.scene
world = context.world
space = context.space_data
if scene:
layout.template_ID(scene, "world", new="world.new")
elif world:
layout.template_ID(space, "pin_id")
class EEVEE_WORLD_PT_mist(WorldButtonsPanel, Panel):
bl_label = "Mist Pass"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE'}
@classmethod
def poll(cls, context):
engine = context.engine
return context.world and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
world = context.world
col = layout.column(align=True)
col.prop(world.mist_settings, "start")
col.prop(world.mist_settings, "depth")
col = layout.column()
col.prop(world.mist_settings, "falloff")
class WORLD_PT_animation(WorldButtonsPanel, PropertiesAnimationMixin, PropertyPanel, Panel):
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE',
'BLENDER_WORKBENCH',
}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
# WorldButtonsPanel.poll ensures this is not None.
world = context.world
col = layout.column(align=True)
col.label(text="World")
self.draw_action_and_slot_selector(context, col, world)
if node_tree := world.node_tree:
col = layout.column(align=True)
col.label(text="Shader Node Tree")
self.draw_action_and_slot_selector(context, col, node_tree)
class WORLD_PT_custom_props(WorldButtonsPanel, PropertyPanel, Panel):
COMPAT_ENGINES = {
'BLENDER_RENDER',
'BLENDER_EEVEE',
'BLENDER_WORKBENCH',
}
_context_path = "world"
_property_type = bpy.types.World
class EEVEE_WORLD_PT_surface(WorldButtonsPanel, Panel):
bl_label = "Surface"
COMPAT_ENGINES = {'BLENDER_EEVEE'}
@classmethod
def poll(cls, context):
engine = context.engine
return context.world and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
world = context.world
layout.separator()
layout.use_property_split = True
ntree = world.node_tree
node = ntree.get_output_node('EEVEE')
if node:
input = find_node_input(node, "Surface")
if input:
layout.template_node_view(ntree, node, input)
else:
layout.label(text="Incompatible output node")
else:
layout.label(text="No output node")
class EEVEE_WORLD_PT_volume(WorldButtonsPanel, Panel):
bl_label = "Volume"
bl_translation_context = i18n_contexts.id_id
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE'}
@classmethod
def poll(cls, context):
engine = context.engine
world = context.world
return world and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
layout = self.layout
world = context.world
ntree = world.node_tree
node = ntree.get_output_node('EEVEE')
layout.use_property_split = True
if world.use_eevee_finite_volume:
layout.operator("world.convert_volume_to_mesh", icon='WORLD', text="Convert Volume")
if node:
input = find_node_input(node, "Volume")
if input:
layout.template_node_view(ntree, node, input)
else:
layout.label(text="Incompatible output node")
else:
layout.label(text="No output node")
class EEVEE_WORLD_PT_settings(WorldButtonsPanel, Panel):
bl_label = "Settings"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE'}
@classmethod
def poll(cls, context):
engine = context.engine
world = context.world
return world and (engine in cls.COMPAT_ENGINES)
def draw(self, context):
pass
class EEVEE_WORLD_PT_lightprobe(WorldButtonsPanel, Panel):
bl_label = "Light Probe"
bl_parent_id = "EEVEE_WORLD_PT_settings"
COMPAT_ENGINES = {'BLENDER_EEVEE'}
def draw(self, context):
layout = self.layout
world = context.world
layout.use_property_split = True
layout.prop(world, "probe_resolution", text="Resolution")
class EEVEE_WORLD_PT_sun(WorldButtonsPanel, Panel):
bl_label = "Sun"
bl_parent_id = "EEVEE_WORLD_PT_settings"
COMPAT_ENGINES = {'BLENDER_EEVEE'}
def draw(self, context):
layout = self.layout
world = context.world
layout.use_property_split = True
layout.prop(world, "sun_threshold", text="Threshold")
layout.prop(world, "sun_angle", text="Angle")
class EEVEE_WORLD_PT_sun_shadow(WorldButtonsPanel, Panel):
bl_label = "Shadow"
bl_parent_id = "EEVEE_WORLD_PT_sun"
bl_options = {'DEFAULT_CLOSED'}
COMPAT_ENGINES = {'BLENDER_EEVEE'}
def draw_header(self, context):
world = context.world
self.layout.prop(world, "use_sun_shadow", text="")
def draw(self, context):
layout = self.layout
layout.use_property_split = True
world = context.world
col = layout.column(align=False, heading="Jitter")
row = col.row(align=True)
sub = row.row(align=True)
sub.prop(world, "use_sun_shadow_jitter", text="")
sub = sub.row(align=True)
sub.active = world.use_sun_shadow_jitter
sub.prop(world, "sun_shadow_jitter_overblur", text="Overblur")
col.separator()
col = layout.column()
col.prop(world, "sun_shadow_filter_radius", text="Filter")
col.prop(world, "sun_shadow_maximum_resolution", text="Resolution Limit")
class WORLD_PT_viewport_display(WorldButtonsPanel, Panel):
bl_label = "Viewport Display"
bl_options = {'DEFAULT_CLOSED'}
bl_order = 10
@classmethod
def poll(cls, context):
return context.world
def draw(self, context):
layout = self.layout
layout.use_property_split = True
world = context.world
layout.prop(world, "color")
classes = (
WORLD_PT_context_world,
EEVEE_WORLD_PT_surface,
EEVEE_WORLD_PT_volume,
EEVEE_WORLD_PT_mist,
EEVEE_WORLD_PT_settings,
EEVEE_WORLD_PT_lightprobe,
EEVEE_WORLD_PT_sun,
EEVEE_WORLD_PT_sun_shadow,
WORLD_PT_viewport_display,
WORLD_PT_animation,
WORLD_PT_custom_props,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)