background_plane_manager/ui/ui_list.py

358 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import bpy
from pathlib import Path
from .. import core
from .. constants import PREFIX
from bpy.types import UIList, PropertyGroup, Menu
from bpy.props import (PointerProperty,
IntProperty,
BoolProperty,
StringProperty,
EnumProperty,
FloatProperty
)
class BPM_UL_bg_list(UIList):
# order_by_distance : BoolProperty(default=True)
show_items : EnumProperty(
name="Show Items", description="Filter items to show, GP objects, Background or all",
default='all', options={'HIDDEN'},
items=(
('all', 'All', 'Show Background and Gp object', '', 0),
('obj', 'Gp Objects', 'Show only Gp object', '', 1),
('bg', 'Backgrounds', 'Show only backgrounds', '', 2),
))
#(key, label, descr, id[, icon])
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
# draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
# layout.alignment = 'CENTER'
## TODO: Find a better solution to get the collection
## Get Collection from plane name -> problem: prefix "BG_" and suffix
# if not item.plane.get('is_background_holder'):
if not item.plane.name.startswith(PREFIX): # (can check if has a parent using PREFIX)
gp_ob = item.plane
layout.prop(gp_ob, 'hide_viewport', text='', emboss=False, icon='HIDE_OFF')
icon_col = layout.row()
icon_col.label(text='', icon='OUTLINER_OB_GREASEPENCIL') # BLANK1
icon_col.ui_units_x = context.scene.bg_props.ui_list_scale # 1.6
row = layout.row()
row.label(text=gp_ob.name)
if gp_ob.data.users > 1:
row.template_ID(item, "data")
else:
row.label(text='', icon='BLANK1')
row.label(text='', icon='BLANK1')# <- add selection
return
layercol = context.view_layer.layer_collection.children['Background'].children.get(core.clean_image_name(item.plane.name[len(PREFIX):]))
if not layercol:
layout.label(text=f'{item.plane.name} (problem with name)', icon='ERROR')
return
# icon = 'HIDE_ON' if layercol.exclude else 'HIDE_OFF'
layout.prop(layercol, 'exclude', text='', emboss=False, icon='HIDE_OFF')
if not item.plane.children:
layout.label(text=f'{item.plane.name} (No children)', icon='ERROR')
## Image preview
image = core.get_image(item.plane.children[0])
# layout.label(icon_value=image.preview_ensure().icon_id)
# layout.template_icon(icon_value=image.preview_ensure().icon_id)
icon_col = layout.row()
icon_col.template_icon(icon_value=image.preview_ensure().icon_id, scale=1.0)
icon_col.ui_units_x = context.scene.bg_props.ui_list_scale # 1.6
## Name
row = layout.row()
row.enabled = not layercol.exclude
# row.label(text=item.plane.name) # <- Object has BG_ prefix, trim or use collection name
row.label(text=layercol.name)
if context.scene.bg_props.show_distance:# and not layercol.exclude:
row = layout.row()
# row.enabled = not layercol.exclude
row.prop(item.plane, '["distance"]', text='')
# layout.prop(item.plane, 'location', index=2, text='') # , emboss=False
ob = context.object
if ob and ob.parent == item.plane:
layout.label(text='', icon='DECORATE_LINKED') # select from prop group
else:
layout.label(text='', icon='BLANK1') # select from prop group
if not layercol.exclude:
icon = 'LAYER_ACTIVE' if item.plane.select_get() else 'LAYER_USED'
layout.prop(item, 'select', icon=icon, text='', emboss=False) # select from prop group
else:
layout.label(text='', icon='BLANK1') # select from prop group
## note
# You should always start your row layout by a label (icon + text), or a non-embossed text field,
# this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
# We use icon_value of label, as our given icon is an integer value, not an enum ID.
# Note "data" names should never be translated!
def draw_filter(self, context, layout):
row = layout.row()
subrow = row.row(align=True)
subrow.prop(self, "filter_name", text="") # Only show items matching this name (use * as wildcard)
# invert result
# icon = 'ZOOM_OUT' if self.use_filter_invert else 'ZOOM_IN'
# subrow.prop(self, "use_filter_invert", text="", icon=icon)
# sort by name : ALPHA SORTING NOT WORKING, MUST CHANGE IN filter_items
# subrow.prop(self, "use_filter_sort_alpha", text="", icon='SORTALPHA') # buit-in sort
subrow.prop(self, "show_items", text="") # type enum filter # icon='DOWNARROW_HLT'
# reverse order
icon = 'SORT_DESC' if self.use_filter_sort_reverse else 'SORT_ASC'
subrow.prop(self, "use_filter_sort_reverse", text="", icon=icon) # built-in reverse
def filter_items(self, context, data, propname):
helpers = bpy.types.UI_UL_list
items = getattr(data, propname)
filtered = []
# ordered = [items[:].index(i) for i in sorted(items[:], key=lambda o: o.plane.location.z)]
items = [(items[:].index(i), i) for i in items[:]]
# needed ?
filtered = [self.bitflag_filter_item] * len(items)
## Filter out out items thata
for i, item in items:
## GP/BG Type filter
if self.show_items != 'all':
if self.show_items != item.type:
filtered[i] &= ~self.bitflag_filter_item
## Search filter
if item.plane and self.filter_name.lower() not in item.plane.name.lower():
filtered[i] &= ~self.bitflag_filter_item
# if self.order_by_distance:
cam = context.scene.objects.get('bg_cam') or context.scene.camera
if not cam:
return filtered, ordered
## Real Distance from bg_cam or active camera
ordered = helpers.sort_items_helper(items, lambda o: (o[1].plane.matrix_world.to_translation() - cam.matrix_world.to_translation()).length)
## By distance attribute (only Backgrounds have distance attr)
# ordered = helpers.sort_items_helper(items, lambda o: o[1].plane.get('distance'))
return filtered, ordered
'''
class BPM_UL_bg_list(UIList):
# order_by_distance : BoolProperty(default=True)
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
# draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
# layout.alignment = 'CENTER'
## TODO: Find a better solution to get the collection
## Get Collection from plane name -> problem: prefix "BG_" and suffix
layercol = context.view_layer.layer_collection.children['Background'].children.get(core.clean_image_name(item.plane.name[len(PREFIX):]))
if not layercol:
layout.label(text=f'{item.plane.name} (problem with name)', icon='ERROR')
return
# icon = 'HIDE_ON' if layercol.exclude else 'HIDE_OFF'
layout.prop(layercol, 'exclude', text='', emboss=False, icon='HIDE_OFF')
if not item.plane.children:
layout.label(text=f'{item.plane.name} (No children)', icon='ERROR')
## Image preview
image = core.get_image(item.plane.children[0])
# layout.label(icon_value=image.preview_ensure().icon_id)
# layout.template_icon(icon_value=image.preview_ensure().icon_id)
icon_col = layout.row()
icon_col.template_icon(icon_value=image.preview_ensure().icon_id, scale=1.0)
icon_col.ui_units_x = context.scene.bg_props.ui_list_scale # 1.6
## Name
row = layout.row()
row.enabled = not layercol.exclude
# row.label(text=item.plane.name) # <- Object has BG_ prefix, trim or use collection name
row.label(text=layercol.name)
ob = context.object
if ob and ob.parent == item.plane:
layout.label(text='', icon='DECORATE_LINKED') # select from prop group
else:
layout.label(text='', icon='BLANK1') # select from prop group
if not layercol.exclude:
icon = 'LAYER_ACTIVE' if item.plane.select_get() else 'LAYER_USED'
layout.prop(item, 'select', icon=icon, text='', emboss=False) # select from prop group
else:
layout.label(text='', icon='BLANK1') # select from prop group
if context.scene.bg_props.show_distance:# and not layercol.exclude:
row = layout.row()
# row.enabled = not layercol.exclude
row.prop(item.plane, '["distance"]', text='')
# layout.prop(item.plane, 'location', index=2, text='') # , emboss=False
## note
# You should always start your row layout by a label (icon + text), or a non-embossed text field,
# this will also make the row easily selectable in the list! The later also enables ctrl-click rename.
# We use icon_value of label, as our given icon is an integer value, not an enum ID.
# Note "data" names should never be translated!
def filter_items(self, context, data, propname):
helpers = bpy.types.UI_UL_list
items = getattr(data, propname)
filtered = []
# ordered = [items[:].index(i) for i in sorted(items[:], key=lambda o: o.plane.location.z)]
items = [(items[:].index(i), i) for i in items[:]]
# needed ?
filtered = [self.bitflag_filter_item] * len(items)
for i, item in items:
if item.plane and self.filter_name.lower() not in item.plane.name.lower():
filtered[i] &= ~self.bitflag_filter_item
#else:
# if self.order_by_distance:
ordered = helpers.sort_items_helper(items, lambda o: o[1].plane.get('distance'))
return filtered, ordered
# def draw_filter(self, context, layout):
# """UI code for the filtering/sorting/search area."""
# col = layout.column(align=True)
# row = col.row(align=True)
# # row.prop(self, 'order_by_distance', text='', icon='DRIVER_DISTANCE')
# # row.prop(self, 'use_filter_invert', text='', icon='ARROW_LEFTRIGHT')
'''
### --- updates
def get_plane_targets(context):
selection = [i.plane for i in context.scene.bg_props.planes if i.plane.select_get()]
if selection:
return selection
## active (even not selected)
return [context.scene.bg_props.planes[context.scene.bg_props.index].plane]
def update_opacity(self, context):
pool = get_plane_targets(context)
for ob in pool:
if not ob or not ob.children:
continue
core.set_opacity(ob.children[0], opacity=context.scene.bg_props.opacity)
def update_on_index_change(self, context):
# print('index change:', context.scene.bg_props.index)
props = context.scene.bg_props
planes_list = props.planes
item = planes_list[props.index]
if not item.plane:
## remove slot !
print(f'bg_props.planes: No plane/object at index {props.index}, removing item')
planes_list.remove(props.index)
return
if item.type == 'bg':
plane = item.plane
if not plane.children:
return
opacity = core.get_opacity(plane.children[0])
if not opacity:
return
props['opacity'] = opacity
elif item.type == 'obj':
core.gp_transfer_mode(item.plane, context)
def update_select(self, context):
# print('index change:', context.scene.bg_props.index)
plane = self.plane
plane.select_set(not plane.select_get())
context.scene.bg_props['index'] = context.scene.bg_props.planes[:].index(self)
class BPM_more_option(Menu):
bl_idname = "BPM_MT_more_option"
bl_label = "Options"
def draw(self, context):
layout = self.layout
layout.operator("bpm.reload_list", text="Refresh", icon="FILE_REFRESH")
layout.operator("bpm.open_bg_folder", icon="FILE_FOLDER")
layout.operator("bpm.import_bg_images", text="Import Planes", icon="IMPORT")
layout.operator("bpm.update_bg_images", text="Update Versions", icon="SEQ_SPLITVIEW")
layout.operator("bpm.convert_planes", text="Convert Planes", icon="OUTLINER_OB_IMAGE")
layout.separator()
layout.prop(context.scene.bg_props, 'show_distance', icon='DRIVER_DISTANCE')
layout.prop(context.scene.bg_props, 'ui_list_scale')
# Create sniptool property group
class BPM_bg_list_prop(PropertyGroup):
plane : PointerProperty(type=bpy.types.Object)
select: BoolProperty(update=update_select) # use and update to set the plane selection
type: StringProperty(default='bg')
# is_bg: BoolProperty(default=True)
class BPM_bg_settings(PropertyGroup):
index : IntProperty(update=update_on_index_change)
planes : bpy.props.CollectionProperty(type=BPM_bg_list_prop)
show_distance : BoolProperty(name='Show Distance', default=False)
opacity : FloatProperty(name='Opacity', default=1.0, min=0.0, max=1.0, update=update_opacity)
move_hided : BoolProperty(name='Move hided', default=True)
ui_list_scale : FloatProperty(name='UI Item Y Scale', default=1.6, min=0.6, soft_min=1.0, max=2.0)
# distance : FloatProperty(name='Distance', default=0.0, update=update_distance)
### --- REGISTER ---
classes=(
# prop and UIlist
BPM_bg_list_prop,
BPM_bg_settings,
BPM_UL_bg_list,
BPM_more_option,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.bg_props = bpy.props.PointerProperty(type=BPM_bg_settings)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.bg_props