diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a4cdb..6c23199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,19 @@ Activate / deactivate layer opacity according to prefix Activate / deactivate all masks using MA layers --> +1.8.13 + +- changed: improve `connect to fileoutput` feature: + - possible to choose `node_name + socket name` or `node name` instead of `socket name` only + - default is now `node_name_socket_name` + - added some exception behavior: + - if a node has single output : write the node name only + - render layers use `scene_viewlayer` as node_name intead of "Render Layer" + - interface shows source node label (with source node name in parenthesis) + - interface shows render layer scene/viewlayer name (with name in parenthesis) + +- fixed: a potential name overlapping bug in connect to fileoutput + 1.8.12 - changed: Use GP_RENDER_FILE_FORMAT env var to set file output nodes diff --git a/OP_manage_outputs.py b/OP_manage_outputs.py index 0d797b5..b2cb887 100644 --- a/OP_manage_outputs.py +++ b/OP_manage_outputs.py @@ -239,12 +239,17 @@ class GPEXP_OT_reset_render_settings(bpy.types.Operator): return {"FINISHED"} class GPEXP_PG_selectable_prop(bpy.types.PropertyGroup): - node_name: bpy.props.StringProperty(name="Object Name") + node_name: bpy.props.StringProperty(name="Node Name") + node_label: bpy.props.StringProperty(name="Node Label") name: bpy.props.StringProperty(name="Name or Path") socket_name: bpy.props.StringProperty(name="Source socket Name") # Source socket name as reference select: bpy.props.BoolProperty(name="Selected", default=True) is_linked: bpy.props.BoolProperty(name="Linked", default=False) # is_valid: bpy.props.BoolProperty(name="Valid", default=True) + + ## extra output naming options + name_from_node: bpy.props.StringProperty(name="Name From Node") + name_from_node_and_socket: bpy.props.StringProperty(name="Name From Node And Socket") class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): bl_idname = "gp.connect_selected_to_file_out" @@ -259,6 +264,24 @@ class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): name='Settings', default=False) + ## enum choice for naming: socket_name, node_name, node_and_socket_name, + name_type : bpy.props.EnumProperty( + name='Output Name From', + description='Choose the output name\ + \nNode name use Label (Use node name when there is no Label)', + default='node_and_socket_name', + items=( + ('node_and_socket_name', 'Node_Socket Name', 'Use the node name prefix and socket name', 0), + ('socket_name', 'Socket Name', 'Use the socket name as output name', 1), + ('node_name', 'Node Name', 'Use the node name as output name', 2), + ) + ) + + # prefix_with_node_name : bpy.props.BoolProperty( + # name='Prefix With Node Name', + # description='Add the node name as prefix to the output name', + # default=False) + base_path : bpy.props.StringProperty( name='Custom base path', default='', @@ -291,7 +314,6 @@ class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): ), ) - color_depth : bpy.props.EnumProperty( name='Color Depth', default='16', @@ -322,7 +344,27 @@ class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): continue item = self.socket_collection.add() item.node_name = n.name - item.socket_name = item.name = o.name + item.node_label = n.label.strip() + item.socket_name = o.name + + ## Set editable names + item.name = o.name + + ## Store other naming options (cleaned at exec with bpy.path.clean_name) + node_name = n.label.strip() if n.label.strip() else n.name + + ## Change node_name for render layers: scene_viewlayer_name + if n.type == 'R_LAYERS' and node_name != n.label: # skip if a label is set + node_name = f'{n.scene.name}_{n.layer}' + + item.name_from_node = node_name + + if len(n.outputs) == 1: + ## Only one output, just pick node name, no need to add socket name + item.name_from_node_and_socket = node_name + else: + item.name_from_node_and_socket = f'{node_name}_{o.name}' + ## TODO: rename item.name according to tamplate pairs in preferences (to add later) if o.is_linked: item.is_linked = True @@ -332,13 +374,15 @@ class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): def draw(self, context): layout = self.layout + box = layout.box() expand_icon = 'DISCLOSURE_TRI_DOWN' if self.show_custom_settings else 'DISCLOSURE_TRI_RIGHT' - layout.prop(self, 'show_custom_settings', emboss=False, icon=expand_icon) + box.prop(self, 'show_custom_settings', emboss=False, icon=expand_icon) ## Settings if self.show_custom_settings: - layout.use_property_split = True - layout.prop(self, 'base_path') - col = layout.column() + box.use_property_split = True + box.row().prop(self, 'name_type', expand=False) + box.prop(self, 'base_path') + col = box.column() col.prop(self, 'file_format') col.prop(self, 'exr_codec') col.row().prop(self, 'color_depth', expand=True) @@ -351,7 +395,16 @@ class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): if item.node_name != current_node_name: current_node_name = item.node_name col.separator() - col.label(text=item.node_name, icon='NODE_SEL') + ## Display node label + name or name only + if item.node_label: + display_node_name = f'{item.node_label} ({item.node_name})' + else: + display_node_name = item.node_name + ## A bit dirty: For render layer node, show render layer-node name. + if display_node_name.startswith('Render Layer'): + rln = context.scene.node_tree.nodes.get(item.node_name) + display_node_name = f'{rln.scene.name}/{rln.layer} ({display_node_name})' + col.label(text=display_node_name, icon='NODE_SEL') row = col.row() if item.is_linked: @@ -362,23 +415,46 @@ class GPEXP_OT_connect_selected_to_file_out(bpy.types.Operator): display_name = item.socket_name if 'crypto' in display_name.lower(): - display_name = f'{display_name} (-> separate 32bit output node)' + display_name = f'{display_name} -> 32bit output node' row.label(text=display_name) row.label(text='', icon='RIGHTARROW') - row.prop(item, 'name', text='') + + if self.name_type == 'socket_name': + row.prop(item, 'name', text='') + elif self.name_type == 'node_name': + row.prop(item, 'name_from_node', text='') + elif self.name_type == 'node_and_socket_name': + row.prop(item, 'name_from_node_and_socket', text='') def execute(self, context): # Build exclude dict from selection excludes = {} remap_names = {} + ## Old system + # if len(self.socket_collection): + # for item in self.socket_collection: + # if not item.select: + # # All deselected goes to exclude with {node_name: [socket1, ...]} + # excludes.setdefault(item.node_name, []).append(item.name) + # elif item.socket_name != item.name: + # remap_names[item.socket_name] = item.name + if len(self.socket_collection): for item in self.socket_collection: + final_name = item.name + ## change name if other options were used + if self.name_type == 'node_name': + final_name = item.name_from_node + elif self.name_type == 'node_and_socket_name': + final_name = item.name_from_node_and_socket + if not item.select: # All deselected goes to exclude with {node_name: [socket1, ...]} - excludes.setdefault(item.node_name, []).append(item.name) - elif item.socket_name != item.name: - remap_names[item.socket_name] = item.name + excludes.setdefault(item.node_name, []).append(item.socket_name) + elif item.socket_name != final_name: + remap_names.setdefault(item.node_name, {})[item.socket_name] = final_name + # remap_names[item.socket_name] = final_name ## Handle default file format file_ext = self.file_format diff --git a/__init__.py b/__init__.py index 9b5cec0..3f94914 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ bl_info = { "name": "GP Render", "description": "Organise export of gp layers through compositor output", "author": "Samuel Bernou", - "version": (1, 8, 12), + "version": (1, 8, 13), "blender": (3, 0, 0), "location": "View3D", "warning": "", diff --git a/fn.py b/fn.py index caf216c..1f8640c 100644 --- a/fn.py +++ b/fn.py @@ -1906,6 +1906,9 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None excludes (dict, optionnal): List of output names to exclude {node_name: [outputs,]}. Defaults toNone. + + remap_names (dict, optionnal): List of output names to remap {node_name: {output_name: new_name}}. + frame (bpy.types.CompositorNode, optional): If given, create nodes into a frame. Defaults to None. @@ -1969,7 +1972,7 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None if next((l for l in o.links if recursive_node_connect_check(l, fo)), None): continue - if remap_names and (custom_name := remap_names.get(o.name)): + if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)): slot_name = bpy.path.clean_name(custom_name) # clean name ? else: slot_name = bpy.path.clean_name(o.name) @@ -2032,7 +2035,8 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None if next((l for l in o.links if recursive_node_connect_check(l, fo)), None): continue - if remap_names and (custom_name := remap_names.get(o.name)): + # if remap_names and (custom_name := remap_names.get(o.name)): + if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)): slot_name = bpy.path.clean_name(custom_name) # clean name ? else: slot_name = bpy.path.clean_name(o.name) # directly use name in multi layer exr