diff --git a/__init__.py b/__init__.py index 10c47e4..b247234 100755 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ bl_info = { "name": "Render Toolbox", "description": "Setup outputs and perform cheks for rendering", "author": "Samuel Bernou", - "version": (0, 8, 0), + "version": (1, 0, 0), "blender": (4, 0, 0), "location": "View3D", "warning": "", diff --git a/fn.py b/fn.py index bea7c13..542dc5d 100755 --- a/fn.py +++ b/fn.py @@ -7,6 +7,9 @@ from .constant import TECH_PASS_KEYWORDS from pathlib import Path +def get_addon_prefs(): + return bpy.context.preferences.addons[__package__].preferences + # region Manage nodes def real_loc(n): @@ -208,7 +211,7 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path, def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None, remap_names=None, - file_format=None, split_tech_passes=False, tech_file_format=None, template=None): + file_format=None, tech_pass_names=None, tech_file_format=None, template=None): """Connect selected nodes output to file output(s) if a file output is selected, add intputs on it @@ -230,8 +233,12 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None remap_names (dict, optionnal): List of output names to remap {node_name: {output_name: new_name}}. - split_tech_passes (bool, optional): When True, create a separate file output for technical passes - Defaults to False. + tech_pass_names (list[str], optional): list of of socket names to set as separate file output for technical passes + Defaults to None + + tech_file_format (dict, optionnal): converts each dictionary key into a file output format for tech passes + if not passed, will use file_format. + Defaults to None. Returns: list[bpy.types.CompositorNode]: All nodes created. @@ -278,9 +285,9 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None # Categorize outputs crypto_outputs = [o for o in all_outputs if 'crypto' in o.name.lower()] - if split_tech_passes: + if tech_pass_names: # Filter tech passes - tech_outputs = [o for o in all_outputs if o.name.lower() in TECH_PASS_KEYWORDS] # any(keyword in o.name.lower() for keyword in TECH_PASS_KEYWORDS)] + tech_outputs = [o for o in all_outputs if o.name.lower() in tech_pass_names] # TECH_PASS_KEYWORDS # Regular outputs (excluding crypto and tech passes) regular_outputs = [o for o in all_outputs if o not in crypto_outputs and o not in tech_outputs] else: diff --git a/operators/conform_collection_hierarchy.py b/operators/conform_collection_hierarchy.py index b2a6711..916e164 100644 --- a/operators/conform_collection_hierarchy.py +++ b/operators/conform_collection_hierarchy.py @@ -282,8 +282,8 @@ class RT_OT_conform_collection_hierarchy(Operator): for _, v in affected_items_dict.items(): for item, attr_list in v.items(): line = f'{item.name} : {", ".join(attr_list)}' - print(line) - message.append(line) + print(line) + message.append(line) fn.show_message_box( message=message, diff --git a/operators/output_search_and_replace.py b/operators/output_search_and_replace.py index 28ebd61..708f25e 100644 --- a/operators/output_search_and_replace.py +++ b/operators/output_search_and_replace.py @@ -7,9 +7,6 @@ from bpy.props import (StringProperty, EnumProperty, CollectionProperty) -from ..constant import TECH_PASS_KEYWORDS - - class RT_OT_outputs_search_and_replace(bpy.types.Operator): bl_idname = "rt.outputs_search_and_replace" bl_label = "Search And Replace Outputs Paths" diff --git a/operators/output_setup.py b/operators/output_setup.py index d929a4e..52817be 100755 --- a/operators/output_setup.py +++ b/operators/output_setup.py @@ -122,6 +122,7 @@ class RT_PG_selectable_prop(bpy.types.PropertyGroup): 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_tech_pass: BoolProperty(name="Tech Pass", default=False) # is_valid: BoolProperty(name="Valid", default=True) ## Specific to render layer nodes @@ -337,6 +338,14 @@ class RT_OT_create_output_layers(bpy.types.Operator): # self.blend_name = Path(self.blend_name).stem # self.version = fn.get_rightmost_number_in_string(self.blend_name) + ## tech_passes + prefs = fn.get_addon_prefs() + tech_passes_names = prefs.tech_passes_names + if prefs.use_env_technical_passes: + tech_passes_names = os.getenv('RENDERTOOLBOX_TECH_PASSES', prefs.tech_passes_names) + + tech_passes_names = [tp.strip().lower() for tp in tech_passes_names.split(',') if tp.strip()] + 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') @@ -381,6 +390,10 @@ class RT_OT_create_output_layers(bpy.types.Operator): if o.is_linked: item.is_linked = True item.select = False + + if item.socket_name.lower() in tech_passes_names: + item.is_tech_pass = True + ## Assign default template values (Disabled for now) ## TRIGGER update to firstly fill template slot/names @@ -401,7 +414,7 @@ class RT_OT_create_output_layers(bpy.types.Operator): self.scene_name = ref.scene.name self.view_layer_name = ref.layer self.final_base_path_template = '' - return context.window_manager.invoke_props_dialog(self, width=500) + return context.window_manager.invoke_props_dialog(self, width=530) def draw(self, context): layout = self.layout @@ -483,8 +496,12 @@ Possible variables: col.prop(self, 'exr_codec') col.row().prop(self, 'color_depth', expand=True) col.prop(self, 'split_tech_passes') - if self.split_tech_passes: - col.prop(self, 'tech_exr_codec', text='Tech Passes Codec') + + row = col.row() + row.prop(self, 'tech_exr_codec', text='Tech Passes Codec') + row.active = self.split_tech_passes + # if self.split_tech_passes: + # col.prop(self, 'tech_exr_codec', text='Tech Passes Codec') search_row = layout.row() op = search_row.operator("rt.colprop_search_and_replace", icon='BORDERMOVE') @@ -520,6 +537,7 @@ Possible variables: col.label(text=display_node_name, icon='NODE_SEL') row = col.row() + row.active = item.select if item.is_linked: row.label(text='', icon='LINKED') # NODETREE else: @@ -529,7 +547,7 @@ Possible variables: 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: + elif self.split_tech_passes and item.is_tech_pass: display_name = f'{display_name} -> Tech pass' row.label(text=display_name) @@ -546,6 +564,9 @@ Possible variables: elif self.name_type == 'template': row.prop(item, 'name_from_template', text='') + if self.split_tech_passes: + row.prop(item, 'is_tech_pass', text='') + def execute(self, context): # Build exclude dict from selection @@ -560,6 +581,8 @@ Possible variables: # elif item.socket_name != item.name: # remap_names[item.socket_name] = item.name + tech_pass_names = [] + if len(self.socket_collection): for item in self.socket_collection: final_name = item.name @@ -580,6 +603,9 @@ Possible variables: remap_names.setdefault(item.node_name, {})[item.socket_name] = final_name # remap_names[item.socket_name] = final_name + if self.split_tech_passes and item.is_tech_pass: + tech_pass_names.append(item.socket_name.lower()) + ## Handle default file format file_ext = self.file_format if self.file_format == 'NONE': @@ -598,14 +624,14 @@ Possible variables: 'exr_codec' : self.tech_exr_codec, 'color_depth' : '32', } - + 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( @@ -615,7 +641,7 @@ Possible variables: excludes=excludes, remap_names=remap_names, file_format=file_format, - split_tech_passes=self.split_tech_passes, + tech_pass_names=tech_pass_names, tech_file_format=tech_file_format, template=self.final_base_path_template) diff --git a/preferences.py b/preferences.py index 648ceae..13847dd 100644 --- a/preferences.py +++ b/preferences.py @@ -18,11 +18,12 @@ from bpy.app.handlers import persistent class RT_prefs(bpy.types.AddonPreferences): bl_idname = __package__ + ## path templates use_env_base_path_templates : BoolProperty( name="Use Environment Base Path Templates", description="Use environmenet variables to set base path for file output templates\ \nFollowing env variable are available:\ - \nRENDERTOOLBOX_EXR_PATH_TEMPLATE fo single base path file output\ + \nRENDERTOOLBOX_EXR_PATH_TEMPLATE for single base path file output\ \nRENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE for multilayer base path file output", default=True, ) @@ -39,24 +40,74 @@ class RT_prefs(bpy.types.AddonPreferences): default="//render/{node_label}/{node_label}_", ) + ## Tech passes + use_env_technical_passes : BoolProperty( + name="Use Environment Technical Passes", + description="Use environmenet variables to set the technical passes name:\ + \nRENDERTOOLBOX_TECH_PASSES", + default=True, + ) + + tech_passes_names : StringProperty( + name="Tech Passes Names", + description="Comma separated list of tech passes names to use for file output creation (Use lossless EXR 32bit)", + default="uv, normal, depth, position, vector, ao", + ) + def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False - layout.prop(self, "use_env_base_path_templates", text="Use Environment Base Path Templates") col = layout.column() + col.label(text="Output files path templates:") + col.prop(self, "use_env_base_path_templates", text="Use Environment Base Path Templates") # col.active = not self.use_env_base_path_templates col.prop(self, "base_path_template", text="Base Path Template") col.prop(self, "base_path_multilayer_template", text="Base Path Multilayer Template") if self.use_env_base_path_templates: - col.separator() - col.label(text="Environment variables will override above templates if set", icon='INFO') - # col.label(text="RENDERTOOLBOX_EXR_PATH_TEMPLATE for single base path file output") - # col.label(text="RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE for multilayer base path file output") - col.label(text='If environment variables are not found, preferences will be used instead') + box=col.box() + boxcol = box.column() + env_path_template = os.getenv('RENDERTOOLBOX_EXR_PATH_TEMPLATE') + env_multi_template = os.getenv('RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE') + + if not env_path_template or not env_multi_template: + boxcol.label(text="Environment variables override above templates if set", icon='INFO') + + if env_path_template or env_multi_template: + boxcol.label(text="Environment variables found:", icon='CHECKMARK') + + if env_path_template: + boxcol.label(text=f"RENDERTOOLBOX_EXR_PATH_TEMPLATE:") + boxcol.label(text=env_path_template) + else: + boxcol.label(text="RENDERTOOLBOX_EXR_PATH_TEMPLATE env not found, using preferences ") # (single base path file output) + + if env_multi_template: + boxcol.label(text=f"RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE:") + boxcol.label(text=env_multi_template) + else: + boxcol.label(text="RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE env not found, using preferences") # (multilayer base path file output) + + layout.separator() + col = layout.column() + col.label(text="Default Separated Technical Passes:") + col.prop(self, "use_env_technical_passes", text="Use Environment Technical Passes") + col.prop(self, "tech_passes_names", text="Technical Passes Names", placeholder="e.g. uv, normal, depth, vector, ...") + if self.use_env_technical_passes: + box=col.box() + boxcol = box.column() + + env_tech_pass = os.getenv('RENDERTOOLBOX_TECH_PASSES') + if env_tech_pass: + boxcol.label(text="Environment variable found:", icon='CHECKMARK') + boxcol.label(text=f"RENDERTOOLBOX_TECH_PASSES:") + boxcol.label(text=env_tech_pass) + else: + boxcol.label(text="Environment variable override tech passes names if set", icon='INFO') + boxcol.label(text='RENDERTOOLBOX_TECH_PASSES env not found, using preferences') # region Handlers