Add base visibility conflict checkers
This commit is contained in:
parent
90f0a49de3
commit
2a438ed924
93
fn.py
93
fn.py
@ -5,7 +5,7 @@ import json
|
|||||||
|
|
||||||
from .constant import TECH_PASS_KEYWORDS
|
from .constant import TECH_PASS_KEYWORDS
|
||||||
|
|
||||||
### --- Manage nodes --- ###
|
# region Manage nodes
|
||||||
|
|
||||||
def real_loc(n):
|
def real_loc(n):
|
||||||
if not n.parent:
|
if not n.parent:
|
||||||
@ -274,3 +274,94 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
|
|||||||
|
|
||||||
|
|
||||||
return created_nodes
|
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)
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
from . import (
|
from . import (
|
||||||
|
utility,
|
||||||
outputs_setup,
|
outputs_setup,
|
||||||
outputs_search_and_replace,
|
outputs_search_and_replace,
|
||||||
|
visibility_conflicts,
|
||||||
)
|
)
|
||||||
|
|
||||||
mods = (
|
mods = (
|
||||||
|
utility,
|
||||||
outputs_setup,
|
outputs_setup,
|
||||||
outputs_search_and_replace,
|
outputs_search_and_replace,
|
||||||
|
visibility_conflicts,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
78
operators/utility.py
Normal file
78
operators/utility.py
Normal file
@ -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)
|
572
operators/visibility_conflicts.py
Normal file
572
operators/visibility_conflicts.py
Normal file
@ -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)
|
9
ui.py
9
ui.py
@ -14,6 +14,15 @@ class RT_PT_gp_node_ui(Panel):
|
|||||||
layout.operator("rt.create_output_layers", icon="NODE")
|
layout.operator("rt.create_output_layers", icon="NODE")
|
||||||
layout.operator("rt.outputs_search_and_replace", text='Search and replace outputs', icon="BORDERMOVE")
|
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 = (
|
classes = (
|
||||||
RT_PT_gp_node_ui,
|
RT_PT_gp_node_ui,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user