diff --git a/setup_outputs.py b/setup_outputs.py deleted file mode 100755 index 886cfd0..0000000 --- a/setup_outputs.py +++ /dev/null @@ -1,421 +0,0 @@ -import bpy -import os -import re -from . import fn -from bpy.props import (StringProperty, - BoolProperty, - EnumProperty, - CollectionProperty) - -from .constant import TECH_PASS_KEYWORDS - - -# region Search and replace -## -- Search and replace to batch rename item in collection property - -class RT_OT_colprop_search_and_replace(bpy.types.Operator): - bl_idname = "rt.colprop_search_and_replace" - bl_label = "Search And Replace" - bl_description = "Search/Replace texts" - bl_options = {"REGISTER", "INTERNAL"} - - ## target to affect - data_path: StringProperty(name="Data Path", description="Path to collection prop to affect", default="") - target_prop: StringProperty(name="Target Prop", description="Name of the property to affect in whole collection", default="") - - ## Search and replace options - find: StringProperty(name="Find", description="Name to replace", default="", maxlen=0, options={'HIDDEN'}, subtype='NONE') - replace: StringProperty(name="Repl", description="New name placed", default="", maxlen=0, options={'HIDDEN'}, subtype='NONE') - prefix: BoolProperty(name="Prefix Only", description="Affect only prefix of name (skipping names without separator)", default=False) - use_regex: BoolProperty(name="Regex", description="Use regular expression (advanced), equivalent to python re.sub()", default=False) - - separator: StringProperty(name="Separator", description="Separator to get prefix", default='_') - # selected: BoolProperty(name="Selected Only", description="Affect only selection", default=False) - - def rename(self, source): - if not self.find: - return - - old = source - if self.use_regex: - new = re.sub(self.find, self.replace, source) - if old != new: - return new - return - - if self.prefix: - if not self.separator in source: - # Only if separator exists - return - splited = source.split(self.separator) - prefix = splited[0] - new_prefix = prefix.replace(self.find, self.replace) - if prefix != new_prefix: - splited[0] = new_prefix - return self.separator.join(splited) - - else: - new = source.replace(self.find, self.replace) - if old != new: - return new - - def execute(self, context): - ## Get the collection prop from data path - collection_prop = eval(self.data_path) - - count = 0 - for item in collection_prop: - ## Get the target prop - name = getattr(item, self.target_prop) - # prop = prop.replace(self.find, self.replace) - new = self.rename(name) - - if new is None or name == new: - continue - - ## Rename in case of difference - print(f'rename: {name} --> {new}') - setattr(item, self.target_prop, new) - count += 1 - - if count: - mess = str(count) + ' rename(s)' - self.report({'INFO'}, mess) - else: - self.report({'WARNING'}, 'Nothing changed') - return{'FINISHED'} - - def invoke(self, context, event): - return context.window_manager.invoke_props_dialog(self) - - def draw(self, context): - layout = self.layout - row = layout.row() - # row_a= row.row() - # row_a.prop(self, "separator") - # # row_a.prop(self, "selected") - - row_b= row.row() - row_b.prop(self, "prefix") - row_c= row.row() - - row_c.prop(self, "use_regex") - # row_a.active = not self.use_regex - row_b.active = not self.use_regex - - layout.prop(self, "find") - layout.prop(self, "replace") - -# endregion - -# region Create file output - -## -- properties and operator for file output connect - -class RT_PG_selectable_prop(bpy.types.PropertyGroup): - node_name: StringProperty(name="Node Name") - node_label: StringProperty(name="Node Label") - name: StringProperty(name="Name or Path") - socket_name: StringProperty(name="Source socket Name") # Source socket name as reference - select: BoolProperty(name="Selected", default=True) - is_linked: BoolProperty(name="Linked", default=False) - # is_valid: BoolProperty(name="Valid", default=True) - - ## extra output naming options - name_from_node: StringProperty(name="Name From Node") - name_from_node_and_socket: StringProperty(name="Name From Node And Socket") - name_from_node_and_socket_with_scene: StringProperty(name="Name From Node And Socket With Scene prefixed") - -class RT_OT_create_output_layers(bpy.types.Operator): - bl_idname = "rt.create_output_layers" - bl_label = "Create Output Layers" - bl_description = "Connect Selected Nodes to a new file output node\ - \nIf an existing file output node is selected, socket are added to it" - bl_options = {"REGISTER", "UNDO"} - - ## ! collection prop -> Now stored on window manager at invoke - # socket_collection : CollectionProperty(type=RT_PG_selectable_prop) - - show_custom_settings : BoolProperty( - name='Settings', - default=False) - - split_tech_passes : BoolProperty( - name='Separate Tech Passes', - default=True, - description='Create a separate file output for technical passes (32 bit)') - - ## enum choice for naming: socket_name, node_name, node_and_socket_name, - name_type : 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), - ('node_and_socket_name_with_scene', 'Scene + Node Socket Name', 'Use the node name prefix and socket name, prefix scene with render layer nodes', 1), - ('socket_name', 'Socket Name', 'Use the socket name as output name', 2), - ('node_name', 'Node Name', 'Use the node name as output name', 3), - ) - ) - - # prefix_with_node_name : BoolProperty( - # name='Prefix With Node Name', - # description='Add the node name as prefix to the output name', - # default=False) - - base_path : StringProperty( - name='Custom base path', - default='', - description='Set the base path of created file_output (not if already exists)') - - file_format : 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 : 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 : 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): - if not hasattr(context.window_manager, 'rt_socket_collection'): - ## Create collection prop on window manager if not existing - ## (not stored in self anymore, so that other operators can access it, ex: search and replace) - bpy.types.WindowManager.rt_socket_collection = CollectionProperty(type=RT_PG_selectable_prop) - self.socket_collection = context.window_manager.rt_socket_collection - - 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 - scene_node_name = node_name # same by default - - if n.type == 'R_LAYERS': - scene_node_name = f'{n.scene.name}_{n.layer}' - - ## 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}' - - 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 - item.name_from_node_and_socket_with_scene = scene_node_name - else: - print(f'node_name: {node_name} VS {scene_node_name}') - item.name_from_node_and_socket = f'{node_name}_{o.name}' - item.name_from_node_and_socket_with_scene = f'{scene_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) - col.prop(self, 'split_tech_passes') - - search_row = layout.row() - op = search_row.operator("rt.colprop_search_and_replace", icon='BORDERMOVE') - op.data_path = 'bpy.context.window_manager.rt_socket_collection' - if self.name_type == 'socket_name': - op.target_prop = 'name' - elif self.name_type == 'node_name': - op.target_prop = 'name_from_node' - elif self.name_type == 'node_and_socket_name': - op.target_prop = 'name_from_node_and_socket' - elif self.name_type == 'node_and_socket_name_with_scene': - op.target_prop = 'name_from_node_and_socket_with_scene' - - ## 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' - elif self.split_tech_passes and display_name.lower() in TECH_PASS_KEYWORDS: - display_name = f'{display_name} -> Tech pass' - - 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='') - elif self.name_type == 'node_and_socket_name_with_scene': - row.prop(item, 'name_from_node_and_socket_with_scene', 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 - elif self.name_type == 'node_and_socket_name_with_scene': - final_name = item.name_from_node_and_socket_with_scene - - 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, - split_tech_passes=self.split_tech_passes) - - return {"FINISHED"} - -# endregion - -classes=( -RT_OT_colprop_search_and_replace, -RT_PG_selectable_prop, -RT_OT_create_output_layers, -) - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - -def unregister(): - for cls in reversed(classes): - bpy.utils.unregister_class(cls) \ No newline at end of file