import bpy from bpy.types import Operator from bpy.props import (BoolProperty, EnumProperty, PointerProperty, CollectionProperty, 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... if self.hierarchy_type == 'COLLECTION': return [(c.name, '') for c in bpy.context.scene.collection.children_recursive if edit_text.lower() in c.name.lower()] else: return [(o.name, '') for o in bpy.context.scene.collection.all_objects if edit_text.lower() in o.name.lower()] def toggle_viewlayer_hide_state(self, context): target_object = None if self.target_name: target_object = bpy.data.objects.get(self.target_name) else: target_object = context.object if target_object: # toggle the view layer hide state target_object.hide_set(not target_object.hide_get()) def outliner_conform(apply=False, context=None): """conform the outliner hierarchy of a target children based on scene properties""" context = context or bpy.context props = context.view_layer.render_toolbox_conform ## Dict model (with object/collection items in one key, and list of strings for affected properties) # affected_items = { # 'objects': { # obj: ['hide_viewlayer, hide_viewport'], # obj_2: ['hide_viewlayer, hide_select'], # }, # 'collections': { # vlc_1 : ['exclude','hide_viewlayer','holdout'], # vlc_2 : ['exclude', 'holdout', 'hide_viewport'], # } # } hierarchy_type = props.hierarchy_type target_name = props.target_name affect_target = props.affect_target conform_selectability = props.conform_selectability conform_viewlayer = props.conform_viewlayer conform_viewport = props.conform_viewport conform_render = props.conform_render conform_exclude = props.conform_exclude conform_holdout = props.conform_holdout conform_use_indirect = props.conform_use_indirect affected_items = { 'objects': {}, 'collections': {} } if hierarchy_type == 'COLLECTION': ref_collection = fn.get_target_collection(target_name, context) ref_vlc = fn.get_view_layer_collection(ref_collection) if not ref_vlc: print({'ERROR'}, f"View Layer Collection for '{ref_collection.name}' not found") return # print(f'Conform parameters on collection hierarchy from {ref_vlc.name}') if affect_target in {'ALL', 'COLLECTION'}: sub_vlc = fn.get_collection_children_recursive(ref_vlc) for vlc in sub_vlc: col_attrs = [] if conform_exclude and vlc.exclude != ref_vlc.exclude: if apply: vlc.exclude = ref_vlc.exclude col_attrs.append("exclude") if conform_viewlayer and vlc.hide_viewport != ref_vlc.hide_viewport: if apply: vlc.hide_viewport = ref_vlc.hide_viewport col_attrs.append("hide_viewlayer") if conform_holdout and vlc.holdout != ref_vlc.holdout: if apply: vlc.holdout = ref_vlc.holdout col_attrs.append("holdout") if conform_use_indirect and vlc.indirect_only != ref_vlc.indirect_only: if apply: vlc.indirect_only = ref_vlc.indirect_only col_attrs.append("indirect_only") if conform_selectability and vlc.collection.hide_select != ref_collection.hide_select: if apply: vlc.collection.hide_select = ref_collection.hide_select col_attrs.append("hide_select") if conform_viewport and vlc.collection.hide_viewport != ref_collection.hide_viewport: if apply: vlc.collection.hide_viewport = ref_collection.hide_viewport col_attrs.append("hide_viewport") if conform_render and vlc.collection.hide_render != ref_collection.hide_render: if apply: vlc.collection.hide_render = ref_collection.hide_render col_attrs.append("hide_render") if col_attrs: affected_items['collections'][vlc] = col_attrs if affect_target in {'ALL', 'OBJECT'}: for obj in ref_collection.all_objects: obj_attrs = [] if conform_selectability and obj.hide_select != ref_collection.hide_select: if apply: obj.hide_select = ref_collection.hide_select obj_attrs.append("hide_select") if conform_viewlayer and obj.hide_get() != ref_vlc.hide_viewport: if apply: obj.hide_set(ref_vlc.hide_viewport) obj_attrs.append("hide_viewlayer") if conform_viewport and obj.hide_viewport != ref_collection.hide_viewport: if apply: obj.hide_viewport = ref_collection.hide_viewport obj_attrs.append("hide_viewport") if conform_render and obj.hide_render != ref_collection.hide_render: if apply: obj.hide_render = ref_collection.hide_render obj_attrs.append("hide_render") if obj_attrs: affected_items['objects'][obj] = obj_attrs else: ref_object = fn.get_target_object(target_name, context) if not ref_object: print({'ERROR'}, "No target object found") return # print(f'Conform parameters on object childrens hierarchy from {ref_object.name}') for obj in ref_object.children_recursive: obj_attrs = [] if conform_selectability and obj.hide_select != ref_object.hide_select: if apply: obj.hide_select = ref_object.hide_select obj_attrs.append("hide_select") if conform_viewlayer and obj.hide_get() != ref_object.hide_get(): if apply: obj.hide_set(ref_object.hide_get()) obj_attrs.append("hide_viewlayer") if conform_viewport and obj.hide_viewport != ref_object.hide_viewport: if apply: obj.hide_viewport = ref_object.hide_viewport obj_attrs.append("hide_viewport") if conform_render and obj.hide_render != ref_object.hide_render: if apply: obj.hide_render = ref_object.hide_render obj_attrs.append("hide_render") if obj_attrs: affected_items['objects'][obj] = obj_attrs return affected_items class RT_OT_conform_collection_hierarchy(Operator): bl_idname = "rt.conform_collection_hierarchy" bl_label = "Conform Collection Hierarchy" bl_description = "Conform collection/object hierarchy state.\ \nCan affect collection, objects or both" bl_options = {"REGISTER", "UNDO"} def invoke(self, context, event): props = context.view_layer.render_toolbox_conform ## Check if ready to continue OK if props.hierarchy_type == 'COLLECTION': ref_collection = fn.get_target_collection(props.target_name, context) self.ref_item = fn.get_view_layer_collection(ref_collection) if not self.ref_item: self.report({'ERROR'}, f"View Layer Collection for '{ref_collection.name}' not found") return {'CANCELLED'} fn.store_collection_states(ref_collection, context=context) else: self.ref_item = fn.get_target_object(props.target_name, context) if not self.ref_item: self.report({'ERROR'}, "No target object found") return {'CANCELLED'} self.affected_items_dict = outliner_conform(apply=False, context=context) ## Merge collection and viewlauer colleciton on the same keys by name # merged_collection_dict = {} # for k, v in self.affected_items_dict.items(): # if isinstance(k, bpy.types.Object): # merged_collection_dict[k] = v # else: # if k.name not in merged_collection_dict.keys(): # merged_collection_dict[k.name] = [] # merged_collection_dict[k.name].extend(v) # self.affected_items_dict = merged_collection_dict return context.window_manager.invoke_props_dialog(self, width=400) def draw(self, context): layout = self.layout # show non conform items props = context.view_layer.render_toolbox_conform layout.label(text=f"Conform Hierarchy from '{self.ref_item.name}'", icon='OUTLINER_COLLECTION' if props.hierarchy_type == 'COLLECTION' else 'OBJECT_DATA') ## show conform parameters row = layout.row(align=True) row.enabled = False # don't want to mes with source item here ! TODO : convert to labels row.label(text="Conforming:") if props.hierarchy_type == 'COLLECTION': if props.conform_exclude: row.prop(self.ref_item, 'exclude', text="", emboss=False) if props.conform_viewlayer: row.prop(self.ref_item, 'hide_viewport', text="", emboss=False) if props.conform_holdout: row.prop(self.ref_item, 'holdout', text="", emboss=False) if props.conform_use_indirect: row.prop(self.ref_item, 'indirect_only', text="", emboss=False) ## collection props if props.conform_selectability: row.prop(self.ref_item.collection, 'hide_select', text="", emboss=False) if props.conform_viewport: row.prop(self.ref_item.collection, 'hide_viewport', text="", emboss=False) if props.conform_render: row.prop(self.ref_item.collection, 'hide_render', text="", emboss=False) else: ## Object if props.conform_selectability: row.prop(self.ref_item, 'hide_select', text="", emboss=False) if props.conform_viewlayer: row.label(text="", icon='HIDE_ON' if self.ref_item.hide_get() else 'HIDE_OFF') if props.conform_viewport: row.prop(self.ref_item, 'hide_viewport', text="", emboss=False) if props.conform_render: row.prop(self.ref_item, 'hide_render', text="", emboss=False) layout.separator() outliner_state = context.scene.get('outliner_state', {}) ## Should have been saved at invoke if outliner_state.get(self.ref_item.name): # layout.label(text=f"Stored visibility states for '{self.ref_item.name}'", icon='FILE_TICK') layout.operator("rt.apply_visibility_states", text=f"Restore State: {self.ref_item.name}", icon='FILE_REFRESH').collection_name = self.ref_item.name layout.separator() # vl_attr_list = ['exclude', 'hide_viewlayer', 'holdout', 'indirect_only'] attr_list = ['hide_select', 'hide_viewport', 'hide_render'] full_attr_list = ['exclude', 'hide_select', 'hide_viewlayer', 'hide_viewport', 'hide_render', 'holdout', 'indirect_only'] for item_type, type_dict in self.affected_items_dict.items(): for k, v in type_dict.items(): if item_type == "objects": icon = f'OUTLINER_OB_{k.type}' name = k.name else: icon = 'OUTLINER_COLLECTION' name = k.name row = layout.row(align=True) row.label(text=name, icon=icon) for attr in full_attr_list: if attr in v: if attr == 'hide_viewlayer': ## Special case for view_layer hide (hide_viewport for layer collection -- hide_get for object) if item_type == "objects": ## TODO: create a dynamic property to toggle object VL viz row.label(text="", icon='HIDE_ON' if k.hide_get() else 'HIDE_OFF') # , emboss=False, toggle=True else: row.prop(k, 'hide_viewport', text="", emboss=False) # row.prop(k, 'hide_viewport', text="", icon='HIDE_ON' if k.hide_viewport else 'HIDE_OFF', emboss=False) # , toggle=True elif attr in attr_list and item_type == "collections": ## accessed on collection object from vl_col row.prop(k.collection, attr, text="", emboss=False) else: row.prop(k, attr, text="", emboss=False) else: row.label(text="", icon='BLANK1') def execute(self, context): # props = context.scene.render_toolbox_conform affected_items_dict = outliner_conform(apply=True, context=context) message = [] 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) fn.show_message_box( message=message, title=f"Conformed {len(affected_items_dict)} in hierarchy", icon='INFO' ) return {'FINISHED'} # endregion def register(): bpy.utils.register_class(RT_OT_conform_collection_hierarchy) def unregister(): bpy.utils.unregister_class(RT_OT_conform_collection_hierarchy)