From 7d8535a7eb9b3486e216610204651e670b9d7fd1 Mon Sep 17 00:00:00 2001 From: pullusb Date: Thu, 24 Jul 2025 18:19:07 +0200 Subject: [PATCH] better UI, debug texts and support for object children --- __init__.py | 2 +- operators/conform_collection_hierarchy.py | 191 +++++++++++++--------- 2 files changed, 116 insertions(+), 77 deletions(-) diff --git a/__init__.py b/__init__.py index f0bd563..5a74912 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, 6, 0), + "version": (0, 6, 1), "blender": (4, 0, 0), "location": "View3D", "warning": "", diff --git a/operators/conform_collection_hierarchy.py b/operators/conform_collection_hierarchy.py index bcc0f95..3baeac5 100644 --- a/operators/conform_collection_hierarchy.py +++ b/operators/conform_collection_hierarchy.py @@ -7,6 +7,9 @@ from bpy.props import (BoolProperty, StringProperty) from .. import fn +## TODO : handle linked collection / object +## add properties to choose if linked/overidden object/collec should be used + def name_search_callback(self, context, edit_text): """Search callback for collection names""" ## second arg is not displayed, can be and empty string... @@ -108,19 +111,19 @@ class RT_OT_conform_collection_hierarchy(Operator): conform_exclude: BoolProperty( name="Exclude View Layer State", description="Conform the exclude from view layer", - default=True + default=False ) conform_holdout: BoolProperty( name="Holdout State", description="Conform Collection Holdout State", - default=True + default=False ) conform_use_indirect: BoolProperty( name="Indirect Only State", description="Conform Collection Indirect Only", - default=True + default=False ) def invoke(self, context, event): @@ -144,52 +147,60 @@ class RT_OT_conform_collection_hierarchy(Operator): layout = self.layout # layout.use_property_split = True - # layout.prop(self, "hierarchy_type", text="Root Type", expand=True) + layout.prop(self, "hierarchy_type", text="Work On") # , expand=True + layout.separator() + tgt_row = layout.row() layout.prop(self, "target_name", text="Search (Optional)") - if self.hierarchy_type == 'COLLECTION': - root_collection = self.get_target_collection(context) - if not root_collection: + ref_collection = self.get_target_collection(context) + if not ref_collection or ref_collection == context.scene.collection: layout.label(text="Select a collection or search by name", icon='INFO') + if ref_collection == context.scene.collection: + layout.label(text="Cannot use the scene collection", icon='ERROR') + layout.label(text="An excluded collection collection cannot be active (use search)") return - if not root_collection: - layout.label(text=f"Error: Collection '{root_collection}' not found", icon='ERROR') + + if not ref_collection: + layout.label(text=f"Error: Collection '{ref_collection.name}' not found", icon='ERROR') return - vlc_root = fn.get_view_layer_collection(root_collection) - if not vlc_root: - layout.label(text=f"Error: Viewlayer Collection '{root_collection}' not found", icon='ERROR') + ref_vlc = fn.get_view_layer_collection(ref_collection) + if not ref_vlc: + layout.label(text=f"Error: Viewlayer Collection '{ref_collection.name}' not found", icon='ERROR') return - row = layout.row(align=True) - row.label(text="", icon='TRIA_RIGHT') - row.label(text=root_collection.name, icon='OUTLINER_COLLECTION') + # tgt_row = layout.row(align=True) + tgt_row.label(text="", icon='TRIA_RIGHT') + tgt_row.label(text=ref_collection.name, icon='OUTLINER_COLLECTION') layout.prop(self, "affect_target", text="Target Items") - - row = layout.row(align=True) - row.prop(vlc_root, "exclude", text="", emboss=False) - row.prop(root_collection, "hide_select", text="", emboss=False) - row.prop(vlc_root, "hide_viewport", text="", emboss=False) - row.prop(root_collection, "hide_viewport", text="", emboss=False) - row.prop(root_collection, "hide_render", text="", emboss=False) - row.prop(vlc_root, "holdout", text="", emboss=False) - row.prop(vlc_root, "indirect_only", text="", emboss=False) + layout.separator() + ## Show current collection state (behave badly when changed, should be tweaked before) + # col = layout.column(align=True) + # row = col.row(align=True) + # row.label(text="Reference Collection State:") + # row.prop(ref_vlc, "exclude", text="", emboss=False) + # row.prop(ref_collection, "hide_select", text="", emboss=False) + # row.prop(ref_vlc, "hide_viewport", text="", emboss=False) + # row.prop(ref_collection, "hide_viewport", text="", emboss=False) + # row.prop(ref_collection, "hide_render", text="", emboss=False) + # row.prop(ref_vlc, "holdout", text="", emboss=False) + # row.prop(ref_vlc, "indirect_only", text="", emboss=False) col = layout.column(align=True) - col.label(text="Parameter To Conform:") row = col.row(align=True) + row.label(text="Parameter To Conform:") ## Same order, greyout unused options collec_row = row.row(align=True) - collec_row.prop(self, "conform_exclude", text="", icon='CHECKBOX_HLT') # Exclude from View Layer + collec_row.prop(self, "conform_exclude", text="", icon='CHECKBOX_DEHLT' if ref_vlc.exclude else 'CHECKBOX_HLT') # Exclude from View Layer collec_row.active = self.affect_target != 'OBJECT' ## Object and collections - row.prop(self, "conform_selectability", text="", icon='RESTRICT_SELECT_OFF') # Hide Select - row.prop(self, "conform_viewlayer", text="", icon='HIDE_OFF') # Hide in current viewlayer (eye) - row.prop(self, "conform_viewport", text="", icon='RESTRICT_VIEW_OFF') # Disable in Viewports - row.prop(self, "conform_render", text="", icon='RESTRICT_RENDER_OFF') # Disable in Renders + row.prop(self, "conform_selectability", text="", icon='RESTRICT_SELECT_ON' if ref_collection.hide_select else 'RESTRICT_SELECT_OFF') # Hide Select + row.prop(self, "conform_viewlayer", text="", icon='HIDE_ON' if ref_vlc.hide_viewport else 'HIDE_OFF') # Hide in current viewlayer (eye) + row.prop(self, "conform_viewport", text="", icon='RESTRICT_VIEW_ON' if ref_collection.hide_viewport else 'RESTRICT_VIEW_OFF') # Disable in Viewports + row.prop(self, "conform_render", text="", icon='RESTRICT_RENDER_ON' if ref_collection.hide_render else 'RESTRICT_RENDER_OFF') # Disable in Renders ## Specific to collections collec_row = row.row(align=True) @@ -198,31 +209,37 @@ class RT_OT_conform_collection_hierarchy(Operator): collec_row.active = self.affect_target != 'OBJECT' else: - target_object = self.get_target_object(context) + ref_obj = self.get_target_object(context) - if not target_object: + if not ref_obj: layout.label(text="Make object active or search by name", icon='INFO') return - - row = layout.row(align=True) - row.label(text="", icon='TRIA_RIGHT') - row.label(text=target_object.name, icon='OBJECT_DATA') - if not target_object.children_recursive: + # tgt_row = layout.row(align=True) + tgt_row.label(text="", icon='TRIA_RIGHT') + tgt_row.label(text=ref_obj.name, icon='OBJECT_DATA') + + if not ref_obj.children_recursive: layout.label(text="Object has no children", icon='ERROR') return - col = layout.column(align=True) - row.prop(target_object, "hide_select", text="", emboss=False) - row.prop(self, "active_object_viewlayer_hide", text="", icon='HIDE_ON' if target_object.hide_get() else 'HIDE_OFF', emboss=False) # hack - row.prop(target_object, "hide_viewport", text="", emboss=False) - row.prop(target_object, "hide_render", text="", emboss=False) + layout.separator() + ## Show current collection state (can behave badly when changed, should be tweaked before) + # col = layout.column(align=True) + # row = col.row(align=True) + # row.label(text="Reference Object State:") + # row.prop(ref_obj, "hide_select", text="", emboss=False) + # row.prop(self, "active_object_viewlayer_hide", text="", icon='HIDE_ON' if ref_obj.hide_get() else 'HIDE_OFF', emboss=False) # hack + # row.prop(ref_obj, "hide_viewport", text="", emboss=False) + # row.prop(ref_obj, "hide_render", text="", emboss=False) - col.label(text="Parameter To Conform:") - row.prop(self, "conform_selectability", text="", icon='RESTRICT_SELECT_OFF') # Hide Select - row.prop(self, "conform_viewlayer", text="", icon='HIDE_OFF') # Hide in current viewlayer (eye) - row.prop(self, "conform_viewport", text="", icon='RESTRICT_VIEW_OFF') # Disable in Viewports - row.prop(self, "conform_render", text="", icon='RESTRICT_RENDER_OFF') # Disable in Renders + col = layout.column(align=True) + row = col.row(align=True) + row.label(text="Parameter To Conform:") + row.prop(self, "conform_selectability", text="", icon='RESTRICT_SELECT_ON' if ref_obj.hide_select else 'RESTRICT_SELECT_OFF') # Hide Select + row.prop(self, "conform_viewlayer", text="", icon='HIDE_ON' if ref_obj.hide_get() else 'HIDE_OFF') # Hide in current viewlayer (eye) + row.prop(self, "conform_viewport", text="", icon='RESTRICT_VIEW_ON' if ref_obj.hide_viewport else 'RESTRICT_VIEW_OFF') # Disable in Viewports + row.prop(self, "conform_render", text="", icon='RESTRICT_RENDER_ON' if ref_obj.hide_render else 'RESTRICT_RENDER_OFF') # Disable in Renders ## Show dynamically wich object/collection are affected by the current confo. @@ -235,25 +252,25 @@ class RT_OT_conform_collection_hierarchy(Operator): # collec_name_list = ['hide_select', 'hide_viewport', 'hide_render'] # ## VL props # if self.conform_exclude: - # to_conform_viewlayer['exclude'] = vlc_root.exclude + # to_conform_viewlayer['exclude'] = ref_vlc.exclude # if self.conform_viewlayer: - # to_conform_viewlayer['hide_viewport'] = vlc_root.hide_viewport + # to_conform_viewlayer['hide_viewport'] = ref_vlc.hide_viewport # if self.conform_holdout: - # to_conform_viewlayer['holdout'] = vlc_root.holdout + # to_conform_viewlayer['holdout'] = ref_vlc.holdout # if self.conform_use_indirect: - # to_conform_viewlayer['indirect_only'] = vlc_root.indirect_only + # to_conform_viewlayer['indirect_only'] = ref_vlc.indirect_only # ## collection props # if self.conform_selectability: - # to_conform_collection['hide_select'] = vlc_root.collection.hide_select + # to_conform_collection['hide_select'] = ref_vlc.collection.hide_select # if self.conform_viewport: - # to_conform_collection['hide_viewport'] = vlc_root.collection.hide_viewport + # to_conform_collection['hide_viewport'] = ref_vlc.collection.hide_viewport # if self.conform_render: - # to_conform_collection['hide_render'] = vlc_root.collection.hide_render + # to_conform_collection['hide_render'] = ref_vlc.collection.hide_render # col = layout.column(align=True) - # sub_vlc = fn.get_collection_children_recursive(vlc_root) + # sub_vlc = fn.get_collection_children_recursive(ref_vlc) # for vlc in sub_vlc: # viewlayer_conflicts = [attr for attr, value in to_conform_viewlayer.items() if value != getattr(vlc, attr, None)] # collection_conflicts = [attr for attr, value in to_conform_collection.items() if value != getattr(vlc.collection, attr, None)] @@ -277,35 +294,35 @@ class RT_OT_conform_collection_hierarchy(Operator): def execute(self, context): + affected_items = [] # List to log affected items if self.hierarchy_type == 'COLLECTION': ref_collection = self.get_target_collection(context) - vlc_root = fn.get_view_layer_collection(ref_collection) - if not vlc_root: + ref_vlc = fn.get_view_layer_collection(ref_collection) + if not ref_vlc: self.report({'ERROR'}, f"View Layer Collection for '{ref_collection.name}' not found") return {'CANCELLED'} + print(f'Conform parameters on collection hierarchy from {ref_vlc.name}') - affected_items = [] # List to log affected items if self.affect_target in {'ALL', 'COLLECTION'}: ## Apply on collection - sub_vlc = fn.get_collection_children_recursive(vlc_root) + sub_vlc = fn.get_collection_children_recursive(ref_vlc) for vlc in sub_vlc: # Apply view layer collection properties + if self.conform_exclude and vlc.exclude != ref_vlc.exclude: + vlc.exclude = ref_vlc.exclude + affected_items.append((vlc.name, "exclude", ref_vlc.exclude)) - if self.conform_exclude and vlc.exclude != vlc_root.exclude: - vlc.exclude = vlc_root.exclude - affected_items.append((vlc.name, "exclude", vlc_root.exclude)) + if self.conform_viewlayer and vlc.hide_viewport != ref_vlc.hide_viewport: + vlc.hide_viewport = ref_vlc.hide_viewport + affected_items.append((vlc.name, "hide_viewport", ref_vlc.hide_viewport)) - if self.conform_viewlayer and vlc.hide_viewport != vlc_root.hide_viewport: - vlc.hide_viewport = vlc_root.hide_viewport - affected_items.append((vlc.name, "hide_viewport", vlc_root.hide_viewport)) + if self.conform_holdout and vlc.holdout != ref_vlc.holdout: + vlc.holdout = ref_vlc.holdout + affected_items.append((vlc.name, "holdout", ref_vlc.holdout)) - if self.conform_holdout and vlc.holdout != vlc_root.holdout: - vlc.holdout = vlc_root.holdout - affected_items.append((vlc.name, "holdout", vlc_root.holdout)) - - if self.conform_use_indirect and vlc.indirect_only != vlc_root.indirect_only: - vlc.indirect_only = vlc_root.indirect_only - affected_items.append((vlc.name, "indirect_only", vlc_root.indirect_only)) + if self.conform_use_indirect and vlc.indirect_only != ref_vlc.indirect_only: + vlc.indirect_only = ref_vlc.indirect_only + affected_items.append((vlc.name, "indirect_only", ref_vlc.indirect_only)) # Apply collection properties @@ -330,9 +347,9 @@ class RT_OT_conform_collection_hierarchy(Operator): obj.hide_select = ref_collection.hide_select affected_items.append((obj.name, "hide_select", ref_collection.hide_select)) - if self.conform_viewlayer and obj.hide_get() != vlc_root.hide_viewport: - obj.hide_set(vlc_root.hide_viewport) - affected_items.append((obj.name, "hide_viewlayer", vlc_root.hide_viewport)) + if self.conform_viewlayer and obj.hide_get() != ref_vlc.hide_viewport: + obj.hide_set(ref_vlc.hide_viewport) + affected_items.append((obj.name, "hide_viewlayer", ref_vlc.hide_viewport)) if self.conform_viewport and obj.hide_viewport != ref_collection.hide_viewport: obj.hide_viewport = ref_collection.hide_viewport @@ -342,6 +359,29 @@ class RT_OT_conform_collection_hierarchy(Operator): obj.hide_render = ref_collection.hide_render affected_items.append((obj.name, "hide_render", ref_collection.hide_render)) + else: + ref_object = self.get_target_object(context) + if not ref_object: + self.report({'ERROR'}, "No target object found") + return {'CANCELLED'} + print(f'Conform parameters on object childrens hierarchy from {ref_object.name}') + for obj in ref_object.children_recursive: + # Apply object properties + if self.conform_selectability and obj.hide_select != ref_object.hide_select: + obj.hide_select = ref_object.hide_select + affected_items.append((obj.name, "hide_select", ref_object.hide_select)) + + if self.conform_viewlayer and obj.hide_get() != ref_object.hide_get(): + obj.hide_set(ref_object.hide_get()) + affected_items.append((obj.name, "hide_viewlayer", ref_object.hide_get())) + + if self.conform_viewport and obj.hide_viewport != ref_object.hide_viewport: + obj.hide_viewport = ref_object.hide_viewport + affected_items.append((obj.name, "hide_viewport", ref_object.hide_viewport)) + + if self.conform_render and obj.hide_render != ref_object.hide_render: + obj.hide_render = ref_object.hide_render + affected_items.append((obj.name, "hide_render", ref_object.hide_render)) # Log the affected items message = [] @@ -350,7 +390,6 @@ class RT_OT_conform_collection_hierarchy(Operator): print(line) message.append(line) - # f" items in the hierarchy." + '\n'.join(message) fn.show_message_box( message=message, title=f"Conformed {len(affected_items)} in hierarchy",