https://sinestesia.co/blog/tutorials/using-uilists-in-blender/
完整代码
我偷偷加了个清除所有项,自己研究
import bpy
from bpy.props import StringProperty, IntProperty, CollectionProperty
from bpy.types import PropertyGroup, UIList, Operator, Panel
bl_info = {
"name": "test",
"author": "yl",
"description": "",
"blender": (3, 0, 0),
"version": (0, 0, 1),
"location": "",
"warning": "",
"category": "Yuelili",
"doc_url": "https://yuelili.com"
}
class ListItem(PropertyGroup):
"""用于列表中单个项目的属性组"""
name: StringProperty(
name="Name",
description="项目名称",
default="未命名")
random_prop: StringProperty(
name="Porperty",
description="其他属性",
default="")
class MY_UL_List(UIList):
""" UI List 测试 """
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index):
# 这里可以自定义图标,取决于你的项目...
custom_icon = 'OBJECT_DATAMODE'
# draw_item必须处理三种布局类型... 通常'DEFAULT'和'COMPACT'可以共享同一个代码。
if self.layout_type in {'DEFAULT', 'COMPACT'}:
# 开始行布局:以标签(图标+文本)或非浮雕的文本字段。
# 这样使该行在列表中容易被选择! 后者还可以让通过ctrl点击重命名。
# 使用标签的icon_value,因为给定的图标是一个整数值,而不是一个枚举ID。
layout.label(text=item.name, icon=custom_icon)
# 'GRID'布局类型应尽可能紧凑(通常是一个单一的图标!)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon=custom_icon)
class LIST_OT_NewItem(Operator):
""" 添加子项到列表 """
bl_idname = "my_list.new_item"
bl_label = "添加一项"
def execute(self, context):
context.scene.my_list.add()
return{'FINISHED'}
class LIST_OT_DeleteItem(Operator):
""" 从列表中删除选中项 """
bl_idname = "my_list.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(cls, context):
return context.scene.my_list
def execute(self, context):
# 从scene接收collection和list_index
my_list = context.scene.my_list
index = context.scene.list_index
# 删除项,更改当前选择项索引
my_list.remove(index)
context.scene.list_index = min(max(0, index - 1), len(my_list) - 1)
return{'FINISHED'}
class LIST_OT_MoveItem(Operator):
""" 在列表中移动项目 """
bl_idname = "my_list.move_item"
bl_label = "Move an item in the list"
direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
('DOWN', 'Down', ""),
("CLEAR","Clear","")
))
@classmethod
def poll(cls, context):
return context.scene.my_list
def move_index(self):
""" 移动项目索引,并限制在0~项目数 """
my_list = bpy.context.scene.my_list
index = bpy.context.scene.list_index
list_length = len(my_list) - 1
if self.direction == 'CLEAR':
my_list.clear()
new_index = index + (-1 if self.direction == 'UP' else 1)
bpy.context.scene.list_index = max(0, min(new_index, list_length))
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
neighbor = index + (-1 if self.direction == 'UP' else 1)
my_list.move(neighbor, index)
self.move_index()
return{'FINISHED'}
class PT_ListExample(Panel):
""" UI list测试面板 """
bl_label = "UI_List 示例"
bl_idname = "SCENE_PT_LIST_DEMO"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
row = layout.row()
# template_list 现在需要两个新参数。
# 第一个参数:注册UIList的要用的标识符(如果你只要默认列表,没有自定义的绘制代码,使用 "UI_UL_list")。
# 第二个参数:通常可以保留为空字符串。
# 这是个额外的ID,用于区分列表,以防你在某个区域多次使用同一个列表。
row.template_list("MY_UL_List", "The_List", scene,
"my_list", scene, "list_index")
row = layout.row()
row.operator('my_list.new_item', text='NEW')
row.operator('my_list.delete_item', text='REMOVE')
row.operator('my_list.move_item', text='UP').direction = 'UP'
row.operator('my_list.move_item', text='DOWN').direction = 'DOWN'
row.operator('my_list.move_item', text='CLEAR').direction = 'CLEAR'
if scene.list_index >= 0 and scene.my_list:
item = scene.my_list[scene.list_index]
row = layout.row()
row.prop(item, "name")
row.prop(item, "random_prop")
# 注册
def register():
bpy.utils.register_class(ListItem)
bpy.utils.register_class(MY_UL_List)
bpy.utils.register_class(LIST_OT_NewItem)
bpy.utils.register_class(LIST_OT_DeleteItem)
bpy.utils.register_class(LIST_OT_MoveItem)
bpy.utils.register_class(PT_ListExample)
bpy.types.Scene.my_list = CollectionProperty(type=ListItem)
bpy.types.Scene.list_index = IntProperty(name="Index for my_list",
default=0)
def unregister():
del bpy.types.Scene.my_list
del bpy.types.Scene.list_index
bpy.utils.unregister_class(ListItem)
bpy.utils.unregister_class(MY_UL_List)
bpy.utils.unregister_class(LIST_OT_NewItem)
bpy.utils.unregister_class(LIST_OT_DeleteItem)
bpy.utils.unregister_class(LIST_OT_MoveItem)
bpy.utils.unregister_class(PT_ListExample)
if __name__ == "__main__":
register()
全流程
定义属性
创建一些属性来保存列表中的数据。
class ListItem(PropertyGroup):
"""代表列表中一个项目的属性组"""
name: StringProperty(
name="Name",
description="项目名称",
default="未命名")
random_prop: StringProperty(
name="Porperty",
description="其他属性",
default="")
我们需要一个 Collection 属性来保存列表数据,以及 Integer 属性来保存索引号。
来添加到register()函数中。
def register():
bpy.types.Scene.my_list = CollectionProperty(type = ListItem)
bpy.types.Scene.list_index = IntProperty(name = "Index for my_list",
default = 0)
列表小部件
使用draw_item函数
- layout: UILayout, 不会为None,用于绘制元素的布局。
- data:AnyType,包含在集合属性中的对象(RNA 对象)。
- item:AnyType,集合(collection)中当前绘制的项目(item) 。
- icon:整数 [0, inf],集合中项目的图标。如材料或纹理有自定义的图标ID。
- active_data:AnyType, 不会为None,包含在集合内活动属性的RNA对象。
- active_property:字符串,可选,Identifier of property in active_data, for the active element。
- index: 整数 [0, inf],可选,集合中当前项目的索引。
- flt_flag: 整数 [0, inf],可选,The filter-flag result for this item 。
class MY_UL_List(UIList):
""" UIList测试 """
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index):
# 这里可以自定义图标,取决于你的项目...
custom_icon = 'OBJECT_DATAMODE'
# 确保你的代码支持这3种layout类型
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.label(text=item.name, icon=custom_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon=custom_icon)
此时可以使用temple_list函数调用
row = layout.row()
row.template_list("MY_UL_List", "The_List", scene,
"my_list", scene, "list_index")
让用户修改列表
添加项目非常简单。只需调用列表中的 add() 方法。
class LIST_OT_NewItem(Operator):
""" 添加子项到列表 """
bl_idname = "my_list.new_item"
bl_label = "添加一项"
def execute(self, context):
context.scene.my_list.add()
return{'FINISHED'}
删除项目
首先需要检查是否有要删除的内容。
使用 poll() 方法检查列表中是否至少有一项。 poll() 还会自动禁用 ui 中的操作项按钮。
通过使用列表索引调用 remove() 来完成的(也就是 ui 中选择的内容)。
最后必须修复索引。0<=值<列表长度
class LIST_OT_DeleteItem(Operator):
""" 从列表中删除选中项 """
bl_idname = "my_list.delete_item"
bl_label = "Deletes an item"
@classmethod
def poll(cls, context):
return context.scene.my_list
def execute(self, context):
# 从scene接收collection和list_index
my_list = context.scene.my_list
index = context.scene.list_index
# 删除项,更改当前选择项索引
my_list.remove(index)
context.scene.list_index = min(max(0, index - 1), len(my_list) - 1)
return{'FINISHED'}
移动项目
需要更多考虑,因为必须确保索引正确。
move() 方法允许在两个不同的索引处交换项目。
最后调用自己的 move_index() 方法来更改索引,将其限制在 0 和列表长度之间。
class LIST_OT_MoveItem(Operator):
""" 在列表中移动项目 """
bl_idname = "my_list.move_item"
bl_label = "Move an item in the list"
direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
('DOWN', 'Down', ""),
("CLEAR","Clear","")
))
@classmethod
def poll(cls, context):
return context.scene.my_list
def move_index(self):
""" 移动项目索引,并限制在0~项目数 """
my_list = bpy.context.scene.my_list
index = bpy.context.scene.list_index
list_length = len(my_list) - 1
if self.direction == 'CLEAR':
my_list.clear()
new_index = index + (-1 if self.direction == 'UP' else 1)
bpy.context.scene.list_index = max(0, min(new_index, list_length))
def execute(self, context):
my_list = context.scene.my_list
index = context.scene.list_index
neighbor = index + (-1 if self.direction == 'UP' else 1)
my_list.move(neighbor, index)
self.move_index()
return{'FINISHED'}
项目数据
显示列表项中的数据也很容易,只需使用索引即可。但要确保有效
if scene.list_index >= 0 and scene.my_list:
item = scene.my_list[scene.list_index]
row = layout.row()
row.prop(item, "name")
row.prop(item, "random_prop")