diff --git a/scripts/addons_core/node_wrangler/__init__.py b/scripts/addons_core/node_wrangler/__init__.py index e04c48b1014..2d5deafe809 100644 --- a/scripts/addons_core/node_wrangler/__init__.py +++ b/scripts/addons_core/node_wrangler/__init__.py @@ -7,8 +7,8 @@ bl_info = { # This is now displayed as the maintainer, so show the foundation. # "author": "Bartek Skorupa, Greg Zaal, Sebastian Koenig, Christian Brinkmann, Florian Meyer", # Original Authors "author": "Blender Foundation", - "version": (3, 56), - "blender": (4, 2, 0), + "version": (4, 0, 0), + "blender": (5, 0, 0), "location": "Node Editor Toolbar or Shift-W", "description": "Various tools to enhance and speed up node-based workflow", "warning": "", diff --git a/scripts/addons_core/node_wrangler/interface.py b/scripts/addons_core/node_wrangler/interface.py index aa47103327d..a0494562844 100644 --- a/scripts/addons_core/node_wrangler/interface.py +++ b/scripts/addons_core/node_wrangler/interface.py @@ -440,11 +440,12 @@ def bgreset_menu_func(self, context): def save_viewer_menu_func(self, context): space = context.space_data if (space.type == 'NODE_EDITOR' + and space.tree_type == 'CompositorNodeTree' + and space.node_tree_sub_type == 'SCENE' and space.node_tree is not None and space.node_tree.library is None - and space.tree_type == 'CompositorNodeTree' - and context.scene.compositing_node_group.nodes.active - and context.scene.compositing_node_group.nodes.active.type == "VIEWER"): + and space.edit_tree.nodes.active + and space.edit_tree.nodes.active.type == "VIEWER"): self.layout.operator(operators.NWSaveViewer.bl_idname, icon='FILE_IMAGE') diff --git a/scripts/addons_core/node_wrangler/operators.py b/scripts/addons_core/node_wrangler/operators.py index 7660c11570a..b42372a0eeb 100644 --- a/scripts/addons_core/node_wrangler/operators.py +++ b/scripts/addons_core/node_wrangler/operators.py @@ -34,7 +34,7 @@ from .utils.paths import match_files_to_socket_names, split_into_components from .utils.nodes import (node_mid_pt, autolink, node_at_pos, get_nodes_links, force_update, nw_check, nw_check_not_empty, nw_check_selected, nw_check_active, nw_check_space_type, - nw_check_node_type, nw_check_visible_outputs, nw_check_viewer_node, NWBase, + nw_check_node_type, nw_check_visible_outputs, get_viewer_image, nw_check_viewer_node, NWBase, get_first_enabled_output, is_visible_socket) @@ -2191,7 +2191,7 @@ class NWAddSequence(Operator, NWBase, ImportHelper): class NWSaveViewer(bpy.types.Operator, ExportHelper): """Save the current viewer node to an image file""" bl_idname = "node.nw_save_viewer" - bl_label = "Save This Image" + bl_label = "Save Viewer Image" filepath: StringProperty(subtype="FILE_PATH") filename_ext: EnumProperty( name="Format", @@ -2206,7 +2206,9 @@ class NWSaveViewer(bpy.types.Operator, ExportHelper): ('.dpx', 'DPX', ""), ('.exr', 'OPEN_EXR', ""), ('.hdr', 'HDR', ""), - ('.tif', 'TIFF', "")), + ('.tif', 'TIFF', ""), + ('.webp', 'WEBP', ""), + ), default='.png', ) @@ -2218,36 +2220,39 @@ class NWSaveViewer(bpy.types.Operator, ExportHelper): def execute(self, context): fp = self.filepath - if fp: - formats = { - '.bmp': 'BMP', - '.rgb': 'IRIS', - '.png': 'PNG', - '.jpg': 'JPEG', - '.jpeg': 'JPEG', - '.jp2': 'JPEG2000', - '.tga': 'TARGA', - '.cin': 'CINEON', - '.dpx': 'DPX', - '.exr': 'OPEN_EXR', - '.hdr': 'HDR', - '.tiff': 'TIFF', - '.tif': 'TIFF'} - basename, ext = path.splitext(fp) - image_settings = context.scene.render.image_settings - old_media_type = image_settings.media_type - old_file_format = image_settings.file_format - old_tree_type = context.space_data.tree_type - image_settings.media_type = 'IMAGE' - image_settings.file_format = formats[self.filename_ext] - context.area.type = "IMAGE_EDITOR" - context.area.spaces[0].image = bpy.data.images['Viewer Node'] - context.area.spaces[0].image.save_render(fp) - context.area.type = "NODE_EDITOR" - context.space_data.tree_type = old_tree_type - image_settings.media_type = old_media_type - image_settings.file_format = old_file_format - return {'FINISHED'} + if not fp: + return {'CANCELLED'} + + formats = { + '.bmp': 'BMP', + '.rgb': 'IRIS', + '.png': 'PNG', + '.jpg': 'JPEG', + '.jpeg': 'JPEG', + '.jp2': 'JPEG2000', + '.tga': 'TARGA', + '.cin': 'CINEON', + '.dpx': 'DPX', + '.exr': 'OPEN_EXR', + '.hdr': 'HDR', + '.tiff': 'TIFF', + '.tif': 'TIFF', + '.webp': 'WEBP', + } + image_settings = context.scene.render.image_settings + old_media_type = image_settings.media_type + old_file_format = image_settings.file_format + image_settings.media_type = 'IMAGE' + image_settings.file_format = formats[self.filename_ext] + + try: + get_viewer_image().save_render(fp) + except RuntimeError as e: + self.report({'ERROR'}, rpt_("Could not write image: {}").format(e)) + + image_settings.media_type = old_media_type + image_settings.file_format = old_file_format + return {'FINISHED'} class NWResetNodes(bpy.types.Operator): diff --git a/scripts/addons_core/node_wrangler/utils/nodes.py b/scripts/addons_core/node_wrangler/utils/nodes.py index 8f770de713e..885f33e9cdb 100644 --- a/scripts/addons_core/node_wrangler/utils/nodes.py +++ b/scripts/addons_core/node_wrangler/utils/nodes.py @@ -183,6 +183,14 @@ def get_output_location(tree): return loc_x, loc_y +def get_viewer_image(): + for img in bpy.data.images: + if (img.source == 'VIEWER' + and len(img.render_slots) == 0 + and sum(img.size) > 0): + return img + + def nw_check(cls, context): space = context.space_data if space.type != 'NODE_EDITOR': @@ -258,14 +266,10 @@ def nw_check_visible_outputs(cls, context): def nw_check_viewer_node(cls): - for img in bpy.data.images: - # False if not connected or connected but no image - if (img.source == 'VIEWER' - and len(img.render_slots) == 0 - and sum(img.size) > 0): - return True - cls.poll_message_set("Viewer image not found.") - return False + if get_viewer_image() is None: + cls.poll_message_set("Viewer image not found.") + return False + return True def get_first_enabled_output(node):