Add an unloaded operator (for potential later use) to adjust scale custom prop of the plane to fit camera frame.
358 lines
14 KiB
Python
358 lines
14 KiB
Python
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_hidden : BoolProperty(name='Move Hidden', 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
|