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.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