first commit, transfer connect selected to file out from gp_render
This commit is contained in:
		
							parent
							
								
									016d3f827c
								
							
						
					
					
						commit
						d56d32ad57
					
				| @ -1,5 +1,6 @@ | ||||
| # git_template | ||||
| A Template from which to create new repository from. | ||||
| # Render Toolbox | ||||
| 
 | ||||
| Blender addon for rendering setup and checks | ||||
| 
 | ||||
| ## Development | ||||
| ### Cloning | ||||
|  | ||||
							
								
								
									
										50
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| bl_info = { | ||||
|     "name": "Render Toolbox", | ||||
|     "description": "Perform checks and setup outputs", | ||||
|     "author": "Samuel Bernou", | ||||
|     "version": (0, 1, 0), | ||||
|     "blender": (3, 0, 0), | ||||
|     "location": "View3D", | ||||
|     "warning": "", | ||||
|     "doc_url": "https://git.autourdeminuit.com/autour_de_minuit/render_toolbox", | ||||
|     "tracker_url": "https://git.autourdeminuit.com/autour_de_minuit/render_toolbox/issues", | ||||
|     "category": "Object" | ||||
| } | ||||
| 
 | ||||
| ## TODO: | ||||
| ## Transfer useful and generic render function from gp_render: | ||||
| # - mute/unmute all file output nodes | ||||
| # - export crop infos to json (hardcoded path in gp_render. make an export dialog instead, with eventually an environnement variable for the path) | ||||
| # - Viewlayer list view | ||||
| 
 | ||||
| ## Transfer useful and genereric render stuff from gp_toolbox: | ||||
| # - ? Conflict visibility checker ? This also fit other tasks, but more often use to debug at precomp/rendering step. | ||||
| 
 | ||||
| ## Improve existing: | ||||
| # - Connect to file output node should have the option to add scene name as output prefix | ||||
| # - Connect to file output node should have search and replace options for output names in window | ||||
| # - Connect to file output node should not remove unlinked output by defaut (should be an option in window) | ||||
| 
 | ||||
| 
 | ||||
| from . import setup_outputs | ||||
| from . import ui | ||||
| 
 | ||||
| bl_modules = ( | ||||
|     setup_outputs, | ||||
|     ui, | ||||
|     # prefs, | ||||
| ) | ||||
| 
 | ||||
| import bpy | ||||
| 
 | ||||
| def register(): | ||||
|     for mod in bl_modules: | ||||
|         mod.register() | ||||
| 
 | ||||
| 
 | ||||
| def unregister(): | ||||
|     for mod in reversed(bl_modules): | ||||
|         mod.unregister() | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     register() | ||||
							
								
								
									
										237
									
								
								fn.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								fn.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,237 @@ | ||||
| import bpy | ||||
| import os | ||||
| import re | ||||
| import json | ||||
| 
 | ||||
| ### --- Manage nodes --- ### | ||||
| 
 | ||||
| def real_loc(n): | ||||
|     if not n.parent: | ||||
|         return n.location | ||||
|     return n.location + real_loc(n.parent) | ||||
| 
 | ||||
| def set_file_output_format(fo): | ||||
|     """Default fileout format for output file node | ||||
|     Get from OUTPUT_RENDER_FILE_FORMAT environment variable | ||||
|     else fallback to multilayer EXR | ||||
|     """ | ||||
| 
 | ||||
|     env_file_format = json.loads(os.environ.get('OUTPUT_RENDER_FILE_FORMAT', '{}')) | ||||
|     if not env_file_format: | ||||
|         env_file_format = { | ||||
|             'file_format': 'OPEN_EXR_MULTILAYER', | ||||
|             'exr_codec': 'ZIP', | ||||
|             'color_depth': '16', | ||||
|             'color_mode': 'RGBA' | ||||
|         } | ||||
| 
 | ||||
|     for k, v in env_file_format.items(): | ||||
|         setattr(fo.format, k, v) | ||||
| 
 | ||||
| def clear_disconnected(fo): | ||||
|     '''Remove unlinked inputs from file output node''' | ||||
|     for inp in reversed(fo.inputs): | ||||
|         if not inp.is_linked: | ||||
|             print(f'Deleting unlinked fileout slot: {inp.name}') | ||||
|             fo.inputs.remove(inp) | ||||
| 
 | ||||
| def create_node(type, tree=None, **kargs): | ||||
|     '''Get a type, a tree to add in, and optionnaly multiple attribute to set | ||||
|     return created node | ||||
|     ''' | ||||
|     tree = tree or bpy.context.scene.node_tree | ||||
| 
 | ||||
|     node = tree.nodes.new(type) | ||||
|     for k,v in kargs.items(): | ||||
|         setattr(node, k, v) | ||||
| 
 | ||||
|     return node | ||||
| 
 | ||||
| def recursive_node_connect_check(l, target_node): | ||||
|     '''Get a link and a node     | ||||
|     return True if link is connected to passed target_node further in the node tree | ||||
|     ''' | ||||
|     if l.to_node == target_node: | ||||
|         return True     | ||||
|     for o in l.to_node.outputs: | ||||
|         for sl in o.links: | ||||
|             if recursive_node_connect_check(sl, target_node): | ||||
|                 return True | ||||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None, remap_names=None, file_format=None): | ||||
|     """Connect selected nodes output to file output(s) | ||||
|     if a file output is selected, add intputs on it | ||||
| 
 | ||||
|     Args: | ||||
|         node_list (list[bpy.types.Nodes,]): Nodes to connect | ||||
|      | ||||
|         file_out (bpy.types.CompositorNode, optional): File output node to connect to instead of new | ||||
|             Defaults to None | ||||
|         base_path (str, optional): Directory of images to render. | ||||
|             if not passed, will use source node layer name | ||||
|             Defaults to ''. | ||||
| 
 | ||||
|         file_format (dict, optionnal): converts each dictionary key into a file output format  | ||||
|             attribute and assigns the corresponding value. | ||||
|             Defaults to 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. | ||||
| 
 | ||||
|     Returns: | ||||
|         list[bpy.types.CompositorNode]: All nodes created. | ||||
|     """ | ||||
| 
 | ||||
|     scene = bpy.context.scene | ||||
|     nodes = scene.node_tree.nodes | ||||
|     links = scene.node_tree.links | ||||
|     if not isinstance(node_list, list): | ||||
|         node_list = [node_list] | ||||
|     node_list = [n for n in node_list if n.type != 'OUTPUT_FILE'] | ||||
|     if not node_list: | ||||
|         return | ||||
| 
 | ||||
|     excludes = excludes or {} | ||||
| 
 | ||||
|     for node in node_list: | ||||
|         exclusions = excludes.get(node.name) or [] | ||||
|         ## create one output facing node and connect all | ||||
|         outs = [o for o in node.outputs if not o.is_unavailable and not 'crypto' in o.name.lower() and o.name not in exclusions] | ||||
|         cryptout = [o for o in node.outputs if not o.is_unavailable and 'crypto' in o.name.lower() and o.name not in exclusions] | ||||
|          | ||||
|         if node.type == 'R_LAYERS': | ||||
|             out_base = node.layer | ||||
|         elif node.label: | ||||
|             out_base = node.label | ||||
|         else: | ||||
|             out_base = node.name | ||||
|         out_base = bpy.path.clean_name(out_base) | ||||
|         out_name = f'OUT_{out_base}' | ||||
| 
 | ||||
|         if outs: | ||||
|             fo = file_out | ||||
|             if not fo: | ||||
|                 fo = nodes.get(out_name) | ||||
|             if not fo: | ||||
|                 # color = (0.2,0.3,0.5) | ||||
|                 fo = create_node('CompositorNodeOutputFile', tree=scene.node_tree, location=(real_loc(node)[0]+500, real_loc(node)[1]+50), width=600) | ||||
|                 fo.inputs.remove(fo.inputs[0]) # Remove default image input | ||||
|                 if file_format: | ||||
|                     for k, v in file_format.items(): | ||||
|                         setattr(fo.format, k, v) | ||||
|                 else: | ||||
|                     set_file_output_format(fo) | ||||
| 
 | ||||
|                 fo.name = out_name | ||||
|                 if node.parent: | ||||
|                     fo.parent = node.parent | ||||
| 
 | ||||
|                 if base_path: | ||||
|                     fo.base_path = base_path | ||||
|                 else: | ||||
|                     if fo.format.file_format == 'OPEN_EXR_MULTILAYER': | ||||
|                         fo.base_path = f'//render/{out_base}/{out_base}_' | ||||
|                     else: | ||||
|                         fo.base_path = f'//render/{out_base}' | ||||
| 
 | ||||
|             for o in outs: | ||||
|                 if next((l for l in o.links if recursive_node_connect_check(l, fo)), None): | ||||
|                     continue | ||||
|                  | ||||
|                 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) | ||||
| 
 | ||||
|                 # if fo.format.file_format == 'OPEN_EXR_MULTILAYER': | ||||
|                 #     slot_name = slot_name | ||||
|                 # else: | ||||
|                 #     slot_name = f'{slot_name}/{slot_name}_' | ||||
|                 # fo.file_slots.new(slot_name) | ||||
|                 fs = fo.file_slots.new('tmp') # slot_name) | ||||
|                 ls = fo.layer_slots.new('tmp') # slot_name + 'layer') | ||||
| 
 | ||||
|                 ls = fo.layer_slots[-1] | ||||
|                 ls.name = slot_name | ||||
| 
 | ||||
|                 fs = fo.file_slots[-1] | ||||
|                 fs.path = f'{slot_name}/{slot_name}_' # Error 'NodeSocketColor' object has no attribute 'path' | ||||
|                  | ||||
| 
 | ||||
|                 out_input = fo.inputs[-1] | ||||
|                 links.new(o, out_input) | ||||
| 
 | ||||
|             clear_disconnected(fo) | ||||
|             fo.update() | ||||
| 
 | ||||
|         ## Create separate file out for cryptos | ||||
|         if cryptout: | ||||
|             out_name += '_cryptos' | ||||
|             fo = file_out | ||||
|             if not fo: | ||||
|                 fo = nodes.get(out_name) | ||||
|             if not fo: | ||||
|                 # color = (0.2,0.3,0.5) | ||||
|                 fo = create_node('CompositorNodeOutputFile', tree=scene.node_tree, location=(real_loc(node)[0]+400, real_loc(node)[1]-200), width=220) | ||||
|                 fo.inputs.remove(fo.inputs[0]) # Remove default image input | ||||
|                 if file_format: | ||||
|                     for k, v in file_format.items(): | ||||
|                         setattr(fo.format, k, v) | ||||
|                 else: | ||||
|                     set_file_output_format(fo) # OPEN_EXR_MULTILAYER, RGBA, ZIP | ||||
|                 fo.format.color_depth = '32' # For crypto force 32bit | ||||
|                  | ||||
|                 fo.name = out_name | ||||
|                 if node.parent: | ||||
|                     fo.parent = node.parent | ||||
| 
 | ||||
|                 if base_path: | ||||
|                     fo.base_path = base_path | ||||
|                 else: | ||||
|                     if fo.format.file_format == 'OPEN_EXR_MULTILAYER': | ||||
|                         ## FIXME: find a better organization for separated crypto pass | ||||
|                         fo.base_path = f'//render/{out_base}/cryptos/cryptos_' | ||||
|                     else: | ||||
|                         fo.base_path = f'//render/{out_base}' | ||||
| 
 | ||||
|             for o in cryptout: | ||||
|                 ## Skip already connected | ||||
|                 ## TODO Test recusively to find fo (some have interconnected sockets) | ||||
|                 # if next((l for l in o.links if l.to_node == fo), 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) # directly use name in multi layer exr | ||||
| 
 | ||||
|                 # if fo.format.file_format == 'OPEN_EXR_MULTILAYER': | ||||
|                 #     slot_name = slot_name | ||||
|                 # else: | ||||
|                 #     slot_name = f'{slot_name}/{slot_name}_' | ||||
|                 # fo.file_slots.new(slot_name) | ||||
|                  | ||||
|                 # Setting both file_slots and layer_slots... | ||||
|                 fs = fo.file_slots.new('tmp') | ||||
|                 ls = fo.layer_slots.new('tmp') | ||||
| 
 | ||||
|                 ls = fo.layer_slots[-1] | ||||
|                 ls.name = slot_name | ||||
| 
 | ||||
|                 fs = fo.file_slots[-1] | ||||
|                 fs.path = f'{slot_name}/{slot_name}_' # Error 'NodeSocketColor' object has no attribute 'path' | ||||
|                  | ||||
| 
 | ||||
|                 out_input = fo.inputs[-1] | ||||
|                 links.new(o, out_input) | ||||
|             clear_disconnected(fo) | ||||
|             fo.update() | ||||
							
								
								
									
										259
									
								
								setup_outputs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								setup_outputs.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,259 @@ | ||||
| import bpy | ||||
| import os | ||||
| from . import fn | ||||
| 
 | ||||
| class RT_PG_selectable_prop(bpy.types.PropertyGroup): | ||||
|     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 RT_OT_connect_selected_to_file_out(bpy.types.Operator): | ||||
|     bl_idname = "rt.connect_selected_to_file_out" | ||||
|     bl_label = "Connect Selected To File Output" | ||||
|     bl_description = "Connect Selected Nodes to a new fileoutput node\ | ||||
|         \nIf a fileoutput node is selected, socket are added to it" | ||||
|     bl_options = {"REGISTER", "UNDO"} | ||||
| 
 | ||||
|     socket_collection : bpy.props.CollectionProperty(type=RT_PG_selectable_prop) | ||||
| 
 | ||||
|     show_custom_settings : bpy.props.BoolProperty( | ||||
|         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='', | ||||
|         description='Set the base path of created file_output (not if already exists)') | ||||
|      | ||||
|     file_format : bpy.props.EnumProperty( | ||||
|         name='Output Format', | ||||
|         default='NONE', | ||||
|         items=( | ||||
|                 ('NONE', 'Default', 'Use default settings'), | ||||
|                 ('OPEN_EXR_MULTILAYER', 'OpenEXR MultiLayer', 'Output image in multilayer OpenEXR format'), | ||||
|                 ('OPEN_EXR', 'OpenEXR', 'Output image in OpenEXR format'), | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|     exr_codec : bpy.props.EnumProperty( | ||||
|         name='Codec', | ||||
|         default='PIZ', | ||||
|         description='Codec settings for OpenEXR', | ||||
|         items=( | ||||
|             ('PIZ', 'PIZ (lossless)', ''), | ||||
|             ('ZIP', 'ZIP (lossless)', ''), | ||||
|             ('RLE', 'RLE (lossless)', ''), | ||||
|             ('ZIPS', 'ZIPS (lossless)', ''), | ||||
|             ('PXR24', 'Pxr24 (lossy)', ''), | ||||
|             ('B44', 'B44 (lossy)', ''), | ||||
|             ('B44A', 'B44A (lossy)', ''), | ||||
|             ('DWAA', 'DWAA (lossy)', ''), | ||||
|             ('DWAB', 'DWAB (lossy)', ''), | ||||
|             ), | ||||
|         ) | ||||
|      | ||||
|     color_depth : bpy.props.EnumProperty( | ||||
|         name='Color Depth', | ||||
|         default='16', | ||||
|         description='Bit depth per channel', | ||||
|         items=( | ||||
|                 # ('8', '8', '8-bit color channels'), | ||||
|                 # ('10', '10', '10-bit color channels'), | ||||
|                 # ('12', '12', '12-bit color channels'), | ||||
|                 ('16', '16 (Half)', '16-bit color channels'), | ||||
|                 ('32', '32 (Full)', '32-bit color channels'), | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|     def invoke(self, context, event): | ||||
|         self.socket_collection.clear() | ||||
|         if event.ctrl: | ||||
|             # Direct connect, do not use any options | ||||
|             self.base_path = '' | ||||
|             return self.execute(context) | ||||
| 
 | ||||
|         selected = [n for n in context.scene.node_tree.nodes if n.select and n.type != 'OUTPUT_FILE'] | ||||
|         if not selected: | ||||
|             self.report({'ERROR'}, 'No render layer nodes selected') | ||||
|             return {'CANCELLED'} | ||||
|         for n in selected: | ||||
|             for o in n.outputs: | ||||
|                 if o.is_unavailable: | ||||
|                     continue | ||||
|                 item = self.socket_collection.add() | ||||
|                 item.node_name = n.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.layer}' | ||||
|                     # 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 template pairs in preferences (to add later) | ||||
|                 if o.is_linked: | ||||
|                     item.is_linked = True | ||||
|                     item.select = False | ||||
| 
 | ||||
|         return context.window_manager.invoke_props_dialog(self, width=500) | ||||
| 
 | ||||
|     def draw(self, context): | ||||
|         layout = self.layout | ||||
|         box = layout.box() | ||||
|         expand_icon = 'DISCLOSURE_TRI_DOWN' if self.show_custom_settings else 'DISCLOSURE_TRI_RIGHT' | ||||
|         box.prop(self, 'show_custom_settings', emboss=False, icon=expand_icon) | ||||
|         ## Settings | ||||
|         if self.show_custom_settings: | ||||
|             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) | ||||
| 
 | ||||
|         ## Node Sockets | ||||
|         layout.use_property_split = False | ||||
|         col = layout.column() | ||||
|         current_node_name = '' | ||||
|         for item in self.socket_collection: | ||||
|             if item.node_name != current_node_name: | ||||
|                 current_node_name = item.node_name | ||||
|                 col.separator() | ||||
|                 ## 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: | ||||
|                 row.label(text='', icon='LINKED') # NODETREE | ||||
|             else: | ||||
|                 row.label(text='', icon='BLANK1') | ||||
|             row.prop(item, 'select', text='') | ||||
| 
 | ||||
|             display_name = item.socket_name | ||||
|             if 'crypto' in display_name.lower(): | ||||
|                 display_name = f'{display_name} -> 32bit output node' | ||||
|             row.label(text=display_name) | ||||
|             row.label(text='', icon='RIGHTARROW') | ||||
| 
 | ||||
|             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.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 | ||||
|         if self.file_format == 'NONE': | ||||
|             env_file_format = os.environ.get('FILE_FORMAT') | ||||
|             file_ext = env_file_format if env_file_format else 'OPEN_EXR_MULTILAYER' | ||||
| 
 | ||||
|         file_format = { | ||||
|             'file_format' : file_ext, | ||||
|             'exr_codec' : self.exr_codec, | ||||
|             'color_depth' : self.color_depth, | ||||
|         } | ||||
|          | ||||
|         scn = context.scene | ||||
|         nodes = scn.node_tree.nodes | ||||
|         selected = [n for n in nodes if n.select] | ||||
|         outfile = next((n for n in selected if n.type == 'OUTPUT_FILE'), None) | ||||
|         # Exclude output file from  | ||||
|         selected = [n for n in selected if n.type != 'OUTPUT_FILE']         | ||||
|          | ||||
|         # fn.connect_to_file_output(selected, outfile) | ||||
|         for n in selected: | ||||
|             fn.connect_to_file_output(n, outfile, base_path=self.base_path, excludes=excludes, remap_names=remap_names, file_format=file_format) | ||||
|         return {"FINISHED"} | ||||
| 
 | ||||
| classes=( | ||||
| RT_PG_selectable_prop, | ||||
| RT_OT_connect_selected_to_file_out, | ||||
| ) | ||||
| 
 | ||||
| def register(): | ||||
|     for cls in classes: | ||||
|         bpy.utils.register_class(cls) | ||||
| 
 | ||||
| def unregister(): | ||||
|     for cls in reversed(classes): | ||||
|         bpy.utils.unregister_class(cls) | ||||
							
								
								
									
										27
									
								
								ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								ui.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| import bpy | ||||
| 
 | ||||
| from bpy.types import Panel | ||||
| 
 | ||||
| 
 | ||||
| class RT_PT_gp_node_ui(Panel): | ||||
|     bl_space_type = "NODE_EDITOR" | ||||
|     bl_region_type = "UI" | ||||
|     bl_category = "Render" | ||||
|     bl_label = "Render Toolbox" | ||||
| 
 | ||||
|     def draw(self, context): | ||||
|         layout = self.layout | ||||
|         layout.operator("rt.connect_selected_to_file_out", icon="NODE") | ||||
| 
 | ||||
| 
 | ||||
| classes = ( | ||||
| RT_PT_gp_node_ui, | ||||
| ) | ||||
| 
 | ||||
| def register(): | ||||
|     for cls in classes: | ||||
|         bpy.utils.register_class(cls) | ||||
| 
 | ||||
| def unregister(): | ||||
|     for cls in reversed(classes): | ||||
|         bpy.utils.unregister_class(cls) | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user