Hi, im a beginner at blender scripting. One of the first thing I have looked at is my own version of treelist UI (eg like a scene tree or directory tree) … but I want to be able to add my own buttons and info to a treelist item etc.
I have struggled to find ways to implement it just in Python. This is the best I have come up with: to use the template_list, and adjust the list behaviour to be somewhat treelike. This is just a prototype and not really useful. Mainly I am using test data. I haven’t figured out how to watch something more useful like the scene tree and update based on that.
It sort of works but does anyone have any better ideas?
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
CollectionProperty,
IntProperty,
BoolProperty,
StringProperty,
PointerProperty,
)
#
# This is what I am using to hold a single tree node in my raw example data.
# The entire example data is stored in **bpy.context.scene.myNodes**
#
class MyListTreeNode(bpy.types.PropertyGroup):
name : ""
selfIndex : bpy.props.IntProperty(default=-1)
parentIndex : bpy.props.IntProperty(default=-1)
childCount : bpy.props.IntProperty(default=0)
#
# This represents an item that in the collection being rendered by
# props.template_list. This collection is stored in ______
# The collection represents a currently visible subset of MyListTreeNode
# plus some extra info to render in a treelike fashion, eg indent.
#
class MyListTreeItem(bpy.types.PropertyGroup):
indent: bpy.props.IntProperty(default=0)
expanded: bpy.props.BoolProperty(default=False)
nodeIndex : bpy.props.IntProperty(default=-1) #index into the real tree data.
childCount: bpy.props.IntProperty(default=0) #should equal myNodes[nodeIndex].childCount
def SetupNodeData():
bpy.types.Scene.myNodes = bpy.props.CollectionProperty(type=MyListTreeNode)
myNodes = bpy.context.scene.myNodes
myNodes.clear()
for i in range(5):
node = myNodes.add()
node.name = "node {}".format(i)
node.selfIndex = len(myNodes)-1
for i in range(4):
node = myNodes.add()
node.name = "subnode {}".format(i)
node.selfIndex = len(myNodes)-1
node.parentIndex = 2
parentIndex = len(myNodes)-2
for i in range(2):
node = myNodes.add()
node.name = "subnode {}".format(i)
node.selfIndex = len(myNodes)-1
node.parentIndex = parentIndex
parentIndex = len(myNodes)-3
for i in range(2):
node = myNodes.add()
node.name = "subnode {}".format(i)
node.selfIndex = len(myNodes)-1
node.parentIndex = parentIndex
parentIndex = len(myNodes)-1
for i in range(2):
node = myNodes.add()
node.name = "subnode {}".format(i)
node.selfIndex = len(myNodes)-1
node.parentIndex = parentIndex
# calculate childCount for all nodes
for node in myNodes :
if node.parentIndex != -1:
parent = myNodes[node.parentIndex]
parent.childCount = parent.childCount + 1
print("++++ SetupNodeData ++++")
print("Node count: {}".format(len(myNodes)))
for i in range(len(myNodes)):
node = myNodes[i]
print("{} node:{} child:{}".format(i, node.name, node.childCount))
def NewListItem( treeList, node):
item = treeList.add()
item.name = node.name
item.nodeIndex = node.selfIndex
item.childCount = node.childCount
return item
def SetupListFromNodeData():
bpy.types.Scene.myListTree = bpy.props.CollectionProperty(type=MyListTreeItem)
bpy.types.Scene.myListTree_index = IntProperty()
treeList = bpy.context.scene.myListTree
treeList.clear()
myNodes = bpy.context.scene.myNodes
for node in myNodes:
#print("node name:{} parent:{} kids:{}".format(node.name, node.parentIndex, node.children))
if -1 == node.parentIndex :
NewListItem(treeList, node)
#
# Inserts a new item into myListTree at position item_index
# by copying data from node
#
def InsertBeneath( treeList, parentIndex, parentIndent, node):
after_index =parentIndex + 1
item = NewListItem(treeList,node)
item.indent = parentIndent+1
item_index = len(treeList) -1 #because add() appends to end.
treeList.move(item_index,after_index)
def IsChild( child_node_index, parent_node_index, node_list):
if child_node_index == -1:
print("bad node index")
return False
child = node_list[child_node_index]
if child.parentIndex == parent_node_index:
return True
return False
#
# Operation to Expand a list item.
#
class MyListTreeItem_Expand(bpy.types.Operator):
bl_idname = "object.mylisttree_expand" #NOT SURE WHAT TO PUT HERE.
bl_label = "Tool Name"
button_id: IntProperty(default=0)
def execute(self, context):
item_index = self.button_id
item_list = context.scene.myListTree
item = item_list[item_index]
item_indent = item.indent
nodeIndex = item.nodeIndex
myNodes = context.scene.myNodes
print(item)
if item.expanded:
print("=== Collapse Item {} ===".format(item_index))
item.expanded = False
nextIndex = item_index+1
while True:
if nextIndex >= len(item_list):
break
if item_list[nextIndex].indent <= item_indent:
break
item_list.remove(nextIndex)
else:
print("=== Expand Item {} ===".format(item_index))
item.expanded = True
for n in myNodes:
if nodeIndex == n.parentIndex:
InsertBeneath(item_list, item_index, item_indent, n)
return {'FINISHED'}
#
# Several debug operations
# (bundled into a single operator with an "action" property)
#
class MyListTreeItem_Debug(bpy.types.Operator):
bl_idname = "object.mylisttree_debug"
bl_label = "Debug"
action: StringProperty(default="default")
def execute(self, context):
action = self.action
if "print" == action:
print("=== Debug Print ====")
elif "reset3" == action:
print("=== Debug Reset ====")
SetupListFromNodeData()
elif "clear" == action:
print("=== Debug Clear ====")
bpy.context.scene.myListTree.clear()
else:
print("unknown debug action: "+action)
return {'FINISHED'}
#
# My List UI class to draw my MyListTreeItem
# (The most important thing it does is show how to draw a list item)
#
#note this naming convention is important. For more info search for _UL_ in:
# https://wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/Addons
class MYLISTTREEITEM_UL_basic(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
scene = data
#print(data, item, active_data, active_propname)
if self.layout_type in {'DEFAULT', 'COMPACT'}:
for i in range(item.indent):
split = layout.split(factor = 0.1)
col = layout.column()
#print("item:{} childCount:{}".format(item.name, item.childCount))
if item.childCount == 0:
op = col.operator("object.mylisttree_expand", text="", icon='DOT')
op.button_id = index
col.enabled = False
#if False:
# pass
elif item.expanded :
op = col.operator("object.mylisttree_expand", text="", icon='TRIA_DOWN')
op.button_id = index
else:
op = col.operator("object.mylisttree_expand", text="", icon='TRIA_RIGHT')
op.button_id = index
col = layout.column()
col.label(text=item.name)
#
# My Panel UI, assigned to view.
#
class SCENE_PT_mylisttree(bpy.types.Panel):
bl_label = "My List Tree"
bl_idname = "SCENE_PT_materials"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "My Category"
def draw(self, context):
scn = context.scene
layout = self.layout
row = layout.row()
row.template_list(
"MYLISTTREEITEM_UL_basic",
"",
scn,
"myListTree",
scn,
"myListTree_index",
sort_lock = True
)
grid = layout.grid_flow( columns = 2 )
grid.operator("object.mylisttree_debug", text="Reset").action = "reset3"
grid.operator("object.mylisttree_debug", text="Clear").action = "clear"
grid.operator("object.mylisttree_debug", text="Print").action = "print"
classes = (
MyListTreeNode,
MyListTreeItem,
MyListTreeItem_Expand,
MyListTreeItem_Debug,
MYLISTTREEITEM_UL_basic,
SCENE_PT_mylisttree)
def register():
for cls in classes:
bpy.utils.register_class(cls)
SetupNodeData()
SetupListFromNodeData()
def unregister():
# fill this in.
pass
if __name__ == "__main__":
register()