hiding/unhinding while interating on objects can void an object, giving back None), because Blender's cached object list is invalidated
307 lines
14 KiB
Python
Executable File
307 lines
14 KiB
Python
Executable File
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'}:
|
|
## Iterate on a copy: changing visibility can invalidate all_objects cache while iterating
|
|
## (iteration then yields None and stops early)
|
|
for obj in list(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 = fn.get_object_icon(k)
|
|
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)
|