305 lines
14 KiB
Python
Executable File
305 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'}:
|
|
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)
|