Node Wrangler: add back exposure compensation for Preview Node #105136
@ -5,7 +5,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Node Wrangler",
|
"name": "Node Wrangler",
|
||||||
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
|
"author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer",
|
||||||
"version": (3, 52),
|
"version": (3, 53),
|
||||||
"blender": (4, 0, 0),
|
"blender": (4, 0, 0),
|
||||||
"location": "Node Editor Toolbar or Shift-W",
|
"location": "Node Editor Toolbar or Shift-W",
|
||||||
"description": "Various tools to enhance and speed up node-based workflow",
|
"description": "Various tools to enhance and speed up node-based workflow",
|
||||||
|
@ -499,6 +499,7 @@ class NWPreviewNode(Operator, NWBase):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.shader_output_type = ""
|
self.shader_output_type = ""
|
||||||
self.shader_output_ident = ""
|
self.shader_output_ident = ""
|
||||||
|
self.shader_viewer_ident = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@ -519,10 +520,12 @@ class NWPreviewNode(Operator, NWBase):
|
|||||||
else:
|
else:
|
||||||
self.shader_output_type = "OUTPUT_MATERIAL"
|
self.shader_output_type = "OUTPUT_MATERIAL"
|
||||||
self.shader_output_ident = "ShaderNodeOutputMaterial"
|
self.shader_output_ident = "ShaderNodeOutputMaterial"
|
||||||
|
self.shader_viewer_ident = "ShaderNodeEmission"
|
||||||
|
|
||||||
elif shader_type == 'WORLD':
|
elif shader_type == 'WORLD':
|
||||||
self.shader_output_type = "OUTPUT_WORLD"
|
self.shader_output_type = "OUTPUT_WORLD"
|
||||||
self.shader_output_ident = "ShaderNodeOutputWorld"
|
self.shader_output_ident = "ShaderNodeOutputWorld"
|
||||||
|
self.shader_viewer_ident = "ShaderNodeBackground"
|
||||||
|
|
||||||
def ensure_viewer_socket(self, node_tree, socket_type, connect_socket=None):
|
def ensure_viewer_socket(self, node_tree, socket_type, connect_socket=None):
|
||||||
"""Check if a viewer output already exists in a node group, otherwise create it"""
|
"""Check if a viewer output already exists in a node group, otherwise create it"""
|
||||||
@ -565,6 +568,46 @@ class NWPreviewNode(Operator, NWBase):
|
|||||||
groupout.is_active_output = True
|
groupout.is_active_output = True
|
||||||
return groupout
|
return groupout
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_scene_intensity(context):
|
||||||
|
"""Calculate intensity compensation based on scene exposure"""
|
||||||
|
# CM exposure is measured in stops/EVs (2^x)
|
||||||
|
intensity = 1.0 / (2.0 ** context.scene.view_settings.exposure)
|
||||||
|
if context.scene.render.engine == 'CYCLES' and hasattr(context.scene, 'cycles'):
|
||||||
|
if context.scene.cycles.film_exposure == 0.0:
|
||||||
|
# Avoid divide by zero error
|
||||||
|
return 1.0
|
||||||
|
# Film exposure is a multiplier
|
||||||
|
intensity /= context.scene.cycles.film_exposure
|
||||||
|
return intensity
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_emission_node(base_node_tree):
|
||||||
|
for node in base_node_tree.nodes:
|
||||||
|
if "Emission Viewer" in node.name:
|
||||||
|
return node
|
||||||
|
|
||||||
|
def ensure_emission_node(self, context, base_node_tree, output_node):
|
||||||
|
emission_node = self.get_emission_node(base_node_tree)
|
||||||
|
if emission_node is None:
|
||||||
|
emission_node = base_node_tree.nodes.new(self.shader_viewer_ident)
|
||||||
|
emission_node.hide = True
|
||||||
|
emission_node.location = output_node.location + Vector((0, 40))
|
||||||
|
emission_node.label = "Viewer"
|
||||||
|
emission_node.name = "Emission Viewer"
|
||||||
|
emission_node.use_custom_color = True
|
||||||
|
emission_node.color = (0.6, 0.5, 0.4)
|
||||||
|
emission_node.select = False
|
||||||
|
|
||||||
|
# If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
|
||||||
|
if (len(emission_node.outputs[0].links) == 0
|
||||||
|
or emission_node.outputs[0].links[0].to_node != output_node):
|
||||||
|
connect_sockets(emission_node.outputs[0], output_node.inputs[0])
|
||||||
|
|
||||||
|
# Set brightness of viewer to compensate for Film and CM exposure
|
||||||
|
emission_node.inputs[1].default_value = self.get_scene_intensity(context)
|
||||||
|
return emission_node
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_sockets(cls, node, sockets, index=None):
|
def search_sockets(cls, node, sockets, index=None):
|
||||||
"""Recursively scan nodes for viewer sockets and store them in a list"""
|
"""Recursively scan nodes for viewer sockets and store them in a list"""
|
||||||
@ -771,11 +814,23 @@ class NWPreviewNode(Operator, NWBase):
|
|||||||
if active_node_socket_index is None:
|
if active_node_socket_index is None:
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
if active.outputs[active_node_socket_index].name == "Volume":
|
node_output = active.outputs[active_node_socket_index]
|
||||||
|
if node_output.name == "Volume":
|
||||||
output_node_socket_index = 1
|
output_node_socket_index = 1
|
||||||
else:
|
else:
|
||||||
output_node_socket_index = 0
|
output_node_socket_index = 0
|
||||||
|
|
||||||
|
# Use an emission node if needed to compensate for scene exposure
|
||||||
|
settings = context.preferences.addons[__package__].preferences
|
||||||
|
if (settings.use_viewer_exposure_compensation
|
||||||
|
and self.get_scene_intensity(context) != 1.0
|
||||||
|
and node_output.type != 'SHADER'):
|
||||||
|
output_node = self.ensure_emission_node(context, base_node_tree, output_node)
|
||||||
|
output_node_socket_index = 0
|
||||||
|
else:
|
||||||
|
if (emission_node := self.get_emission_node(base_node_tree)) is not None:
|
||||||
|
base_node_tree.nodes.remove(emission_node)
|
||||||
|
|
||||||
# If there are no nested node groups, the link starts at the active node
|
# If there are no nested node groups, the link starts at the active node
|
||||||
node_output = active.outputs[active_node_socket_index]
|
node_output = active.outputs[active_node_socket_index]
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
|
@ -85,6 +85,11 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
|
|||||||
),
|
),
|
||||||
default='CENTER',
|
default='CENTER',
|
||||||
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
|
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
|
||||||
|
use_viewer_exposure_compensation: BoolProperty(
|
||||||
|
name="Use Viewer Exposure Compensation",
|
||||||
|
default=False,
|
||||||
|
description="When using the viewer to preview colors in the shader editor, take the scene exposure into account to display the original color"
|
||||||
|
)
|
||||||
|
|
||||||
show_hotkey_list: BoolProperty(
|
show_hotkey_list: BoolProperty(
|
||||||
name="Show Hotkey List",
|
name="Show Hotkey List",
|
||||||
@ -109,6 +114,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
|
|||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.prop(self, "merge_position")
|
col.prop(self, "merge_position")
|
||||||
col.prop(self, "merge_hide")
|
col.prop(self, "merge_hide")
|
||||||
|
col.prop(self, "use_viewer_exposure_compensation")
|
||||||
|
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
col = box.column(align=True)
|
col = box.column(align=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user