diff --git a/fn.py b/fn.py index 1f9b326..e7b81bc 100755 --- a/fn.py +++ b/fn.py @@ -5,7 +5,7 @@ import json from .constant import TECH_PASS_KEYWORDS -### --- Manage nodes --- ### +# region Manage nodes def real_loc(n): if not n.parent: @@ -273,4 +273,95 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None created_nodes.append(fo_crypto) - return created_nodes \ No newline at end of file + return created_nodes + +# endregion + + +# region Utilities + +def set_properties_editor_tab(tab, skip_if_exists=True): + '''Take a tab name and apply it to properties editor + tab: identifier of the tab, possible name in: + ['TOOL', 'SCENE', 'RENDER', 'OUTPUT', 'VIEW_LAYER', + 'WORLD', 'COLLECTION', 'OBJECT', 'CONSTRAINT', 'MODIFIER', + 'DATA', 'BONE', 'BONE_CONSTRAINT', 'MATERIAL', 'TEXTURE', + 'PARTICLES', 'PHYSICS', 'SHADERFX'] + + skip_if_exists: do nothing if a properties editor is alrteady on this tab + ''' + if bpy.context.area.type == 'PROPERTIES': + bpy.context.area.spaces.active.context = tab + return + + prop_space = None + for area in bpy.context.screen.areas: + if area.type == 'PROPERTIES': + for space in area.spaces: + if space.type == 'PROPERTIES': + if skip_if_exists and space.context == tab: + return + if prop_space is None: + prop_space = space + + if prop_space is not None: + prop_space.context = tab + + return 1 + + +def show_and_active_object(obj, make_active=True, select=True, unhide=True): + ''' + Show the object + Disable exclude parent collection collection and select with options + Activate and show all parents collections + active : Make the object active + select : Select the object (independent of active state) + unhide : show object in viewport (activate both visibility status) + ''' + + # Activate parents collections + # activate_parent_collections(obj, ensure_visible=ensure_collection_visible) + if unhide: + # Show obj object + if obj.hide_viewport: + obj.hide_viewport = False + if obj.hide_get(): + obj.hide_set(False) + + # Make object Active + if make_active: + bpy.context.view_layer.objects.active = obj + # Select object + if select: + obj.select_set(True) + +def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): + '''Show message box with element passed as string or list + if _message if a list of lists: + if sublist have 2 element: + considered a label [text, icon] + if sublist have 3 element: + considered as an operator [ops_id_name, text, icon] + if sublist have 4 element: + considered as a property [object, propname, text, icon] + ''' + + def draw(self, context): + layout = self.layout + for l in _message: + if isinstance(l, str): + layout.label(text=l) + elif len(l) == 2: # label with icon + layout.label(text=l[0], icon=l[1]) + elif len(l) == 3: # ops + layout.operator_context = "INVOKE_DEFAULT" + layout.operator(l[0], text=l[1], icon=l[2], emboss=False) # <- highligh the entry + elif len(l) == 4: # prop + row = layout.row(align=True) + row.label(text=l[2], icon=l[3]) + row.prop(l[0], l[1], text='') + + if isinstance(_message, str): + _message = [_message] + bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) diff --git a/operators/__init__.py b/operators/__init__.py index cd864f4..0a6e768 100755 --- a/operators/__init__.py +++ b/operators/__init__.py @@ -1,11 +1,15 @@ from . import ( + utility, outputs_setup, outputs_search_and_replace, + visibility_conflicts, ) mods = ( + utility, outputs_setup, outputs_search_and_replace, + visibility_conflicts, ) def register(): diff --git a/operators/utility.py b/operators/utility.py new file mode 100644 index 0000000..58900a3 --- /dev/null +++ b/operators/utility.py @@ -0,0 +1,78 @@ +import bpy + +from bpy.props import (BoolProperty, + EnumProperty, + PointerProperty, + CollectionProperty, + StringProperty) + +from .. import fn + +class RT_OT_select_object_by_name(bpy.types.Operator): + bl_idname = "rt.select_object_by_name" + bl_label = "Select Object By name" + bl_description = "Select object and modifier" + bl_options = {"REGISTER", "INTERNAL"} + + object_name: StringProperty( + name="Object Name", + description="Name of the object to select", + default="", + options={'SKIP_SAVE'} + ) + + modifier_name: StringProperty( + name="Modifier Name", + description="Name of the modifier to show (optional)", + default="", + options={'SKIP_SAVE'} + ) + + def execute(self, context): + # Check if object name is provided + if not self.object_name: + self.report({'ERROR'}, "No object name provided") + return {'CANCELLED'} + + # Get the object + target_object = context.scene.objects.get(self.object_name) + if not target_object: + self.report({'WARNING'}, f"Object '{self.object_name}' not found in scene") + return {'CANCELLED'} + + # Make it the active object + # fn.show_and_active_object(context, target_object, make_active=True, ) + context.view_layer.objects.active = target_object + + # Handle modifier expansion if modifier name is provided + if self.modifier_name: + # Check if the object has modifiers + if target_object.modifiers: + # Look for the specified modifier + modifier = target_object.modifiers.get(self.modifier_name) + if modifier: + # Expand only this modifier + for mod in target_object.modifiers: + # Collapse all modifiers first + mod.show_expanded = mod == modifier + + ## Make it the active modifier + target_object.modifiers.active = modifier + ## Show modifier panel + fn.set_properties_editor_tab('MODIFIER') + + return {'FINISHED'} + + +classes = ( +RT_OT_select_object_by_name, +) + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) diff --git a/operators/visibility_conflicts.py b/operators/visibility_conflicts.py new file mode 100644 index 0000000..ea52a9e --- /dev/null +++ b/operators/visibility_conflicts.py @@ -0,0 +1,572 @@ +import bpy + +from bpy.props import (BoolProperty, + EnumProperty, + PointerProperty, + CollectionProperty, + StringProperty) + + +# region Object visibility + +class RT_OT_sync_visibility(bpy.types.Operator): + bl_idname = "rt.sync_visibility" + bl_label = "Sync Visibility" + bl_description = "Sync visibility properties with optional locking" + bl_options = {"REGISTER", "UNDO"} + + sync_mode: EnumProperty( + name="Sync From", + description="Choose which visibility property to sync from", + items=[ + ('FROM_VIEWLAYER', "Viewlayer", "Use viewlayer visibility as source"), + ('FROM_VIEWPORT', "Viewport", "Use viewport visibility as source"), + ('FROM_RENDER', "Render", "Use render visibility as source"), + ('VISIBLE_TO_RENDER', "Overall Visible", "Use overall viewport visibility (combination of viewport + viewlayer)") + ], + default='FROM_VIEWLAYER' + ) + + affect_viewlayer: BoolProperty( + name="Affect Viewlayer", + description="Update viewlayer visibility", + default=True + ) + + affect_viewport: BoolProperty( + name="Affect Viewport", + description="Update viewport visibility", + default=True + ) + + affect_render: BoolProperty( + name="Affect Render", + description="Update render visibility", + default=True + ) + + popup: BoolProperty( + name="Popup", + description="Show this operator as a popup dialog, else directly call execute", + default=True, + options={'HIDDEN'} + ) + + def invoke(self, context, event): + if not self.popup: + # If not a popup, just execute directly + return self.execute(context) + + # Auto-disable the source property to avoid self-sync + if self.sync_mode == 'FROM_VIEWLAYER': + self.affect_viewlayer = False + elif self.sync_mode == 'FROM_VIEWPORT': + self.affect_viewport = False + elif self.sync_mode == 'FROM_RENDER': + self.affect_render = False + elif self.sync_mode == 'VISIBLE_TO_RENDER': + # Only render makes sense for this mode + self.affect_viewlayer = False + self.affect_viewport = False + self.affect_render = True + + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + layout = self.layout + + layout.prop(self, "sync_mode") + layout.separator() + + # Target selection + col = layout.column(align=True) + col.label(text="Affect Properties:") + + if self.sync_mode == 'VISIBLE_TO_RENDER': + # For this mode, only render makes sense + col.prop(self, "affect_render") + if not self.affect_render: + col.label(text="No targets selected", icon='ERROR') + else: + col.prop(self, "affect_viewlayer") + col.prop(self, "affect_viewport") + col.prop(self, "affect_render") + + # Show warning if no targets selected + if not any([self.affect_viewlayer, self.affect_viewport, self.affect_render]): + col.label(text="No targets selected", icon='ERROR') + + layout.separator() + + # Show info about what will be affected + if self.sync_mode == 'VISIBLE_TO_RENDER': + if self.affect_render: + layout.label(text="Will update: Render visibility", icon='INFO') + else: + layout.label(text="Nothing will change", icon='INFO') + else: + affected = [] + if self.affect_viewlayer: + affected.append("Viewlayer") + if self.affect_viewport: + affected.append("Viewport") + if self.affect_render: + affected.append("Render") + + if affected: + layout.label(text=f"Will update: {', '.join(affected)}", icon='INFO') + else: + layout.label(text="Nothing will change", icon='INFO') + + def execute(self, context): + if not self.popup: + # Always + if self.sync_mode == 'FROM_VIEWLAYER': + self.affect_viewlayer = False + elif self.sync_mode == 'FROM_VIEWPORT': + self.affect_viewport = False + elif self.sync_mode == 'FROM_RENDER': + self.affect_render = False + elif self.sync_mode == 'VISIBLE_TO_RENDER': + # Only render makes sense for this mode + self.affect_viewlayer = False + self.affect_viewport = False + self.affect_render = True + + for obj in context.scene.objects: + # Get source visibility value + if self.sync_mode == 'FROM_VIEWLAYER': + source_hidden = obj.hide_get() + elif self.sync_mode == 'FROM_VIEWPORT': + source_hidden = obj.hide_viewport + elif self.sync_mode == 'FROM_RENDER': + source_hidden = obj.hide_render + elif self.sync_mode == 'VISIBLE_TO_RENDER': + # For this mode, we use the inverse of visible_get() + source_hidden = not obj.visible_get() + + # Apply to selected target properties + if self.sync_mode == 'VISIBLE_TO_RENDER': + # Special case: only affects render visibility + if self.affect_render: + obj.hide_render = source_hidden + else: + # Standard sync modes: apply to selected properties + if self.affect_viewlayer: + obj.hide_set(source_hidden) + + if self.affect_viewport: + obj.hide_viewport = source_hidden + + if self.affect_render: + obj.hide_render = source_hidden + + return {'FINISHED'} + +class RT_PG_object_visibility(bpy.types.PropertyGroup): + """Property group to handle object visibility""" + is_hidden: BoolProperty( + name="Hide in Viewport", + description="Toggle object visibility in viewport", + get=lambda self: self.get("is_hidden", False), + set=lambda self, value: self.set_visibility(value) + ) + + object_name: StringProperty(name="Object Name") + + def set_visibility(self, value): + """Set the visibility using hide_set()""" + obj = bpy.context.view_layer.objects.get(self.object_name) + if obj: + obj.hide_set(value) + self["is_hidden"] = value + +class RT_OT_list_object_visibility_conflicts(bpy.types.Operator): + bl_idname = "rt.list_object_visibility_conflicts" + bl_label = "List Objects Visibility Conflicts" + bl_description = "List objects visibility conflicts.\ + \nWhen Viewlayer, viewport and render have different values\ + \nAlso allow to set all from one of the 3" + bl_options = {"REGISTER"} + + visibility_items: CollectionProperty(type=RT_PG_object_visibility) + + affect_viewlayer: BoolProperty( + name="Affect Viewlayer", + description="Update viewlayer visibility", + default=True + ) + + affect_viewport: BoolProperty( + name="Affect Viewport", + description="Update viewport visibility", + default=True + ) + + affect_render: BoolProperty( + name="Affect Render", + description="Update render visibility", + default=True + ) + + def invoke(self, context, event): + # Clear and rebuild both collections + self.visibility_items.clear() + + # Store objects with conflicts + ## TODO: Maybe better (but less detailed) to just check o.visible_get (global visiblity) against render viz ? + objects_with_conflicts = [o for o in context.scene.objects if not (o.hide_get() == o.hide_viewport == o.hide_render)] + + # Create visibility items in same order + for obj in objects_with_conflicts: + item = self.visibility_items.add() + item.object_name = obj.name + item["is_hidden"] = obj.hide_get() + + return context.window_manager.invoke_props_dialog(self, width=250) + + def draw(self, context): + layout = self.layout + + col = layout.column(align=True) + row = col.row(align=False) + row.label(text="Affect Visibility On:") + + row.prop(self, "affect_viewlayer", text="", icon='CHECKBOX_HLT' if self.affect_viewlayer else 'CHECKBOX_DEHLT') # Viewlayer + row.prop(self, "affect_viewport", text="", icon='CHECKBOX_HLT' if self.affect_viewport else 'CHECKBOX_DEHLT') # Viewport + row.prop(self, "affect_render", text="", icon='CHECKBOX_HLT' if self.affect_render else 'CHECKBOX_DEHLT') # Render + if not any([self.affect_viewlayer, self.affect_viewport, self.affect_render]): + col.label(text="Need to select one target", icon='ERROR') + + # Add sync buttons at the top + row = col.row(align=False) + row.label(text="Set Visibility State From:") + row_vl = row.row(align=True) + row_vl.active = self.affect_viewlayer + op = row_vl.operator("rt.sync_visibility", text="", icon='HIDE_OFF') + op.sync_mode = 'FROM_VIEWLAYER' + op.affect_viewlayer = self.affect_viewlayer + op.affect_viewport = self.affect_viewport + op.affect_render = self.affect_render + op.popup = False + + row_vp = row.row(align=True) + row_vp.active = self.affect_viewport + op = row_vp.operator("rt.sync_visibility", text="", icon='RESTRICT_VIEW_OFF') + op.sync_mode = 'FROM_VIEWPORT' + op.affect_viewlayer = self.affect_viewlayer + op.affect_viewport = self.affect_viewport + op.affect_render = self.affect_render + op.popup = False + + row_rd = row.row(align=True) + row_rd.active = self.affect_render + op = row_rd.operator("rt.sync_visibility", text="", icon='RESTRICT_RENDER_OFF') + op.sync_mode = 'FROM_RENDER' + op.affect_viewlayer = self.affect_viewlayer + op.affect_viewport = self.affect_viewport + op.affect_render = self.affect_render + op.popup = False + + ## Add that in a separate view mode + col.separator() + op = col.operator("rt.sync_visibility", text="Set Render state from current visibility", icon='RESTRICT_RENDER_OFF') + op.sync_mode = 'VISIBLE_TO_RENDER' + op.popup = False + layout.separator() + + col = layout.column() + # We can safely iterate over visibility_items since objects are stored in same order + for vis_item in self.visibility_items: + obj = context.view_layer.objects.get(vis_item.object_name) + if not obj: + continue + + row = col.row(align=False) + row.label(text=obj.name) + + ## Viewlayer visibility "as prop" to allow slide toggle + # hide_icon='HIDE_ON' if vis_item.is_hidden else 'HIDE_OFF' + row_vl = row.row(align=True) + row_vl.enabled = self.affect_viewlayer + hide_icon='HIDE_ON' if obj.hide_get() else 'HIDE_OFF' # based on object state + row_vl.prop(vis_item, "is_hidden", text="", icon=hide_icon, emboss=False) + + # Direct object properties + row_vp = row.row(align=True) + row_vp.enabled = self.affect_viewport + row_vp.prop(obj, 'hide_viewport', text='', emboss=False) + + row_rd = row.row(align=True) + row_rd.enabled = self.affect_render + row_rd.prop(obj, 'hide_render', text='', emboss=False) + + def execute(self, context): + return {'FINISHED'} + + +## Basic version with only viewport and render visibility listed +class RT_OT_list_viewport_render_visibility(bpy.types.Operator): + bl_idname = "rt.list_viewport_render_visibility" + bl_label = "List Viewport And Render Visibility Conflicts" + bl_description = "List objects visibility conflicts, when viewport and render have different values" + bl_options = {"REGISTER"} + + def invoke(self, context, event): + self.ob_list = [o for o in context.scene.objects if o.hide_viewport != o.hide_render] + return context.window_manager.invoke_props_dialog(self, width=250) + + def draw(self, context): + # TODO: Add visibility check with viewlayer visibility as well + layout = self.layout + for o in self.ob_list: + row = layout.row() + row.label(text=o.name) + row.prop(o, 'hide_viewport', text='', emboss=False) # invert_checkbox=True + row.prop(o, 'hide_render', text='', emboss=False) # invert_checkbox=True + + def execute(self, context): + return {'FINISHED'} + +# endregion + +# region Collection Visibility + +def get_collection_children_recursive(col, cols=None) -> list: + '''return a list of all the child collections + and their subcollections in the passed collection''' + + cols = cols or [] + for sub in col.children: + if sub not in cols: + cols.append(sub) + if len(sub.children): + cols = get_collection_children_recursive(sub, cols) + return cols + +class RT_OT_list_collection_visibility_conflicts(bpy.types.Operator): + bl_idname = "rt.list_collection_visibility_conflicts" + bl_label = "List Collection Visibility Conflicts" + bl_description = "List collection visibility conflicts, when viewport and render have different values" + bl_options = {"REGISTER"} + + # visibility_items: CollectionProperty(type=RT_PG_collection_visibility) + show_filter : bpy.props.EnumProperty( + name="View Filter", + description="Filter collections based on their exclusion status", + items=( + ('ALL', "All", "Show all collections", 0), + ('NOT_EXCLUDED', "Not Excluded", "Show collections that are not excluded", 1), + ('EXCLUDED', "Excluded", "Show collections that are excluded", 2) + ), + default='NOT_EXCLUDED') + + def invoke(self, context, event): + ## get all viewlayer collections + vcols = get_collection_children_recursive(context.view_layer.layer_collection) + vcols = list(set(vcols)) # ensure no duplicates + + ## Store collection with conflicts + # layer_collection.is_visible against render visibility ? + ## Do not list currently excluded collections + self.conflict_collections = [vc for vc in vcols if not (vc.hide_viewport == vc.collection.hide_viewport == vc.collection.hide_render)] + self.included_collection = [vc for vc in self.conflict_collections if not vc.exclude] + self.excluded_collection = [vc for vc in self.conflict_collections if vc.exclude] + + return context.window_manager.invoke_props_dialog(self, width=274) + + def draw(self, context): + layout = self.layout + layout.prop(self, 'show_filter', expand=True) + + # Add sync buttons at the top + row = layout.row(align=False) + # TODO: Add "set all from" ops on collection (optionnal) + # row.label(text="Sync All Visibility From:") + # row.operator("rt.sync_visibility_from_viewlayer", text="", icon='HIDE_OFF') + # row.operator("rt.sync_visibility_from_viewport", text="", icon='RESTRICT_VIEW_OFF') + # row.operator("rt.sync_visibility_from_render", text="", icon='RESTRICT_RENDER_OFF') + layout.separator() + + if self.show_filter == 'ALL': + vl_collections = self.conflict_collections + elif self.show_filter == 'EXCLUDED': + vl_collections = self.excluded_collection + elif self.show_filter == 'NOT_EXCLUDED': + vl_collections = self.included_collection + + col = layout.column() + for vlcol in vl_collections: + row = col.row(align=False) + row.label(text=vlcol.name) + + # Viewlayer collection settings + row.prop(vlcol, "exclude", text="", emboss=False) + row.prop(vlcol, "hide_viewport", text="", emboss=False) + + # Direct collection properties + row.prop(vlcol.collection, 'hide_viewport', text='', emboss=False) + row.prop(vlcol.collection, 'hide_render', text='', emboss=False) + + def execute(self, context): + return {'FINISHED'} + +# endregion + +# region Modifier Visibility + +## fn.set_properties_editor_tab('MODIFIER') + +class RT_OT_select_object_by_name(bpy.types.Operator): + bl_idname = "rt.select_object_by_name" + bl_label = "Select Object By name" + bl_description = "Select object by passed name" + bl_options = {"REGISTER", "INTERNAL"} + + + +class RT_OT_list_modifier_visibility(bpy.types.Operator): + bl_idname = "rt.list_modifier_visibility" + bl_label = "List Objects Modifiers Visibility Conflicts" + bl_description = "List Modifier visibility conflicts, when viewport and render have different values" + bl_options = {"REGISTER"} + + def invoke(self, context, event): + self.ob_list = [] + for o in context.scene.objects: + if not len(o.modifiers): + continue + mods = [] + for m in o.modifiers: + if m.show_viewport != m.show_render: + if not mods: + self.ob_list.append([o, mods, "OUTLINER_OB_" + o.type]) + mods.append(m) + self.ob_list.sort(key=lambda x: x[2]) # regroup by objects type (this or x[0] for object name) + return context.window_manager.invoke_props_dialog(self, width=350) + + def draw(self, context): + layout = self.layout + if not self.ob_list: + layout.label(text='No modifier visibility conflict found', icon='CHECKMARK') + return + + col = layout.column(align=False) + for ct, o in enumerate(self.ob_list): + ## UI V1 (modfier offset after name) + # sub_col = col.column(align=True) + # sub_col.label(text=o[0].name, icon=o[2]) + # for m in o[1]: + # row = sub_col.row() + # row.label(text='') + # row.label(text=m.name, icon='MODIFIER_ON') + # row.prop(m, 'show_viewport', text='', emboss=False) # invert_checkbox=True + # row.prop(m, 'show_render', text='', emboss=False) # invert_checkbox=True + + ## UI V2 - more compact with show object-> modifier prop + if ct > 0: + col.separator() + + for i, m in enumerate(o[1]): + row = col.row() + if i == 0: + # show object name and icon for first item + row.label(text=o[0].name, icon=o[2]) # Label only + ## Select object + # row.operator('rt.select_object_by_name', text=o[0].name, icon=o[2], emboss=False).object_name = o[0].name + else: + # Subsequent rows, show empty label + row.label(text=' ', icon='BLANK1') + # row.label(text=m.name, icon='MODIFIER_ON') + op = row.operator('rt.select_object_by_name', text=m.name, icon='MODIFIER_ON') + op.object_name = o[0].name + op.modifier_name = m.name + + row.prop(m, 'show_viewport', text='', emboss=False) # invert_checkbox=True + row.prop(m, 'show_render', text='', emboss=False) # invert_checkbox=True + + def execute(self, context): + return {'FINISHED'} + +# endregion + +# region Scene check + +class RT_OT_scene_checker(bpy.types.Operator): + bl_idname = "rt.scene_checker" + bl_label = "Check Scene " + bl_description = "Check / correct some aspect of the scene and objects, properties, etc. and report" + bl_options = {"REGISTER"} + + ## List of possible actions calls : + # set scene res + # set scene percentage at 100: + # Disabled animation + # Objects visibility conflict + # Objects modifiers visibility conflict + + apply_fixes : bpy.props.BoolProperty(name="Apply Fixes", default=False, + description="Apply possible fixes instead of just listing (pop the list again in fix mode)", + options={'SKIP_SAVE'}) + + def invoke(self, context, event): + self.ctrl = event.ctrl + return self.execute(context) + + def execute(self, context): + problems = [] + + ## Old method : Apply fixes based on pref (inverted by ctrl key) + # # If Ctrl is pressed, invert behavior (invert boolean) + # apply ^= self.ctrl + + # apply = self.apply_fixes + # if self.ctrl: + # apply = True + + ## Object visibility conflict + viz_ct = 0 + for o in context.scene.objects: + if not (o.hide_get() == o.hide_viewport == o.hide_render): + hv = 'No' if o.hide_get() else 'Yes' + vp = 'No' if o.hide_viewport else 'Yes' + rd = 'No' if o.hide_render else 'Yes' + viz_ct += 1 + print(f'{o.name} : viewlayer {hv} - viewport {vp} - render {rd}') + if viz_ct: + problems.append(['rt.list_object_visibility_conflicts', f'{viz_ct} objects visibility conflicts (details in console)', 'OBJECT_DATAMODE']) + + ## GP modifiers visibility conflict + mod_viz_ct = 0 + for o in context.scene.objects: + for m in o.modifiers: + if m.show_viewport != m.show_render: + vp = 'Yes' if m.show_viewport else 'No' + rd = 'Yes' if m.show_render else 'No' + mod_viz_ct += 1 + print(f'{o.name} - modifier {m.name}: viewport {vp} != render {rd}') + if mod_viz_ct: + problems.append(['rt.list_modifier_visibility', f'{mod_viz_ct} modifiers visibility conflicts (details in console)', 'MODIFIER_DATA']) + + return {'FINISHED'} + +# endregion + +classes = ( +RT_PG_object_visibility, +RT_OT_sync_visibility, +RT_OT_list_viewport_render_visibility, # Only viewport and render +RT_OT_list_object_visibility_conflicts, +RT_OT_list_collection_visibility_conflicts, +RT_OT_list_modifier_visibility, +# RT_OT_scene_checker, +) + + +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 diff --git a/ui.py b/ui.py index 408aa39..0805dc2 100755 --- a/ui.py +++ b/ui.py @@ -14,6 +14,15 @@ 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:") + 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") + classes = ( RT_PT_gp_node_ui,