From 37a9e0ca43085fa1c7482e953db9e633012de0cf Mon Sep 17 00:00:00 2001 From: pullusb Date: Mon, 21 Jul 2025 16:24:27 +0200 Subject: [PATCH] add template in create output path base node templates for slot are in code but disabled, not useful in the end --- __init__.py | 4 +- fn.py | 146 +++++++++++++++++++++++--- operators/outputs_setup.py | 210 +++++++++++++++++++++++++++++++++++-- operators/utility.py | 40 ++++++- properties.py | 86 +++++++++++++++ ui.py | 49 ++++++++- 6 files changed, 506 insertions(+), 29 deletions(-) create mode 100644 properties.py diff --git a/__init__.py b/__init__.py index 5e94031..4dd9989 100755 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ bl_info = { "name": "Render Toolbox", "description": "Perform checks and setup outputs", "author": "Samuel Bernou", - "version": (0, 4, 0), + "version": (0, 5, 0), "blender": (4, 0, 0), "location": "View3D", "warning": "", @@ -11,10 +11,12 @@ bl_info = { "category": "Render" } +from . import properties from . import operators from . import ui modules = ( + properties, operators, ui, # prefs, diff --git a/fn.py b/fn.py index e7b81bc..30dc2b6 100755 --- a/fn.py +++ b/fn.py @@ -4,6 +4,7 @@ import re import json from .constant import TECH_PASS_KEYWORDS +from pathlib import Path # region Manage nodes @@ -62,9 +63,11 @@ def recursive_node_connect_check(l, target_node): return False +# region create and connect file output + def create_and_connect_file_output(node, outputs, file_out, out_name, base_path, out_base, scene, remap_names, file_format, suffix='', color_depth=None, - location_offset=(500, 50)): + location_offset=(500, 50), base_path_template=None, use_slot_template=False): """Helper function to create file output node and connect it to outputs Args: @@ -112,13 +115,31 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path, if node.parent: fo.parent = node.parent - if base_path: - fo.base_path = base_path + if base_path_template is not None: + fo.base_path = build_path_from_template(base_path_template, + node_name=node.name.strip(), + node_label=node.label.strip(), + scene_name=node.scene.name if node.type == 'R_LAYERS' else None, + viewlayer_name=node.layer if node.type == 'R_LAYERS' else None, + ) + if suffix: + if fo.format.file_format == 'OPEN_EXR_MULTILAYER': + ## insert suffix right after last slash as folder + path_list = re.split('(\\/)', fo.base_path) + path_list.insert(-1, suffix) + fo.base_path = ''.join(path_list) + else: + ## Ensure slash separator then add suffix + fo.base_path = fo.base_path.rstrip('\\/') + '/' + suffix else: - if fo.format.file_format == 'OPEN_EXR_MULTILAYER': - fo.base_path = f'//render/{out_base}/{suffix}{out_base}_' + # default behavior without template + if base_path: + fo.base_path = base_path else: - fo.base_path = f'//render/{out_base}/{suffix}' + if fo.format.file_format == 'OPEN_EXR_MULTILAYER': + fo.base_path = f'//render/{out_base}/{suffix}{out_base}_' + else: + fo.base_path = f'//render/{out_base}/{suffix}' for o in outputs: if next((l for l in o.links if recursive_node_connect_check(l, fo)), None): @@ -134,9 +155,11 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path, ls = fo.layer_slots[-1] ls.name = slot_name - fs = fo.file_slots[-1] fs.path = f'{slot_name}/{slot_name}_' + if use_slot_template: + ## Always False (currently never use slot template) + fs.path = slot_name out_input = fo.inputs[-1] links.new(o, out_input) @@ -147,7 +170,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): + file_format=None, split_tech_passes=False, template=None): """Connect selected nodes output to file output(s) if a file output is selected, add intputs on it @@ -234,7 +257,8 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None fo_regular = create_and_connect_file_output( node, regular_outputs, file_out, out_name, base_path, out_base, scene, remap_names, file_format, - location_offset=(500, y_offset) + location_offset=(500, y_offset), + base_path_template=template ) if fo_regular and fo_regular not in created_nodes: created_nodes.append(fo_regular) @@ -249,7 +273,8 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None node, tech_outputs, None, out_name, base_path, out_base, scene, remap_names, tech_file_format, suffix='tech/', color_depth='32', - location_offset=(500, y_offset) + location_offset=(500, y_offset), + base_path_template=template ) if fo_tech and fo_tech not in created_nodes: @@ -266,17 +291,19 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None node, crypto_outputs, None, out_name, base_path, out_base, scene, remap_names, tech_file_format, suffix='cryptos/', color_depth='32', - location_offset=(500, y_offset) + location_offset=(500, y_offset), + base_path_template=template ) if fo_crypto and fo_crypto not in created_nodes: created_nodes.append(fo_crypto) - return created_nodes # endregion +# endregion + # region Utilities @@ -365,3 +392,98 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): if isinstance(_message, str): _message = [_message] bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) + + +def get_rightmost_number_in_string(string) -> str: + """Get the rightmost number passed string + return the number as string, empty string if no number found + """ + res = re.search(r'(\d+)(?!.*\d)', string) + if not res: + return '' + return res.group(1) + + +def build_path_from_template(template: str, + node_name: str=None, + node_label: str=None, + scene_name: str=None, + viewlayer_name: str=None, + socket_name: str=None, + socket_index: str=None, + ) -> str: + """Get a name from a template string + Possible keyword: + {node_name} : name of the node + {node_label} : label of the node (fallback to node_name if not set) + {scene_name} : name of the source render layer node scene + {viewlayer_name} : name of the source render layer viewlayer + {socket_name} : name of source the node socket + {socket_index} : index of the node in the node tree (010, 020, etc.) + + ## Info from Blend + {blend_name} : name of the blend file + {blend_version} : version of the blend file + {date} : current date (YYYYMMDD format) + {time} : current time (HHMMSS format) + """ + + ## note: socket index is a bad idea because the final order is not decided before execute + if not template: + return '' + + # Get blend file info + blend_path = bpy.data.filepath + blend_name = '' + blend_version = '' + + if blend_path: + blend_name = Path(blend_path).stem + blend_version = get_rightmost_number_in_string(blend_name) + + # Get current date and time + import datetime + now = datetime.datetime.now() + current_date = now.strftime("%Y%m%d") + current_time = now.strftime("%H%M%S") + + # Handle node_label to name fallback + if "{node_label}" in template: + if node_label is None or not node_label: + node_label = node_name + + if socket_index: + # Ensure socket_index is formatted as a zero-padded string and increment on base 10 (allow to insert inbetween renders) + socket_index = f"{int(socket_index)*10:03d}" + + def clean_name(name): + # Clean names to be file-system safe + if name is None: + return '' + return bpy.path.clean_name(name) + + # Create a dictionary of all available variables + template_vars = { + 'node_name': clean_name(node_name), + 'node_label': clean_name(node_label), + 'socket_name': clean_name(socket_name), + 'scene_name': clean_name(scene_name), + 'viewlayer_name': clean_name(viewlayer_name), + 'socket_index': socket_index or '', + 'blend_name': clean_name(blend_name), + 'blend_version': blend_version, + 'date': current_date, + 'time': current_time, + } + + # Apply template using format_map to handle missing keys gracefully + try: + applied_template = template.format_map(template_vars) + except KeyError as e: + # If a key is missing, return template with error message + # print(f"Template error: Unknown variable {e}") + return f"Template error: Unknown {e}" + + return applied_template + +# endregion \ No newline at end of file diff --git a/operators/outputs_setup.py b/operators/outputs_setup.py index 789e0eb..c1333e0 100755 --- a/operators/outputs_setup.py +++ b/operators/outputs_setup.py @@ -1,6 +1,9 @@ import bpy import os import re + +from pathlib import Path + from .. import fn from bpy.props import (StringProperty, BoolProperty, @@ -120,11 +123,43 @@ class RT_PG_selectable_prop(bpy.types.PropertyGroup): select: BoolProperty(name="Selected", default=True) is_linked: BoolProperty(name="Linked", default=False) # is_valid: BoolProperty(name="Valid", default=True) + + ## Specific to render layer nodes + scene_name : StringProperty(name="Scene Name", default='') + viewlayer_name : StringProperty(name="ViewLayer Name", default='') + ## 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") + name_from_node_and_socket_with_scene: StringProperty(name="Name From Node And Socket With Scene prefixed") + name_from_template: StringProperty(name="Name From Templates") + ## Individual ? (not transmitted to assignation function currently, only used to build remap name) + # name_from_template: StringProperty(name="Layer Name From Templates") + # slot_from_template: StringProperty(name="File Slot From Templates") + +def update_template_file_slot(self, context): + for item in context.window_manager.rt_socket_collection: + item.name_from_template = fn.build_path_from_template( + self.template_file_slot, + node_name=item.node_name, + node_label=item.node_label, + scene_name=item.scene_name, + viewlayer_name=item.viewlayer_name, + socket_name=item.socket_name, + ) + +def update_template_multilayer_name(self, context): + for item in context.window_manager.rt_socket_collection: + item.name_from_template = fn.build_path_from_template( + self.template_multilayer_name, + node_name=item.node_name, + node_label=item.node_label, + scene_name=item.scene_name, + viewlayer_name=item.viewlayer_name, + socket_name=item.socket_name, + ) class RT_OT_create_output_layers(bpy.types.Operator): bl_idname = "rt.create_output_layers" @@ -150,25 +185,21 @@ class RT_OT_create_output_layers(bpy.types.Operator): 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', + default='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), + # ('template', 'From Template', 'Use custom template for naming', 4), # Disable slot template for now ) ) - # 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', @@ -209,6 +240,68 @@ class RT_OT_create_output_layers(bpy.types.Operator): ), ) + # --- templates + + use_base_path_templates : BoolProperty( + name='Use Base Path Template', + default=True, + description='Use template strings for base path formatting') + + ## -- base path + template_base_path : StringProperty( + name="Base Path Template", + description="Template for file output base path\ + \nFolder containing subsequent filepath", + default="//render/{node_name}/", + # options={'SKIP_SAVE'} + ) + + ## Multilayer base path : output on file instead of folder + template_multilayer_base_path : StringProperty( + name="Base Path Template", + description="Template for multilayer file output base paths\ + \nOutput of the file sequence", + default="//render/{node_name}/{node_name}_", + # options={'SKIP_SAVE'} + ) + + + ## -- slots/layer_names (UNUSED for now) + template_file_slot : StringProperty( + name="File Slot Template", + description="Template for file output file slots\ + \nSubpath added to base path for file sequence", + default="{socket_name}/{socket_name}_", + update=update_template_file_slot, + # options={'SKIP_SAVE'} + ) + + ## Multilayer's layer: layer name in EXR instead of subpath to file + template_multilayer_name : StringProperty( + name="Layer Name Template", + description="Template for multilayer file output layer names\ + \nLayer name in EXR multilayer file", + default="{socket_name}", + update=update_template_multilayer_name, + # options={'SKIP_SAVE'} + ) + + ## extra suffix passes (unused) + # template_tech_pass_name : StringProperty( + # name="Tech Pass Name Template", + # description="Template for tech pass file output layer names\ + # \nLayer name in EXR multilayer file", + # default="tech", + # ) + + # template_crypto_name : StringProperty( + # name="Crypto Pass Name Template", + # description="Template for crypto pass file output layer names\ + # \nLayer name in EXR multilayer file", + # default="crypto", + # ) + + def invoke(self, context, event): if not hasattr(context.window_manager, 'rt_socket_collection'): ## Create collection prop on window manager if not existing @@ -222,6 +315,13 @@ class RT_OT_create_output_layers(bpy.types.Operator): self.base_path = '' return self.execute(context) + ## Store variable usable in templates + # self.blend_name = bpy.data.filepath + # self.version = '' + # if self.blend_name: + # self.blend_name = Path(self.blend_name).stem + # self.version = fn.get_rightmost_number_in_string(self.blend_name) + 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') @@ -244,6 +344,8 @@ class RT_OT_create_output_layers(bpy.types.Operator): if n.type == 'R_LAYERS': scene_node_name = f'{n.scene.name}_{n.layer}' + item.scene_name = n.scene.name + item.viewlayer_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 @@ -256,7 +358,7 @@ class RT_OT_create_output_layers(bpy.types.Operator): 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}') + # 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}' @@ -265,6 +367,25 @@ class RT_OT_create_output_layers(bpy.types.Operator): item.is_linked = True item.select = False + ## Assign default template values (Disabled for now) + ## TRIGGER update to firstly fill template slot/names + # self.template_file_slot = context.scene.render_toolbox.default_file_slot + # self.template_multilayer_name = context.scene.render_toolbox.default_multilayer_name + + ## Replace self base path + # self.template_base_path = context.scene.render_toolbox.default_base_path + # self.template_multilayer_base_path = context.scene.render_toolbox.default_multilayer_base_path + + ## For template preview display, Store some infos + ref = selected[0] + self.node_name = ref.name.strip() + self.node_label = ref.label.strip() + self.scene_name = '' + self.view_layer_name = '' + if ref.type == 'R_LAYERS': + 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) def draw(self, context): @@ -276,8 +397,68 @@ class RT_OT_create_output_layers(bpy.types.Operator): ## Settings if self.show_custom_settings: box.use_property_split = True + + # Template settings + col = box.column() + row = col.row(align=True) + row.prop(self, 'use_base_path_templates') + row.operator("rt.info_note", text='', icon='INFO').text = """Format the base path using templates. +Possible variables: +{node_name} : name of the node +{node_label} : label of the node, fallback to node_name when nothing +{scene_name} : name of the scene from render layer node +{viewlayer_name} : name of the viewlayer from render layer node +{blend_name} : stem of the blend +{blend_version} : version (rightmost number in blend name) +{date} : (YYYYMMDD format) +{time} : (HHMMSS format)""" # {socket_name} : name of the socket (only availabe in slots) + + final_format = self.file_format + if final_format == 'NONE': + final_format = os.environ.get('FILE_FORMAT', 'OPEN_EXR_MULTILAYER') + + if self.use_base_path_templates: + + if final_format == 'OPEN_EXR_MULTILAYER': + ## self local template + # box.prop(self, 'template_multilayer_base_path') + # self.final_base_path_template = self.template_multilayer_base_path + + ## scene prop + box.prop(context.scene.render_toolbox, 'default_multilayer_base_path') + self.final_base_path_template = context.scene.render_toolbox.default_multilayer_base_path + else: + ## self local template + # box.prop(self, 'template_base_path') + # self.final_base_path_template = self.template_base_path + + ## scene prop + box.prop(context.scene.render_toolbox, 'default_base_path') + self.final_base_path_template = context.scene.render_toolbox.default_base_path + ## display the applied template + box.label(text=fn.build_path_from_template(self.final_base_path_template, + node_name = self.node_name, + node_label=self.node_label, + scene_name=self.scene_name, + viewlayer_name=self.view_layer_name, + ), + icon='INFO' + ) + + else: + box.prop(self, 'base_path') + self.final_base_path_template = None + + if self.name_type == 'template': + row_single = box.row() + row_single.prop(self, 'template_file_slot') + row_single.active = final_format != 'OPEN_EXR_MULTILAYER' + row_multi = box.row() + row_multi.prop(self, 'template_multilayer_name') + row_multi.active = final_format == 'OPEN_EXR_MULTILAYER' + + ## Todo: Add rt.info_note to explain templates 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') @@ -295,6 +476,8 @@ class RT_OT_create_output_layers(bpy.types.Operator): 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' + elif self.name_type == 'template': + op.target_prop = 'name_from_template' ## Node Sockets layout.use_property_split = False @@ -339,6 +522,8 @@ class RT_OT_create_output_layers(bpy.types.Operator): 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='') + elif self.name_type == 'template': + row.prop(item, 'name_from_template', text='') def execute(self, context): @@ -364,7 +549,9 @@ class RT_OT_create_output_layers(bpy.types.Operator): 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 - + elif self.name_type == 'name_from_template': + final_name = item.name_from_template + if not item.select: # All deselected goes to exclude with {node_name: [socket1, ...]} excludes.setdefault(item.node_name, []).append(item.socket_name) @@ -400,7 +587,8 @@ class RT_OT_create_output_layers(bpy.types.Operator): excludes=excludes, remap_names=remap_names, file_format=file_format, - split_tech_passes=self.split_tech_passes) + split_tech_passes=self.split_tech_passes, + template=self.final_base_path_template) return {"FINISHED"} diff --git a/operators/utility.py b/operators/utility.py index 58900a3..77e423c 100644 --- a/operators/utility.py +++ b/operators/utility.py @@ -1,5 +1,6 @@ import bpy +from bpy.types import Operator from bpy.props import (BoolProperty, EnumProperty, PointerProperty, @@ -8,7 +9,7 @@ from bpy.props import (BoolProperty, from .. import fn -class RT_OT_select_object_by_name(bpy.types.Operator): +class RT_OT_select_object_by_name(Operator): bl_idname = "rt.select_object_by_name" bl_label = "Select Object By name" bl_description = "Select object and modifier" @@ -41,6 +42,7 @@ class RT_OT_select_object_by_name(bpy.types.Operator): return {'CANCELLED'} # Make it the active object + ## function to make active and selected # fn.show_and_active_object(context, target_object, make_active=True, ) context.view_layer.objects.active = target_object @@ -64,8 +66,44 @@ class RT_OT_select_object_by_name(bpy.types.Operator): return {'FINISHED'} +class RT_OT_info_note(Operator): + bl_idname = "rt.info_note" + bl_label = "Info Note" + bl_description = "Info Note" + bl_options = {"REGISTER", "INTERNAL"} + + text : bpy.props.StringProperty(default='', options={'SKIP_SAVE'}) + title : bpy.props.StringProperty(default='Help', options={'SKIP_SAVE'}) + icon : bpy.props.StringProperty(default='INFO', options={'SKIP_SAVE'}) + + @classmethod + def description(self, context, properties): + return properties.text + + def execute(self, context): + ## Split text in list of lines to display + lines = self.text.split('\n') + fn.show_message_box(_message=lines, _title=self.title, _icon=self.icon) + return {"FINISHED"} + +# Not registered yet +class RT_set_env_settings(bpy.types.Operator): + """manually reset environnement settings""" + bl_idname = "rt.set_env_settings" + bl_label = "Reset Templates From Env" + bl_description = "Reset Render Toolbox templates from environment variables" + bl_options = {"REGISTER", "UNDO", "INTERNAL"} + + # mode : bpy.props.StringProperty(default='ALL', options={'SKIP_SAVE'}) # 'HIDDEN', + + def execute(self, context): + from ..properties import set_env_properties + set_env_properties() + return {'FINISHED'} + classes = ( RT_OT_select_object_by_name, +RT_OT_info_note ) diff --git a/properties.py b/properties.py new file mode 100644 index 0000000..877ffe8 --- /dev/null +++ b/properties.py @@ -0,0 +1,86 @@ +import bpy +import os + +from bpy.types import PropertyGroup +from bpy.props import StringProperty + + +class RT_PG_render_toolbox_props(PropertyGroup): + default_base_path : StringProperty( + name="Base Path Template", + description="Template for file output base path\ + \nFolder containing subsequent filepath", + default="//render/{node_name}/", + # options={'SKIP_SAVE'} + ) + + ## Multilayer base path : output on file instead of folder + default_multilayer_base_path : StringProperty( + name="Base Path Template", + description="Template for multilayer file output base paths\ + \nOutput of the file sequence", + default="//render/{node_name}/{node_name}_", + # options={'SKIP_SAVE'} + ) + + ### file slots (not used currently) + + default_file_slot : StringProperty( + name="File Slot Template", + description="Template for file output file slots\ + \nSubpath added to base path for file sequence", + default="{socket_name}/{socket_name}_", + # options={'SKIP_SAVE'} + ) + + ## Multilayer's layer: layer name in EXR instead of subpath to file + default_multilayer_name : StringProperty( + name="Layer Name Template", + description="Template for multilayer file output layer names\ + \nLayer name in EXR multilayer file", + default="{socket_name}", + # options={'SKIP_SAVE'} + ) + + # default_tech_pass_name : StringProperty( + # name="Tech Pass Name Template", + # description="Template for tech pass file output layer names\ + # \nLayer name in EXR multilayer file", + # default="tech", + # # options={'SKIP_SAVE'} + # ) + + # default_crypto_name : StringProperty( + # name="Crypto Pass Name Template", + # description="Template for crypto pass file output layer names\ + # \nLayer name in EXR multilayer file", + # default="crypto", + # # options={'SKIP_SAVE'} + # ) + +def set_env_properties(): + ## set default template from environment variable if available + exr_base_path_template = os.getenv('RENDERTOOLBOX_EXR_PATH_TEMPLATE') + if exr_base_path_template: + bpy.context.scene.render_toolbox.default_base_path = exr_base_path_template + + multilayer_base_path_template = os.getenv('RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE') + if multilayer_base_path_template: + bpy.context.scene.render_toolbox.default_multilayer_base_path = multilayer_base_path_template + +classes = ( + RT_PG_render_toolbox_props, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + bpy.types.Scene.render_toolbox = bpy.props.PointerProperty(type=RT_PG_render_toolbox_props) + set_env_properties() + +def unregister(): + del bpy.types.Scene.render_toolbox + + for cls in reversed(classes): + bpy.utils.unregister_class(cls) diff --git a/ui.py b/ui.py index 0805dc2..0165aad 100755 --- a/ui.py +++ b/ui.py @@ -3,7 +3,7 @@ import bpy from bpy.types import Panel -class RT_PT_gp_node_ui(Panel): +class RT_PT_render_toolbox_ui(Panel): bl_space_type = "NODE_EDITOR" bl_region_type = "UI" bl_category = "Render" # Wrangler @@ -14,18 +14,59 @@ class RT_PT_gp_node_ui(Panel): layout.operator("rt.create_output_layers", icon="NODE") layout.operator("rt.outputs_search_and_replace", text='Search and replace outputs', icon="BORDERMOVE") - layout.separator() - layout.label(text="Visibily Checks:") +class RT_PT_visibility_check(Panel): + bl_space_type = "NODE_EDITOR" + bl_region_type = "UI" + bl_category = "Render" # Wrangler + bl_label = "Visibility Checks" + # bl_parent_id = "RT_PT_render_toolbox_ui" + # bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + # layout.label(text="Visibily Checks:") layout.operator("rt.list_object_visibility_conflicts", icon="OBJECT_DATAMODE") layout.operator("rt.list_viewport_render_visibility", text="List Viewport Vs Render Visibility", icon="OBJECT_DATAMODE") layout.operator("rt.list_modifier_visibility", text="List Modifiers Visibility Conflicts", icon="MODIFIER") layout.operator("rt.list_collection_visibility_conflicts", text="List Collections Visibility Conflicts", icon="OUTLINER_COLLECTION") + + # layout.separator() + # layout.operator("rt.list_object_affected_by_simplify", text="List Object Affected By Simplify", icon="MOD_SIMPLIFY") + + +## Unused, only exposed in Create output panel +class RT_PT_output_template(Panel): + bl_space_type = "NODE_EDITOR" + bl_region_type = "UI" + bl_category = "Render" # Wrangler + bl_label = "File Output Templates" + bl_parent_id = "RT_PT_render_toolbox_ui" + # bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + settings = context.scene.render_toolbox + + col = layout.column(align=True) + col.label(text='Single file:') + col.prop(settings, "default_base_path", text="Base Path") + col.prop(settings, "default_file_slot", text="File Slot") + + col.separator() + col = layout.column(align=True) + col.label(text='Multilayers:') + col.prop(settings, "default_multilayer_base_path", text="Base Path") + col.prop(settings, "default_multilayer_name", text="Layer Name") + + ## Handle separate tech passes names ? classes = ( -RT_PT_gp_node_ui, + RT_PT_render_toolbox_ui, + # RT_PT_output_template, + RT_PT_visibility_check, ) def register():